aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsigoden <sigoden@gmail.com>2024-12-11 08:42:16 +0800
committerGitHub <noreply@github.com>2024-12-11 08:42:16 +0800
commitc58abcbaf89f27e5e3806f4309880a1eac2b7095 (patch)
treeae85083bc61e19a136017458a157c76cd58ce2cc
parent7ff2af8959f1e55b4fc815be59f01666a7b40ace (diff)
parentbc50fd23f18c3ed6b47e9a525c9e592bb88ea2e0 (diff)
downloadllm-functions-docker-c58abcbaf89f27e5e3806f4309880a1eac2b7095.tar.gz
Merge pull request #139 from sigoden/feat-mcp-server
-rw-r--r--mcp/server/README.md36
-rwxr-xr-xmcp/server/index.js120
-rw-r--r--mcp/server/package.json24
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"
+ }
+}