tmcp 1.0.1 โ 1.1.0
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 +3 -2
- package/src/index.d.ts +15 -0
- package/src/index.js +62 -6
- package/src/internal/internal.d.ts +7 -2
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "The main tmcp library",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"scripts": {
|
|
35
35
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
36
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"
|
|
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"
|
|
38
39
|
}
|
|
39
40
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -7,6 +7,13 @@ export class McpServer<StandardSchema extends StandardSchemaV1> {
|
|
|
7
7
|
* @param {ServerOptions<StandardSchema>} options
|
|
8
8
|
*/
|
|
9
9
|
constructor(server_info: ServerInfo, options: ServerOptions<StandardSchema>);
|
|
10
|
+
/**
|
|
11
|
+
* @template {keyof McpEvents} TEvent
|
|
12
|
+
* @param {TEvent} event
|
|
13
|
+
* @param {McpEvents[TEvent]} callback
|
|
14
|
+
* @param {AddEventListenerOptions} [options]
|
|
15
|
+
*/
|
|
16
|
+
on<TEvent extends keyof McpEvents>(event: TEvent, callback: McpEvents[TEvent], options?: AddEventListenerOptions): void;
|
|
10
17
|
/**
|
|
11
18
|
* @template {StandardSchema | undefined} [TSchema=undefined]
|
|
12
19
|
* @param {{ name: string; description: string; schema?: StandardSchemaV1.InferInput<TSchema extends undefined ? never : TSchema> extends Record<string, unknown> ? TSchema : never }} options
|
|
@@ -52,11 +59,19 @@ export class McpServer<StandardSchema extends StandardSchemaV1> {
|
|
|
52
59
|
* @returns {ReturnType<JSONRPCServer['receive']>}
|
|
53
60
|
*/
|
|
54
61
|
receive(request: JSONRPCRequest): ReturnType<JSONRPCServer["receive"]>;
|
|
62
|
+
/**
|
|
63
|
+
* Send a notification for subscriptions
|
|
64
|
+
* @param {SubscriptionsKeys} what
|
|
65
|
+
* @param {string} id
|
|
66
|
+
*/
|
|
67
|
+
changed(what: SubscriptionsKeys, id: string): void;
|
|
55
68
|
#private;
|
|
56
69
|
}
|
|
57
70
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
71
|
+
import type { McpEvents } from "./internal/internal.js";
|
|
58
72
|
import type { Completion } from "./internal/internal.js";
|
|
59
73
|
import type { JSONRPCRequest } from "json-rpc-2.0";
|
|
60
74
|
import { JSONRPCServer } from 'json-rpc-2.0';
|
|
75
|
+
import type { SubscriptionsKeys } from "./internal/internal.js";
|
|
61
76
|
import type { ServerInfo } from "./internal/internal.js";
|
|
62
77
|
import type { ServerOptions } from "./internal/internal.js";
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
3
|
* @import { JSONRPCRequest, JSONRPCParams } from "json-rpc-2.0";
|
|
4
|
-
* @import { Tool, Completion, Prompt, Resource, ServerOptions, ServerInfo } from "./internal/internal.js";
|
|
4
|
+
* @import { Tool, Completion, Prompt, Resource, ServerOptions, ServerInfo, SubscriptionsKeys, McpEvents } from "./internal/internal.js";
|
|
5
5
|
*/
|
|
6
6
|
import { JSONRPCServer, JSONRPCClient } from 'json-rpc-2.0';
|
|
7
7
|
import { UriTemplateMatcher } from 'uri-template-matcher';
|
|
@@ -36,17 +36,25 @@ export class McpServer {
|
|
|
36
36
|
'ref/prompt': new Map(),
|
|
37
37
|
'ref/resource': new Map(),
|
|
38
38
|
};
|
|
39
|
+
|
|
40
|
+
#event_target = new EventTarget();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @type {Record<SubscriptionsKeys, Set<string>>}
|
|
44
|
+
*/
|
|
45
|
+
#subscriptions = {
|
|
46
|
+
/**
|
|
47
|
+
* @type {Set<string>}
|
|
48
|
+
*/
|
|
49
|
+
resource: new Set(),
|
|
50
|
+
};
|
|
51
|
+
|
|
39
52
|
/**
|
|
40
53
|
* @param {ServerInfo} server_info
|
|
41
54
|
* @param {ServerOptions<StandardSchema>} options
|
|
42
55
|
*/
|
|
43
56
|
constructor(server_info, options) {
|
|
44
57
|
this.#options = options;
|
|
45
|
-
if (options.send) {
|
|
46
|
-
this.#client = new JSONRPCClient((payload) => {
|
|
47
|
-
options.send?.(payload);
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
58
|
this.#server.addMethod('initialize', ({ protocolVersion }) => {
|
|
51
59
|
return {
|
|
52
60
|
protocolVersion,
|
|
@@ -66,6 +74,30 @@ export class McpServer {
|
|
|
66
74
|
this.#init_completion();
|
|
67
75
|
}
|
|
68
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @template {keyof McpEvents} TEvent
|
|
79
|
+
* @param {TEvent} event
|
|
80
|
+
* @param {McpEvents[TEvent]} callback
|
|
81
|
+
* @param {AddEventListenerOptions} [options]
|
|
82
|
+
*/
|
|
83
|
+
on(event, callback, options) {
|
|
84
|
+
if (event === 'send' && !this.#client) {
|
|
85
|
+
this.#client = new JSONRPCClient((payload) => {
|
|
86
|
+
this.#event_target.dispatchEvent(
|
|
87
|
+
new CustomEvent('send', { detail: payload }),
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.#event_target.addEventListener(
|
|
93
|
+
event,
|
|
94
|
+
(e) => {
|
|
95
|
+
callback(/** @type {CustomEvent} */ (e).detail);
|
|
96
|
+
},
|
|
97
|
+
options,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
69
101
|
/**
|
|
70
102
|
* @param {string} method
|
|
71
103
|
* @param {JSONRPCParams} [params]
|
|
@@ -190,6 +222,14 @@ export class McpServer {
|
|
|
190
222
|
*/
|
|
191
223
|
#init_resources() {
|
|
192
224
|
if (!this.#options.capabilities?.resources) return;
|
|
225
|
+
|
|
226
|
+
if (this.#options.capabilities?.resources?.subscribe) {
|
|
227
|
+
this.#server.addMethod('resources/subscribe', async ({ uri }) => {
|
|
228
|
+
this.#subscriptions.resource.add(uri);
|
|
229
|
+
return {};
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
193
233
|
this.#server.addMethod('resources/list', async () => {
|
|
194
234
|
return {
|
|
195
235
|
resources: [...this.#resources].reduce(
|
|
@@ -339,4 +379,20 @@ export class McpServer {
|
|
|
339
379
|
receive(request) {
|
|
340
380
|
return this.#server.receive(request);
|
|
341
381
|
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Send a notification for subscriptions
|
|
385
|
+
* @param {SubscriptionsKeys} what
|
|
386
|
+
* @param {string} id
|
|
387
|
+
*/
|
|
388
|
+
changed(what, id) {
|
|
389
|
+
if (this.#subscriptions[what].has(id)) {
|
|
390
|
+
const resource = this.#resources.get(id);
|
|
391
|
+
if (!resource) return;
|
|
392
|
+
this.#notify(`notifications/resources/updated`, {
|
|
393
|
+
uri: id,
|
|
394
|
+
title: resource.name,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
342
398
|
}
|
|
@@ -31,11 +31,10 @@ export type Resource = {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export type ServerOptions<TSchema extends StandardSchemaV1> = {
|
|
34
|
-
send?: (payload: JSONRPCRequest) => void;
|
|
35
34
|
capabilities?: {
|
|
36
35
|
tools?: { listChanged?: boolean };
|
|
37
36
|
resources?: { listChanged?: boolean; subscribe?: boolean };
|
|
38
|
-
prompts?: { listChanged?: boolean
|
|
37
|
+
prompts?: { listChanged?: boolean };
|
|
39
38
|
};
|
|
40
39
|
instructions?: string;
|
|
41
40
|
adapter: JsonSchemaAdapter<TSchema>;
|
|
@@ -46,3 +45,9 @@ export type ServerInfo = {
|
|
|
46
45
|
version: string;
|
|
47
46
|
description: string;
|
|
48
47
|
};
|
|
48
|
+
|
|
49
|
+
export type SubscriptionsKeys = 'resource';
|
|
50
|
+
|
|
51
|
+
export type McpEvents = {
|
|
52
|
+
send: (message: JSONRPCRequest) => void;
|
|
53
|
+
};
|