@revojs/ai 0.2.16 → 0.2.17
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 +150 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +199 -2
- package/dist/module/index.d.ts +8 -2
- package/dist/module/index.js +20 -7
- package/dist/routes/v1/model-context-protocol/delete.d.ts +5 -0
- package/dist/routes/v1/model-context-protocol/delete.js +6 -0
- package/dist/routes/v1/model-context-protocol/get.d.ts +5 -0
- package/dist/routes/v1/model-context-protocol/get.js +6 -0
- package/dist/routes/v1/model-context-protocol/post.d.ts +5 -0
- package/dist/routes/v1/model-context-protocol/post.js +6 -0
- package/dist/types/index.d.ts +9 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -2,6 +2,153 @@
|
|
|
2
2
|
|
|
3
3
|
## Model Context Protocol
|
|
4
4
|
|
|
5
|
+
### Use local tools
|
|
6
|
+
|
|
7
|
+
`useTools(scope)` discovers the same `./tools` source used by the HTTP endpoint,
|
|
8
|
+
without opening a Model Context Protocol connection or making a network request.
|
|
9
|
+
Definitions and validators are loaded lazily and cached per returned collection.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { useAi, useTools } from "@revojs/ai";
|
|
13
|
+
|
|
14
|
+
const localTools = useTools(scope);
|
|
15
|
+
const { openai } = useAi(scope);
|
|
16
|
+
const result = await openai.textGenerate({
|
|
17
|
+
model,
|
|
18
|
+
prompt,
|
|
19
|
+
tools: await localTools.listTools(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
for (const call of result.content) {
|
|
23
|
+
if (call.type === "tool-call" && !call.providerExecuted) {
|
|
24
|
+
const output = await localTools.executeTool(call, { signal });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`listTools()` returns AI function-tool definitions. `executeTool()` accepts a
|
|
30
|
+
normalized model tool call and returns a `LanguageModelToolResultPart`, preserving
|
|
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
|
|
33
|
+
a model call. Both execution methods validate arguments before running a handler.
|
|
34
|
+
Unknown tools, invalid arguments and unexpected handler failures throw locally;
|
|
35
|
+
HTTP error responses and thrown responses become error results. The HTTP endpoint
|
|
36
|
+
uses the same collection and sanitizes unexpected execution errors at its boundary.
|
|
37
|
+
|
|
38
|
+
Function tools work with ordinary scopes, including Cloudflare Workflow scopes
|
|
39
|
+
without an HTTP request. They receive the caller's scope unchanged, so application
|
|
40
|
+
contexts can supply organization identity, conversation state and authorization.
|
|
41
|
+
Route tools require an HTTP request scope and retain the target route's normal
|
|
42
|
+
session requirements; they are not a workaround for user authentication.
|
|
43
|
+
|
|
44
|
+
Handlers and route resolvers receive `ToolExecutionOptions` as their third
|
|
45
|
+
argument. Its optional `signal` supports cooperative cancellation; pass it to
|
|
46
|
+
database/network/execution APIs that support cancellation. Route requests combine
|
|
47
|
+
it with the incoming request's signal. Already-started side effects cannot be
|
|
48
|
+
rolled back by aborting. Applications own deadlines, model rounds, approval checks,
|
|
49
|
+
tool-name routing, execution records and limits on spawned conversations.
|
|
50
|
+
|
|
51
|
+
### Build a server
|
|
52
|
+
|
|
53
|
+
The landing app includes two runnable examples: `apps/landing/tools/list-items.ts`
|
|
54
|
+
calls the existing `/api/items` route, while `apps/landing/tools/add.ts` directly
|
|
55
|
+
implements a function that adds two numbers. Both are available through
|
|
56
|
+
`/v1/model-context-protocol` when running the landing app.
|
|
57
|
+
|
|
58
|
+
The AI module includes `@revojs/server`, discovers default-exported tools from
|
|
59
|
+
`./tools/**/*.{js,ts}` as `#tools`, and exposes a stateless Streamable HTTP Model Context Protocol
|
|
60
|
+
endpoint at `/v1/model-context-protocol`. Only explicitly registered tools are exposed, not every HTTP route.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { defineTool } from "@revojs/ai";
|
|
64
|
+
|
|
65
|
+
export default defineTool({
|
|
66
|
+
name: "list-items",
|
|
67
|
+
description: "List available items.",
|
|
68
|
+
inputSchema: {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: { limit: { type: "integer", minimum: 1 } },
|
|
71
|
+
additionalProperties: false,
|
|
72
|
+
},
|
|
73
|
+
route: { path: "/api/items", method: "GET" },
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Route paths execute internally through the normal Server router and middleware.
|
|
78
|
+
String routes default to `POST`. Arguments become query parameters for `GET`/`HEAD`
|
|
79
|
+
or a JSON body for other methods. Incoming authorization, cookie, origin and
|
|
80
|
+
cross-site metadata headers are preserved unless explicitly overridden by the
|
|
81
|
+
tool definition; other headers must be supplied explicitly. No external requests
|
|
82
|
+
or redirects are followed, and tools cannot target `/v1/model-context-protocol` recursively.
|
|
83
|
+
|
|
84
|
+
Use a route resolver for custom path, query, body or header mapping:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { defineTool } from "@revojs/ai";
|
|
88
|
+
|
|
89
|
+
export default defineTool<{ id: string; title: string }>({
|
|
90
|
+
name: "update-item",
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: "object",
|
|
93
|
+
properties: { id: { type: "string" }, title: { type: "string" } },
|
|
94
|
+
required: ["id", "title"],
|
|
95
|
+
additionalProperties: false,
|
|
96
|
+
},
|
|
97
|
+
route: (scope, input) => ({
|
|
98
|
+
path: `/api/items/${encodeURIComponent(input.id)}`,
|
|
99
|
+
method: "PATCH",
|
|
100
|
+
body: { title: input.title },
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Alternatively, implement the tool directly with the current Model Context Protocol request scope:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { defineTool } from "@revojs/ai";
|
|
109
|
+
|
|
110
|
+
export default defineTool<{ message: string }>({
|
|
111
|
+
name: "echo",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: { message: { type: "string" } },
|
|
115
|
+
required: ["message"],
|
|
116
|
+
additionalProperties: false,
|
|
117
|
+
},
|
|
118
|
+
execute(scope, input) {
|
|
119
|
+
return { message: input.message };
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`route` also accepts an imported `defineRoute(...)` object. It receives a separate
|
|
125
|
+
POST request containing the tool arguments as JSON, sharing variables and states
|
|
126
|
+
with the Model Context Protocol request. This reuses the handler only: it does **not** execute the
|
|
127
|
+
route's original middleware or infer its original path parameters. Prefer a path
|
|
128
|
+
target when middleware or route parameters are required.
|
|
129
|
+
|
|
130
|
+
Choose exactly one of `route` and `execute`. Input schemas must describe an object;
|
|
131
|
+
plain JSON Schema and Standard JSON Schema providers are supported. Arguments are
|
|
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
|
|
134
|
+
multimodal content. JSON objects from direct handlers also become structured
|
|
135
|
+
content. HTTP error responses and thrown responses become tool errors; unexpected
|
|
136
|
+
exceptions return a generic tool error without disclosing internal details.
|
|
137
|
+
|
|
138
|
+
Configure server identity through `ai.modelContextProtocol.name`, `ai.modelContextProtocol.version`, and optional
|
|
139
|
+
`ai.modelContextProtocol.instructions` in the Vite module configuration. The endpoint uses JSON
|
|
140
|
+
responses without persistent sessions, server-initiated SSE, resources or prompts.
|
|
141
|
+
GET and DELETE are registered and return `405` with `Allow: POST` because there
|
|
142
|
+
are no persistent SSE streams or sessions to terminate.
|
|
143
|
+
|
|
144
|
+
Protect `/v1/model-context-protocol` with application middleware and enforce permissions in each tool
|
|
145
|
+
or its target route. Discovery exposes registered tool metadata to callers allowed
|
|
146
|
+
through that middleware. The endpoint rejects cross-origin requests by default;
|
|
147
|
+
this is not authentication or a complete DNS-rebinding defense. Validate allowed
|
|
148
|
+
hosts in your deployment. Model Context Protocol clients without an Origin header remain supported.
|
|
149
|
+
|
|
150
|
+
### Connect to a server
|
|
151
|
+
|
|
5
152
|
Connect to a Model Context Protocol server using Streamable HTTP. Headers,
|
|
6
153
|
an SDK OAuth provider, a custom fetch implementation,
|
|
7
154
|
and custom SDK transports are supported. Keep credentials on the server and only
|
|
@@ -10,7 +157,9 @@ connect to trusted endpoints.
|
|
|
10
157
|
```ts
|
|
11
158
|
import { useAi, useModelContextProtocol } from "@revojs/ai";
|
|
12
159
|
|
|
13
|
-
const modelContextProtocol = await useModelContextProtocol({
|
|
160
|
+
const modelContextProtocol = await useModelContextProtocol({
|
|
161
|
+
endpoint: "https://example.com/v1/model-context-protocol",
|
|
162
|
+
});
|
|
14
163
|
|
|
15
164
|
try {
|
|
16
165
|
const { openai } = useAi(scope);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,15 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
2
2
|
import { type StreamableHTTPClientTransportOptions } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
3
|
import type { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
4
4
|
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
5
|
+
import { type CallToolResult, type ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { type HttpMethod, type Route } from "@revojs/server";
|
|
5
7
|
import { type Scope, type Source, type SourceScope, type SourceValue } from "revojs";
|
|
6
|
-
import type
|
|
8
|
+
import { type Awaitable, type JSONObject, type JsonSchema, type JSONValue } from "revojs/shared";
|
|
9
|
+
export type ToolResult = JSONValue | CallToolResult | Response | void;
|
|
10
|
+
export type Tool<T extends JSONObject = JSONObject> = FunctionTool<T> | RouteTool<T>;
|
|
11
|
+
export type ToolHandler<T extends JSONObject = JSONObject> = (scope: Scope, input: T, options: ToolExecutionOptions) => Awaitable<ToolResult>;
|
|
12
|
+
export type ToolRouteResolver<T extends JSONObject = JSONObject> = (scope: Scope, input: T, options: ToolExecutionOptions) => Awaitable<ToolRequest>;
|
|
13
|
+
export type ToolRoute<T extends JSONObject = JSONObject> = string | ToolRequest | Route | ToolRouteResolver<T>;
|
|
7
14
|
export type ModelContextProtocolConnectOptions = ModelContextProtocolHttpConnectOptions | ModelContextProtocolTransportConnectOptions;
|
|
8
15
|
export type SharedWarning = {
|
|
9
16
|
type: "unsupported" | "compatibility";
|
|
@@ -34,6 +41,34 @@ export type LanguageModelMessage = ({
|
|
|
34
41
|
providerOptions?: Record<string, JSONObject>;
|
|
35
42
|
};
|
|
36
43
|
export type LanguageModelContent = LanguageModelText | LanguageModelReasoning | LanguageModelCustomContent | LanguageModelReasoningFile | LanguageModelFile | LanguageModelToolApprovalRequest | LanguageModelSource | LanguageModelToolCall | LanguageModelToolResult;
|
|
44
|
+
export interface ToolDefinition {
|
|
45
|
+
name: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
inputSchema: JsonSchema;
|
|
48
|
+
annotations?: ToolAnnotations;
|
|
49
|
+
}
|
|
50
|
+
export interface ToolExecutionOptions {
|
|
51
|
+
signal?: AbortSignal;
|
|
52
|
+
}
|
|
53
|
+
export interface ToolDescription extends LanguageModelFunctionTool {
|
|
54
|
+
inputSchema: JSONObject;
|
|
55
|
+
annotations?: ToolAnnotations;
|
|
56
|
+
}
|
|
57
|
+
export interface FunctionTool<T extends JSONObject = JSONObject> extends ToolDefinition {
|
|
58
|
+
execute: ToolHandler<T>;
|
|
59
|
+
route?: never;
|
|
60
|
+
}
|
|
61
|
+
export interface RouteTool<T extends JSONObject = JSONObject> extends ToolDefinition {
|
|
62
|
+
route: ToolRoute<T>;
|
|
63
|
+
execute?: never;
|
|
64
|
+
}
|
|
65
|
+
export interface ToolRequest {
|
|
66
|
+
path: string;
|
|
67
|
+
method?: HttpMethod;
|
|
68
|
+
query?: Record<string, string> | URLSearchParams;
|
|
69
|
+
body?: JSONValue;
|
|
70
|
+
headers?: HeadersInit;
|
|
71
|
+
}
|
|
37
72
|
export interface ModelContextProtocolClientInfo {
|
|
38
73
|
name: string;
|
|
39
74
|
version: string;
|
|
@@ -642,6 +677,13 @@ export declare class ModelContextProtocolConnection {
|
|
|
642
677
|
executeTool(call: LanguageModelToolCall, options?: RequestOptions): Promise<LanguageModelToolResultPart>;
|
|
643
678
|
close(): Promise<void>;
|
|
644
679
|
}
|
|
680
|
+
export declare function defineTool<T extends JSONObject = JSONObject>(tool: Tool<T>): Tool<T>;
|
|
681
|
+
export declare function useTools(scope: Scope): {
|
|
682
|
+
listTools(): Promise<Array<ToolDescription>>;
|
|
683
|
+
callTool: (name: string, input?: JSONObject, options?: ToolExecutionOptions) => Promise<CallToolResult>;
|
|
684
|
+
executeTool(call: LanguageModelToolCall, options?: ToolExecutionOptions): Promise<LanguageModelToolResultPart>;
|
|
685
|
+
};
|
|
686
|
+
export declare function handleModelContextProtocol(scope: Scope): Promise<Response>;
|
|
645
687
|
export declare function useModelContextProtocol(options: ModelContextProtocolConnectOptions): Promise<ModelContextProtocolConnection>;
|
|
646
688
|
export declare function defineProvider<TProvider extends Provider>(provider: TProvider): TProvider;
|
|
647
689
|
export declare function useAi<TSource extends Source<Provider> = typeof import("#providers").default>(scope: Scope): AiConnections<TSource>;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import config from "#config/ai";
|
|
1
2
|
import providers from "#providers";
|
|
3
|
+
import tools from "#tools";
|
|
2
4
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
5
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4
|
-
import "
|
|
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";
|
|
10
|
+
import { SERVER_CONTEXT, createServerScope, isResponse, requireSameOrigin, useServer } from "@revojs/server";
|
|
11
|
+
import { useVariables } from "revojs";
|
|
12
|
+
import { toJsonSchema } from "revojs/shared";
|
|
5
13
|
//#region src/index.ts
|
|
6
14
|
var ModelContextProtocolConnection = class {
|
|
7
15
|
client;
|
|
@@ -53,6 +61,195 @@ var ModelContextProtocolConnection = class {
|
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
63
|
};
|
|
64
|
+
function defineTool(tool) {
|
|
65
|
+
return tool;
|
|
66
|
+
}
|
|
67
|
+
async function toToolResult(output) {
|
|
68
|
+
if (output instanceof Response) return {
|
|
69
|
+
content: [{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: await output.text()
|
|
72
|
+
}],
|
|
73
|
+
isError: !output.ok
|
|
74
|
+
};
|
|
75
|
+
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
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function useTools(scope) {
|
|
85
|
+
let loading;
|
|
86
|
+
let registered;
|
|
87
|
+
const resolveTools = async () => {
|
|
88
|
+
if (!loading) {
|
|
89
|
+
const pending = new Array();
|
|
90
|
+
for (const source of Object.values(tools)) for (const resolve of Object.values(source)) pending.push(resolve());
|
|
91
|
+
loading = Promise.all(pending);
|
|
92
|
+
}
|
|
93
|
+
const definitions = await loading;
|
|
94
|
+
if (registered) return registered;
|
|
95
|
+
const validator = new CfWorkerJsonSchemaValidator();
|
|
96
|
+
const entries = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const tool of definitions) {
|
|
98
|
+
if (!/^[A-Za-z0-9_.-]{1,128}$/.test(tool.name)) throw new Error(`Invalid Model Context Protocol tool name "${tool.name}".`);
|
|
99
|
+
if (entries.has(tool.name)) throw new Error("Model Context Protocol tool names must be unique across all tool sources.");
|
|
100
|
+
const schema = toJsonSchema(tool.inputSchema);
|
|
101
|
+
if (schema.type !== "object") throw new Error(`Model Context Protocol tool "${tool.name}" must have an object input schema.`);
|
|
102
|
+
entries.set(tool.name, {
|
|
103
|
+
tool,
|
|
104
|
+
schema,
|
|
105
|
+
validate: validator.getValidator(schema)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
registered = entries;
|
|
109
|
+
return registered;
|
|
110
|
+
};
|
|
111
|
+
const callTool = async (name, input = {}, options = {}) => {
|
|
112
|
+
options.signal?.throwIfAborted();
|
|
113
|
+
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.");
|
|
117
|
+
options.signal?.throwIfAborted();
|
|
118
|
+
try {
|
|
119
|
+
const { tool } = entry;
|
|
120
|
+
if (tool.execute) {
|
|
121
|
+
const output = await tool.execute(scope, result.data, options);
|
|
122
|
+
options.signal?.throwIfAborted();
|
|
123
|
+
return await toToolResult(output);
|
|
124
|
+
}
|
|
125
|
+
if (!scope.context[SERVER_CONTEXT]) throw new Error("Route tools require an HTTP request scope. Use function tools in workflows.");
|
|
126
|
+
const { request, states } = useServer(scope);
|
|
127
|
+
const signal = options.signal ? AbortSignal.any([request.signal, options.signal]) : request.signal;
|
|
128
|
+
signal.throwIfAborted();
|
|
129
|
+
let target = tool.route;
|
|
130
|
+
if (typeof target === "function") target = await target(scope, result.data, { signal });
|
|
131
|
+
let direct;
|
|
132
|
+
let targetOptions;
|
|
133
|
+
if (typeof target === "string") targetOptions = { path: target };
|
|
134
|
+
else if ("fetch" in target) {
|
|
135
|
+
direct = target;
|
|
136
|
+
targetOptions = { path: request.url };
|
|
137
|
+
} else targetOptions = target;
|
|
138
|
+
const url = new URL(targetOptions.path, request.url);
|
|
139
|
+
if (url.origin !== new URL(request.url).origin || url.username || url.password) throw new Error("Tool routes must target the application's own origin.");
|
|
140
|
+
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
|
+
const method = targetOptions.method ?? "POST";
|
|
142
|
+
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));
|
|
145
|
+
const headers = new Headers(targetOptions.headers);
|
|
146
|
+
for (const name of [
|
|
147
|
+
"authorization",
|
|
148
|
+
"cookie",
|
|
149
|
+
"origin",
|
|
150
|
+
"sec-fetch-site"
|
|
151
|
+
]) {
|
|
152
|
+
const value = request.headers.get(name);
|
|
153
|
+
if (value !== null && !headers.has(name)) headers.set(name, value);
|
|
154
|
+
}
|
|
155
|
+
let body;
|
|
156
|
+
if (!readOnly) {
|
|
157
|
+
headers.set("content-type", "application/json");
|
|
158
|
+
body = JSON.stringify("body" in targetOptions ? targetOptions.body : result.data);
|
|
159
|
+
}
|
|
160
|
+
const nextRequest = new Request(url, {
|
|
161
|
+
method,
|
|
162
|
+
headers,
|
|
163
|
+
body,
|
|
164
|
+
signal
|
|
165
|
+
});
|
|
166
|
+
const next = await createServerScope(nextRequest, useVariables(scope), states);
|
|
167
|
+
let response;
|
|
168
|
+
if (direct) response = await direct.fetch(next);
|
|
169
|
+
else response = await (await import("#server")).default.fetch(next);
|
|
170
|
+
signal.throwIfAborted();
|
|
171
|
+
if (!isResponse(response)) throw new Error("Tool routes must return an HTTP response.");
|
|
172
|
+
return await toToolResult(response);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
options.signal?.throwIfAborted();
|
|
175
|
+
if (error instanceof Response) return toToolResult(error);
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
return {
|
|
180
|
+
async listTools() {
|
|
181
|
+
const entries = await resolveTools();
|
|
182
|
+
const descriptions = new Array();
|
|
183
|
+
for (const { tool, schema } of entries.values()) descriptions.push({
|
|
184
|
+
type: "function",
|
|
185
|
+
name: tool.name,
|
|
186
|
+
description: tool.description,
|
|
187
|
+
inputSchema: schema,
|
|
188
|
+
annotations: tool.annotations
|
|
189
|
+
});
|
|
190
|
+
return descriptions;
|
|
191
|
+
},
|
|
192
|
+
callTool,
|
|
193
|
+
async executeTool(call, options) {
|
|
194
|
+
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.`);
|
|
196
|
+
const result = await callTool(call.toolName, input, options);
|
|
197
|
+
return {
|
|
198
|
+
type: "tool-result",
|
|
199
|
+
toolCallId: call.toolCallId,
|
|
200
|
+
toolName: call.toolName,
|
|
201
|
+
output: {
|
|
202
|
+
type: result.isError ? "error-json" : "json",
|
|
203
|
+
value: result
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function handleModelContextProtocol(scope) {
|
|
210
|
+
requireSameOrigin(scope);
|
|
211
|
+
const { request } = useServer(scope);
|
|
212
|
+
if (request.method !== "POST") return new Response(null, {
|
|
213
|
+
status: 405,
|
|
214
|
+
headers: { Allow: "POST" }
|
|
215
|
+
});
|
|
216
|
+
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
|
+
server.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: (await collection.listTools()).map((tool) => ({
|
|
227
|
+
name: tool.name,
|
|
228
|
+
description: tool.description,
|
|
229
|
+
inputSchema: tool.inputSchema,
|
|
230
|
+
annotations: tool.annotations
|
|
231
|
+
})) }));
|
|
232
|
+
server.server.setRequestHandler(CallToolRequestSchema, async ({ params }, extra) => {
|
|
233
|
+
try {
|
|
234
|
+
return await collection.callTool(params.name, params.arguments ?? {}, { signal: AbortSignal.any([request.signal, extra.signal]) });
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (error instanceof McpError) throw error;
|
|
237
|
+
return {
|
|
238
|
+
content: [{
|
|
239
|
+
type: "text",
|
|
240
|
+
text: "Tool execution failed."
|
|
241
|
+
}],
|
|
242
|
+
isError: true
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
try {
|
|
247
|
+
await server.connect(transport);
|
|
248
|
+
return await transport.handleRequest(request);
|
|
249
|
+
} finally {
|
|
250
|
+
await server.close();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
56
253
|
async function useModelContextProtocol(options) {
|
|
57
254
|
const client = new Client(options.clientInfo ?? {
|
|
58
255
|
name: "revojs",
|
|
@@ -157,4 +354,4 @@ function toStream(read) {
|
|
|
157
354
|
});
|
|
158
355
|
}
|
|
159
356
|
//#endregion
|
|
160
|
-
export { ModelContextProtocolConnection, defineProvider, mergeHeaders, postJson, readDataFrames, toStream, useAi, useModelContextProtocol };
|
|
357
|
+
export { ModelContextProtocolConnection, defineProvider, defineTool, handleModelContextProtocol, mergeHeaders, postJson, readDataFrames, toStream, useAi, useModelContextProtocol, useTools };
|
package/dist/module/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
1
|
+
export interface AiConfig {
|
|
2
|
+
modelContextProtocol: {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
instructions?: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
declare const _default: import("revojs/module").Module<"ai", AiConfig, readonly [import("revojs/module").Module<"server", Record<string, never>, []>]>;
|
|
3
9
|
export default _default;
|
package/dist/module/index.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import server, { addRoutes } from "@revojs/server/module";
|
|
1
2
|
import { defineModule, useModule } from "revojs/module";
|
|
2
3
|
//#region src/module/index.ts
|
|
3
4
|
var module_default = defineModule({
|
|
4
5
|
name: "ai",
|
|
5
|
-
config: () => ({
|
|
6
|
-
|
|
6
|
+
config: () => ({ modelContextProtocol: {
|
|
7
|
+
name: "revojs",
|
|
8
|
+
version: "1.0.0"
|
|
9
|
+
} }),
|
|
10
|
+
modules: [server],
|
|
11
|
+
setup(config) {
|
|
7
12
|
const { fromModule } = useModule(import.meta.url);
|
|
13
|
+
addRoutes(config, fromModule("../routes"));
|
|
8
14
|
return {
|
|
9
15
|
variables: { server: {
|
|
10
16
|
anthropicApiKey: {
|
|
@@ -36,11 +42,18 @@ var module_default = defineModule({
|
|
|
36
42
|
optional: true
|
|
37
43
|
}
|
|
38
44
|
} },
|
|
39
|
-
sources: {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
sources: {
|
|
46
|
+
tools: {
|
|
47
|
+
match: ["**/*.{js,ts}", "!**/*.d.ts"],
|
|
48
|
+
from: ["./tools"],
|
|
49
|
+
format: "object"
|
|
50
|
+
},
|
|
51
|
+
providers: {
|
|
52
|
+
match: ["**/*.{js,ts}", "!**/*.d.ts"],
|
|
53
|
+
from: ["./providers", fromModule("../providers")],
|
|
54
|
+
format: "object"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
44
57
|
};
|
|
45
58
|
}
|
|
46
59
|
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { handleModelContextProtocol } from "../../../index.js";
|
|
2
|
+
import { defineRoute } from "@revojs/server";
|
|
3
|
+
//#region src/routes/v1/model-context-protocol/delete.ts
|
|
4
|
+
var delete_default = defineRoute({ fetch: handleModelContextProtocol });
|
|
5
|
+
//#endregion
|
|
6
|
+
export { delete_default as default };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { handleModelContextProtocol } from "../../../index.js";
|
|
2
|
+
import { defineRoute } from "@revojs/server";
|
|
3
|
+
//#region src/routes/v1/model-context-protocol/get.ts
|
|
4
|
+
var get_default = defineRoute({ fetch: handleModelContextProtocol });
|
|
5
|
+
//#endregion
|
|
6
|
+
export { get_default as default };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { handleModelContextProtocol } from "../../../index.js";
|
|
2
|
+
import { defineRoute } from "@revojs/server";
|
|
3
|
+
//#region src/routes/v1/model-context-protocol/post.ts
|
|
4
|
+
var post_default = defineRoute({ fetch: handleModelContextProtocol });
|
|
5
|
+
//#endregion
|
|
6
|
+
export { post_default as default };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,8 +7,16 @@
|
|
|
7
7
|
|
|
8
8
|
declare module "#providers" {
|
|
9
9
|
import type { Source } from "revojs";
|
|
10
|
-
import type { Provider } from "
|
|
10
|
+
import type { Provider } from "@revojs/ai";
|
|
11
11
|
|
|
12
12
|
const entries: Source<Provider>;
|
|
13
13
|
export default entries;
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
declare module "#tools" {
|
|
17
|
+
import type { Source } from "revojs";
|
|
18
|
+
import type { Tool } from "@revojs/ai";
|
|
19
|
+
|
|
20
|
+
const entries: Source<Tool>;
|
|
21
|
+
export default entries;
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revojs/ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
"watch": "vite build --watch"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
+
"@cfworker/json-schema": "^4.1.1",
|
|
32
33
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
34
|
+
"@revojs/server": "*",
|
|
33
35
|
"revojs": "*"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|