genai-function-calling/vercel-ai/mcp.js (63 lines of code) (raw):

const {McpServer} = require('@modelcontextprotocol/sdk/server/mcp.js'); const {StdioServerTransport} = require('@modelcontextprotocol/sdk/server/stdio.js'); const {StdioClientTransport} = require('@modelcontextprotocol/sdk/client/stdio.js'); const {experimental_createMCPClient} = require('ai'); const fs = require('fs'); const path = require('path'); const SERVER_ARG = '--mcp-server'; // Get MCP server parameters from package.json let name, version; try { const packageJsonPath = path.join(__dirname, 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); name = packageJson.name; version = packageJson.version; } catch (error) { console.error('Failed to read package.json:', error.message); process.exit(1); } /** * Starts an MCP server and registers the provided tools, listening on stdin/stdout. * * @param {import('ai').ToolSet} tools - The tools to register with the MCP server. */ async function mcpServerMain(tools) { const server = new McpServer({name, version}); // Register each tool with the server Object.entries(tools).forEach(([toolName, tool]) => { console.log(`Registering tool: ${toolName}`); server.tool( toolName, tool.description, tool.parameters.shape, async (params) => { try { const result = await tool.execute(params); return {content: [{type: 'text', text: result}]}; } catch (error) { return {content: [{type: 'text', text: error.message}]}; } } ); }); const transport = new StdioServerTransport(); await server.connect(transport); } /** * Launches an MCP server in a subprocess and runs the agent with its tools via a client. * * @param {Function} runAgent - The function to run the agent with the retrieved tools. */ async function runAgentWithMCPClient(runAgent) { // MCP server is a subprocess, which doesn't inherit anything by default. // Minimally, we need to pass an argument to let the process know it is // running as a server. We also propagate any ENV or arguments to ensure // OpenTelemetry auto-instrumentation propagates to the child, such as // '-r @elastic/opentelemetry-node'. Don't do this with untrusted servers. const transport = new StdioClientTransport({ command: process.execPath, args: [...process.execArgv, ...process.argv.slice(1), SERVER_ARG], env: process.env, }); let client; try { client = await experimental_createMCPClient({transport}); const tools = await client.tools(); await runAgent(tools); } catch (error) { throw error; } finally { await client?.close(); // closing the client closes its transport } } /** * Main entry point to either run the MCP server directly or launch it in a subprocess * and run the agent with as an MCP client. * * @param {Function} runAgent - The function to run the agent with the tools. * @param {import('ai').ToolSet} tools - The tools to register with the MCP server. */ async function mcpClientMain(runAgent, tools) { if (process.argv.includes(SERVER_ARG)) { await mcpServerMain(tools); } else { await runAgentWithMCPClient(runAgent); } } module.exports = {mcpClientMain};