diff options
| author | sigoden <sigoden@gmail.com> | 2024-12-11 07:58:18 +0800 |
|---|---|---|
| committer | sigoden <sigoden@gmail.com> | 2024-12-11 08:36:04 +0800 |
| commit | bc50fd23f18c3ed6b47e9a525c9e592bb88ea2e0 (patch) | |
| tree | 57a1bc44c603d44ae117425d905fbff0a43e63e3 | |
| parent | 22a87a8753ca1ad23ebe0a816c230302edfd7416 (diff) | |
| download | llm-functions-docker-bc50fd23f18c3ed6b47e9a525c9e592bb88ea2e0.tar.gz | |
feat: add mcp server
| -rw-r--r-- | mcp/server/README.md | 36 | ||||
| -rwxr-xr-x | mcp/server/index.js | 120 | ||||
| -rw-r--r-- | mcp/server/package.json | 24 |
3 files changed, 180 insertions, 0 deletions
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": [ + "<path-to-llm-functions>/mcp/server/index.js", + "<path-to-llm-functions>" + ] + } + } +} +``` + +## Serve the agent + +```json +{ + "mcpServers": { + "<agent-name>": { + "command": "node", + "args": [ + "<path-to-llm-functions>/mcp/server/index.js", + "<path-to-llm-functions>", + "<agent-name>", + ] + } + } +} +``` 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 <llm-functions-dir> [<agent-name>]"); + 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 <sigoden@gmail.com>", + "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" + } +} |
