From bc50fd23f18c3ed6b47e9a525c9e592bb88ea2e0 Mon Sep 17 00:00:00 2001 From: sigoden Date: Wed, 11 Dec 2024 07:58:18 +0800 Subject: feat: add mcp server --- mcp/server/README.md | 36 +++++++++++++++ mcp/server/index.js | 120 ++++++++++++++++++++++++++++++++++++++++++++++++ mcp/server/package.json | 24 ++++++++++ 3 files changed, 180 insertions(+) create mode 100644 mcp/server/README.md create mode 100755 mcp/server/index.js create mode 100644 mcp/server/package.json diff --git a/mcp/server/README.md b/mcp/server/README.md new file mode 100644 index 0000000..f2bda68 --- /dev/null +++ b/mcp/server/README.md @@ -0,0 +1,36 @@ +# MCP-Server + +Let LLM-functions tools/agents be used through the Model Context Protocol. + +## Serve tools + +```json +{ + "mcpServers": { + "tools": { + "command": "node", + "args": [ + "/mcp/server/index.js", + "" + ] + } + } +} +``` + +## Serve the agent + +```json +{ + "mcpServers": { + "": { + "command": "node", + "args": [ + "/mcp/server/index.js", + "", + "", + ] + } + } +} +``` diff --git a/mcp/server/index.js b/mcp/server/index.js new file mode 100755 index 0000000..d725568 --- /dev/null +++ b/mcp/server/index.js @@ -0,0 +1,120 @@ +#!/usr/bin/env node + +import * as path from "node:path"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import { v4 as uuid } from "uuid"; +import { spawn } from "node:child_process"; +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ErrorCode, + ListToolsRequestSchema, + McpError, +} from "@modelcontextprotocol/sdk/types.js"; + +let [rootDir, agentName] = process.argv.slice(2); +if (!rootDir) { + console.error("Usage: mcp-llm-functions []"); + process.exit(1); +} +rootDir = path.resolve(rootDir); + +let functionsJsonPath = path.join(rootDir, "functions.json"); +if (agentName) { + functionsJsonPath = path.join(rootDir, "agents", agentName, "functions.json"); +} +let functions = []; +try { + const data = await fs.promises.readFile(functionsJsonPath, "utf8"); + functions = JSON.parse(data); +} catch { + console.error(`Failed to read functions at '${functionsJsonPath}'`); + process.exit(1); +} +const env = Object.assign({}, process.env, { + PATH: `${path.join(rootDir, "bin")}:${process.env.PATH}` +}); + +const server = new Server( + { + name: `llm-functions/${agentName || "common-tools"}`, + version: "0.1.0", + }, + { + capabilities: { + tools: {}, + }, + }, +); + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: functions.map((f) => ({ + name: f.name, + description: f.description, + inputSchema: f.parameters, + })), + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const functionObj = functions.find((f) => f.name === request.params.name); + if (!functionObj) { + throw new McpError(ErrorCode.InvalidRequest, `Unexpected tool '${request.params.name}'`); + } + let command = request.params.name; + let args = [JSON.stringify(request.params.arguments || {})]; + if (agentName && functionObj.agent) { + args.unshift(command); + command = agentName; + } + const tmpFile = path.join(os.tmpdir(), `mcp-llm-functions-${process.pid}-eval-${uuid()}`); + const { exitCode, stderr } = await runCommand(command, args, { ...env, LLM_OUTPUT: tmpFile }); + if (exitCode === 0) { + let output = ''; + try { + output = await fs.promises.readFile(tmpFile, "utf8"); + } catch { }; + return { + content: [{ type: "text", value: output }], + } + } else { + return { + isError: true, + error: stderr, + }; + } +}); + +function runCommand(command, args, env) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + stdio: ['ignore', 'ignore', 'pipe'], + env, + }); + + let stderr = ''; + + child.stderr.on('data', (data) => { + stderr += data.toString(); + }); + + child.on('close', (exitCode) => { + resolve({ exitCode, stderr }); + }); + + child.on('error', (err) => { + reject(err); + }); + }); +} + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("LLM-Functions MCP Server running on stdio"); +} + +runServer().catch(console.error); \ No newline at end of file diff --git a/mcp/server/package.json b/mcp/server/package.json new file mode 100644 index 0000000..69bbcc2 --- /dev/null +++ b/mcp/server/package.json @@ -0,0 +1,24 @@ +{ + "name": "mcp-llm-functions", + "version": "1.0.0", + "description": "Let LLM-functions tools/agents be used through the Model Context Protocol", + "license": "MIT", + "author": "sigoden ", + "homepage": "https://github.com/sigoden/llm-functions/tree/main/mcp/server", + "repository": { + "type": "git", + "url": "git+https://github.com/sigoden/llm-functions.git", + "directory": "mcp/server" + }, + "publishConfig": { + "access": "public" + }, + "type": "module", + "bin": { + "mcp-llm-functions": "index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.3", + "uuid": "^11.0.3" + } +} -- cgit v1.2.3