@effect/ai 0.18.11 → 0.18.13
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/McpSchema/package.json +6 -0
- package/McpServer/package.json +6 -0
- package/dist/cjs/AiTool.js +61 -3
- package/dist/cjs/AiTool.js.map +1 -1
- package/dist/cjs/McpSchema.js +1555 -0
- package/dist/cjs/McpSchema.js.map +1 -0
- package/dist/cjs/McpServer.js +708 -0
- package/dist/cjs/McpServer.js.map +1 -0
- package/dist/cjs/index.js +5 -1
- package/dist/dts/AiTool.d.ts +50 -4
- package/dist/dts/AiTool.d.ts.map +1 -1
- package/dist/dts/McpSchema.d.ts +2527 -0
- package/dist/dts/McpSchema.d.ts.map +1 -0
- package/dist/dts/McpServer.d.ts +366 -0
- package/dist/dts/McpServer.d.ts.map +1 -0
- package/dist/dts/index.d.ts +8 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/AiTool.js +54 -2
- package/dist/esm/AiTool.js.map +1 -1
- package/dist/esm/McpSchema.js +1471 -0
- package/dist/esm/McpSchema.js.map +1 -0
- package/dist/esm/McpServer.js +691 -0
- package/dist/esm/McpServer.js.map +1 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +24 -5
- package/src/AiTool.ts +89 -6
- package/src/McpSchema.ts +1903 -0
- package/src/McpServer.ts +1078 -0
- package/src/index.ts +10 -0
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.toolkit = exports.run = exports.resource = exports.registerToolkit = exports.registerResource = exports.registerPrompt = exports.prompt = exports.layerStdio = exports.layerHttp = exports.layer = exports.McpServer = void 0;
|
|
7
|
+
var Headers = _interopRequireWildcard(require("@effect/platform/Headers"));
|
|
8
|
+
var RpcClient = _interopRequireWildcard(require("@effect/rpc/RpcClient"));
|
|
9
|
+
var RpcSerialization = _interopRequireWildcard(require("@effect/rpc/RpcSerialization"));
|
|
10
|
+
var RpcServer = _interopRequireWildcard(require("@effect/rpc/RpcServer"));
|
|
11
|
+
var Arr = _interopRequireWildcard(require("effect/Array"));
|
|
12
|
+
var Cause = _interopRequireWildcard(require("effect/Cause"));
|
|
13
|
+
var Context = _interopRequireWildcard(require("effect/Context"));
|
|
14
|
+
var Effect = _interopRequireWildcard(require("effect/Effect"));
|
|
15
|
+
var Exit = _interopRequireWildcard(require("effect/Exit"));
|
|
16
|
+
var JsonSchema = _interopRequireWildcard(require("effect/JSONSchema"));
|
|
17
|
+
var Layer = _interopRequireWildcard(require("effect/Layer"));
|
|
18
|
+
var Logger = _interopRequireWildcard(require("effect/Logger"));
|
|
19
|
+
var Mailbox = _interopRequireWildcard(require("effect/Mailbox"));
|
|
20
|
+
var Option = _interopRequireWildcard(require("effect/Option"));
|
|
21
|
+
var Schema = _interopRequireWildcard(require("effect/Schema"));
|
|
22
|
+
var AST = _interopRequireWildcard(require("effect/SchemaAST"));
|
|
23
|
+
var FindMyWay = _interopRequireWildcard(require("find-my-way-ts"));
|
|
24
|
+
var AiTool = _interopRequireWildcard(require("./AiTool.js"));
|
|
25
|
+
var _McpSchema = require("./McpSchema.js");
|
|
26
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
27
|
+
/**
|
|
28
|
+
* @since 1.0.0
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @since 1.0.0
|
|
33
|
+
* @category McpServer
|
|
34
|
+
*/
|
|
35
|
+
class McpServer extends /*#__PURE__*/Context.Tag("@effect/ai/McpServer")() {
|
|
36
|
+
/**
|
|
37
|
+
* @since 1.0.0
|
|
38
|
+
*/
|
|
39
|
+
static make = /*#__PURE__*/Effect.gen(function* () {
|
|
40
|
+
const matcher = makeUriMatcher();
|
|
41
|
+
const tools = Arr.empty();
|
|
42
|
+
const toolMap = new Map();
|
|
43
|
+
const resources = [];
|
|
44
|
+
const resourceTemplates = [];
|
|
45
|
+
const prompts = [];
|
|
46
|
+
const promptMap = new Map();
|
|
47
|
+
const completionsMap = new Map();
|
|
48
|
+
const notificationsMailbox = yield* Mailbox.make();
|
|
49
|
+
const listChangedHandles = new Map();
|
|
50
|
+
const notifications = yield* RpcClient.makeNoSerialization(_McpSchema.ServerNotificationRpcs, {
|
|
51
|
+
onFromClient(options) {
|
|
52
|
+
const message = options.message;
|
|
53
|
+
if (message._tag !== "Request") {
|
|
54
|
+
return Effect.void;
|
|
55
|
+
}
|
|
56
|
+
if (message.tag.includes("list_changed")) {
|
|
57
|
+
if (!listChangedHandles.has(message.tag)) {
|
|
58
|
+
listChangedHandles.set(message.tag, setTimeout(() => {
|
|
59
|
+
notificationsMailbox.unsafeOffer(message);
|
|
60
|
+
listChangedHandles.delete(message.tag);
|
|
61
|
+
}, 0));
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
notificationsMailbox.unsafeOffer(message);
|
|
65
|
+
}
|
|
66
|
+
return notifications.write({
|
|
67
|
+
clientId: 0,
|
|
68
|
+
requestId: message.id,
|
|
69
|
+
_tag: "Exit",
|
|
70
|
+
exit: Exit.void
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return McpServer.of({
|
|
75
|
+
notifications: notifications.client,
|
|
76
|
+
notificationsMailbox,
|
|
77
|
+
get tools() {
|
|
78
|
+
return tools;
|
|
79
|
+
},
|
|
80
|
+
addTool: options => Effect.suspend(() => {
|
|
81
|
+
tools.push(options.tool);
|
|
82
|
+
toolMap.set(options.tool.name, options.handle);
|
|
83
|
+
return notifications.client["notifications/tools/list_changed"]({});
|
|
84
|
+
}),
|
|
85
|
+
callTool: request => Effect.suspend(() => {
|
|
86
|
+
const handle = toolMap.get(request.name);
|
|
87
|
+
if (!handle) {
|
|
88
|
+
return Effect.fail(new _McpSchema.InvalidParams({
|
|
89
|
+
message: `Tool '${request.name}' not found`
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
return handle(request.arguments);
|
|
93
|
+
}),
|
|
94
|
+
get resources() {
|
|
95
|
+
return resources;
|
|
96
|
+
},
|
|
97
|
+
get resourceTemplates() {
|
|
98
|
+
return resourceTemplates;
|
|
99
|
+
},
|
|
100
|
+
addResource: (resource, effect) => Effect.suspend(() => {
|
|
101
|
+
resources.push(resource);
|
|
102
|
+
matcher.add(resource.uri, {
|
|
103
|
+
_tag: "Resource",
|
|
104
|
+
effect
|
|
105
|
+
});
|
|
106
|
+
return notifications.client["notifications/resources/list_changed"]({});
|
|
107
|
+
}),
|
|
108
|
+
addResourceTemplate: ({
|
|
109
|
+
completions,
|
|
110
|
+
handle,
|
|
111
|
+
routerPath,
|
|
112
|
+
template
|
|
113
|
+
}) => Effect.suspend(() => {
|
|
114
|
+
resourceTemplates.push(template);
|
|
115
|
+
matcher.add(routerPath, {
|
|
116
|
+
_tag: "ResourceTemplate",
|
|
117
|
+
handle
|
|
118
|
+
});
|
|
119
|
+
for (const [param, handle] of Object.entries(completions)) {
|
|
120
|
+
completionsMap.set(`ref/resource/${template.uriTemplate}/${param}`, handle);
|
|
121
|
+
}
|
|
122
|
+
return notifications.client["notifications/resources/list_changed"]({});
|
|
123
|
+
}),
|
|
124
|
+
findResource: uri => Effect.suspend(() => {
|
|
125
|
+
const match = matcher.find(uri);
|
|
126
|
+
if (!match) {
|
|
127
|
+
return Effect.succeed(new _McpSchema.ReadResourceResult({
|
|
128
|
+
contents: []
|
|
129
|
+
}));
|
|
130
|
+
} else if (match.handler._tag === "Resource") {
|
|
131
|
+
return match.handler.effect;
|
|
132
|
+
}
|
|
133
|
+
const params = [];
|
|
134
|
+
for (const key of Object.keys(match.params)) {
|
|
135
|
+
params[Number(key)] = match.params[key];
|
|
136
|
+
}
|
|
137
|
+
return match.handler.handle(uri, params);
|
|
138
|
+
}),
|
|
139
|
+
get prompts() {
|
|
140
|
+
return prompts;
|
|
141
|
+
},
|
|
142
|
+
addPrompt: options => Effect.suspend(() => {
|
|
143
|
+
prompts.push(options.prompt);
|
|
144
|
+
promptMap.set(options.prompt.name, options.handle);
|
|
145
|
+
for (const [param, handle] of Object.entries(options.completions)) {
|
|
146
|
+
completionsMap.set(`ref/prompt/${options.prompt.name}/${param}`, handle);
|
|
147
|
+
}
|
|
148
|
+
return notifications.client["notifications/prompts/list_changed"]({});
|
|
149
|
+
}),
|
|
150
|
+
getPromptResult: Effect.fnUntraced(function* ({
|
|
151
|
+
arguments: params,
|
|
152
|
+
name
|
|
153
|
+
}) {
|
|
154
|
+
const handler = promptMap.get(name);
|
|
155
|
+
if (!handler) {
|
|
156
|
+
return yield* new _McpSchema.InvalidParams({
|
|
157
|
+
message: `Prompt '${name}' not found`
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return yield* handler(params ?? {});
|
|
161
|
+
}),
|
|
162
|
+
completion: Effect.fnUntraced(function* (complete) {
|
|
163
|
+
const ref = complete.ref;
|
|
164
|
+
const key = ref.type === "ref/resource" ? `ref/resource/${ref.uri}/${complete.argument.name}` : `ref/prompt/${ref.name}/${complete.argument.name}`;
|
|
165
|
+
const handler = completionsMap.get(key);
|
|
166
|
+
return handler ? yield* handler(complete.argument.value) : _McpSchema.CompleteResult.empty;
|
|
167
|
+
})
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
/**
|
|
171
|
+
* @since 1.0.0
|
|
172
|
+
*/
|
|
173
|
+
static layer = /*#__PURE__*/Layer.scoped(McpServer, McpServer.make);
|
|
174
|
+
}
|
|
175
|
+
exports.McpServer = McpServer;
|
|
176
|
+
const LATEST_PROTOCOL_VERSION = "2025-03-26";
|
|
177
|
+
const SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2024-11-05", "2024-10-07"];
|
|
178
|
+
/**
|
|
179
|
+
* @since 1.0.0
|
|
180
|
+
* @category Constructors
|
|
181
|
+
*/
|
|
182
|
+
const run = exports.run = /*#__PURE__*/Effect.fnUntraced(function* (options) {
|
|
183
|
+
const protocol = yield* RpcServer.Protocol;
|
|
184
|
+
const handlers = yield* Layer.build(layerHandlers(options));
|
|
185
|
+
const server = yield* McpServer;
|
|
186
|
+
const patchedProtocol = RpcServer.Protocol.of({
|
|
187
|
+
...protocol,
|
|
188
|
+
run: f => protocol.run((clientId, request) => {
|
|
189
|
+
if (request._tag === "Request" && request.tag.startsWith("notifications/")) {
|
|
190
|
+
if (request.tag === "notifications/cancelled") {
|
|
191
|
+
return f(clientId, {
|
|
192
|
+
_tag: "Interrupt",
|
|
193
|
+
requestId: String(request.payload.requestId)
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const handler = handlers.unsafeMap.get(request.tag);
|
|
197
|
+
return handler ? handler.handler(request.payload, Headers.fromInput(request.headers)) : Effect.void;
|
|
198
|
+
}
|
|
199
|
+
return f(clientId, request);
|
|
200
|
+
})
|
|
201
|
+
});
|
|
202
|
+
const encodeNotification = Schema.encode(Schema.Union(...Array.from(_McpSchema.ServerNotificationRpcs.requests.values(), rpc => rpc.payloadSchema)));
|
|
203
|
+
yield* server.notificationsMailbox.take.pipe(Effect.flatMap(Effect.fnUntraced(function* (request) {
|
|
204
|
+
const encoded = yield* encodeNotification(request.payload);
|
|
205
|
+
const message = {
|
|
206
|
+
_tag: "Request",
|
|
207
|
+
tag: request.tag,
|
|
208
|
+
payload: encoded
|
|
209
|
+
};
|
|
210
|
+
const clientIds = yield* patchedProtocol.clientIds;
|
|
211
|
+
for (const clientId of clientIds) {
|
|
212
|
+
yield* patchedProtocol.send(clientId, message);
|
|
213
|
+
}
|
|
214
|
+
})), Effect.catchAllCause(() => Effect.void), Effect.forever, Effect.forkScoped);
|
|
215
|
+
return yield* RpcServer.make(_McpSchema.ClientRpcs, {
|
|
216
|
+
spanPrefix: "McpServer",
|
|
217
|
+
disableFatalDefects: true
|
|
218
|
+
}).pipe(Effect.provideService(RpcServer.Protocol, patchedProtocol), Effect.provide(handlers));
|
|
219
|
+
}, Effect.scoped);
|
|
220
|
+
/**
|
|
221
|
+
* @since 1.0.0
|
|
222
|
+
* @category Layers
|
|
223
|
+
*/
|
|
224
|
+
const layer = options => Layer.scopedDiscard(Effect.forkScoped(run(options))).pipe(Layer.provideMerge(McpServer.layer));
|
|
225
|
+
/**
|
|
226
|
+
* Run the McpServer, using stdio for input and output.
|
|
227
|
+
*
|
|
228
|
+
* ```ts
|
|
229
|
+
* import { McpSchema, McpServer } from "@effect/ai"
|
|
230
|
+
* import { NodeRuntime, NodeSink, NodeStream } from "@effect/platform-node"
|
|
231
|
+
* import { Effect, Layer, Logger, Schema } from "effect"
|
|
232
|
+
*
|
|
233
|
+
* const idParam = McpSchema.param("id", Schema.NumberFromString)
|
|
234
|
+
*
|
|
235
|
+
* // Define a resource template for a README file
|
|
236
|
+
* const ReadmeTemplate = McpServer.resource`file://readme/${idParam}`({
|
|
237
|
+
* name: "README Template",
|
|
238
|
+
* // You can add auto-completion for the ID parameter
|
|
239
|
+
* completion: {
|
|
240
|
+
* id: (_) => Effect.succeed([1, 2, 3, 4, 5])
|
|
241
|
+
* },
|
|
242
|
+
* content: Effect.fn(function*(_uri, id) {
|
|
243
|
+
* return `# MCP Server Demo - ID: ${id}`
|
|
244
|
+
* })
|
|
245
|
+
* })
|
|
246
|
+
*
|
|
247
|
+
* // Define a test prompt with parameters
|
|
248
|
+
* const TestPrompt = McpServer.prompt({
|
|
249
|
+
* name: "Test Prompt",
|
|
250
|
+
* description: "A test prompt to demonstrate MCP server capabilities",
|
|
251
|
+
* parameters: Schema.Struct({
|
|
252
|
+
* flightNumber: Schema.String
|
|
253
|
+
* }),
|
|
254
|
+
* completion: {
|
|
255
|
+
* flightNumber: () => Effect.succeed(["FL123", "FL456", "FL789"])
|
|
256
|
+
* },
|
|
257
|
+
* content: ({ flightNumber }) => Effect.succeed(`Get the booking details for flight number: ${flightNumber}`)
|
|
258
|
+
* })
|
|
259
|
+
*
|
|
260
|
+
* // Merge all the resources and prompts into a single server layer
|
|
261
|
+
* const ServerLayer = Layer.mergeAll(
|
|
262
|
+
* ReadmeTemplate,
|
|
263
|
+
* TestPrompt
|
|
264
|
+
* ).pipe(
|
|
265
|
+
* // Provide the MCP server implementation
|
|
266
|
+
* Layer.provide(McpServer.layerStdio({
|
|
267
|
+
* name: "Demo Server",
|
|
268
|
+
* version: "1.0.0",
|
|
269
|
+
* stdin: NodeStream.stdin,
|
|
270
|
+
* stdout: NodeSink.stdout
|
|
271
|
+
* })),
|
|
272
|
+
* // add a stderr logger
|
|
273
|
+
* Layer.provide(Logger.add(Logger.prettyLogger({ stderr: true })))
|
|
274
|
+
* )
|
|
275
|
+
*
|
|
276
|
+
* Layer.launch(ServerLayer).pipe(NodeRuntime.runMain)
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
279
|
+
* @since 1.0.0
|
|
280
|
+
* @category Layers
|
|
281
|
+
*/
|
|
282
|
+
exports.layer = layer;
|
|
283
|
+
const layerStdio = options => layer(options).pipe(Layer.provide(RpcServer.layerProtocolStdio({
|
|
284
|
+
stdin: options.stdin,
|
|
285
|
+
stdout: options.stdout
|
|
286
|
+
})), Layer.provide(RpcSerialization.layerNdJsonRpc()),
|
|
287
|
+
// remove stdout loggers
|
|
288
|
+
Layer.provideMerge(Logger.remove(Logger.defaultLogger)), Layer.provideMerge(Logger.remove(Logger.prettyLoggerDefault)));
|
|
289
|
+
/**
|
|
290
|
+
* Run the McpServer, using HTTP for input and output.
|
|
291
|
+
*
|
|
292
|
+
* ```ts
|
|
293
|
+
* import { McpSchema, McpServer } from "@effect/ai"
|
|
294
|
+
* import { HttpRouter } from "@effect/platform"
|
|
295
|
+
* import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
|
|
296
|
+
* import { Effect, Layer, Schema } from "effect"
|
|
297
|
+
* import { createServer } from "node:http"
|
|
298
|
+
*
|
|
299
|
+
* const idParam = McpSchema.param("id", Schema.NumberFromString)
|
|
300
|
+
*
|
|
301
|
+
* // Define a resource template for a README file
|
|
302
|
+
* const ReadmeTemplate = McpServer.resource`file://readme/${idParam}`({
|
|
303
|
+
* name: "README Template",
|
|
304
|
+
* // You can add auto-completion for the ID parameter
|
|
305
|
+
* completion: {
|
|
306
|
+
* id: (_) => Effect.succeed([1, 2, 3, 4, 5])
|
|
307
|
+
* },
|
|
308
|
+
* content: Effect.fn(function*(_uri, id) {
|
|
309
|
+
* return `# MCP Server Demo - ID: ${id}`
|
|
310
|
+
* })
|
|
311
|
+
* })
|
|
312
|
+
*
|
|
313
|
+
* // Define a test prompt with parameters
|
|
314
|
+
* const TestPrompt = McpServer.prompt({
|
|
315
|
+
* name: "Test Prompt",
|
|
316
|
+
* description: "A test prompt to demonstrate MCP server capabilities",
|
|
317
|
+
* parameters: Schema.Struct({
|
|
318
|
+
* flightNumber: Schema.String
|
|
319
|
+
* }),
|
|
320
|
+
* completion: {
|
|
321
|
+
* flightNumber: () => Effect.succeed(["FL123", "FL456", "FL789"])
|
|
322
|
+
* },
|
|
323
|
+
* content: ({ flightNumber }) => Effect.succeed(`Get the booking details for flight number: ${flightNumber}`)
|
|
324
|
+
* })
|
|
325
|
+
*
|
|
326
|
+
* // Merge all the resources and prompts into a single server layer
|
|
327
|
+
* const ServerLayer = Layer.mergeAll(
|
|
328
|
+
* ReadmeTemplate,
|
|
329
|
+
* TestPrompt,
|
|
330
|
+
* HttpRouter.Default.serve()
|
|
331
|
+
* ).pipe(
|
|
332
|
+
* // Provide the MCP server implementation
|
|
333
|
+
* Layer.provide(McpServer.layerHttp({
|
|
334
|
+
* name: "Demo Server",
|
|
335
|
+
* version: "1.0.0",
|
|
336
|
+
* path: "/mcp"
|
|
337
|
+
* })),
|
|
338
|
+
* Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
|
|
339
|
+
* )
|
|
340
|
+
*
|
|
341
|
+
* Layer.launch(ServerLayer).pipe(NodeRuntime.runMain)
|
|
342
|
+
* ```
|
|
343
|
+
*
|
|
344
|
+
* @since 1.0.0
|
|
345
|
+
* @category Layers
|
|
346
|
+
*/
|
|
347
|
+
exports.layerStdio = layerStdio;
|
|
348
|
+
const layerHttp = options => layer(options).pipe(Layer.provide(RpcServer.layerProtocolHttp(options)), Layer.provide(RpcSerialization.layerJsonRpc()));
|
|
349
|
+
/**
|
|
350
|
+
* Register an AiToolkit with the McpServer.
|
|
351
|
+
*
|
|
352
|
+
* @since 1.0.0
|
|
353
|
+
* @category Tools
|
|
354
|
+
*/
|
|
355
|
+
exports.layerHttp = layerHttp;
|
|
356
|
+
const registerToolkit = exports.registerToolkit = /*#__PURE__*/Effect.fnUntraced(function* (toolkit) {
|
|
357
|
+
const registry = yield* McpServer;
|
|
358
|
+
const built = yield* toolkit;
|
|
359
|
+
const context = yield* Effect.context();
|
|
360
|
+
for (const tool of built.tools) {
|
|
361
|
+
const mcpTool = new _McpSchema.Tool({
|
|
362
|
+
name: tool.name,
|
|
363
|
+
description: tool.description,
|
|
364
|
+
inputSchema: makeJsonSchema(tool.parametersSchema.ast),
|
|
365
|
+
annotations: new _McpSchema.ToolAnnotations({
|
|
366
|
+
...Context.getOption(tool.annotations, AiTool.Title).pipe(Option.map(title => ({
|
|
367
|
+
title
|
|
368
|
+
})), Option.getOrUndefined),
|
|
369
|
+
readOnlyHint: Context.get(tool.annotations, AiTool.Readonly),
|
|
370
|
+
destructiveHint: Context.get(tool.annotations, AiTool.Destructive),
|
|
371
|
+
idempotentHint: Context.get(tool.annotations, AiTool.Idempotent),
|
|
372
|
+
openWorldHint: Context.get(tool.annotations, AiTool.OpenWorld)
|
|
373
|
+
})
|
|
374
|
+
});
|
|
375
|
+
yield* registry.addTool({
|
|
376
|
+
tool: mcpTool,
|
|
377
|
+
handle(payload) {
|
|
378
|
+
return built.handle(tool.name, payload).pipe(Effect.provide(context), Effect.match({
|
|
379
|
+
onFailure: error => new _McpSchema.CallToolResult({
|
|
380
|
+
isError: true,
|
|
381
|
+
content: [new _McpSchema.TextContent({
|
|
382
|
+
text: JSON.stringify(error)
|
|
383
|
+
})]
|
|
384
|
+
}),
|
|
385
|
+
onSuccess: result => new _McpSchema.CallToolResult({
|
|
386
|
+
isError: false,
|
|
387
|
+
content: [new _McpSchema.TextContent({
|
|
388
|
+
text: JSON.stringify(result.encodedResult)
|
|
389
|
+
})]
|
|
390
|
+
})
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
/**
|
|
397
|
+
* Register an AiToolkit with the McpServer.
|
|
398
|
+
*
|
|
399
|
+
* @since 1.0.0
|
|
400
|
+
* @category Tools
|
|
401
|
+
*/
|
|
402
|
+
const toolkit = toolkit => Layer.effectDiscard(registerToolkit(toolkit));
|
|
403
|
+
/**
|
|
404
|
+
* Register a resource with the McpServer.
|
|
405
|
+
*
|
|
406
|
+
* @since 1.0.0
|
|
407
|
+
* @category Resources
|
|
408
|
+
*/
|
|
409
|
+
exports.toolkit = toolkit;
|
|
410
|
+
const registerResource = function () {
|
|
411
|
+
if (arguments.length === 1) {
|
|
412
|
+
const options = arguments[0];
|
|
413
|
+
return Effect.gen(function* () {
|
|
414
|
+
const context = yield* Effect.context();
|
|
415
|
+
const registry = yield* McpServer;
|
|
416
|
+
yield* registry.addResource(new _McpSchema.Resource({
|
|
417
|
+
...options,
|
|
418
|
+
annotations: new _McpSchema.Annotations(options)
|
|
419
|
+
}), options.content.pipe(Effect.provide(context), Effect.map(content => resolveResourceContent(options.uri, content)), Effect.catchAllCause(cause => {
|
|
420
|
+
const prettyError = Cause.prettyErrors(cause)[0];
|
|
421
|
+
return new _McpSchema.InternalError({
|
|
422
|
+
message: prettyError.message
|
|
423
|
+
});
|
|
424
|
+
})));
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
const {
|
|
428
|
+
params,
|
|
429
|
+
routerPath,
|
|
430
|
+
schema,
|
|
431
|
+
uriPath
|
|
432
|
+
} = compileUriTemplate(...arguments);
|
|
433
|
+
return Effect.fnUntraced(function* (options) {
|
|
434
|
+
const context = yield* Effect.context();
|
|
435
|
+
const registry = yield* McpServer;
|
|
436
|
+
const decode = Schema.decodeUnknown(schema);
|
|
437
|
+
const template = new _McpSchema.ResourceTemplate({
|
|
438
|
+
...options,
|
|
439
|
+
uriTemplate: uriPath,
|
|
440
|
+
annotations: new _McpSchema.Annotations(options)
|
|
441
|
+
});
|
|
442
|
+
const completions = {};
|
|
443
|
+
for (const [param, handle] of Object.entries(options.completion ?? {})) {
|
|
444
|
+
const encodeArray = Schema.encodeUnknown(Schema.Array(params[param]));
|
|
445
|
+
const handler = input => handle(input).pipe(Effect.flatMap(encodeArray), Effect.map(values => new _McpSchema.CompleteResult({
|
|
446
|
+
completion: {
|
|
447
|
+
values: values,
|
|
448
|
+
total: values.length,
|
|
449
|
+
hasMore: false
|
|
450
|
+
}
|
|
451
|
+
})), Effect.catchAllCause(cause => {
|
|
452
|
+
const prettyError = Cause.prettyErrors(cause)[0];
|
|
453
|
+
return new _McpSchema.InternalError({
|
|
454
|
+
message: prettyError.message
|
|
455
|
+
});
|
|
456
|
+
}), Effect.provide(context));
|
|
457
|
+
completions[param] = handler;
|
|
458
|
+
}
|
|
459
|
+
yield* registry.addResourceTemplate({
|
|
460
|
+
template,
|
|
461
|
+
routerPath,
|
|
462
|
+
completions,
|
|
463
|
+
handle: (uri, params) => decode(params).pipe(Effect.mapError(error => new _McpSchema.InvalidParams({
|
|
464
|
+
message: error.message
|
|
465
|
+
})), Effect.flatMap(params => options.content(uri, ...params).pipe(Effect.map(content => resolveResourceContent(uri, content)), Effect.catchAllCause(cause => {
|
|
466
|
+
const prettyError = Cause.prettyErrors(cause)[0];
|
|
467
|
+
return new _McpSchema.InternalError({
|
|
468
|
+
message: prettyError.message
|
|
469
|
+
});
|
|
470
|
+
}))), Effect.provide(context))
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
};
|
|
474
|
+
/**
|
|
475
|
+
* Register a resource with the McpServer.
|
|
476
|
+
*
|
|
477
|
+
* @since 1.0.0
|
|
478
|
+
* @category Resources
|
|
479
|
+
*/
|
|
480
|
+
exports.registerResource = registerResource;
|
|
481
|
+
const resource = function () {
|
|
482
|
+
if (arguments.length === 1) {
|
|
483
|
+
return Layer.effectDiscard(registerResource(arguments[0]));
|
|
484
|
+
}
|
|
485
|
+
const register = registerResource(...arguments);
|
|
486
|
+
return options => Layer.effectDiscard(register(options));
|
|
487
|
+
};
|
|
488
|
+
/**
|
|
489
|
+
* Register a prompt with the McpServer.
|
|
490
|
+
*
|
|
491
|
+
* @since 1.0.0
|
|
492
|
+
* @category Resources
|
|
493
|
+
*/
|
|
494
|
+
exports.resource = resource;
|
|
495
|
+
const registerPrompt = options => {
|
|
496
|
+
const args = Arr.empty();
|
|
497
|
+
const props = {};
|
|
498
|
+
const propSignatures = options.parameters ? AST.getPropertySignatures(options.parameters.ast) : [];
|
|
499
|
+
for (const prop of propSignatures) {
|
|
500
|
+
args.push(new _McpSchema.PromptArgument({
|
|
501
|
+
name: prop.name,
|
|
502
|
+
description: Option.getOrUndefined(AST.getDescriptionAnnotation(prop)),
|
|
503
|
+
required: !prop.isOptional
|
|
504
|
+
}));
|
|
505
|
+
props[prop.name] = Schema.make(prop.type);
|
|
506
|
+
}
|
|
507
|
+
const prompt = new _McpSchema.Prompt({
|
|
508
|
+
name: options.name,
|
|
509
|
+
description: options.description,
|
|
510
|
+
arguments: args
|
|
511
|
+
});
|
|
512
|
+
const decode = options.parameters ? Schema.decodeUnknown(options.parameters) : () => Effect.succeed({});
|
|
513
|
+
const completion = options.completion ?? {};
|
|
514
|
+
return Effect.gen(function* () {
|
|
515
|
+
const registry = yield* McpServer;
|
|
516
|
+
const context = yield* Effect.context();
|
|
517
|
+
const completions = {};
|
|
518
|
+
for (const [param, handle] of Object.entries(completion)) {
|
|
519
|
+
const encodeArray = Schema.encodeUnknown(Schema.Array(props[param]));
|
|
520
|
+
const handler = input => handle(input).pipe(Effect.flatMap(encodeArray), Effect.map(values => new _McpSchema.CompleteResult({
|
|
521
|
+
completion: {
|
|
522
|
+
values: values,
|
|
523
|
+
total: values.length,
|
|
524
|
+
hasMore: false
|
|
525
|
+
}
|
|
526
|
+
})), Effect.catchAllCause(cause => {
|
|
527
|
+
const prettyError = Cause.prettyErrors(cause)[0];
|
|
528
|
+
return new _McpSchema.InternalError({
|
|
529
|
+
message: prettyError.message
|
|
530
|
+
});
|
|
531
|
+
}), Effect.provide(context));
|
|
532
|
+
completions[param] = handler;
|
|
533
|
+
}
|
|
534
|
+
yield* registry.addPrompt({
|
|
535
|
+
prompt,
|
|
536
|
+
completions,
|
|
537
|
+
handle: params => decode(params).pipe(Effect.mapError(error => new _McpSchema.InvalidParams({
|
|
538
|
+
message: error.message
|
|
539
|
+
})), Effect.flatMap(params => options.content(params)), Effect.map(messages => {
|
|
540
|
+
messages = typeof messages === "string" ? [new _McpSchema.PromptMessage({
|
|
541
|
+
role: "user",
|
|
542
|
+
content: new _McpSchema.TextContent({
|
|
543
|
+
text: messages
|
|
544
|
+
})
|
|
545
|
+
})] : messages;
|
|
546
|
+
return new _McpSchema.GetPromptResult({
|
|
547
|
+
messages,
|
|
548
|
+
description: prompt.description
|
|
549
|
+
});
|
|
550
|
+
}), Effect.catchAllCause(cause => {
|
|
551
|
+
const prettyError = Cause.prettyErrors(cause)[0];
|
|
552
|
+
return new _McpSchema.InternalError({
|
|
553
|
+
message: prettyError.message
|
|
554
|
+
});
|
|
555
|
+
}), Effect.provide(context))
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
};
|
|
559
|
+
/**
|
|
560
|
+
* Register a prompt with the McpServer.
|
|
561
|
+
*
|
|
562
|
+
* @since 1.0.0
|
|
563
|
+
* @category Resources
|
|
564
|
+
*/
|
|
565
|
+
exports.registerPrompt = registerPrompt;
|
|
566
|
+
const prompt = options => Layer.effectDiscard(registerPrompt(options));
|
|
567
|
+
// -----------------------------------------------------------------------------
|
|
568
|
+
// Internal
|
|
569
|
+
// -----------------------------------------------------------------------------
|
|
570
|
+
exports.prompt = prompt;
|
|
571
|
+
const makeUriMatcher = () => {
|
|
572
|
+
const router = FindMyWay.make({
|
|
573
|
+
ignoreTrailingSlash: true,
|
|
574
|
+
ignoreDuplicateSlashes: true,
|
|
575
|
+
caseSensitive: true
|
|
576
|
+
});
|
|
577
|
+
const add = (uri, value) => {
|
|
578
|
+
router.on("GET", uri, value);
|
|
579
|
+
};
|
|
580
|
+
const find = uri => router.find("GET", uri);
|
|
581
|
+
return {
|
|
582
|
+
add,
|
|
583
|
+
find
|
|
584
|
+
};
|
|
585
|
+
};
|
|
586
|
+
const compileUriTemplate = (segments, ...schemas) => {
|
|
587
|
+
let routerPath = segments[0].replace(":", "::");
|
|
588
|
+
let uriPath = segments[0];
|
|
589
|
+
const params = {};
|
|
590
|
+
let pathSchema = Schema.Tuple();
|
|
591
|
+
if (schemas.length > 0) {
|
|
592
|
+
const arr = [];
|
|
593
|
+
for (let i = 0; i < schemas.length; i++) {
|
|
594
|
+
const schema = schemas[i];
|
|
595
|
+
const key = String(i);
|
|
596
|
+
arr.push(schema);
|
|
597
|
+
routerPath += `:${key}${segments[i + 1].replace(":", "::")}`;
|
|
598
|
+
const paramName = AST.getAnnotation(_McpSchema.ParamAnnotation)(schema.ast).pipe(Option.getOrElse(() => `param${key}`));
|
|
599
|
+
params[paramName] = schema;
|
|
600
|
+
uriPath += `{${paramName}}`;
|
|
601
|
+
}
|
|
602
|
+
pathSchema = Schema.Tuple(...arr);
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
routerPath,
|
|
606
|
+
uriPath,
|
|
607
|
+
schema: pathSchema,
|
|
608
|
+
params
|
|
609
|
+
};
|
|
610
|
+
};
|
|
611
|
+
const layerHandlers = serverInfo => _McpSchema.ClientRpcs.toLayer(Effect.gen(function* () {
|
|
612
|
+
const server = yield* McpServer;
|
|
613
|
+
return {
|
|
614
|
+
// Requests
|
|
615
|
+
ping: () => Effect.succeed({}),
|
|
616
|
+
initialize(params) {
|
|
617
|
+
const requestedVersion = params.protocolVersion;
|
|
618
|
+
const capabilities = {
|
|
619
|
+
completions: {}
|
|
620
|
+
};
|
|
621
|
+
if (server.tools.length > 0) {
|
|
622
|
+
capabilities.tools = {
|
|
623
|
+
listChanged: true
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
if (server.resources.length > 0 || server.resourceTemplates.length > 0) {
|
|
627
|
+
capabilities.resources = {
|
|
628
|
+
listChanged: true,
|
|
629
|
+
subscribe: false
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
if (server.prompts.length > 0) {
|
|
633
|
+
capabilities.prompts = {
|
|
634
|
+
listChanged: true
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
return Effect.succeed({
|
|
638
|
+
capabilities,
|
|
639
|
+
serverInfo,
|
|
640
|
+
protocolVersion: SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : LATEST_PROTOCOL_VERSION
|
|
641
|
+
});
|
|
642
|
+
},
|
|
643
|
+
"completion/complete": server.completion,
|
|
644
|
+
"logging/setLevel": () => _McpSchema.InternalError.notImplemented,
|
|
645
|
+
"prompts/get": server.getPromptResult,
|
|
646
|
+
"prompts/list": () => Effect.sync(() => new _McpSchema.ListPromptsResult({
|
|
647
|
+
prompts: server.prompts
|
|
648
|
+
})),
|
|
649
|
+
"resources/list": () => Effect.sync(() => new _McpSchema.ListResourcesResult({
|
|
650
|
+
resources: server.resources
|
|
651
|
+
})),
|
|
652
|
+
"resources/read": ({
|
|
653
|
+
uri
|
|
654
|
+
}) => server.findResource(uri),
|
|
655
|
+
"resources/subscribe": () => _McpSchema.InternalError.notImplemented,
|
|
656
|
+
"resources/unsubscribe": () => _McpSchema.InternalError.notImplemented,
|
|
657
|
+
"resources/templates/list": () => Effect.sync(() => new _McpSchema.ListResourceTemplatesResult({
|
|
658
|
+
resourceTemplates: server.resourceTemplates
|
|
659
|
+
})),
|
|
660
|
+
"tools/call": server.callTool,
|
|
661
|
+
"tools/list": () => Effect.sync(() => new _McpSchema.ListToolsResult({
|
|
662
|
+
tools: server.tools
|
|
663
|
+
})),
|
|
664
|
+
// Notifications
|
|
665
|
+
"notifications/cancelled": _ => Effect.void,
|
|
666
|
+
"notifications/initialized": _ => Effect.void,
|
|
667
|
+
"notifications/progress": _ => Effect.void,
|
|
668
|
+
"notifications/roots/list_changed": _ => Effect.void
|
|
669
|
+
};
|
|
670
|
+
}));
|
|
671
|
+
const makeJsonSchema = ast => {
|
|
672
|
+
const props = AST.getPropertySignatures(ast);
|
|
673
|
+
if (props.length === 0) {
|
|
674
|
+
return {
|
|
675
|
+
type: "object",
|
|
676
|
+
properties: {},
|
|
677
|
+
required: [],
|
|
678
|
+
additionalProperties: false
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
const $defs = {};
|
|
682
|
+
const schema = JsonSchema.fromAST(ast, {
|
|
683
|
+
definitions: $defs,
|
|
684
|
+
topLevelReferenceStrategy: "skip"
|
|
685
|
+
});
|
|
686
|
+
if (Object.keys($defs).length === 0) return schema;
|
|
687
|
+
schema.$defs = $defs;
|
|
688
|
+
return schema;
|
|
689
|
+
};
|
|
690
|
+
const resolveResourceContent = (uri, content) => {
|
|
691
|
+
if (typeof content === "string") {
|
|
692
|
+
return new _McpSchema.ReadResourceResult({
|
|
693
|
+
contents: [new _McpSchema.TextResourceContents({
|
|
694
|
+
uri,
|
|
695
|
+
text: content
|
|
696
|
+
})]
|
|
697
|
+
});
|
|
698
|
+
} else if (content instanceof Uint8Array) {
|
|
699
|
+
return new _McpSchema.ReadResourceResult({
|
|
700
|
+
contents: [new _McpSchema.BlobResourceContents({
|
|
701
|
+
uri,
|
|
702
|
+
blob: content
|
|
703
|
+
})]
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
return content;
|
|
707
|
+
};
|
|
708
|
+
//# sourceMappingURL=McpServer.js.map
|