Make MCP a resident VoidBot organ
This commit is contained in:
parent
329e33bda0
commit
74abfed109
14 changed files with 175 additions and 171 deletions
|
|
@ -1,50 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
const host = process.env.VOIDBOT_MCP_REMOTE_HOST;
|
||||
const keyPath = process.env.VOIDBOT_MCP_SSH_KEY_PATH;
|
||||
if (!host || !keyPath) {
|
||||
throw new Error("VOIDBOT_MCP_REMOTE_HOST and VOIDBOT_MCP_SSH_KEY_PATH are required.");
|
||||
}
|
||||
|
||||
const server = new Server(
|
||||
{ name: "voidbot-yggdrasil-relay", version: "1.0.0" },
|
||||
{ capabilities: { tools: {} } },
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
||||
withRemoteClient((client) => client.listTools()),
|
||||
);
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) =>
|
||||
withRemoteClient((client) => client.callTool(request.params)),
|
||||
);
|
||||
|
||||
async function withRemoteClient(operation) {
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.env.VOIDBOT_MCP_SSH_EXECUTABLE ?? "ssh",
|
||||
args: [
|
||||
"-T", "-i", keyPath, host,
|
||||
"sudo", "docker", "exec", "-i", "compose-worker-1",
|
||||
"node", "/app/apps/worker/dist/mcp-server.js",
|
||||
],
|
||||
env: process.env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const client = new Client({ name: "voidbot-yggdrasil-relay-upstream", version: "1.0.0" });
|
||||
try {
|
||||
await client.connect(transport);
|
||||
return await operation(client);
|
||||
} finally {
|
||||
await client.close().catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
await server.connect(new StdioServerTransport());
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
|
||||
const node = process.execPath;
|
||||
const transport = new StdioClientTransport({
|
||||
command: node,
|
||||
args: ["scripts/serve-voidbot-remote-mcp-relay.mjs"],
|
||||
env: process.env,
|
||||
stderr: "inherit",
|
||||
});
|
||||
const client = new Client({ name: "voidbot-remote-relay-smoke", version: "1.0.0" });
|
||||
|
||||
try {
|
||||
await client.connect(transport);
|
||||
const tools = await client.listTools();
|
||||
const repositories = await client.callTool({ name: "list_indexed_repos", arguments: {} });
|
||||
const sources = await client.callTool({
|
||||
name: "search_sources",
|
||||
arguments: { query: "VoidBot indexed source retrieval", repoName: "VoidBot", limit: 1 },
|
||||
});
|
||||
if (repositories.isError || sources.isError) {
|
||||
throw new Error("Remote relay returned an MCP error for indexed-source retrieval.");
|
||||
}
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
ok: true,
|
||||
toolCount: tools.tools.length,
|
||||
repoCount: repositories.structuredContent?.repoCount,
|
||||
sourceResultCount: sources.structuredContent?.resultCount,
|
||||
})}\n`);
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
|
||||
const host = process.env.VOIDBOT_MCP_REMOTE_HOST;
|
||||
const keyPath = process.env.VOIDBOT_MCP_SSH_KEY_PATH;
|
||||
if (!host || !keyPath) {
|
||||
throw new Error("VOIDBOT_MCP_REMOTE_HOST and VOIDBOT_MCP_SSH_KEY_PATH are required.");
|
||||
}
|
||||
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.env.VOIDBOT_MCP_SSH_EXECUTABLE ?? "ssh",
|
||||
args: [
|
||||
"-T", "-i", keyPath, host,
|
||||
"sudo", "docker", "exec", "-i", "compose-worker-1",
|
||||
"node", "/app/apps/worker/dist/mcp-server.js",
|
||||
],
|
||||
env: process.env,
|
||||
stderr: "inherit",
|
||||
});
|
||||
const client = new Client({ name: "voidbot-remote-mcp-smoke", version: "1.0.0" });
|
||||
|
||||
try {
|
||||
await client.connect(transport);
|
||||
const tools = await client.listTools();
|
||||
for (const requiredTool of ["search_history", "list_indexed_repos", "search_sources"]) {
|
||||
if (!tools.tools.some((tool) => tool.name === requiredTool)) {
|
||||
throw new Error(`Remote VoidBot MCP did not advertise ${requiredTool}.`);
|
||||
}
|
||||
}
|
||||
const historyResult = await client.callTool({
|
||||
name: "search_history",
|
||||
arguments: { query: "VoidBot deployment Yggdrasil", limit: 2 },
|
||||
});
|
||||
if (historyResult.isError) {
|
||||
const detail = historyResult.content?.map((entry) => entry.type === "text" ? entry.text : entry.type).join(" ");
|
||||
throw new Error(`Remote search_history failed: ${detail || "unknown MCP error"}`);
|
||||
}
|
||||
const repositoriesResult = await client.callTool({ name: "list_indexed_repos", arguments: {} });
|
||||
if (repositoriesResult.isError) {
|
||||
throw new Error("Remote list_indexed_repos failed.");
|
||||
}
|
||||
const sourceResult = await client.callTool({
|
||||
name: "search_sources",
|
||||
arguments: { query: "VoidBot indexed source retrieval", limit: 2 },
|
||||
});
|
||||
if (sourceResult.isError) {
|
||||
const detail = sourceResult.content?.map((entry) => entry.type === "text" ? entry.text : entry.type).join(" ");
|
||||
throw new Error(`Remote search_sources failed: ${detail || "unknown MCP error"}`);
|
||||
}
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
ok: true,
|
||||
toolCount: tools.tools.length,
|
||||
historyResult,
|
||||
repositoriesResult,
|
||||
sourceResult,
|
||||
}, null, 2)}\n`);
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
46
scripts/smoke-voidbot-resident-mcp-http.mjs
Normal file
46
scripts/smoke-voidbot-resident-mcp-http.mjs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
|
||||
const targetUrl = process.env.VOIDBOT_MCP_HTTP_URL;
|
||||
let listener;
|
||||
let endpoint;
|
||||
if (targetUrl) {
|
||||
endpoint = new URL(targetUrl);
|
||||
} else {
|
||||
const { createVoidbotMcpContext } = await import("../apps/worker/dist/mcp-server-context.js");
|
||||
const { startVoidbotMcpHttpServer } = await import("../apps/worker/dist/mcp-http-server.js");
|
||||
const port = Number.parseInt(process.env.VOIDBOT_MCP_HTTP_SMOKE_PORT ?? "17876", 10);
|
||||
listener = await startVoidbotMcpHttpServer(createVoidbotMcpContext(), {
|
||||
host: "127.0.0.1",
|
||||
port,
|
||||
});
|
||||
endpoint = new URL(`http://127.0.0.1:${port}/mcp`);
|
||||
}
|
||||
const client = new Client({ name: "voidbot-resident-mcp-http-smoke", version: "1.0.0" });
|
||||
|
||||
try {
|
||||
await client.connect(new StreamableHTTPClientTransport(endpoint));
|
||||
const tools = await client.listTools();
|
||||
const repositories = await client.callTool({ name: "list_indexed_repos", arguments: {} });
|
||||
if (repositories.isError || !repositories.structuredContent?.repoCount) {
|
||||
throw new Error("Resident MCP HTTP endpoint did not return indexed repositories.");
|
||||
}
|
||||
const sources = await client.callTool({
|
||||
name: "search_sources",
|
||||
arguments: { query: "VoidBot resident MCP", repoName: "VoidBot", limit: 1 },
|
||||
});
|
||||
if (sources.isError || !sources.structuredContent?.resultCount) {
|
||||
throw new Error("Resident MCP HTTP endpoint did not return indexed source results.");
|
||||
}
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
ok: true,
|
||||
toolCount: tools.tools.length,
|
||||
repoCount: repositories.structuredContent.repoCount,
|
||||
sourceResultCount: sources.structuredContent.resultCount,
|
||||
})}\n`);
|
||||
} finally {
|
||||
await client.close().catch(() => undefined);
|
||||
if (listener) {
|
||||
await new Promise((resolve, reject) => listener.close((error) => error ? reject(error) : resolve()));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue