Skip to content

Commit 857b285

Browse files
committed
Allow users to add middlewares on toollist and toolcall
Signed-off-by: Marcos Candeia <[email protected]>
1 parent 1835d9f commit 857b285

File tree

6 files changed

+107
-28
lines changed

6 files changed

+107
-28
lines changed

deno.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@deco/mcp",
3-
"version": "0.2.6",
3+
"version": "0.2.7",
44
"exports": "./mod.ts",
55
"tasks": {
66
"check": "deno fmt && deno lint && deno check mod.ts"
@@ -9,6 +9,7 @@
99
"@deco/deco": "jsr:@deco/deco@^1.110.4",
1010
"@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/sdk@^1.6.1",
1111
"@hono/hono": "jsr:@hono/hono@^4.5.4",
12-
"@std/http": "jsr:@std/http@^1.0.13"
12+
"@std/http": "jsr:@std/http@^1.0.13",
13+
"zod": "npm:zod@^3.24.2"
1314
}
1415
}

deno.lock

Lines changed: 20 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mcp/middleware.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// deno-lint-ignore-file no-explicit-any
2+
3+
export interface RequestMiddlewareContext<T = any> {
4+
next?(): Promise<T>;
5+
}
6+
export type RequestMiddleware<
7+
TRequest = any,
8+
TResponse = any,
9+
> = (request: TRequest, next?: () => Promise<TResponse>) => Promise<TResponse>;
10+
11+
export const compose = <
12+
TRequest,
13+
TResponse,
14+
>(
15+
...middlewares: RequestMiddleware<TRequest, TResponse>[]
16+
): RequestMiddleware<TRequest, TResponse> => {
17+
const last = middlewares[middlewares.length - 1];
18+
return function composedResolver(request: TRequest) {
19+
const dispatch = (
20+
i: number,
21+
): Promise<TResponse> => {
22+
const middleware = middlewares[i];
23+
if (!middleware) {
24+
return last(request);
25+
}
26+
const next = () => dispatch(i + 1);
27+
return middleware(request, next);
28+
};
29+
30+
return dispatch(0);
31+
};
32+
};

mcp/server.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import { SSEServerTransport } from "./sse.ts";
1515
import { HttpServerTransport } from "./http.ts";
1616
import { dereferenceSchema } from "./utils.ts";
1717
import { WebSocketServerTransport } from "./websocket.ts";
18-
18+
import { compose, type RequestMiddleware } from "./middleware.ts";
19+
import type { z } from "zod";
1920
const idFromDefinition = (definition: string) => {
2021
const [_, __, id] = definition.split("/");
2122
return id;
@@ -50,6 +51,10 @@ export interface Options<TManifest extends AppManifest> {
5051
exclude?: Array<keyof (TManifest["actions"] & TManifest["loaders"])>;
5152
blocks?: Array<keyof TManifest>;
5253
basePath?: string;
54+
middlewares?: {
55+
listTools?: ListToolsMiddleware[];
56+
callTool?: CallToolMiddleware[];
57+
};
5358
}
5459

5560
interface RootSchema extends JSONSchema7 {
@@ -183,26 +188,45 @@ export const getTools = <TManifest extends AppManifest>(
183188
return tools.filter((tool) => tool !== undefined);
184189
};
185190

191+
export interface ListToolsResult {
192+
tools: Tool[];
193+
}
194+
195+
export type ListToolsMiddleware = RequestMiddleware<
196+
z.infer<typeof ListToolsRequestSchema>,
197+
ListToolsResult
198+
>;
199+
200+
export type CallToolMiddleware = RequestMiddleware<
201+
z.infer<typeof CallToolRequestSchema>,
202+
{ content: { type: "text"; text: string }[] }
203+
>;
204+
186205
function registerTools<TManifest extends AppManifest>(
187206
mcp: McpServer,
188207
deco: Deco<TManifest>,
189208
options?: Options<TManifest>,
190209
) {
191210
// Add map to store slugified names to original names
192211
let toolNames: null | Map<string, string> = null;
193-
const loadTools = async () => {
212+
const loadTools: ListToolsMiddleware = async (): Promise<ListToolsResult> => {
194213
toolNames ??= new Map<string, string>();
195214
const meta = await deco.meta().then((v) => v?.value);
196215
if (!meta) return { tools: [] };
197216
const schemas = meta.schema;
198217
return { tools: getTools(toolNames, schemas, options) };
199218
};
200219

201-
mcp.server.setRequestHandler(ListToolsRequestSchema, async () => {
202-
return await loadTools();
220+
const listTools: ListToolsMiddleware = compose(
221+
...(options?.middlewares?.listTools ?? []),
222+
loadTools,
223+
);
224+
225+
mcp.server.setRequestHandler(ListToolsRequestSchema, async (request) => {
226+
return await listTools(request);
203227
});
204228

205-
mcp.server.setRequestHandler(CallToolRequestSchema, async (req) => {
229+
const invokeTool = async (req: z.infer<typeof CallToolRequestSchema>) => {
206230
IS_DEBUG && console.log(req);
207231
try {
208232
const state = await deco.prepareState({
@@ -213,7 +237,7 @@ function registerTools<TManifest extends AppManifest>(
213237
});
214238
// Use the original name from the map when invoking
215239
if (!toolNames) {
216-
await loadTools();
240+
await loadTools({ request: {} });
217241
}
218242
const originalName = toolNames!.get(req.params.name);
219243
if (!originalName) {
@@ -227,12 +251,21 @@ function registerTools<TManifest extends AppManifest>(
227251
state,
228252
);
229253
return {
230-
content: [{ type: "text", text: JSON.stringify(result) }],
254+
content: [{ type: "text" as const, text: JSON.stringify(result) }],
231255
};
232256
} catch (err) {
233257
console.error(err);
234258
throw err;
235259
}
260+
};
261+
262+
const callToolMiddleware: CallToolMiddleware = compose(
263+
...(options?.middlewares?.callTool ?? []),
264+
invokeTool,
265+
);
266+
267+
mcp.server.setRequestHandler(CallToolRequestSchema, async (req) => {
268+
return await callToolMiddleware(req);
236269
});
237270
}
238271

mcp/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ export function dereferenceSchema(
9494
);
9595
}
9696

97+
if ("allOf" in result && !Array.isArray(result.allOf)) {
98+
delete result.allOf;
99+
}
100+
97101
return result;
98102
}
99103

mod.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1-
export { getTools, mcpServer, type Tool } from "./mcp/server.ts";
1+
export {
2+
type CallToolMiddleware,
3+
getTools,
4+
type ListToolsMiddleware,
5+
mcpServer,
6+
type Options,
7+
type Tool,
8+
} from "./mcp/server.ts";
29
export { HttpClientTransport } from "./mcp/http-client.ts";

0 commit comments

Comments
 (0)