Merge pull request #139 from sigoden/feat-mcp-server
This commit is contained in:
@@ -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>",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Executable
+120
@@ -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);
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user