@ttoss/http-server-mcp 0.11.1 → 0.12.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 CHANGED
@@ -157,8 +157,91 @@ Generic HTTP helper for use inside MCP tool handlers.
157
157
 
158
158
  **Returns:** `Promise<unknown>` - Parsed JSON response body
159
159
 
160
+ ### `registerToolFromSchema(server, params)`
161
+
162
+ Registers a tool using a **plain JSON Schema** object for `inputSchema` instead of a Zod shape.
163
+
164
+ Use this when tool definitions are shared between the MCP server and an AI SDK agent (e.g. Vercel AI SDK's `tool()` helper). Both consumers accept plain JSON Schema at runtime, so a single definition can feed both without any lossy conversion.
165
+
166
+ **Parameters:**
167
+
168
+ - `server` (`McpServer`) - The MCP server instance
169
+ - `params.name` (`string`) - Unique tool name
170
+ - `params.description` (`string`, optional) - Human-readable description
171
+ - `params.inputSchema` (`JsonObjectSchema`, optional) - Plain JSON Schema object (defaults to `{ type: 'object', properties: {} }`)
172
+ - `params.handler` (`(args: Record<string, unknown>) => CallToolResult | Promise<CallToolResult>`) - Tool handler receiving the raw request arguments
173
+
174
+ **Returns:** `void`
175
+
160
176
  ## Examples
161
177
 
178
+ ### Plain JSON Schema Tool (`registerToolFromSchema`)
179
+
180
+ Use `registerToolFromSchema` when you share tool definitions across the MCP server **and** an AI SDK agent. The plain JSON Schema is forwarded verbatim over the MCP wire protocol — `anyOf`, `$ref`, `pattern`, and other features not supported by Zod v3 are preserved without loss.
181
+
182
+ ```typescript
183
+ import {
184
+ createMcpRouter,
185
+ McpServer,
186
+ registerToolFromSchema,
187
+ } from '@ttoss/http-server-mcp';
188
+
189
+ const server = new McpServer({ name: 'my-server', version: '1.0.0' });
190
+
191
+ registerToolFromSchema(server, {
192
+ name: 'get-project',
193
+ description: 'Get a project by ID',
194
+ inputSchema: {
195
+ type: 'object',
196
+ properties: {
197
+ id: { type: 'string', description: 'Project public ID' },
198
+ // anyOf is preserved — Zod v3 has no direct equivalent
199
+ status: { anyOf: [{ type: 'string' }, { type: 'null' }] },
200
+ },
201
+ required: ['id'],
202
+ },
203
+ handler: async ({ id }) => {
204
+ const data = await apiCall('GET', `/projects/${id}`);
205
+ return { content: [{ type: 'text', text: JSON.stringify(data) }] };
206
+ },
207
+ });
208
+ ```
209
+
210
+ **Single source of truth across MCP and AI SDK:**
211
+
212
+ ```typescript
213
+ // lib/tools.ts — shared tool definition
214
+ export const getProjectTool = {
215
+ name: 'get-project',
216
+ description: 'Get a project by ID',
217
+ inputSchema: {
218
+ type: 'object' as const,
219
+ properties: { id: { type: 'string' } },
220
+ required: ['id'],
221
+ },
222
+ };
223
+
224
+ // MCP server
225
+ import { registerToolFromSchema } from '@ttoss/http-server-mcp';
226
+ registerToolFromSchema(mcpServer, {
227
+ ...getProjectTool,
228
+ handler: async ({ id }) => {
229
+ /* ... */
230
+ },
231
+ });
232
+
233
+ // AI SDK agent
234
+ import { tool } from 'ai';
235
+ import { jsonSchema } from 'ai';
236
+ const agentTool = tool({
237
+ description: getProjectTool.description,
238
+ parameters: jsonSchema(getProjectTool.inputSchema),
239
+ execute: async ({ id }) => {
240
+ /* same logic */
241
+ },
242
+ });
243
+ ```
244
+
162
245
  ### Basic Tool
163
246
 
164
247
  ```typescript
package/dist/esm/index.js CHANGED
@@ -9,8 +9,9 @@ var __name = (target, value) => __defProp(target, "name", {
9
9
  import { AsyncLocalStorage } from "async_hooks";
10
10
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
11
11
  import { Router } from "@ttoss/http-server";
12
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
12
  import { z } from "zod";
13
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import { z as z2 } from "zod";
14
15
  var requestContextStore = new AsyncLocalStorage();
15
16
  var apiCall = /* @__PURE__ */__name(async (method, url, options) => {
16
17
  const context = requestContextStore.getStore();
@@ -147,4 +148,58 @@ var createMcpRouter = /* @__PURE__ */__name((server, options = {}) => {
147
148
  });
148
149
  return router;
149
150
  }, "createMcpRouter");
150
- export { McpServer, apiCall, createMcpRouter, z };
151
+ var rawSchemaRegistryMap = /* @__PURE__ */new WeakMap();
152
+ var patchedServerSet = /* @__PURE__ */new WeakSet();
153
+ var registerToolFromSchema = /* @__PURE__ */__name((server, params) => {
154
+ const {
155
+ name,
156
+ description,
157
+ inputSchema = {
158
+ type: "object",
159
+ properties: {}
160
+ },
161
+ handler
162
+ } = params;
163
+ if (!rawSchemaRegistryMap.has(server)) {
164
+ rawSchemaRegistryMap.set(server, /* @__PURE__ */new Map());
165
+ }
166
+ const registry = rawSchemaRegistryMap.get(server);
167
+ registry.set(name, inputSchema);
168
+ server.registerTool(name, {
169
+ description,
170
+ inputSchema: z.record(z.unknown())
171
+ }, async args => {
172
+ return handler(args);
173
+ });
174
+ if (!patchedServerSet.has(server)) {
175
+ patchedServerSet.add(server);
176
+ const rawServer = server.server;
177
+ const origHandler = rawServer?._requestHandlers?.get("tools/list");
178
+ if (!origHandler && process.env.NODE_ENV !== "production") {
179
+ console.warn("[registerToolFromSchema] Could not patch tools/list \u2014 internal MCP SDK structure may have changed. The tool will still be callable, but tools/list may return a Zod-derived schema instead of the verbatim JSON Schema.");
180
+ }
181
+ if (origHandler) {
182
+ rawServer._requestHandlers.set("tools/list", async (rawRequest, extra) => {
183
+ const result = await origHandler(rawRequest, extra);
184
+ const schemas = rawSchemaRegistryMap.get(server);
185
+ if (!schemas) {
186
+ return result;
187
+ }
188
+ return {
189
+ ...result,
190
+ tools: result.tools.map(tool => {
191
+ const raw = schemas.get(tool.name);
192
+ if (raw !== void 0) {
193
+ return {
194
+ ...tool,
195
+ inputSchema: raw
196
+ };
197
+ }
198
+ return tool;
199
+ })
200
+ };
201
+ });
202
+ }
203
+ }
204
+ }, "registerToolFromSchema");
205
+ export { McpServer, apiCall, createMcpRouter, registerToolFromSchema, z2 as z };
package/dist/index.d.cts CHANGED
@@ -3,6 +3,7 @@ import * as koa from 'koa';
3
3
  import { Context } from 'koa';
4
4
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
5
  export { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
6
7
  export { z } from 'zod';
7
8
 
8
9
  /**
@@ -163,5 +164,81 @@ interface McpRouterOptions {
163
164
  * ```
164
165
  */
165
166
  declare const createMcpRouter: (server: McpServer, options?: McpRouterOptions) => _koa_router.RouterInstance<koa.DefaultState, koa.DefaultContext>;
167
+ /**
168
+ * A plain JSON Schema object (draft-07 compatible) describing the shape of a
169
+ * tool's input. Used with {@link registerToolFromSchema} as an alternative to
170
+ * providing a Zod shape, enabling lossless round-trips for schemas that contain
171
+ * features not expressible in Zod v3 (`anyOf`, `$ref`, `pattern`, `allOf`, …).
172
+ */
173
+ interface JsonObjectSchema {
174
+ type: 'object';
175
+ properties?: Record<string, unknown>;
176
+ required?: string[];
177
+ [key: string]: unknown;
178
+ }
179
+ /**
180
+ * Parameters accepted by {@link registerToolFromSchema}.
181
+ */
182
+ interface RegisterToolFromSchemaParams {
183
+ /** Unique tool name. */
184
+ name: string;
185
+ /** Human-readable description shown to the AI client. */
186
+ description?: string;
187
+ /**
188
+ * Plain JSON Schema that describes the tool's input object.
189
+ * This schema is forwarded verbatim over the MCP wire protocol, so any
190
+ * JSON Schema feature (`anyOf`, `$ref`, `pattern`, …) is preserved without
191
+ * loss. Defaults to `{ type: 'object', properties: {} }` when omitted.
192
+ */
193
+ inputSchema?: JsonObjectSchema;
194
+ /**
195
+ * Tool handler invoked when the AI client calls the tool.
196
+ * Receives the raw request arguments as a plain object (no Zod validation is
197
+ * applied, so the shape matches whatever the client sends).
198
+ */
199
+ handler: (args: Record<string, unknown>) => CallToolResult | Promise<CallToolResult>;
200
+ }
201
+ /**
202
+ * Registers a tool on an MCP server using a **plain JSON Schema** object for
203
+ * `inputSchema` instead of a Zod shape.
204
+ *
205
+ * This is useful when tool definitions are shared between the MCP server and an
206
+ * AI SDK agent (e.g. Vercel AI SDK's `tool()` helper), because both consume a
207
+ * plain JSON Schema at runtime. Using this helper eliminates the lossy
208
+ * JSON-Schema→Zod conversion that would otherwise be required.
209
+ *
210
+ * Internally the helper:
211
+ * 1. Registers the tool via the standard `McpServer.registerTool` API using a
212
+ * Zod `z.record(z.unknown())` pass-through schema so that the existing MCP
213
+ * `tools/call` handler correctly routes requests and delivers raw args to
214
+ * your handler.
215
+ * 2. Stores the original JSON Schema and patches the `tools/list` response
216
+ * handler so clients receive the verbatim JSON Schema over the wire.
217
+ *
218
+ * @param server - The `McpServer` instance to register the tool on.
219
+ * @param params - Tool configuration including name, description, inputSchema,
220
+ * and handler.
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * import { registerToolFromSchema, McpServer } from '@ttoss/http-server-mcp';
225
+ *
226
+ * const server = new McpServer({ name: 'my-server', version: '1.0.0' });
227
+ *
228
+ * registerToolFromSchema(server, {
229
+ * name: 'get-project',
230
+ * description: 'Get a project by ID',
231
+ * inputSchema: {
232
+ * type: 'object',
233
+ * properties: { id: { type: 'string', description: 'Project public ID' } },
234
+ * required: ['id'],
235
+ * },
236
+ * handler: async ({ id }) => ({
237
+ * content: [{ type: 'text', text: `Project: ${id}` }],
238
+ * }),
239
+ * });
240
+ * ```
241
+ */
242
+ declare const registerToolFromSchema: (server: McpServer, params: RegisterToolFromSchemaParams) => void;
166
243
 
167
- export { type ApiCallOptions, type McpRouterOptions, apiCall, createMcpRouter };
244
+ export { type ApiCallOptions, type JsonObjectSchema, type McpRouterOptions, type RegisterToolFromSchemaParams, apiCall, createMcpRouter, registerToolFromSchema };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import * as koa from 'koa';
3
3
  import { Context } from 'koa';
4
4
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
5
  export { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
6
7
  export { z } from 'zod';
7
8
 
8
9
  /**
@@ -163,5 +164,81 @@ interface McpRouterOptions {
163
164
  * ```
164
165
  */
165
166
  declare const createMcpRouter: (server: McpServer, options?: McpRouterOptions) => _koa_router.RouterInstance<koa.DefaultState, koa.DefaultContext>;
167
+ /**
168
+ * A plain JSON Schema object (draft-07 compatible) describing the shape of a
169
+ * tool's input. Used with {@link registerToolFromSchema} as an alternative to
170
+ * providing a Zod shape, enabling lossless round-trips for schemas that contain
171
+ * features not expressible in Zod v3 (`anyOf`, `$ref`, `pattern`, `allOf`, …).
172
+ */
173
+ interface JsonObjectSchema {
174
+ type: 'object';
175
+ properties?: Record<string, unknown>;
176
+ required?: string[];
177
+ [key: string]: unknown;
178
+ }
179
+ /**
180
+ * Parameters accepted by {@link registerToolFromSchema}.
181
+ */
182
+ interface RegisterToolFromSchemaParams {
183
+ /** Unique tool name. */
184
+ name: string;
185
+ /** Human-readable description shown to the AI client. */
186
+ description?: string;
187
+ /**
188
+ * Plain JSON Schema that describes the tool's input object.
189
+ * This schema is forwarded verbatim over the MCP wire protocol, so any
190
+ * JSON Schema feature (`anyOf`, `$ref`, `pattern`, …) is preserved without
191
+ * loss. Defaults to `{ type: 'object', properties: {} }` when omitted.
192
+ */
193
+ inputSchema?: JsonObjectSchema;
194
+ /**
195
+ * Tool handler invoked when the AI client calls the tool.
196
+ * Receives the raw request arguments as a plain object (no Zod validation is
197
+ * applied, so the shape matches whatever the client sends).
198
+ */
199
+ handler: (args: Record<string, unknown>) => CallToolResult | Promise<CallToolResult>;
200
+ }
201
+ /**
202
+ * Registers a tool on an MCP server using a **plain JSON Schema** object for
203
+ * `inputSchema` instead of a Zod shape.
204
+ *
205
+ * This is useful when tool definitions are shared between the MCP server and an
206
+ * AI SDK agent (e.g. Vercel AI SDK's `tool()` helper), because both consume a
207
+ * plain JSON Schema at runtime. Using this helper eliminates the lossy
208
+ * JSON-Schema→Zod conversion that would otherwise be required.
209
+ *
210
+ * Internally the helper:
211
+ * 1. Registers the tool via the standard `McpServer.registerTool` API using a
212
+ * Zod `z.record(z.unknown())` pass-through schema so that the existing MCP
213
+ * `tools/call` handler correctly routes requests and delivers raw args to
214
+ * your handler.
215
+ * 2. Stores the original JSON Schema and patches the `tools/list` response
216
+ * handler so clients receive the verbatim JSON Schema over the wire.
217
+ *
218
+ * @param server - The `McpServer` instance to register the tool on.
219
+ * @param params - Tool configuration including name, description, inputSchema,
220
+ * and handler.
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * import { registerToolFromSchema, McpServer } from '@ttoss/http-server-mcp';
225
+ *
226
+ * const server = new McpServer({ name: 'my-server', version: '1.0.0' });
227
+ *
228
+ * registerToolFromSchema(server, {
229
+ * name: 'get-project',
230
+ * description: 'Get a project by ID',
231
+ * inputSchema: {
232
+ * type: 'object',
233
+ * properties: { id: { type: 'string', description: 'Project public ID' } },
234
+ * required: ['id'],
235
+ * },
236
+ * handler: async ({ id }) => ({
237
+ * content: [{ type: 'text', text: `Project: ${id}` }],
238
+ * }),
239
+ * });
240
+ * ```
241
+ */
242
+ declare const registerToolFromSchema: (server: McpServer, params: RegisterToolFromSchemaParams) => void;
166
243
 
167
- export { type ApiCallOptions, type McpRouterOptions, apiCall, createMcpRouter };
244
+ export { type ApiCallOptions, type JsonObjectSchema, type McpRouterOptions, type RegisterToolFromSchemaParams, apiCall, createMcpRouter, registerToolFromSchema };
package/dist/index.js CHANGED
@@ -34,14 +34,16 @@ __export(index_exports, {
34
34
  McpServer: () => import_mcp.McpServer,
35
35
  apiCall: () => apiCall,
36
36
  createMcpRouter: () => createMcpRouter,
37
- z: () => import_zod.z
37
+ registerToolFromSchema: () => registerToolFromSchema,
38
+ z: () => import_zod2.z
38
39
  });
39
40
  module.exports = __toCommonJS(index_exports);
40
41
  var import_node_async_hooks = require("async_hooks");
41
42
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
42
43
  var import_http_server = require("@ttoss/http-server");
43
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
44
44
  var import_zod = require("zod");
45
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
46
+ var import_zod2 = require("zod");
45
47
  var requestContextStore = new import_node_async_hooks.AsyncLocalStorage();
46
48
  var apiCall = /* @__PURE__ */__name(async (method, url, options) => {
47
49
  const context = requestContextStore.getStore();
@@ -178,10 +180,65 @@ var createMcpRouter = /* @__PURE__ */__name((server, options = {}) => {
178
180
  });
179
181
  return router;
180
182
  }, "createMcpRouter");
183
+ var rawSchemaRegistryMap = /* @__PURE__ */new WeakMap();
184
+ var patchedServerSet = /* @__PURE__ */new WeakSet();
185
+ var registerToolFromSchema = /* @__PURE__ */__name((server, params) => {
186
+ const {
187
+ name,
188
+ description,
189
+ inputSchema = {
190
+ type: "object",
191
+ properties: {}
192
+ },
193
+ handler
194
+ } = params;
195
+ if (!rawSchemaRegistryMap.has(server)) {
196
+ rawSchemaRegistryMap.set(server, /* @__PURE__ */new Map());
197
+ }
198
+ const registry = rawSchemaRegistryMap.get(server);
199
+ registry.set(name, inputSchema);
200
+ server.registerTool(name, {
201
+ description,
202
+ inputSchema: import_zod.z.record(import_zod.z.unknown())
203
+ }, async args => {
204
+ return handler(args);
205
+ });
206
+ if (!patchedServerSet.has(server)) {
207
+ patchedServerSet.add(server);
208
+ const rawServer = server.server;
209
+ const origHandler = rawServer?._requestHandlers?.get("tools/list");
210
+ if (!origHandler && process.env.NODE_ENV !== "production") {
211
+ console.warn("[registerToolFromSchema] Could not patch tools/list \u2014 internal MCP SDK structure may have changed. The tool will still be callable, but tools/list may return a Zod-derived schema instead of the verbatim JSON Schema.");
212
+ }
213
+ if (origHandler) {
214
+ rawServer._requestHandlers.set("tools/list", async (rawRequest, extra) => {
215
+ const result = await origHandler(rawRequest, extra);
216
+ const schemas = rawSchemaRegistryMap.get(server);
217
+ if (!schemas) {
218
+ return result;
219
+ }
220
+ return {
221
+ ...result,
222
+ tools: result.tools.map(tool => {
223
+ const raw = schemas.get(tool.name);
224
+ if (raw !== void 0) {
225
+ return {
226
+ ...tool,
227
+ inputSchema: raw
228
+ };
229
+ }
230
+ return tool;
231
+ })
232
+ };
233
+ });
234
+ }
235
+ }
236
+ }, "registerToolFromSchema");
181
237
  // Annotate the CommonJS export names for ESM import in node:
182
238
  0 && (module.exports = {
183
239
  McpServer,
184
240
  apiCall,
185
241
  createMcpRouter,
242
+ registerToolFromSchema,
186
243
  z
187
244
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/http-server-mcp",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
5
5
  "license": "MIT",
6
6
  "author": "ttoss",
@@ -29,6 +29,9 @@
29
29
  "zod": "^3.25.0",
30
30
  "@ttoss/http-server": "^0.5.9"
31
31
  },
32
+ "peerDependencies": {
33
+ "@modelcontextprotocol/sdk": "^1.0.0"
34
+ },
32
35
  "devDependencies": {
33
36
  "@types/koa": "^3.0.1",
34
37
  "jest": "^30.3.0",