|
| 1 | +> [!WARNING] |
| 2 | +> Unfortunately i published the 1.0 by mistake...this package is currently under heavy development so there will be breaking changes in minors...threat this `1.x` as the `0.x` of any other package. Sorry for the disservice, every breaking will be properly labeled in the PR name. |
| 3 | +
|
| 4 | +# tmcp |
| 5 | + |
| 6 | +A lightweight, schema-agnostic Model Context Protocol (MCP) server implementation with unified API design. |
| 7 | + |
| 8 | +## Why tmcp? |
| 9 | + |
| 10 | +tmcp offers significant advantages over the official MCP SDK: |
| 11 | + |
| 12 | +- **🔄 Schema Agnostic**: Works with any validation library through adapters |
| 13 | +- **📦 No Weird Dependencies**: Minimal footprint with only essential dependencies (looking at you `express`) |
| 14 | +- **🎯 Unified API**: Consistent, intuitive interface across all MCP capabilities |
| 15 | +- **🔌 Extensible**: Easy to add support for new schema libraries |
| 16 | +- **⚡ Lightweight**: No bloat, just what you need |
| 17 | + |
| 18 | +## Supported Schema Libraries |
| 19 | + |
| 20 | +tmcp works with all major schema validation libraries through its adapter system: |
| 21 | + |
| 22 | +- **Zod** - `@tmcp/adapter-zod` |
| 23 | +- **Valibot** - `@tmcp/adapter-valibot` |
| 24 | +- **ArkType** - `@tmcp/adapter-arktype` |
| 25 | +- **Effect Schema** - `@tmcp/adapter-effect` |
| 26 | +- **Zod v3** - `@tmcp/adapter-zod-v3` |
| 27 | + |
| 28 | +## Installation |
| 29 | + |
| 30 | +```bash |
| 31 | +pnpm install tmcp |
| 32 | +# Choose your preferred schema library adapter |
| 33 | +pnpm install @tmcp/adapter-zod zod |
| 34 | +# Choose your preferred transport |
| 35 | +pnpm install @tmcp/transport-stdio # For CLI/desktop apps |
| 36 | +pnpm install @tmcp/transport-http # For web-based clients |
| 37 | +``` |
| 38 | + |
| 39 | +## Quick Start |
| 40 | + |
| 41 | +### Standard I/O Transport (CLI/Desktop) |
| 42 | + |
| 43 | +```javascript |
| 44 | +import { McpServer } from 'tmcp'; |
| 45 | +import { ZodJsonSchemaAdapter } from '@tmcp/adapter-zod'; |
| 46 | +import { StdioTransport } from '@tmcp/transport-stdio'; |
| 47 | +import { z } from 'zod'; |
| 48 | + |
| 49 | +const adapter = new ZodJsonSchemaAdapter(); |
| 50 | +const server = new McpServer( |
| 51 | + { |
| 52 | + name: 'my-server', |
| 53 | + version: '1.0.0', |
| 54 | + description: 'My awesome MCP server', |
| 55 | + }, |
| 56 | + { |
| 57 | + adapter, |
| 58 | + capabilities: { |
| 59 | + tools: { listChanged: true }, |
| 60 | + prompts: { listChanged: true }, |
| 61 | + resources: { listChanged: true }, |
| 62 | + }, |
| 63 | + }, |
| 64 | +); |
| 65 | + |
| 66 | +// Define a tool with type-safe schema |
| 67 | +server.tool( |
| 68 | + { |
| 69 | + name: 'calculate', |
| 70 | + description: 'Perform mathematical calculations', |
| 71 | + schema: z.object({ |
| 72 | + operation: z.enum(['add', 'subtract', 'multiply', 'divide']), |
| 73 | + a: z.number(), |
| 74 | + b: z.number(), |
| 75 | + }), |
| 76 | + }, |
| 77 | + async ({ operation, a, b }) => { |
| 78 | + switch (operation) { |
| 79 | + case 'add': |
| 80 | + return a + b; |
| 81 | + case 'subtract': |
| 82 | + return a - b; |
| 83 | + case 'multiply': |
| 84 | + return a * b; |
| 85 | + case 'divide': |
| 86 | + return a / b; |
| 87 | + } |
| 88 | + }, |
| 89 | +); |
| 90 | + |
| 91 | +// Start the server with stdio transport |
| 92 | +const transport = new StdioTransport(server); |
| 93 | +transport.listen(); |
| 94 | +``` |
| 95 | + |
| 96 | +### HTTP Transport (Web-based) |
| 97 | + |
| 98 | +```javascript |
| 99 | +import { McpServer } from 'tmcp'; |
| 100 | +import { ZodJsonSchemaAdapter } from '@tmcp/adapter-zod'; |
| 101 | +import { HttpTransport } from '@tmcp/transport-http'; |
| 102 | +import { z } from 'zod'; |
| 103 | + |
| 104 | +const adapter = new ZodJsonSchemaAdapter(); |
| 105 | +const server = new McpServer(/* ... same server config ... */); |
| 106 | + |
| 107 | +// Add tools as above... |
| 108 | + |
| 109 | +// Create HTTP transport |
| 110 | +const transport = new HttpTransport(server); |
| 111 | + |
| 112 | +// Use with your preferred HTTP server (Bun example) |
| 113 | +Bun.serve({ |
| 114 | + port: 3000, |
| 115 | + async fetch(req) { |
| 116 | + const response = await transport.respond(req); |
| 117 | + if (response === null) { |
| 118 | + return new Response('Not Found', { status: 404 }); |
| 119 | + } |
| 120 | + return response; |
| 121 | + }, |
| 122 | +}); |
| 123 | +``` |
| 124 | + |
| 125 | +## API Reference |
| 126 | + |
| 127 | +### McpServer |
| 128 | + |
| 129 | +The main server class that handles MCP protocol communications. |
| 130 | + |
| 131 | +#### Constructor |
| 132 | + |
| 133 | +```javascript |
| 134 | +new McpServer(serverInfo, options); |
| 135 | +``` |
| 136 | + |
| 137 | +- `serverInfo`: Server metadata (name, version, description) |
| 138 | +- `options`: Configuration object with adapter and capabilities |
| 139 | + |
| 140 | +#### Methods |
| 141 | + |
| 142 | +##### `tool(definition, handler)` |
| 143 | + |
| 144 | +Register a tool with optional schema validation. |
| 145 | + |
| 146 | +```javascript |
| 147 | +server.tool( |
| 148 | + { |
| 149 | + name: 'tool-name', |
| 150 | + description: 'Tool description', |
| 151 | + schema: yourSchema, // optional |
| 152 | + }, |
| 153 | + async (input) => { |
| 154 | + // Tool implementation |
| 155 | + return result; |
| 156 | + }, |
| 157 | +); |
| 158 | +``` |
| 159 | + |
| 160 | +##### `prompt(definition, handler)` |
| 161 | + |
| 162 | +Register a prompt template with optional schema validation. |
| 163 | + |
| 164 | +```javascript |
| 165 | +server.prompt( |
| 166 | + { |
| 167 | + name: 'prompt-name', |
| 168 | + description: 'Prompt description', |
| 169 | + schema: yourSchema, // optional |
| 170 | + complete: (arg, context) => ['completion1', 'completion2'] // optional |
| 171 | + }, |
| 172 | + async (input) => { |
| 173 | + // Prompt implementation |
| 174 | + return { messages: [...] }; |
| 175 | + } |
| 176 | +); |
| 177 | +``` |
| 178 | + |
| 179 | +##### `resource(definition, handler)` |
| 180 | + |
| 181 | +Register a static resource. |
| 182 | + |
| 183 | +```javascript |
| 184 | +server.resource( |
| 185 | + { |
| 186 | + name: 'resource-name', |
| 187 | + description: 'Resource description', |
| 188 | + uri: 'file://path/to/resource' |
| 189 | + }, |
| 190 | + async (uri, params) => { |
| 191 | + // Resource implementation |
| 192 | + return { contents: [...] }; |
| 193 | + } |
| 194 | +); |
| 195 | +``` |
| 196 | + |
| 197 | +##### `template(definition, handler)` |
| 198 | + |
| 199 | +Register a URI template for dynamic resources. |
| 200 | + |
| 201 | +```javascript |
| 202 | +server.template( |
| 203 | + { |
| 204 | + name: 'template-name', |
| 205 | + description: 'Template description', |
| 206 | + uri: 'file://path/{id}/resource', |
| 207 | + complete: (arg, context) => ['id1', 'id2'] // optional |
| 208 | + }, |
| 209 | + async (uri, params) => { |
| 210 | + // Template implementation using params.id |
| 211 | + return { contents: [...] }; |
| 212 | + } |
| 213 | +); |
| 214 | +``` |
| 215 | + |
| 216 | +##### `receive(request)` |
| 217 | + |
| 218 | +Process an incoming MCP request. |
| 219 | + |
| 220 | +```javascript |
| 221 | +const response = server.receive(jsonRpcRequest); |
| 222 | +``` |
| 223 | + |
| 224 | +## Advanced Examples |
| 225 | + |
| 226 | +### Multiple Schema Libraries |
| 227 | + |
| 228 | +```javascript |
| 229 | +// Use different schemas for different tools |
| 230 | +import { z } from 'zod'; |
| 231 | +import * as v from 'valibot'; |
| 232 | + |
| 233 | +server.tool( |
| 234 | + { |
| 235 | + name: 'zod-tool', |
| 236 | + schema: z.object({ name: z.string() }), |
| 237 | + }, |
| 238 | + async ({ name }) => `Hello ${name}`, |
| 239 | +); |
| 240 | + |
| 241 | +server.tool( |
| 242 | + { |
| 243 | + name: 'valibot-tool', |
| 244 | + schema: v.object({ age: v.number() }), |
| 245 | + }, |
| 246 | + async ({ age }) => `Age: ${age}`, |
| 247 | +); |
| 248 | +``` |
| 249 | + |
| 250 | +### Resource Templates with Completion |
| 251 | + |
| 252 | +```javascript |
| 253 | +server.template( |
| 254 | + { |
| 255 | + name: 'user-profile', |
| 256 | + description: 'Get user profile by ID', |
| 257 | + uri: 'users/{userId}/profile', |
| 258 | + complete: (arg, context) => { |
| 259 | + // Provide completions for userId parameter |
| 260 | + return ['user1', 'user2', 'user3']; |
| 261 | + }, |
| 262 | + }, |
| 263 | + async (uri, params) => { |
| 264 | + const user = await getUserById(params.userId); |
| 265 | + return { |
| 266 | + contents: [ |
| 267 | + { |
| 268 | + uri, |
| 269 | + mimeType: 'application/json', |
| 270 | + text: JSON.stringify(user), |
| 271 | + }, |
| 272 | + ], |
| 273 | + }; |
| 274 | + }, |
| 275 | +); |
| 276 | +``` |
| 277 | + |
| 278 | +### Complex Validation |
| 279 | + |
| 280 | +```javascript |
| 281 | +const complexSchema = z.object({ |
| 282 | + user: z.object({ |
| 283 | + name: z.string().min(1), |
| 284 | + email: z.string().email(), |
| 285 | + age: z.number().min(18).max(120), |
| 286 | + }), |
| 287 | + preferences: z |
| 288 | + .object({ |
| 289 | + theme: z.enum(['light', 'dark']), |
| 290 | + notifications: z.boolean(), |
| 291 | + }) |
| 292 | + .optional(), |
| 293 | + tags: z.array(z.string()).default([]), |
| 294 | +}); |
| 295 | + |
| 296 | +server.tool( |
| 297 | + { |
| 298 | + name: 'create-user', |
| 299 | + description: 'Create a new user with preferences', |
| 300 | + schema: complexSchema, |
| 301 | + }, |
| 302 | + async (input) => { |
| 303 | + // Input is fully typed and validated |
| 304 | + const { user, preferences, tags } = input; |
| 305 | + return await createUser(user, preferences, tags); |
| 306 | + }, |
| 307 | +); |
| 308 | +``` |
| 309 | + |
| 310 | +## Contributing |
| 311 | + |
| 312 | +Contributions are welcome! Please see our [contributing guidelines](../../CONTRIBUTING.md) for details. |
| 313 | + |
| 314 | +## Acknowledgments |
| 315 | + |
| 316 | +Huge thanks to Sean O'Bannon that provided us with the `@tmcp` scope on npm. |
| 317 | + |
| 318 | +## License |
| 319 | + |
| 320 | +MIT © Paolo Ricciuti |
0 commit comments