tmcp 1.0.0 โ 1.0.2
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 +277 -0
- package/package.json +39 -12
- package/src/adapter.d.ts +16 -0
- package/src/adapter.js +19 -0
- package/src/index.d.ts +62 -0
- package/src/index.js +342 -0
- package/src/internal/internal.d.ts +48 -0
- package/.claude/settings.local.json +0 -8
- package/index.js +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# tmcp
|
|
2
|
+
|
|
3
|
+
A lightweight, schema-agnostic Model Context Protocol (MCP) server implementation with unified API design.
|
|
4
|
+
|
|
5
|
+
## Why tmcp?
|
|
6
|
+
|
|
7
|
+
tmcp offers significant advantages over the official MCP SDK:
|
|
8
|
+
|
|
9
|
+
- **๐ Schema Agnostic**: Works with any validation library through adapters
|
|
10
|
+
- **๐ฆ No Weird Dependencies**: Minimal footprint with only essential dependencies (looking at you `express`)
|
|
11
|
+
- **๐ฏ Unified API**: Consistent, intuitive interface across all MCP capabilities
|
|
12
|
+
- **๐ Extensible**: Easy to add support for new schema libraries
|
|
13
|
+
- **โก Lightweight**: No bloat, just what you need
|
|
14
|
+
|
|
15
|
+
## Supported Schema Libraries
|
|
16
|
+
|
|
17
|
+
tmcp works with all major schema validation libraries through its adapter system:
|
|
18
|
+
|
|
19
|
+
- **Zod** - `@tmcpkit/adapter-zod`
|
|
20
|
+
- **Valibot** - `@tmcpkit/adapter-valibot`
|
|
21
|
+
- **ArkType** - `@tmcpkit/adapter-arktype`
|
|
22
|
+
- **Effect Schema** - `@tmcpkit/adapter-effect`
|
|
23
|
+
- **Zod v3** - `@tmcpkit/adapter-zod-v3`
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm install tmcp
|
|
29
|
+
# Choose your preferred schema library adapter
|
|
30
|
+
รจnpm install @tmcpkit/adapter-zod zod
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
import { McpServer } from 'tmcp';
|
|
37
|
+
import { ZodJsonSchemaAdapter } from '@tmcpkit/adapter-zod';
|
|
38
|
+
import { z } from 'zod';
|
|
39
|
+
|
|
40
|
+
const adapter = new ZodJsonSchemaAdapter();
|
|
41
|
+
const server = new McpServer(
|
|
42
|
+
{
|
|
43
|
+
name: 'my-server',
|
|
44
|
+
version: '1.0.0',
|
|
45
|
+
description: 'My awesome MCP server',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
adapter,
|
|
49
|
+
capabilities: {
|
|
50
|
+
tools: { listChanged: true },
|
|
51
|
+
prompts: { listChanged: true },
|
|
52
|
+
resources: { listChanged: true },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Define a tool with type-safe schema
|
|
58
|
+
server.tool(
|
|
59
|
+
{
|
|
60
|
+
name: 'calculate',
|
|
61
|
+
description: 'Perform mathematical calculations',
|
|
62
|
+
schema: z.object({
|
|
63
|
+
operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
|
|
64
|
+
a: z.number(),
|
|
65
|
+
b: z.number(),
|
|
66
|
+
}),
|
|
67
|
+
},
|
|
68
|
+
async ({ operation, a, b }) => {
|
|
69
|
+
switch (operation) {
|
|
70
|
+
case 'add':
|
|
71
|
+
return a + b;
|
|
72
|
+
case 'subtract':
|
|
73
|
+
return a - b;
|
|
74
|
+
case 'multiply':
|
|
75
|
+
return a * b;
|
|
76
|
+
case 'divide':
|
|
77
|
+
return a / b;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Process incoming requests
|
|
83
|
+
server.receive(request);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API Reference
|
|
87
|
+
|
|
88
|
+
### McpServer
|
|
89
|
+
|
|
90
|
+
The main server class that handles MCP protocol communications.
|
|
91
|
+
|
|
92
|
+
#### Constructor
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
new McpServer(serverInfo, options);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- `serverInfo`: Server metadata (name, version, description)
|
|
99
|
+
- `options`: Configuration object with adapter and capabilities
|
|
100
|
+
|
|
101
|
+
#### Methods
|
|
102
|
+
|
|
103
|
+
##### `tool(definition, handler)`
|
|
104
|
+
|
|
105
|
+
Register a tool with optional schema validation.
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
server.tool(
|
|
109
|
+
{
|
|
110
|
+
name: 'tool-name',
|
|
111
|
+
description: 'Tool description',
|
|
112
|
+
schema: yourSchema, // optional
|
|
113
|
+
},
|
|
114
|
+
async (input) => {
|
|
115
|
+
// Tool implementation
|
|
116
|
+
return result;
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
##### `prompt(definition, handler)`
|
|
122
|
+
|
|
123
|
+
Register a prompt template with optional schema validation.
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
server.prompt(
|
|
127
|
+
{
|
|
128
|
+
name: 'prompt-name',
|
|
129
|
+
description: 'Prompt description',
|
|
130
|
+
schema: yourSchema, // optional
|
|
131
|
+
complete: (arg, context) => ['completion1', 'completion2'] // optional
|
|
132
|
+
},
|
|
133
|
+
async (input) => {
|
|
134
|
+
// Prompt implementation
|
|
135
|
+
return { messages: [...] };
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
##### `resource(definition, handler)`
|
|
141
|
+
|
|
142
|
+
Register a static resource.
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
server.resource(
|
|
146
|
+
{
|
|
147
|
+
name: 'resource-name',
|
|
148
|
+
description: 'Resource description',
|
|
149
|
+
uri: 'file://path/to/resource'
|
|
150
|
+
},
|
|
151
|
+
async (uri, params) => {
|
|
152
|
+
// Resource implementation
|
|
153
|
+
return { contents: [...] };
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
##### `template(definition, handler)`
|
|
159
|
+
|
|
160
|
+
Register a URI template for dynamic resources.
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
server.template(
|
|
164
|
+
{
|
|
165
|
+
name: 'template-name',
|
|
166
|
+
description: 'Template description',
|
|
167
|
+
uri: 'file://path/{id}/resource',
|
|
168
|
+
complete: (arg, context) => ['id1', 'id2'] // optional
|
|
169
|
+
},
|
|
170
|
+
async (uri, params) => {
|
|
171
|
+
// Template implementation using params.id
|
|
172
|
+
return { contents: [...] };
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
##### `receive(request)`
|
|
178
|
+
|
|
179
|
+
Process an incoming MCP request.
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
const response = server.receive(jsonRpcRequest);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Advanced Examples
|
|
186
|
+
|
|
187
|
+
### Multiple Schema Libraries
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
// Use different schemas for different tools
|
|
191
|
+
import { z } from 'zod';
|
|
192
|
+
import * as v from 'valibot';
|
|
193
|
+
|
|
194
|
+
server.tool(
|
|
195
|
+
{
|
|
196
|
+
name: 'zod-tool',
|
|
197
|
+
schema: z.object({ name: z.string() }),
|
|
198
|
+
},
|
|
199
|
+
async ({ name }) => `Hello ${name}`,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
server.tool(
|
|
203
|
+
{
|
|
204
|
+
name: 'valibot-tool',
|
|
205
|
+
schema: v.object({ age: v.number() }),
|
|
206
|
+
},
|
|
207
|
+
async ({ age }) => `Age: ${age}`,
|
|
208
|
+
);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Resource Templates with Completion
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
server.template(
|
|
215
|
+
{
|
|
216
|
+
name: 'user-profile',
|
|
217
|
+
description: 'Get user profile by ID',
|
|
218
|
+
uri: 'users/{userId}/profile',
|
|
219
|
+
complete: (arg, context) => {
|
|
220
|
+
// Provide completions for userId parameter
|
|
221
|
+
return ['user1', 'user2', 'user3'];
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
async (uri, params) => {
|
|
225
|
+
const user = await getUserById(params.userId);
|
|
226
|
+
return {
|
|
227
|
+
contents: [
|
|
228
|
+
{
|
|
229
|
+
uri,
|
|
230
|
+
mimeType: 'application/json',
|
|
231
|
+
text: JSON.stringify(user),
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
},
|
|
236
|
+
);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Complex Validation
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
const complexSchema = z.object({
|
|
243
|
+
user: z.object({
|
|
244
|
+
name: z.string().min(1),
|
|
245
|
+
email: z.string().email(),
|
|
246
|
+
age: z.number().min(18).max(120),
|
|
247
|
+
}),
|
|
248
|
+
preferences: z
|
|
249
|
+
.object({
|
|
250
|
+
theme: z.enum(['light', 'dark']),
|
|
251
|
+
notifications: z.boolean(),
|
|
252
|
+
})
|
|
253
|
+
.optional(),
|
|
254
|
+
tags: z.array(z.string()).default([]),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
server.tool(
|
|
258
|
+
{
|
|
259
|
+
name: 'create-user',
|
|
260
|
+
description: 'Create a new user with preferences',
|
|
261
|
+
schema: complexSchema,
|
|
262
|
+
},
|
|
263
|
+
async (input) => {
|
|
264
|
+
// Input is fully typed and validated
|
|
265
|
+
const { user, preferences, tags } = input;
|
|
266
|
+
return await createUser(user, preferences, tags);
|
|
267
|
+
},
|
|
268
|
+
);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Contributing
|
|
272
|
+
|
|
273
|
+
Contributions are welcome! Please see our [contributing guidelines](../../CONTRIBUTING.md) for details.
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
MIT ยฉ Paolo Ricciuti
|
package/package.json
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
"name": "tmcp",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "The main tmcp library",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [],
|
|
8
|
+
"author": "Paolo Ricciuti",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"files": [
|
|
11
|
+
"src/"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./src/index.d.ts",
|
|
16
|
+
"default": "./src/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./adapter": {
|
|
19
|
+
"types": "./src/adapter.d.ts",
|
|
20
|
+
"default": "./src/adapter.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"json-rpc-2.0": "^1.7.1",
|
|
26
|
+
"uri-template-matcher": "^1.1.0",
|
|
27
|
+
"valibot": "^1.1.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@standard-schema/spec": "^1.0.0",
|
|
31
|
+
"@types/json-schema": "^7.0.15",
|
|
32
|
+
"publint": "^0.3.12"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"generate:types": "rm -f ./src/*.d.ts && tsc ./src/*.js --declaration --emitDeclarationOnly --target esnext --allowJs --moduleresolution nodenext --module nodenext && publint",
|
|
38
|
+
"prepublish": "pnpm generate:types"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/adapter.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
+
* @import { JSONSchema7 } from "json-schema";
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @template {StandardSchemaV1} TSchema
|
|
7
|
+
*/
|
|
8
|
+
export class JsonSchemaAdapter<TSchema extends StandardSchemaV1> {
|
|
9
|
+
/**
|
|
10
|
+
* @param {TSchema} schema
|
|
11
|
+
* @returns {Promise<JSONSchema7>}
|
|
12
|
+
*/
|
|
13
|
+
toJsonSchema(schema: TSchema): Promise<JSONSchema7>;
|
|
14
|
+
}
|
|
15
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
16
|
+
import type { JSONSchema7 } from "json-schema";
|
package/src/adapter.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
5
|
+
* @import { JSONSchema7 } from "json-schema";
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @template {StandardSchemaV1} TSchema
|
|
10
|
+
*/
|
|
11
|
+
export class JsonSchemaAdapter {
|
|
12
|
+
/**
|
|
13
|
+
* @param {TSchema} schema
|
|
14
|
+
* @returns {Promise<JSONSchema7>}
|
|
15
|
+
*/
|
|
16
|
+
toJsonSchema(schema) {
|
|
17
|
+
throw new Error('toJsonSchema method not implemented');
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template {StandardSchemaV1} StandardSchema
|
|
3
|
+
*/
|
|
4
|
+
export class McpServer<StandardSchema extends StandardSchemaV1> {
|
|
5
|
+
/**
|
|
6
|
+
* @param {ServerInfo} server_info
|
|
7
|
+
* @param {ServerOptions<StandardSchema>} options
|
|
8
|
+
*/
|
|
9
|
+
constructor(server_info: ServerInfo, options: ServerOptions<StandardSchema>);
|
|
10
|
+
/**
|
|
11
|
+
* @template {StandardSchema | undefined} [TSchema=undefined]
|
|
12
|
+
* @param {{ name: string; description: string; schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never }} options
|
|
13
|
+
* @param {TSchema extends undefined ? (()=>Promise<unknown> | unknown) : ((input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<unknown> | unknown)} execute
|
|
14
|
+
*/
|
|
15
|
+
tool<TSchema extends StandardSchema | undefined = undefined>({ name, description, schema }: {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never;
|
|
19
|
+
}, execute: TSchema extends undefined ? (() => Promise<unknown> | unknown) : ((input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<unknown> | unknown)): void;
|
|
20
|
+
/**
|
|
21
|
+
* @template {StandardSchema | undefined} [TSchema=undefined]
|
|
22
|
+
* @param {{ name: string; description: string; schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never; complete?: Completion }} options
|
|
23
|
+
* @param {TSchema extends undefined ? (()=>Promise<unknown> | unknown) : (input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<unknown> | unknown} execute
|
|
24
|
+
*/
|
|
25
|
+
prompt<TSchema extends StandardSchema | undefined = undefined>({ name, description, schema, complete }: {
|
|
26
|
+
name: string;
|
|
27
|
+
description: string;
|
|
28
|
+
schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never;
|
|
29
|
+
complete?: Completion;
|
|
30
|
+
}, execute: TSchema extends undefined ? (() => Promise<unknown> | unknown) : (input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<unknown> | unknown): void;
|
|
31
|
+
/**
|
|
32
|
+
* @param {{ name: string; description: string; uri: string }} options
|
|
33
|
+
* @param {(uri: string, params?: unknown) => Promise<unknown> | unknown} execute
|
|
34
|
+
*/
|
|
35
|
+
resource({ name, description, uri }: {
|
|
36
|
+
name: string;
|
|
37
|
+
description: string;
|
|
38
|
+
uri: string;
|
|
39
|
+
}, execute: (uri: string, params?: unknown) => Promise<unknown> | unknown): void;
|
|
40
|
+
/**
|
|
41
|
+
* @param {{ name: string; description: string; uri: string; complete?: Completion }} options
|
|
42
|
+
* @param {(uri: string, params?: unknown) => Promise<unknown> | unknown} execute
|
|
43
|
+
*/
|
|
44
|
+
template({ name, description, uri, complete }: {
|
|
45
|
+
name: string;
|
|
46
|
+
description: string;
|
|
47
|
+
uri: string;
|
|
48
|
+
complete?: Completion;
|
|
49
|
+
}, execute: (uri: string, params?: unknown) => Promise<unknown> | unknown): void;
|
|
50
|
+
/**
|
|
51
|
+
* @param {JSONRPCRequest} request
|
|
52
|
+
* @returns {ReturnType<JSONRPCServer['receive']>}
|
|
53
|
+
*/
|
|
54
|
+
receive(request: JSONRPCRequest): ReturnType<JSONRPCServer["receive"]>;
|
|
55
|
+
#private;
|
|
56
|
+
}
|
|
57
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
58
|
+
import type { Completion } from "./internal/internal.js";
|
|
59
|
+
import type { JSONRPCRequest } from "json-rpc-2.0";
|
|
60
|
+
import { JSONRPCServer } from 'json-rpc-2.0';
|
|
61
|
+
import type { ServerInfo } from "./internal/internal.js";
|
|
62
|
+
import type { ServerOptions } from "./internal/internal.js";
|
package/src/index.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
+
* @import { JSONRPCRequest, JSONRPCParams } from "json-rpc-2.0";
|
|
4
|
+
* @import { Tool, Completion, Prompt, Resource, ServerOptions, ServerInfo } from "./internal/internal.js";
|
|
5
|
+
*/
|
|
6
|
+
import { JSONRPCServer, JSONRPCClient } from 'json-rpc-2.0';
|
|
7
|
+
import { UriTemplateMatcher } from 'uri-template-matcher';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @template {StandardSchemaV1} StandardSchema
|
|
11
|
+
*/
|
|
12
|
+
export class McpServer {
|
|
13
|
+
#server = new JSONRPCServer();
|
|
14
|
+
/**
|
|
15
|
+
* @type {JSONRPCClient | undefined}
|
|
16
|
+
*/
|
|
17
|
+
#client;
|
|
18
|
+
#options;
|
|
19
|
+
/**
|
|
20
|
+
* @type {Map<string, Tool<any>>}
|
|
21
|
+
*/
|
|
22
|
+
#tools = new Map();
|
|
23
|
+
/**
|
|
24
|
+
* @type {Map<string, Prompt<any>>}
|
|
25
|
+
*/
|
|
26
|
+
#prompts = new Map();
|
|
27
|
+
/**
|
|
28
|
+
* @type {Map<string, Resource>}
|
|
29
|
+
*/
|
|
30
|
+
#resources = new Map();
|
|
31
|
+
#templates = new UriTemplateMatcher();
|
|
32
|
+
/**
|
|
33
|
+
* @type {{ [ref: string]: Map<string, Completion> }}
|
|
34
|
+
*/
|
|
35
|
+
#completions = {
|
|
36
|
+
'ref/prompt': new Map(),
|
|
37
|
+
'ref/resource': new Map(),
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* @param {ServerInfo} server_info
|
|
41
|
+
* @param {ServerOptions<StandardSchema>} options
|
|
42
|
+
*/
|
|
43
|
+
constructor(server_info, options) {
|
|
44
|
+
this.#options = options;
|
|
45
|
+
if (options.send) {
|
|
46
|
+
this.#client = new JSONRPCClient((payload) => {
|
|
47
|
+
options.send?.(payload);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
this.#server.addMethod('initialize', ({ protocolVersion }) => {
|
|
51
|
+
return {
|
|
52
|
+
protocolVersion,
|
|
53
|
+
...options,
|
|
54
|
+
serverInfo: server_info,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
this.#server.addMethod('ping', () => {
|
|
58
|
+
return {};
|
|
59
|
+
});
|
|
60
|
+
this.#server.addMethod('notifications/initialized', () => {
|
|
61
|
+
return null;
|
|
62
|
+
});
|
|
63
|
+
this.#init_tools();
|
|
64
|
+
this.#init_prompts();
|
|
65
|
+
this.#init_resources();
|
|
66
|
+
this.#init_completion();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {string} method
|
|
71
|
+
* @param {JSONRPCParams} [params]
|
|
72
|
+
*/
|
|
73
|
+
#notify(method, params) {
|
|
74
|
+
this.#client?.notify(method, params);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
*
|
|
79
|
+
*/
|
|
80
|
+
#init_tools() {
|
|
81
|
+
if (!this.#options.capabilities?.tools) return;
|
|
82
|
+
this.#server.addMethod('tools/list', async () => {
|
|
83
|
+
const available_tools = await Promise.all(
|
|
84
|
+
[...this.#tools].map(async ([name, tool]) => {
|
|
85
|
+
return {
|
|
86
|
+
name,
|
|
87
|
+
title: tool.description,
|
|
88
|
+
description: tool.description,
|
|
89
|
+
inputSchema: tool.schema
|
|
90
|
+
? await this.#options.adapter.toJsonSchema(
|
|
91
|
+
tool.schema,
|
|
92
|
+
)
|
|
93
|
+
: { type: 'object', properties: {} },
|
|
94
|
+
};
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
return {
|
|
98
|
+
tools: available_tools,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
this.#server.addMethod(
|
|
102
|
+
'tools/call',
|
|
103
|
+
async ({ name, arguments: args }) => {
|
|
104
|
+
const tool = this.#tools.get(name);
|
|
105
|
+
if (!tool) {
|
|
106
|
+
throw new Error(`Tool ${name} not found`);
|
|
107
|
+
}
|
|
108
|
+
if (!tool.schema) {
|
|
109
|
+
return tool.execute();
|
|
110
|
+
}
|
|
111
|
+
let validated_args = tool.schema['~standard'].validate(args);
|
|
112
|
+
if (validated_args instanceof Promise)
|
|
113
|
+
validated_args = await validated_args;
|
|
114
|
+
if (validated_args.issues) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Invalid arguments for tool ${name}: ${JSON.stringify(validated_args.issues)}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return tool.execute(validated_args.value);
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
*
|
|
125
|
+
*/
|
|
126
|
+
#init_prompts() {
|
|
127
|
+
if (!this.#options.capabilities?.prompts) return;
|
|
128
|
+
this.#server.addMethod('prompts/list', async () => {
|
|
129
|
+
const available_prompts = await Promise.all(
|
|
130
|
+
[...this.#prompts].map(async ([name, prompt]) => {
|
|
131
|
+
const arguments_schema = prompt.schema
|
|
132
|
+
? await this.#options.adapter.toJsonSchema(
|
|
133
|
+
prompt.schema,
|
|
134
|
+
)
|
|
135
|
+
: {
|
|
136
|
+
type: 'object',
|
|
137
|
+
properties:
|
|
138
|
+
/** @type {Record<string, {description: string}>} */ ({}),
|
|
139
|
+
required: [],
|
|
140
|
+
};
|
|
141
|
+
const keys = Object.keys(arguments_schema.properties ?? {});
|
|
142
|
+
const required = arguments_schema.required ?? [];
|
|
143
|
+
return {
|
|
144
|
+
name,
|
|
145
|
+
title: prompt.description,
|
|
146
|
+
description: prompt.description,
|
|
147
|
+
arguments: keys.map((key) => {
|
|
148
|
+
const property = arguments_schema.properties?.[key];
|
|
149
|
+
const description =
|
|
150
|
+
property && property !== true
|
|
151
|
+
? property.description
|
|
152
|
+
: key;
|
|
153
|
+
return {
|
|
154
|
+
name: key,
|
|
155
|
+
required: required.includes(key),
|
|
156
|
+
description,
|
|
157
|
+
};
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
return {
|
|
163
|
+
prompts: available_prompts,
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
this.#server.addMethod(
|
|
167
|
+
'prompts/get',
|
|
168
|
+
async ({ name, arguments: args }) => {
|
|
169
|
+
const prompt = this.#prompts.get(name);
|
|
170
|
+
if (!prompt) {
|
|
171
|
+
throw new Error(`Tool ${name} not found`);
|
|
172
|
+
}
|
|
173
|
+
if (!prompt.schema) {
|
|
174
|
+
return prompt.execute();
|
|
175
|
+
}
|
|
176
|
+
let validated_args = prompt.schema['~standard'].validate(args);
|
|
177
|
+
if (validated_args instanceof Promise)
|
|
178
|
+
validated_args = await validated_args;
|
|
179
|
+
if (validated_args.issues) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Invalid arguments for tool ${name}: ${JSON.stringify(validated_args.issues)}`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return prompt.execute(validated_args.value);
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
*
|
|
190
|
+
*/
|
|
191
|
+
#init_resources() {
|
|
192
|
+
if (!this.#options.capabilities?.resources) return;
|
|
193
|
+
this.#server.addMethod('resources/list', async () => {
|
|
194
|
+
return {
|
|
195
|
+
resources: [...this.#resources].reduce(
|
|
196
|
+
(arr, [uri, { description, name, template }]) => {
|
|
197
|
+
if (!template) {
|
|
198
|
+
arr.push({
|
|
199
|
+
name,
|
|
200
|
+
description,
|
|
201
|
+
uri,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return arr;
|
|
205
|
+
},
|
|
206
|
+
/** @type {Array<{name: string, description: string, uri: string}>} */ ([]),
|
|
207
|
+
),
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
this.#server.addMethod('resources/templates/list', async () => {
|
|
211
|
+
return {
|
|
212
|
+
resourceTemplates: [...this.#resources].reduce(
|
|
213
|
+
(arr, [uri, { description, name, template }]) => {
|
|
214
|
+
if (template) {
|
|
215
|
+
arr.push({
|
|
216
|
+
name,
|
|
217
|
+
description,
|
|
218
|
+
uriTemplate: uri,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return arr;
|
|
222
|
+
},
|
|
223
|
+
/** @type {Array<{name: string, description: string, uriTemplate: string}>} */ ([]),
|
|
224
|
+
),
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
this.#server.addMethod('resources/read', async ({ uri }) => {
|
|
228
|
+
let resource = this.#resources.get(uri);
|
|
229
|
+
let params;
|
|
230
|
+
if (!resource) {
|
|
231
|
+
const match = this.#templates.match(uri);
|
|
232
|
+
if (match) {
|
|
233
|
+
resource = this.#resources.get(match.template);
|
|
234
|
+
params = match.params;
|
|
235
|
+
}
|
|
236
|
+
if (!resource) {
|
|
237
|
+
throw new Error(`Resource ${uri} not found`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return resource.execute(uri, params);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
*
|
|
245
|
+
*/
|
|
246
|
+
#init_completion() {
|
|
247
|
+
this.#server.addMethod(
|
|
248
|
+
'completion/complete',
|
|
249
|
+
({ argument, ref, context }) => {
|
|
250
|
+
const completions = this.#completions[ref.type];
|
|
251
|
+
if (!completions) return null;
|
|
252
|
+
const complete = completions.get(ref.uri ?? ref.name);
|
|
253
|
+
if (!complete) return null;
|
|
254
|
+
return {
|
|
255
|
+
completion: {
|
|
256
|
+
values: complete(argument, context),
|
|
257
|
+
hasMore: false,
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* @template {StandardSchema | undefined} [TSchema=undefined]
|
|
265
|
+
* @param {{ name: string; description: string; schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never }} options
|
|
266
|
+
* @param {TSchema extends undefined ? (()=>Promise<unknown> | unknown) : ((input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<unknown> | unknown)} execute
|
|
267
|
+
*/
|
|
268
|
+
tool({ name, description, schema }, execute) {
|
|
269
|
+
if (this.#options.capabilities?.tools?.listChanged) {
|
|
270
|
+
this.#notify('notifications/tools/list_changed', {});
|
|
271
|
+
}
|
|
272
|
+
this.#tools.set(name, {
|
|
273
|
+
description,
|
|
274
|
+
schema,
|
|
275
|
+
execute,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* @template {StandardSchema | undefined} [TSchema=undefined]
|
|
280
|
+
* @param {{ name: string; description: string; schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never; complete?: Completion }} options
|
|
281
|
+
* @param {TSchema extends undefined ? (()=>Promise<unknown> | unknown) : (input: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema>) => Promise<unknown> | unknown} execute
|
|
282
|
+
*/
|
|
283
|
+
prompt({ name, description, schema, complete }, execute) {
|
|
284
|
+
if (complete) {
|
|
285
|
+
this.#completions['ref/prompt'].set(name, complete);
|
|
286
|
+
}
|
|
287
|
+
if (this.#options.capabilities?.prompts?.listChanged) {
|
|
288
|
+
this.#notify('notifications/prompts/list_changed', {});
|
|
289
|
+
}
|
|
290
|
+
this.#prompts.set(name, {
|
|
291
|
+
description,
|
|
292
|
+
schema,
|
|
293
|
+
execute,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* @param {string} name
|
|
298
|
+
* @param {string} description
|
|
299
|
+
* @param {string} uri
|
|
300
|
+
* @param {(uri: string, params?: unknown) => Promise<unknown> | unknown} execute
|
|
301
|
+
* @param {Completion} [complete]
|
|
302
|
+
* @param {boolean} [template=false]
|
|
303
|
+
*/
|
|
304
|
+
#resource(name, description, uri, execute, complete, template = false) {
|
|
305
|
+
if (complete) {
|
|
306
|
+
this.#completions['ref/resource'].set(uri, complete);
|
|
307
|
+
}
|
|
308
|
+
if (template) {
|
|
309
|
+
this.#templates.add(uri);
|
|
310
|
+
}
|
|
311
|
+
if (this.#options.capabilities?.resources?.listChanged) {
|
|
312
|
+
this.#notify('notifications/resources/list_changed', {});
|
|
313
|
+
}
|
|
314
|
+
this.#resources.set(uri, {
|
|
315
|
+
description,
|
|
316
|
+
name,
|
|
317
|
+
execute,
|
|
318
|
+
template,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* @param {{ name: string; description: string; uri: string }} options
|
|
323
|
+
* @param {(uri: string, params?: unknown) => Promise<unknown> | unknown} execute
|
|
324
|
+
*/
|
|
325
|
+
resource({ name, description, uri }, execute) {
|
|
326
|
+
this.#resource(name, description, uri, execute, undefined, false);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* @param {{ name: string; description: string; uri: string; complete?: Completion }} options
|
|
330
|
+
* @param {(uri: string, params?: unknown) => Promise<unknown> | unknown} execute
|
|
331
|
+
*/
|
|
332
|
+
template({ name, description, uri, complete }, execute) {
|
|
333
|
+
this.#resource(name, description, uri, execute, complete, true);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* @param {JSONRPCRequest} request
|
|
337
|
+
* @returns {ReturnType<JSONRPCServer['receive']>}
|
|
338
|
+
*/
|
|
339
|
+
receive(request) {
|
|
340
|
+
return this.#server.receive(request);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import { JSONRPCRequest } from 'json-rpc-2.0';
|
|
3
|
+
import { JsonSchemaAdapter } from '../adapter.js';
|
|
4
|
+
|
|
5
|
+
export type Tool<TSchema extends StandardSchemaV1 = StandardSchemaV1<any>> = {
|
|
6
|
+
description: string;
|
|
7
|
+
schema: TSchema;
|
|
8
|
+
execute: (
|
|
9
|
+
input?: StandardSchemaV1.InferInput<TSchema>,
|
|
10
|
+
) => Promise<unknown> | unknown;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type Completion = (
|
|
14
|
+
argument: { name: string; value: string },
|
|
15
|
+
context: { arguments: Record<string, string> },
|
|
16
|
+
) => string[];
|
|
17
|
+
|
|
18
|
+
export type Prompt<TSchema extends StandardSchemaV1 = StandardSchemaV1<any>> = {
|
|
19
|
+
description: string;
|
|
20
|
+
schema: TSchema;
|
|
21
|
+
execute: (
|
|
22
|
+
input?: StandardSchemaV1.InferInput<TSchema>,
|
|
23
|
+
) => Promise<unknown> | unknown;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type Resource = {
|
|
27
|
+
description: string;
|
|
28
|
+
name: string;
|
|
29
|
+
template: boolean;
|
|
30
|
+
execute: (uri: string, params?: unknown) => Promise<unknown> | unknown;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ServerOptions<TSchema extends StandardSchemaV1> = {
|
|
34
|
+
send?: (payload: JSONRPCRequest) => void;
|
|
35
|
+
capabilities?: {
|
|
36
|
+
tools?: { listChanged?: boolean };
|
|
37
|
+
resources?: { listChanged?: boolean; subscribe?: boolean };
|
|
38
|
+
prompts?: { listChanged?: boolean; subscribe?: boolean };
|
|
39
|
+
};
|
|
40
|
+
instructions?: string;
|
|
41
|
+
adapter: JsonSchemaAdapter<TSchema>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type ServerInfo = {
|
|
45
|
+
name: string;
|
|
46
|
+
version: string;
|
|
47
|
+
description: string;
|
|
48
|
+
};
|
package/index.js
DELETED
|
File without changes
|