Created
March 29, 2025 17:05
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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