From 7e085d051938fa5735a1d1a1901fe3270e2c0326 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 6 Nov 2024 11:38:02 +0000 Subject: [PATCH 1/7] Update for spec changes --- src/types.ts | 202 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 157 insertions(+), 45 deletions(-) diff --git a/src/types.ts b/src/types.ts index 8fd478631..a358c3ecf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -export const PROTOCOL_VERSION = "2024-10-07"; +export const PROTOCOL_VERSION = "2024-11-05"; /* JSON-RPC types */ export const JSONRPC_VERSION = "2.0"; @@ -148,46 +148,6 @@ export const JSONRPCMessageSchema = z.union([ export const EmptyResultSchema = ResultSchema.strict(); /* Initialization */ -/** - * Text provided to or from an LLM. - */ -export const TextContentSchema = z - .object({ - type: z.literal("text"), - /** - * The text content of the message. - */ - text: z.string(), - }) - .passthrough(); - -/** - * An image provided to or from an LLM. - */ -export const ImageContentSchema = z - .object({ - type: z.literal("image"), - /** - * The base64-encoded image data. - */ - data: z.string().base64(), - /** - * The MIME type of the image. Different providers may support different image types. - */ - mimeType: z.string(), - }) - .passthrough(); - -/** - * Describes a message issued to or received from an LLM API. - */ -export const SamplingMessageSchema = z - .object({ - role: z.enum(["user", "assistant"]), - content: z.union([TextContentSchema, ImageContentSchema]), - }) - .passthrough(); - /** * Describes the name and version of an MCP implementation. */ @@ -211,6 +171,17 @@ export const ClientCapabilitiesSchema = z * Present if the client supports sampling from an LLM. */ sampling: z.optional(z.object({}).passthrough()), + /** + * Present if the client supports listing roots. + */ + roots: z.optional( + z.object({ + /** + * Whether the client supports notifications for changes to the roots list. + */ + listChanged: z.optional(z.boolean()), + }).passthrough(), + ), }) .passthrough(); @@ -626,6 +597,56 @@ export const GetPromptRequestSchema = RequestSchema.extend({ }), }); +/** + * Text provided to or from an LLM. + */ +export const TextContentSchema = z + .object({ + type: z.literal("text"), + /** + * The text content of the message. + */ + text: z.string(), + }) + .passthrough(); + +/** + * An image provided to or from an LLM. + */ +export const ImageContentSchema = z + .object({ + type: z.literal("image"), + /** + * The base64-encoded image data. + */ + data: z.string().base64(), + /** + * The MIME type of the image. Different providers may support different image types. + */ + mimeType: z.string(), + }) + .passthrough(); + +/** + * The contents of a resource, embedded into a prompt or tool call result. + */ +export const EmbeddedResourceSchema = z + .object({ + type: z.literal("resource"), + resource: z.union([TextResourceContentsSchema, BlobResourceContentsSchema]), + }) + .passthrough(); + +/** + * Describes a message returned as part of a prompt. + */ +export const PromptMessageSchema = z + .object({ + role: z.enum(["user", "assistant"]), + content: z.union([TextContentSchema, ImageContentSchema, EmbeddedResourceSchema]), + }) + .passthrough(); + /** * The server's response to a prompts/get request from the client. */ @@ -634,7 +655,7 @@ export const GetPromptResultSchema = ResultSchema.extend({ * An optional description for the prompt. */ description: z.optional(z.string()), - messages: z.array(SamplingMessageSchema), + messages: z.array(PromptMessageSchema), }); /** @@ -688,7 +709,8 @@ export const ListToolsResultSchema = PaginatedResultSchema.extend({ * The server's response to a tool call. */ export const CallToolResultSchema = ResultSchema.extend({ - toolResult: z.unknown(), + content: z.array(z.union([TextContentSchema, ImageContentSchema, EmbeddedResourceSchema])), + isError: z.boolean(), }); /** @@ -713,7 +735,16 @@ export const ToolListChangedNotificationSchema = NotificationSchema.extend({ /** * The severity of a log message. */ -export const LoggingLevelSchema = z.enum(["debug", "info", "warning", "error"]); +export const LoggingLevelSchema = z.enum([ + "debug", + "info", + "notice", + "warning", + "error", + "critical", + "alert", + "emergency" +]); /** * A request from the client to the server, to enable or adjust logging. @@ -752,6 +783,40 @@ export const LoggingMessageNotificationSchema = NotificationSchema.extend({ }); /* Sampling */ +/** + * The server's preferences for model selection, requested of the client during sampling. + */ +export const ModelPreferencesSchema = z + .object({ + /** + * Optional string hints to use for model selection. + */ + hints: z.optional(z.array(z.record(z.string()))), + /** + * How much to prioritize cost when selecting a model. + */ + costPriority: z.optional(z.number().min(0).max(1)), + /** + * How much to prioritize sampling speed (latency) when selecting a model. + */ + speedPriority: z.optional(z.number().min(0).max(1)), + /** + * How much to prioritize intelligence and capabilities when selecting a model. + */ + intelligencePriority: z.optional(z.number().min(0).max(1)), + }) + .passthrough(); + +/** + * Describes a message issued to or received from an LLM API. + */ +export const SamplingMessageSchema = z + .object({ + role: z.enum(["user", "assistant"]), + content: z.union([TextContentSchema, ImageContentSchema]), + }) + .passthrough(); + /** * A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it. */ @@ -777,6 +842,10 @@ export const CreateMessageRequestSchema = RequestSchema.extend({ * Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. */ metadata: z.optional(z.object({}).passthrough()), + /** + * The server's preferences for which model to select. + */ + modelPreferences: z.optional(ModelPreferencesSchema), }), }); @@ -791,7 +860,7 @@ export const CreateMessageResultSchema = ResultSchema.extend({ /** * The reason why sampling stopped. */ - stopReason: z.enum(["endTurn", "stopSequence", "maxTokens"]), + stopReason: z.optional(z.string()), role: z.enum(["user", "assistant"]), content: z.discriminatedUnion("type", [ TextContentSchema, @@ -873,6 +942,44 @@ export const CompleteResultSchema = ResultSchema.extend({ .passthrough(), }); +/* Roots */ +/** + * Represents a root directory or file that the server can operate on. + */ +export const RootSchema = z + .object({ + /** + * The URI identifying the root. This *must* start with file:// for now. + */ + uri: z.string().startsWith("file://"), + /** + * An optional name for the root. + */ + name: z.optional(z.string()), + }) + .passthrough(); + +/** + * Sent from the server to request a list of root URIs from the client. + */ +export const ListRootsRequestSchema = RequestSchema.extend({ + method: z.literal("roots/list"), +}); + +/** + * The client's response to a roots/list request from the server. + */ +export const ListRootsResultSchema = ResultSchema.extend({ + roots: z.array(RootSchema), +}); + +/** + * A notification from the client to the server, informing it that the list of roots has changed. + */ +export const RootsListChangedNotificationSchema = NotificationSchema.extend({ + method: z.literal("notifications/roots/list_changed"), +}); + /* Client messages */ export const ClientRequestSchema = z.union([ PingRequestSchema, @@ -887,21 +994,26 @@ export const ClientRequestSchema = z.union([ UnsubscribeRequestSchema, CallToolRequestSchema, ListToolsRequestSchema, + ListRootsRequestSchema, ]); export const ClientNotificationSchema = z.union([ ProgressNotificationSchema, InitializedNotificationSchema, + RootsListChangedNotificationSchema, ]); + export const ClientResultSchema = z.union([ EmptyResultSchema, CreateMessageResultSchema, + ListRootsResultSchema, ]); /* Server messages */ export const ServerRequestSchema = z.union([ PingRequestSchema, CreateMessageRequestSchema, + ListRootsRequestSchema, ]); export const ServerNotificationSchema = z.union([ From ce50ba49150f571ff0200f870749417819eb685c Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 6 Nov 2024 11:42:09 +0000 Subject: [PATCH 2/7] Fix resources/templates/list not being included in client/server schemas --- src/types.ts | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/types.ts b/src/types.ts index a358c3ecf..5d7bb76f5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -175,12 +175,14 @@ export const ClientCapabilitiesSchema = z * Present if the client supports listing roots. */ roots: z.optional( - z.object({ - /** - * Whether the client supports notifications for changes to the roots list. - */ - listChanged: z.optional(z.boolean()), - }).passthrough(), + z + .object({ + /** + * Whether the client supports notifications for changes to the roots list. + */ + listChanged: z.optional(z.boolean()), + }) + .passthrough(), ), }) .passthrough(); @@ -643,7 +645,11 @@ export const EmbeddedResourceSchema = z export const PromptMessageSchema = z .object({ role: z.enum(["user", "assistant"]), - content: z.union([TextContentSchema, ImageContentSchema, EmbeddedResourceSchema]), + content: z.union([ + TextContentSchema, + ImageContentSchema, + EmbeddedResourceSchema, + ]), }) .passthrough(); @@ -709,7 +715,9 @@ export const ListToolsResultSchema = PaginatedResultSchema.extend({ * The server's response to a tool call. */ export const CallToolResultSchema = ResultSchema.extend({ - content: z.array(z.union([TextContentSchema, ImageContentSchema, EmbeddedResourceSchema])), + content: z.array( + z.union([TextContentSchema, ImageContentSchema, EmbeddedResourceSchema]), + ), isError: z.boolean(), }); @@ -743,7 +751,7 @@ export const LoggingLevelSchema = z.enum([ "error", "critical", "alert", - "emergency" + "emergency", ]); /** @@ -989,6 +997,7 @@ export const ClientRequestSchema = z.union([ GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, + ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, @@ -1032,6 +1041,7 @@ export const ServerResultSchema = z.union([ GetPromptResultSchema, ListPromptsResultSchema, ListResourcesResultSchema, + ListResourceTemplatesResultSchema, ReadResourceResultSchema, CallToolResultSchema, ListToolsResultSchema, From 579ac51f21da35ba264c7588f405ccfc61d366d3 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 6 Nov 2024 11:44:37 +0000 Subject: [PATCH 3/7] Export type aliases for roots --- src/types.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/types.ts b/src/types.ts index 5d7bb76f5..58ed02ca2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1160,6 +1160,14 @@ export type PromptReference = z.infer; export type CompleteRequest = z.infer; export type CompleteResult = z.infer; +/* Roots */ +export type Root = z.infer; +export type ListRootsRequest = z.infer; +export type ListRootsResult = z.infer; +export type RootsListChangedNotification = z.infer< + typeof RootsListChangedNotificationSchema +>; + /* Client messages */ export type ClientRequest = z.infer; export type ClientNotification = z.infer; From 45a4c9ee376a42b64eb41484c34d9028b4dd8f88 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 6 Nov 2024 11:45:19 +0000 Subject: [PATCH 4/7] List roots is not a client request --- src/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 58ed02ca2..bbcd44240 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1003,7 +1003,6 @@ export const ClientRequestSchema = z.union([ UnsubscribeRequestSchema, CallToolRequestSchema, ListToolsRequestSchema, - ListRootsRequestSchema, ]); export const ClientNotificationSchema = z.union([ From 099c2e2be36d1cae2a72f2f28f665bbd72f9d7ea Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 6 Nov 2024 11:46:55 +0000 Subject: [PATCH 5/7] Update client and server convenience methods --- src/client/index.ts | 17 +++++++++++++++++ src/server/index.ts | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/client/index.ts b/src/client/index.ts index a18b45d08..5649866ac 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -29,6 +29,8 @@ import { ListToolsResultSchema, EmptyResultSchema, LoggingLevel, + ListResourceTemplatesRequest, + ListResourceTemplatesResultSchema, } from "../types.js"; /** @@ -177,6 +179,17 @@ export class Client< ); } + async listResourceTemplates( + params?: ListResourceTemplatesRequest["params"], + onprogress?: ProgressCallback, + ) { + return this.request( + { method: "resources/templates/list", params }, + ListResourceTemplatesResultSchema, + onprogress, + ); + } + async readResource( params: ReadResourceRequest["params"], onprogress?: ProgressCallback, @@ -223,4 +236,8 @@ export class Client< onprogress, ); } + + async sendRootsListChanged() { + return this.notification({ method: "notifications/roots/list_changed" }); + } } diff --git a/src/server/index.ts b/src/server/index.ts index c6350fffe..dc5466251 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -23,6 +23,8 @@ import { EmptyResultSchema, LoggingMessageNotification, ResourceUpdatedNotification, + ListRootsRequest, + ListRootsResultSchema, } from "../types.js"; /** @@ -154,6 +156,17 @@ export class Server< ); } + async listRoots( + params?: ListRootsRequest["params"], + onprogress?: ProgressCallback, + ) { + return this.request( + { method: "roots/list", params }, + ListRootsResultSchema, + onprogress, + ); + } + async sendLoggingMessage(params: LoggingMessageNotification["params"]) { return this.notification({ method: "notifications/message", params }); } From f7f0ccff0e04ebf3eb50b7562d15506b7a4b9c54 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 6 Nov 2024 11:56:05 +0000 Subject: [PATCH 6/7] Declare known `stopReason`s, as a kind of documentation --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index bbcd44240..8ff6f65be 100644 --- a/src/types.ts +++ b/src/types.ts @@ -868,7 +868,7 @@ export const CreateMessageResultSchema = ResultSchema.extend({ /** * The reason why sampling stopped. */ - stopReason: z.optional(z.string()), + stopReason: z.optional(z.enum(["endTurn", "stopSequence", "maxTokens"]).or(z.string())), role: z.enum(["user", "assistant"]), content: z.discriminatedUnion("type", [ TextContentSchema, From f74a28921125e4dcc6dee93bba7a79d18556f0bb Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 6 Nov 2024 11:56:20 +0000 Subject: [PATCH 7/7] Split out `ModelHintSchema` for better typing and docs --- src/types.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index 8ff6f65be..b22e8fb29 100644 --- a/src/types.ts +++ b/src/types.ts @@ -791,15 +791,25 @@ export const LoggingMessageNotificationSchema = NotificationSchema.extend({ }); /* Sampling */ +/** + * Hints to use for model selection. + */ +export const ModelHintSchema = z.object({ + /** + * A hint for a model name. + */ + name: z.string().optional(), +}).passthrough(); + /** * The server's preferences for model selection, requested of the client during sampling. */ export const ModelPreferencesSchema = z .object({ /** - * Optional string hints to use for model selection. + * Optional hints to use for model selection. */ - hints: z.optional(z.array(z.record(z.string()))), + hints: z.optional(z.array(ModelHintSchema)), /** * How much to prioritize cost when selecting a model. */