Skip to content

Instantly share code, notes, and snippets.

@tunnckoCore
Created March 29, 2025 17:05
Show Gist options
  • Save tunnckoCore/f98c037f46a23f2da9f3018a08af1355 to your computer and use it in GitHub Desktop.
Save tunnckoCore/f98c037f46a23f2da9f3018a08af1355 to your computer and use it in GitHub Desktop.
converting oRPC routers to MCP tools - beginning, it's buggy.. can't figure out how to provide the procedure's inputSchema as mcp tool schema... should be `inputSchema.shape` but.. it errors
interface McpToolResult {
content: Array<{ type: string; text: string }>;
isError?: boolean;
}
interface OrpcProcedureMeta {
meta?: {
tool_name?: string;
};
route: {
summary?: string;
tags?: string[];
};
inputSchema: ZodSchema;
handler: (input: any) => Promise<any>;
}
// Helper to convert procedure result to MCP format
function toMcpResult(result: any): McpToolResult {
return {
content: [{ type: 'text', text: JSON.stringify(result) }],
};
}
// Helper to convert error to MCP format
function toMcpError(error: any): McpToolResult {
return {
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
isError: true,
};
}
export function convertRouterToMcpTools(orpcRouter: any, options?: any) {
const tools = new Map();
const opts = {
context: {},
name: 'orpc router',
version: '0.0.1',
...options,
};
// Recursively walk through router to find procedures
function walkRouter(node: any, path: string[] = []) {
for (const [key, value] of Object.entries<any>(node)) {
if (value?.['~orpc']) {
// Found a procedure
const procedure = value['~orpc'] as OrpcProcedureMeta;
const toolName = procedure.meta?.tool_name || [...path, key].join('_');
tools.set(toolName, {
name: toolName,
path: (procedure.route as any)?.path?.slice(1),
description: procedure.route.summary || '',
inputSchema: procedure.inputSchema,
handler: async (input: any) => {
try {
const result = await procedure.handler({
context: opts.context,
input,
path: toolName.split('_'),
procedure: value['~orpc'],
});
return toMcpResult(result);
} catch (err) {
return toMcpError(err);
}
},
});
} else if (typeof value === 'object') {
// Continue walking
walkRouter(value, [...path, key]);
}
}
}
walkRouter(orpcRouter);
return { ...opts, tools };
}
// USAGE ---
const mcp = convertRouterToMcpTools(router, {
name: 'orpc-contract-first-playground-demo-mcp-server',
version: '0.1.0',
context: {
db: createFakeDB(),
},
});
// Create server instance
const server = new McpServer({
name: mcp.name,
version: mcp.version,
});
for (const [_, tool] of mcp.tools.entries()) {
server.tool(tool.name, tool.inputSchema.shape, tool.handler);
}
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('orpc contract first playground mcp server running on stdio');
}
main().catch((err) => {
console.error('Fatal error in main():', err);
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment