@revojs/ai 0.2.25 → 0.2.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,7 +29,7 @@ for (const call of result.content) {
29
29
  `listTools()` returns AI function-tool definitions. `executeTool()` accepts a
30
30
  normalized model tool call and returns a `LanguageModelToolResultPart`, preserving
31
31
  the entire tool result as JSON, like the Model Context Protocol client. Use
32
- `callTool(name, input, { signal })` for the original SDK `CallToolResult` without
32
+ `callTool(name, input, { signal })` for the raw `CallToolResult` without
33
33
  a model call. Both execution methods validate arguments before running a handler.
34
34
  Unknown tools, invalid arguments and unexpected handler failures throw locally;
35
35
  HTTP error responses and thrown responses become error results. The HTTP endpoint
@@ -130,7 +130,7 @@ target when middleware or route parameters are required.
130
130
  Choose exactly one of `route` and `execute`. Input schemas must describe an object;
131
131
  plain JSON Schema and Standard JSON Schema providers are supported. Arguments are
132
132
  validated before execution, and tool names must be unique across sources.
133
- Handlers can return JSON, text, a `Response`, or an SDK `CallToolResult` for custom
133
+ Handlers can return JSON, text, a `Response`, or a `CallToolResult` for custom
134
134
  multimodal content. JSON objects from direct handlers also become structured
135
135
  content. HTTP error responses and thrown responses become tool errors; unexpected
136
136
  exceptions return a generic tool error without disclosing internal details.
@@ -149,10 +149,9 @@ hosts in your deployment. Model Context Protocol clients without an Origin heade
149
149
 
150
150
  ### Connect to a server
151
151
 
152
- Connect to a Model Context Protocol server using Streamable HTTP. Headers,
153
- an SDK OAuth provider, a custom fetch implementation,
154
- and custom SDK transports are supported. Keep credentials on the server and only
155
- connect to trusted endpoints.
152
+ Connect to a Model Context Protocol server using Streamable HTTP. Headers and a custom
153
+ fetch implementation are supported; OAuth and stdio transports are not. Keep credentials on
154
+ the server and only connect to trusted endpoints.
156
155
 
157
156
  ```ts
158
157
  import { useAi, useModelContextProtocol } from "@revojs/ai";
@@ -187,17 +186,14 @@ transport/protocol errors throw. Tool execution is explicit: apply authorization
187
186
  or approval checks before calling it. The application controls the conversation
188
187
  loop, stream consumption, and tool namespacing/routing when combining multiple servers.
189
188
 
190
- Use `modelContextProtocol.client.callTool({ name, arguments })` for the original SDK result.
191
- Use `modelContextProtocol.client` for SDK
192
- operations such as `listResources()`, `readResource({ uri })`, `listPrompts()`, and
193
- `getPrompt({ name, arguments })`. SDK request options support cancellation and
194
- timeouts; `useModelContextProtocol` accepts them as `requestOptions`, and the tool methods take
195
- them as their last argument.
196
-
197
- For local stdio servers, create a `StdioClientTransport` from the Model Context Protocol SDK in
198
- Node/Bun application code and pass it as `transport`. The runtime entry itself
199
- does not import Node process APIs. Always close the connection when finished;
200
- HTTP sessions are terminated before disconnecting.
189
+ `modelContextProtocol.client` is the underlying `ModelContextProtocolClient`:
190
+ `client.listTools({ cursor })` returns one raw page (including tool annotations),
191
+ `client.callTool({ name, arguments })` the raw result, and `client.request(method, params)`
192
+ sends any other JSON-RPC request such as `resources/list` or `prompts/get`. Request options
193
+ (`{ signal, timeout }`, default timeout 60 seconds) are accepted by `useModelContextProtocol`
194
+ as `requestOptions` and by every method as its last argument; aborted requests send
195
+ `notifications/cancelled`. Always close the connection when finished; HTTP sessions are
196
+ terminated with `DELETE` before disconnecting.
201
197
 
202
198
  ## OpenCode Go
203
199
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,3 @@
1
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
- import { type StreamableHTTPClientTransportOptions } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
- import type { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
4
- import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
5
- import { type CallToolResult, type ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
6
1
  import { type HttpMethod, type Route } from "@revojs/server";
7
2
  import { type Scope, type Source, type SourceScope, type SourceValue } from "revojs";
8
3
  import { type Awaitable, type JSONObject, type JsonSchema, type JSONValue } from "revojs/shared";
@@ -11,7 +6,10 @@ export type Tool<T extends JSONObject = JSONObject> = FunctionTool<T> | RouteToo
11
6
  export type ToolHandler<T extends JSONObject = JSONObject> = (scope: Scope, input: T, options: ToolExecutionOptions) => Awaitable<ToolResult>;
12
7
  export type ToolRouteResolver<T extends JSONObject = JSONObject> = (scope: Scope, input: T, options: ToolExecutionOptions) => Awaitable<ToolRequest>;
13
8
  export type ToolRoute<T extends JSONObject = JSONObject> = string | ToolRequest | Route | ToolRouteResolver<T>;
14
- export type ModelContextProtocolConnectOptions = ModelContextProtocolHttpConnectOptions | ModelContextProtocolTransportConnectOptions;
9
+ export type ModelContextProtocolContent = ModelContextProtocolTextContent | ModelContextProtocolImageContent | ModelContextProtocolAudioContent | ModelContextProtocolResourceLink | ModelContextProtocolEmbeddedResource;
10
+ export type ModelContextProtocolFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
11
+ export type JsonRpcId = string | number;
12
+ export type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse;
15
13
  export type SharedWarning = {
16
14
  type: "unsupported" | "compatibility";
17
15
  feature: string;
@@ -72,24 +70,105 @@ export interface ToolRequest {
72
70
  body?: JSONValue;
73
71
  headers?: HeadersInit;
74
72
  }
75
- export interface ModelContextProtocolClientInfo {
73
+ export interface ModelContextProtocolImplementation {
76
74
  name: string;
77
75
  version: string;
78
76
  }
79
- export interface ModelContextProtocolConnectionOptions {
80
- clientInfo?: ModelContextProtocolClientInfo;
81
- requestOptions?: RequestOptions;
77
+ export interface ModelContextProtocolRequestOptions {
78
+ signal?: AbortSignal;
79
+ timeout?: number;
82
80
  }
83
- export interface ModelContextProtocolHttpConnectOptions extends ModelContextProtocolConnectionOptions {
81
+ export interface ModelContextProtocolConnectOptions {
84
82
  endpoint: string | URL;
85
- transport?: "http";
86
83
  headers?: Record<string, string>;
87
- authProvider?: StreamableHTTPClientTransportOptions["authProvider"];
88
- fetch?: StreamableHTTPClientTransportOptions["fetch"];
84
+ fetch?: ModelContextProtocolFetch;
85
+ clientInfo?: ModelContextProtocolImplementation;
86
+ requestOptions?: ModelContextProtocolRequestOptions;
87
+ }
88
+ export interface ToolAnnotations {
89
+ title?: string;
90
+ readOnlyHint?: boolean;
91
+ destructiveHint?: boolean;
92
+ idempotentHint?: boolean;
93
+ openWorldHint?: boolean;
94
+ }
95
+ export interface ModelContextProtocolTextContent {
96
+ type: "text";
97
+ text: string;
98
+ }
99
+ export interface ModelContextProtocolImageContent {
100
+ type: "image";
101
+ data: string;
102
+ mimeType: string;
103
+ }
104
+ export interface ModelContextProtocolAudioContent {
105
+ type: "audio";
106
+ data: string;
107
+ mimeType: string;
108
+ }
109
+ export interface ModelContextProtocolResourceLink {
110
+ type: "resource_link";
111
+ uri: string;
112
+ name: string;
113
+ description?: string;
114
+ mimeType?: string;
115
+ }
116
+ export interface ModelContextProtocolResourceContents {
117
+ uri: string;
118
+ mimeType?: string;
119
+ text?: string;
120
+ blob?: string;
121
+ }
122
+ export interface ModelContextProtocolEmbeddedResource {
123
+ type: "resource";
124
+ resource: ModelContextProtocolResourceContents;
125
+ }
126
+ export interface CallToolResult {
127
+ content: Array<ModelContextProtocolContent>;
128
+ structuredContent?: JSONObject;
129
+ isError?: boolean;
130
+ }
131
+ export interface CallToolParams {
132
+ name: string;
133
+ arguments?: JSONObject;
89
134
  }
90
- export interface ModelContextProtocolTransportConnectOptions extends ModelContextProtocolConnectionOptions {
91
- transport: Transport;
92
- endpoint?: never;
135
+ export interface ListToolsParams {
136
+ cursor?: string;
137
+ }
138
+ export interface ModelContextProtocolTool {
139
+ name: string;
140
+ title?: string;
141
+ description?: string;
142
+ inputSchema: JSONObject;
143
+ outputSchema?: JSONObject;
144
+ annotations?: ToolAnnotations;
145
+ }
146
+ export interface ListToolsResult {
147
+ tools: Array<ModelContextProtocolTool>;
148
+ nextCursor?: string;
149
+ }
150
+ export interface InitializeResult {
151
+ protocolVersion: string;
152
+ capabilities: JSONObject;
153
+ serverInfo: ModelContextProtocolImplementation;
154
+ instructions?: string;
155
+ }
156
+ export interface JsonRpcRequest {
157
+ jsonrpc: "2.0";
158
+ id?: JsonRpcId;
159
+ method: string;
160
+ params?: JSONObject;
161
+ }
162
+ export interface JsonRpcError {
163
+ code: number;
164
+ message: string;
165
+ data?: JSONValue;
166
+ }
167
+ export interface JsonRpcResponse {
168
+ jsonrpc: "2.0";
169
+ id: JsonRpcId | null;
170
+ result?: unknown;
171
+ error?: JsonRpcError;
93
172
  }
94
173
  export interface LanguageModelText {
95
174
  type: "text";
@@ -737,12 +816,39 @@ export interface SkillsUploadSkillResult {
737
816
  export interface Provider {
738
817
  setup: (scope: Scope) => Awaitable<ProviderConnection>;
739
818
  }
819
+ export declare class ModelContextProtocolError extends Error {
820
+ readonly code: number;
821
+ readonly data?: JSONValue;
822
+ constructor(code: number, message: string, data?: JSONValue);
823
+ }
824
+ export declare class ModelContextProtocolClient {
825
+ readonly endpoint: URL;
826
+ sessionId?: string;
827
+ protocolVersion?: string;
828
+ serverInfo?: ModelContextProtocolImplementation;
829
+ capabilities?: JSONObject;
830
+ instructions?: string;
831
+ private readonly headers;
832
+ private readonly fetch;
833
+ private readonly clientInfo;
834
+ private readonly requestOptions;
835
+ private requests;
836
+ constructor(options: ModelContextProtocolConnectOptions);
837
+ connect(options?: ModelContextProtocolRequestOptions): Promise<void>;
838
+ listTools(params?: ListToolsParams, options?: ModelContextProtocolRequestOptions): Promise<ListToolsResult>;
839
+ callTool(params: CallToolParams, options?: ModelContextProtocolRequestOptions): Promise<CallToolResult>;
840
+ request<T>(method: string, params?: JSONObject, options?: ModelContextProtocolRequestOptions): Promise<T>;
841
+ notify(method: string, params?: JSONObject): Promise<void>;
842
+ close(): Promise<void>;
843
+ private send;
844
+ private receive;
845
+ private answer;
846
+ }
740
847
  export declare class ModelContextProtocolConnection {
741
- readonly client: Client;
742
- private readonly transport;
743
- constructor(client: Client, transport: Transport);
744
- listTools(options?: RequestOptions): Promise<Array<LanguageModelFunctionTool>>;
745
- executeTool(call: LanguageModelToolCall, options?: RequestOptions): Promise<LanguageModelToolResultPart>;
848
+ readonly client: ModelContextProtocolClient;
849
+ constructor(client: ModelContextProtocolClient);
850
+ listTools(options?: ModelContextProtocolRequestOptions): Promise<Array<LanguageModelFunctionTool>>;
851
+ executeTool(call: LanguageModelToolCall, options?: ModelContextProtocolRequestOptions): Promise<LanguageModelToolResultPart>;
746
852
  close(): Promise<void>;
747
853
  }
748
854
  export declare function defineTool<T extends JSONObject = JSONObject>(tool: Tool<T>): Tool<T>;
@@ -769,3 +875,12 @@ export declare function postJson(url: string, body: unknown, options?: {
769
875
  }): Promise<Response>;
770
876
  export declare function readDataFrames(response: Response): AsyncGenerator<string>;
771
877
  export declare function toStream<T>(read: () => AsyncGenerator<T>): ReadableStream<T>;
878
+ export declare const MODEL_CONTEXT_PROTOCOL_VERSIONS: string[];
879
+ export declare const MODEL_CONTEXT_PROTOCOL_TIMEOUT = 60000;
880
+ export declare const ModelContextProtocolErrorCode: {
881
+ readonly ParseError: -32700;
882
+ readonly InvalidRequest: -32600;
883
+ readonly MethodNotFound: -32601;
884
+ readonly InvalidParams: -32602;
885
+ readonly InternalError: -32603;
886
+ };
package/dist/index.js CHANGED
@@ -1,22 +1,181 @@
1
1
  import config from "#config/ai";
2
2
  import providers from "#providers";
3
3
  import tools from "#tools";
4
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
5
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
6
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
- import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
8
- import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
9
- import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker-provider.js";
4
+ import { Validator } from "@cfworker/json-schema";
10
5
  import { SERVER_CONTEXT, createServerScope, isResponse, requireSameOrigin, useServer } from "@revojs/server";
11
6
  import { useVariables } from "revojs";
12
7
  import { toJsonSchema } from "revojs/shared";
13
8
  //#region src/index.ts
9
+ var ModelContextProtocolError = class extends Error {
10
+ code;
11
+ data;
12
+ constructor(code, message, data) {
13
+ super(message);
14
+ this.name = "ModelContextProtocolError";
15
+ this.code = code;
16
+ this.data = data;
17
+ }
18
+ };
19
+ var ModelContextProtocolClient = class {
20
+ endpoint;
21
+ sessionId;
22
+ protocolVersion;
23
+ serverInfo;
24
+ capabilities;
25
+ instructions;
26
+ headers;
27
+ fetch;
28
+ clientInfo;
29
+ requestOptions;
30
+ requests = 0;
31
+ constructor(options) {
32
+ this.endpoint = new URL(options.endpoint);
33
+ this.headers = options.headers ?? {};
34
+ this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
35
+ this.clientInfo = options.clientInfo ?? {
36
+ name: "revojs",
37
+ version: "1.0.0"
38
+ };
39
+ this.requestOptions = options.requestOptions ?? {};
40
+ }
41
+ async connect(options) {
42
+ const result = await this.request("initialize", {
43
+ protocolVersion: MODEL_CONTEXT_PROTOCOL_VERSIONS[0],
44
+ capabilities: {},
45
+ clientInfo: {
46
+ name: this.clientInfo.name,
47
+ version: this.clientInfo.version
48
+ }
49
+ }, options);
50
+ if (!MODEL_CONTEXT_PROTOCOL_VERSIONS.includes(result.protocolVersion)) throw new Error(`Unsupported Model Context Protocol version "${result.protocolVersion}".`);
51
+ this.protocolVersion = result.protocolVersion;
52
+ this.serverInfo = result.serverInfo;
53
+ this.capabilities = result.capabilities;
54
+ this.instructions = result.instructions;
55
+ await this.notify("notifications/initialized");
56
+ }
57
+ async listTools(params = {}, options) {
58
+ const request = {};
59
+ if (params.cursor !== void 0) request.cursor = params.cursor;
60
+ return await this.request("tools/list", request, options);
61
+ }
62
+ async callTool(params, options) {
63
+ return await this.request("tools/call", {
64
+ name: params.name,
65
+ arguments: params.arguments ?? {}
66
+ }, options);
67
+ }
68
+ async request(method, params = {}, options = this.requestOptions) {
69
+ this.requests += 1;
70
+ const id = this.requests;
71
+ const signals = [AbortSignal.timeout(options.timeout ?? 6e4)];
72
+ if (options.signal) signals.push(options.signal);
73
+ const signal = AbortSignal.any(signals);
74
+ try {
75
+ const response = await this.send({
76
+ jsonrpc: "2.0",
77
+ id,
78
+ method,
79
+ params
80
+ }, signal);
81
+ const message = await this.receive(response, id);
82
+ if (message.error) throw new ModelContextProtocolError(message.error.code, message.error.message, message.error.data);
83
+ return message.result;
84
+ } catch (error) {
85
+ if (signal.aborted && this.protocolVersion !== void 0) (async () => {
86
+ try {
87
+ await this.notify("notifications/cancelled", {
88
+ requestId: id,
89
+ reason: "Request aborted"
90
+ });
91
+ } catch {}
92
+ })();
93
+ throw error;
94
+ }
95
+ }
96
+ async notify(method, params) {
97
+ const notification = {
98
+ jsonrpc: "2.0",
99
+ method
100
+ };
101
+ if (params !== void 0) notification.params = params;
102
+ await (await this.send(notification)).body?.cancel();
103
+ }
104
+ async close() {
105
+ const sessionId = this.sessionId;
106
+ this.sessionId = void 0;
107
+ if (sessionId === void 0) return;
108
+ const response = await this.fetch(this.endpoint, {
109
+ method: "DELETE",
110
+ headers: {
111
+ ...this.headers,
112
+ "mcp-session-id": sessionId
113
+ }
114
+ });
115
+ await response.body?.cancel();
116
+ if (!response.ok && response.status !== 405) throw new Error(`Model Context Protocol session termination failed with HTTP ${response.status}.`);
117
+ }
118
+ async send(message, signal) {
119
+ const headers = new Headers(this.headers);
120
+ headers.set("content-type", "application/json");
121
+ headers.set("accept", "application/json, text/event-stream");
122
+ if (this.sessionId !== void 0) headers.set("mcp-session-id", this.sessionId);
123
+ if (this.protocolVersion !== void 0) headers.set("mcp-protocol-version", this.protocolVersion);
124
+ const response = await this.fetch(this.endpoint, {
125
+ method: "POST",
126
+ headers,
127
+ body: JSON.stringify(message),
128
+ signal
129
+ });
130
+ if (!response.ok) {
131
+ await response.body?.cancel();
132
+ throw new Error(`Model Context Protocol request failed with HTTP ${response.status}.`);
133
+ }
134
+ const sessionId = response.headers.get("mcp-session-id");
135
+ if (sessionId !== null) this.sessionId = sessionId;
136
+ return response;
137
+ }
138
+ async receive(response, id) {
139
+ if (response.headers.get("content-type")?.includes("text/event-stream")) {
140
+ for await (const data of readDataFrames(response)) {
141
+ if (data === "") continue;
142
+ const message = JSON.parse(data);
143
+ if ("method" in message) {
144
+ await this.answer(message);
145
+ continue;
146
+ }
147
+ if (message.id === id) return message;
148
+ }
149
+ throw new Error("Model Context Protocol stream ended without a response.");
150
+ }
151
+ const body = await response.json();
152
+ const messages = new Array();
153
+ if (Array.isArray(body)) for (const message of body) messages.push(message);
154
+ else messages.push(body);
155
+ for (const message of messages) {
156
+ if ("method" in message) continue;
157
+ if (message.id === id) return message;
158
+ }
159
+ throw new Error("Model Context Protocol response does not contain the requested result.");
160
+ }
161
+ async answer(request) {
162
+ if (request.id === void 0) return;
163
+ const reply = {
164
+ jsonrpc: "2.0",
165
+ id: request.id
166
+ };
167
+ if (request.method === "ping") reply.result = {};
168
+ else reply.error = {
169
+ code: ModelContextProtocolErrorCode.MethodNotFound,
170
+ message: `Method "${request.method}" is not supported by this client.`
171
+ };
172
+ await (await this.send(reply)).body?.cancel();
173
+ }
174
+ };
14
175
  var ModelContextProtocolConnection = class {
15
176
  client;
16
- transport;
17
- constructor(client, transport) {
177
+ constructor(client) {
18
178
  this.client = client;
19
- this.transport = transport;
20
179
  }
21
180
  async listTools(options) {
22
181
  const tools = new Array();
@@ -42,7 +201,7 @@ var ModelContextProtocolConnection = class {
42
201
  const result = await this.client.callTool({
43
202
  name: call.toolName,
44
203
  arguments: input
45
- }, void 0, options);
204
+ }, options);
46
205
  return {
47
206
  type: "tool-result",
48
207
  toolCallId: call.toolCallId,
@@ -54,11 +213,7 @@ var ModelContextProtocolConnection = class {
54
213
  };
55
214
  }
56
215
  async close() {
57
- try {
58
- if (this.transport instanceof StreamableHTTPClientTransport && this.transport.sessionId !== void 0) await this.transport.terminateSession();
59
- } finally {
60
- await this.client.close();
61
- }
216
+ await this.client.close();
62
217
  }
63
218
  };
64
219
  function defineTool(tool) {
@@ -73,13 +228,14 @@ async function toToolResult(output) {
73
228
  isError: !output.ok
74
229
  };
75
230
  if (output !== null && typeof output === "object" && "content" in output && Array.isArray(output.content)) return output;
76
- return {
77
- content: [{
78
- type: "text",
79
- text: typeof output === "string" ? output : JSON.stringify(output ?? null)
80
- }],
81
- ...output !== null && typeof output === "object" && !Array.isArray(output) ? { structuredContent: output } : {}
82
- };
231
+ let text = JSON.stringify(output ?? null);
232
+ if (typeof output === "string") text = output;
233
+ const result = { content: [{
234
+ type: "text",
235
+ text
236
+ }] };
237
+ if (output !== null && typeof output === "object" && !Array.isArray(output)) result.structuredContent = output;
238
+ return result;
83
239
  }
84
240
  function useTools(scope) {
85
241
  let loading;
@@ -92,17 +248,18 @@ function useTools(scope) {
92
248
  }
93
249
  const definitions = await loading;
94
250
  if (registered) return registered;
95
- const validator = new CfWorkerJsonSchemaValidator();
96
251
  const entries = /* @__PURE__ */ new Map();
97
252
  for (const tool of definitions) {
98
253
  if (!/^[A-Za-z0-9_.-]{1,128}$/.test(tool.name)) throw new Error(`Invalid Model Context Protocol tool name "${tool.name}".`);
99
254
  if (entries.has(tool.name)) throw new Error("Model Context Protocol tool names must be unique across all tool sources.");
100
255
  const schema = toJsonSchema(tool.inputSchema);
101
256
  if (schema.type !== "object") throw new Error(`Model Context Protocol tool "${tool.name}" must have an object input schema.`);
257
+ let draft = "2020-12";
258
+ if (typeof schema.$schema === "string" && JSON_SCHEMA_DRAFTS[schema.$schema]) draft = JSON_SCHEMA_DRAFTS[schema.$schema];
102
259
  entries.set(tool.name, {
103
260
  tool,
104
261
  schema,
105
- validate: validator.getValidator(schema)
262
+ validator: new Validator(schema, draft, true)
106
263
  });
107
264
  }
108
265
  registered = entries;
@@ -111,14 +268,20 @@ function useTools(scope) {
111
268
  const callTool = async (name, input = {}, options = {}) => {
112
269
  options.signal?.throwIfAborted();
113
270
  const entry = (await resolveTools()).get(name);
114
- if (!entry) throw new McpError(ErrorCode.InvalidParams, `Unknown tool "${name}".`);
115
- const result = entry.validate(input);
116
- if (!result.valid) throw new McpError(ErrorCode.InvalidParams, result.errorMessage ?? "Invalid tool arguments.");
271
+ if (!entry) throw new ModelContextProtocolError(ModelContextProtocolErrorCode.InvalidParams, `Unknown tool "${name}".`);
272
+ const validation = entry.validator.validate(input);
273
+ if (!validation.valid) {
274
+ const errors = new Array();
275
+ for (const error of validation.errors) errors.push(`${error.instanceLocation}: ${error.error}`);
276
+ let message = "Invalid tool arguments.";
277
+ if (errors.length > 0) message = errors.join("; ");
278
+ throw new ModelContextProtocolError(ModelContextProtocolErrorCode.InvalidParams, message);
279
+ }
117
280
  options.signal?.throwIfAborted();
118
281
  try {
119
282
  const { tool } = entry;
120
283
  if (tool.execute) {
121
- const output = await tool.execute(scope, result.data, options);
284
+ const output = await tool.execute(scope, input, options);
122
285
  options.signal?.throwIfAborted();
123
286
  return await toToolResult(output);
124
287
  }
@@ -127,7 +290,7 @@ function useTools(scope) {
127
290
  const signal = options.signal ? AbortSignal.any([request.signal, options.signal]) : request.signal;
128
291
  signal.throwIfAborted();
129
292
  let target = tool.route;
130
- if (typeof target === "function") target = await target(scope, result.data, { signal });
293
+ if (typeof target === "function") target = await target(scope, input, { signal });
131
294
  let direct;
132
295
  let targetOptions;
133
296
  if (typeof target === "string") targetOptions = { path: target };
@@ -140,8 +303,15 @@ function useTools(scope) {
140
303
  if (!direct && (url.pathname === "/v1/model-context-protocol" || url.pathname === "/v1/model-context-protocol/")) throw new Error("A tool cannot call the Model Context Protocol endpoint.");
141
304
  const method = targetOptions.method ?? "POST";
142
305
  const readOnly = method === "GET" || method === "HEAD";
143
- const query = targetOptions.query ?? (readOnly ? result.data : {});
144
- for (const [name, value] of query instanceof URLSearchParams ? query : Object.entries(query)) if (value !== void 0) url.searchParams.append(name, typeof value === "string" ? value : JSON.stringify(value));
306
+ let query = targetOptions.query ?? {};
307
+ if (targetOptions.query === void 0 && readOnly) query = input;
308
+ let parameters = Object.entries(query);
309
+ if (query instanceof URLSearchParams) parameters = query;
310
+ for (const [name, value] of parameters) {
311
+ if (value === void 0) continue;
312
+ if (typeof value === "string") url.searchParams.append(name, value);
313
+ else url.searchParams.append(name, JSON.stringify(value));
314
+ }
145
315
  const headers = new Headers(targetOptions.headers);
146
316
  for (const name of [
147
317
  "authorization",
@@ -155,7 +325,9 @@ function useTools(scope) {
155
325
  let body;
156
326
  if (!readOnly) {
157
327
  headers.set("content-type", "application/json");
158
- body = JSON.stringify("body" in targetOptions ? targetOptions.body : result.data);
328
+ let payload = input;
329
+ if ("body" in targetOptions) payload = targetOptions.body;
330
+ body = JSON.stringify(payload);
159
331
  }
160
332
  const nextRequest = new Request(url, {
161
333
  method,
@@ -192,7 +364,7 @@ function useTools(scope) {
192
364
  callTool,
193
365
  async executeTool(call, options) {
194
366
  const input = JSON.parse(call.input);
195
- if (input === null || typeof input !== "object" || Array.isArray(input)) throw new McpError(ErrorCode.InvalidParams, `Tool "${call.toolName}" requires a JSON object as input.`);
367
+ if (input === null || typeof input !== "object" || Array.isArray(input)) throw new ModelContextProtocolError(ModelContextProtocolErrorCode.InvalidParams, `Tool "${call.toolName}" requires a JSON object as input.`);
196
368
  const result = await callTool(call.toolName, input, options);
197
369
  return {
198
370
  type: "tool-result",
@@ -213,67 +385,149 @@ async function handleModelContextProtocol(scope, options = {}) {
213
385
  status: 405,
214
386
  headers: { Allow: "POST" }
215
387
  });
388
+ const version = request.headers.get("mcp-protocol-version");
389
+ if (version !== null && !MODEL_CONTEXT_PROTOCOL_VERSIONS.includes(version)) return Response.json({
390
+ jsonrpc: "2.0",
391
+ id: null,
392
+ error: {
393
+ code: ModelContextProtocolErrorCode.InvalidRequest,
394
+ message: `Unsupported Model Context Protocol version "${version}".`
395
+ }
396
+ }, { status: 400 });
397
+ let body;
398
+ try {
399
+ body = await request.json();
400
+ } catch {
401
+ return Response.json({
402
+ jsonrpc: "2.0",
403
+ id: null,
404
+ error: {
405
+ code: ModelContextProtocolErrorCode.ParseError,
406
+ message: "Parse error."
407
+ }
408
+ }, { status: 400 });
409
+ }
216
410
  const collection = useTools(scope);
217
- const server = new McpServer({
218
- name: config.modelContextProtocol.name,
219
- version: config.modelContextProtocol.version
220
- }, {
221
- capabilities: { tools: {} },
222
- instructions: config.modelContextProtocol.instructions,
223
- jsonSchemaValidator: new CfWorkerJsonSchemaValidator()
224
- });
225
- const transport = new WebStandardStreamableHTTPServerTransport({ enableJsonResponse: true });
226
411
  const listTools = async () => {
227
- const tools = await collection.listTools();
228
- return options.filter ? tools.filter(options.filter) : tools;
412
+ const visible = new Array();
413
+ for (const tool of await collection.listTools()) if (!options.filter || options.filter(tool)) visible.push(tool);
414
+ return visible;
229
415
  };
230
- server.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: (await listTools()).map((tool) => ({
231
- name: tool.name,
232
- description: tool.description,
233
- inputSchema: tool.inputSchema,
234
- annotations: tool.annotations
235
- })) }));
236
- server.server.setRequestHandler(CallToolRequestSchema, async ({ params }, extra) => {
237
- if (options.filter && !(await listTools()).some((tool) => tool.name === params.name)) throw new McpError(ErrorCode.InvalidParams, `Unknown tool "${params.name}".`);
416
+ const batch = Array.isArray(body);
417
+ const entries = new Array();
418
+ if (Array.isArray(body)) for (const entry of body) entries.push(entry);
419
+ else entries.push(body);
420
+ const responses = new Array();
421
+ for (const entry of entries) {
422
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
423
+ responses.push({
424
+ jsonrpc: "2.0",
425
+ id: null,
426
+ error: {
427
+ code: ModelContextProtocolErrorCode.InvalidRequest,
428
+ message: "Invalid request."
429
+ }
430
+ });
431
+ continue;
432
+ }
433
+ const message = entry;
434
+ if (typeof message.method !== "string" || message.id === void 0) continue;
435
+ const params = message.params ?? {};
238
436
  try {
239
- return await collection.callTool(params.name, params.arguments ?? {}, { signal: AbortSignal.any([request.signal, extra.signal]) });
437
+ let result;
438
+ switch (message.method) {
439
+ case "initialize": {
440
+ const requested = params.protocolVersion;
441
+ let protocolVersion = MODEL_CONTEXT_PROTOCOL_VERSIONS[0];
442
+ if (typeof requested === "string" && MODEL_CONTEXT_PROTOCOL_VERSIONS.includes(requested)) protocolVersion = requested;
443
+ result = {
444
+ protocolVersion,
445
+ capabilities: { tools: {} },
446
+ serverInfo: {
447
+ name: config.modelContextProtocol.name,
448
+ version: config.modelContextProtocol.version
449
+ },
450
+ instructions: config.modelContextProtocol.instructions
451
+ };
452
+ break;
453
+ }
454
+ case "ping":
455
+ result = {};
456
+ break;
457
+ case "tools/list": {
458
+ const tools = new Array();
459
+ for (const tool of await listTools()) tools.push({
460
+ name: tool.name,
461
+ description: tool.description,
462
+ inputSchema: tool.inputSchema,
463
+ annotations: tool.annotations
464
+ });
465
+ result = { tools };
466
+ break;
467
+ }
468
+ case "tools/call": {
469
+ const name = params.name;
470
+ const input = params.arguments ?? {};
471
+ if (typeof name !== "string" || input === null || typeof input !== "object" || Array.isArray(input)) throw new ModelContextProtocolError(ModelContextProtocolErrorCode.InvalidParams, "Tool calls require a name and object arguments.");
472
+ if (options.filter) {
473
+ let visible = false;
474
+ for (const tool of await listTools()) if (tool.name === name) {
475
+ visible = true;
476
+ break;
477
+ }
478
+ if (!visible) throw new ModelContextProtocolError(ModelContextProtocolErrorCode.InvalidParams, `Unknown tool "${name}".`);
479
+ }
480
+ try {
481
+ result = await collection.callTool(name, input, { signal: request.signal });
482
+ } catch (error) {
483
+ if (error instanceof ModelContextProtocolError) throw error;
484
+ result = {
485
+ content: [{
486
+ type: "text",
487
+ text: "Tool execution failed."
488
+ }],
489
+ isError: true
490
+ };
491
+ }
492
+ break;
493
+ }
494
+ default: throw new ModelContextProtocolError(ModelContextProtocolErrorCode.MethodNotFound, `Method "${message.method}" not found.`);
495
+ }
496
+ responses.push({
497
+ jsonrpc: "2.0",
498
+ id: message.id,
499
+ result
500
+ });
240
501
  } catch (error) {
241
- if (error instanceof McpError) throw error;
242
- return {
243
- content: [{
244
- type: "text",
245
- text: "Tool execution failed."
246
- }],
247
- isError: true
502
+ let failure = {
503
+ code: ModelContextProtocolErrorCode.InternalError,
504
+ message: "Internal error."
505
+ };
506
+ if (error instanceof ModelContextProtocolError) failure = {
507
+ code: error.code,
508
+ message: error.message,
509
+ data: error.data
248
510
  };
511
+ responses.push({
512
+ jsonrpc: "2.0",
513
+ id: message.id,
514
+ error: failure
515
+ });
249
516
  }
250
- });
251
- try {
252
- await server.connect(transport);
253
- return await transport.handleRequest(request);
254
- } finally {
255
- await server.close();
256
517
  }
518
+ if (responses.length === 0) return new Response(null, { status: 202 });
519
+ if (batch) return Response.json(responses);
520
+ return Response.json(responses[0]);
257
521
  }
258
522
  async function useModelContextProtocol(options) {
259
- const client = new Client(options.clientInfo ?? {
260
- name: "revojs",
261
- version: "1.0.0"
262
- });
263
- let transport = options.transport;
264
- if (typeof transport !== "object") {
265
- if (options.endpoint === void 0) throw new Error("Model Context Protocol requires an endpoint or a custom transport.");
266
- transport = new StreamableHTTPClientTransport(new URL(options.endpoint), {
267
- authProvider: options.authProvider,
268
- fetch: options.fetch,
269
- requestInit: { headers: options.headers }
270
- });
271
- }
523
+ const client = new ModelContextProtocolClient(options);
272
524
  try {
273
- await client.connect(transport, options.requestOptions);
274
- return new ModelContextProtocolConnection(client, transport);
525
+ await client.connect();
526
+ return new ModelContextProtocolConnection(client);
275
527
  } catch (error) {
276
- await client.close().catch(() => {});
528
+ try {
529
+ await client.close();
530
+ } catch {}
277
531
  throw error;
278
532
  }
279
533
  }
@@ -398,8 +652,15 @@ async function* readDataFrames(response) {
398
652
  buffer += decoder.decode(chunk, { stream: true });
399
653
  const frames = buffer.split(/\r?\n\r?\n/);
400
654
  buffer = frames.pop() ?? "";
401
- for (const frame of frames) for (const line of frame.split(/\r?\n/)) if (line.startsWith("data:")) yield line.slice(5).trim();
655
+ for (const frame of frames) {
656
+ const lines = new Array();
657
+ for (const line of frame.split(/\r?\n/)) if (line.startsWith("data:")) lines.push(line.slice(5).trim());
658
+ if (lines.length > 0) yield lines.join("\n");
659
+ }
402
660
  }
661
+ const lines = new Array();
662
+ for (const line of buffer.split(/\r?\n/)) if (line.startsWith("data:")) lines.push(line.slice(5).trim());
663
+ if (lines.length > 0) yield lines.join("\n");
403
664
  }
404
665
  function toStream(read) {
405
666
  const iterator = read();
@@ -423,5 +684,27 @@ function toStream(read) {
423
684
  });
424
685
  }
425
686
  var modelsDevCache = { loadedAt: 0 };
687
+ var MODEL_CONTEXT_PROTOCOL_VERSIONS = [
688
+ "2025-11-25",
689
+ "2025-06-18",
690
+ "2025-03-26",
691
+ "2024-11-05"
692
+ ];
693
+ var MODEL_CONTEXT_PROTOCOL_TIMEOUT = 6e4;
694
+ var ModelContextProtocolErrorCode = {
695
+ ParseError: -32700,
696
+ InvalidRequest: -32600,
697
+ MethodNotFound: -32601,
698
+ InvalidParams: -32602,
699
+ InternalError: -32603
700
+ };
701
+ var JSON_SCHEMA_DRAFTS = {
702
+ "https://json-schema.org/draft/2020-12/schema": "2020-12",
703
+ "https://json-schema.org/draft/2019-09/schema": "2019-09",
704
+ "http://json-schema.org/draft-07/schema": "7",
705
+ "http://json-schema.org/draft-07/schema#": "7",
706
+ "http://json-schema.org/draft-04/schema": "4",
707
+ "http://json-schema.org/draft-04/schema#": "4"
708
+ };
426
709
  //#endregion
427
- export { ModelContextProtocolConnection, defineProvider, defineTool, handleModelContextProtocol, mergeHeaders, modelsDevProtocol, postJson, readDataFrames, toStream, toToolInput, useAi, useModelContextProtocol, useModelsDev, useTools };
710
+ export { MODEL_CONTEXT_PROTOCOL_TIMEOUT, MODEL_CONTEXT_PROTOCOL_VERSIONS, ModelContextProtocolClient, ModelContextProtocolConnection, ModelContextProtocolError, ModelContextProtocolErrorCode, defineProvider, defineTool, handleModelContextProtocol, mergeHeaders, modelsDevProtocol, postJson, readDataFrames, toStream, toToolInput, useAi, useModelContextProtocol, useModelsDev, useTools };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revojs/ai",
3
- "version": "0.2.25",
3
+ "version": "0.2.27",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,7 +30,6 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@cfworker/json-schema": "^4.1.1",
33
- "@modelcontextprotocol/sdk": "^1.32.1",
34
33
  "@revojs/server": "*",
35
34
  "revojs": "*"
36
35
  },