refactor to not use custom websocket protocol
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
"dev": "tsx watch --clear-screen=false src/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/eventsource": "^1.1.15",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/ws": "^8.5.12",
|
||||
@@ -17,8 +18,10 @@
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.0",
|
||||
"mcp-typescript": "file:../packages/mcp-typescript",
|
||||
"ws": "^8.18.0"
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.23.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ import {
|
||||
ListToolsResultSchema,
|
||||
CallToolResult,
|
||||
CallToolResultSchema,
|
||||
GetPromptRequest,
|
||||
ReadResourceRequest,
|
||||
CallToolRequest,
|
||||
} from "mcp-typescript/types.js";
|
||||
|
||||
export class McpClient {
|
||||
@@ -60,11 +63,13 @@ export class McpClient {
|
||||
);
|
||||
}
|
||||
|
||||
async readResource(uri: string): Promise<ReadResourceResult> {
|
||||
async readResource(
|
||||
params: ReadResourceRequest["params"],
|
||||
): Promise<ReadResourceResult> {
|
||||
return await this.client.request(
|
||||
{
|
||||
method: "resources/read",
|
||||
params: { uri },
|
||||
params,
|
||||
},
|
||||
ReadResourceResultSchema,
|
||||
);
|
||||
@@ -81,13 +86,12 @@ export class McpClient {
|
||||
}
|
||||
|
||||
async getPrompt(
|
||||
name: string,
|
||||
args?: Record<string, string>,
|
||||
params: GetPromptRequest["params"],
|
||||
): Promise<GetPromptResult> {
|
||||
return await this.client.request(
|
||||
{
|
||||
method: "prompts/get",
|
||||
params: { name, arguments: args },
|
||||
params,
|
||||
},
|
||||
GetPromptResultSchema,
|
||||
);
|
||||
@@ -102,14 +106,11 @@ export class McpClient {
|
||||
);
|
||||
}
|
||||
|
||||
async callTool(
|
||||
name: string,
|
||||
params: Record<string, unknown>,
|
||||
): Promise<CallToolResult> {
|
||||
async callTool(params: CallToolRequest["params"]): Promise<CallToolResult> {
|
||||
return await this.client.request(
|
||||
{
|
||||
method: "tools/call",
|
||||
params: { name, arguments: params },
|
||||
params,
|
||||
},
|
||||
CallToolResultSchema,
|
||||
);
|
||||
|
||||
@@ -1,71 +1,84 @@
|
||||
import McpClient from "./client.js";
|
||||
import cors from "cors";
|
||||
|
||||
import { Server } from "mcp-typescript/server/index.js";
|
||||
import { SSEServerTransport } from "mcp-typescript/server/sse.js";
|
||||
import express from "express";
|
||||
import http from "http";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
GetPromptRequestSchema,
|
||||
ListPromptsRequestSchema,
|
||||
ListResourcesRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
ReadResourceRequestSchema,
|
||||
} from "mcp-typescript/types.js";
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const wss = new WebSocketServer({ server });
|
||||
app.use(cors());
|
||||
|
||||
let mcpClient: McpClient | null = null;
|
||||
let servers: Server[] = [];
|
||||
|
||||
wss.on("connection", (ws: WebSocket) => {
|
||||
ws.on("message", async (message: string) => {
|
||||
try {
|
||||
const command = JSON.parse(message);
|
||||
app.get("/sse", async (req, res) => {
|
||||
console.log("New SSE connection");
|
||||
const command = decodeURIComponent(req.query.command as string);
|
||||
const args = decodeURIComponent(req.query.args as string).split(",");
|
||||
const mcpClient = new McpClient("MyApp", "1.0.0");
|
||||
await mcpClient.connectStdio(command, args);
|
||||
|
||||
if (command.type === "connect" && command.command && command.args) {
|
||||
mcpClient = new McpClient("MyApp", "1.0.0");
|
||||
await mcpClient.connectStdio(command.command, command.args);
|
||||
ws.send(JSON.stringify({ type: "connected" }));
|
||||
} else if (!mcpClient) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "error",
|
||||
message: "Not connected to MCP server",
|
||||
}),
|
||||
);
|
||||
} else if (command.type === "listResources") {
|
||||
const resources = await mcpClient.listResources();
|
||||
ws.send(JSON.stringify({ type: "resources", data: resources }));
|
||||
} else if (command.type === "readResource" && command.uri) {
|
||||
const resource = await mcpClient.readResource(command.uri);
|
||||
ws.send(JSON.stringify({ type: "resource", data: resource }));
|
||||
} else if (command.type === "listPrompts") {
|
||||
const prompts = await mcpClient.listPrompts();
|
||||
ws.send(JSON.stringify({ type: "prompts", data: prompts }));
|
||||
} else if (command.type === "getPrompt" && command.name) {
|
||||
const prompt = await mcpClient.getPrompt(command.name, command.args);
|
||||
ws.send(JSON.stringify({ type: "prompt", data: prompt }));
|
||||
} else if (command.type === "listTools") {
|
||||
const tools = await mcpClient.listTools();
|
||||
ws.send(JSON.stringify({ type: "tools", data: tools }));
|
||||
} else if (
|
||||
command.type === "callTool" &&
|
||||
command.name &&
|
||||
command.params
|
||||
) {
|
||||
const result = await mcpClient.callTool(command.name, command.params);
|
||||
ws.send(
|
||||
JSON.stringify({ type: "toolResult", data: result.toolResult }),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
ws.send(JSON.stringify({ type: "error", message: String(error) }));
|
||||
}
|
||||
const transport = new SSEServerTransport("/message");
|
||||
const server = new Server({
|
||||
name: "mcp-server-inspector",
|
||||
version: "0.0.1",
|
||||
});
|
||||
servers.push(server);
|
||||
|
||||
server.onclose = async () => {
|
||||
console.log("SSE connection closed");
|
||||
servers = servers.filter((s) => s !== server);
|
||||
await mcpClient.close();
|
||||
};
|
||||
|
||||
server.setRequestHandler(ListResourcesRequestSchema, () => {
|
||||
return mcpClient.listResources();
|
||||
});
|
||||
|
||||
server.setRequestHandler(ReadResourceRequestSchema, (params) => {
|
||||
return mcpClient.readResource(params.params);
|
||||
});
|
||||
|
||||
server.setRequestHandler(ListPromptsRequestSchema, () => {
|
||||
return mcpClient.listPrompts();
|
||||
});
|
||||
|
||||
server.setRequestHandler(GetPromptRequestSchema, (params) => {
|
||||
return mcpClient.getPrompt(params.params);
|
||||
});
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, () => {
|
||||
return mcpClient.listTools();
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, (params) => {
|
||||
return mcpClient.callTool(params.params);
|
||||
});
|
||||
await transport.connectSSE(req, res);
|
||||
await server.connect(transport);
|
||||
});
|
||||
|
||||
app.post("/message", async (req, res) => {
|
||||
console.log("Received message");
|
||||
|
||||
const transport = servers
|
||||
.map((s) => s.transport as SSEServerTransport)
|
||||
.find((t) => true);
|
||||
if (!transport) {
|
||||
res.status(404).send("Session not found");
|
||||
return;
|
||||
}
|
||||
await transport.handlePostMessage(req, res);
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
server.listen(PORT, () => {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
||||
|
||||
// Close the client when the server is shutting down
|
||||
process.on("SIGINT", async () => {
|
||||
if (mcpClient) {
|
||||
await mcpClient.close();
|
||||
}
|
||||
process.exit();
|
||||
});
|
||||
|
||||
22
server/yarn.lock
generated
22
server/yarn.lock
generated
@@ -137,6 +137,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/cors@^2.8.17":
|
||||
version "2.8.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b"
|
||||
integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/eventsource@^1.1.15":
|
||||
version "1.1.15"
|
||||
resolved "https://registry.yarnpkg.com/@types/eventsource/-/eventsource-1.1.15.tgz#949383d3482e20557cbecbf3b038368d94b6be27"
|
||||
@@ -282,6 +289,14 @@ cookie@0.6.0:
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
|
||||
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
|
||||
|
||||
cors@^2.8.5:
|
||||
version "2.8.5"
|
||||
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
|
||||
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
|
||||
dependencies:
|
||||
object-assign "^4"
|
||||
vary "^1"
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
@@ -588,6 +603,11 @@ negotiator@0.6.3:
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
||||
|
||||
object-assign@^4:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
object-inspect@^1.13.1:
|
||||
version "1.13.2"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff"
|
||||
@@ -769,7 +789,7 @@ utils-merge@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
||||
|
||||
vary@~1.1.2:
|
||||
vary@^1, vary@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||
|
||||
Reference in New Issue
Block a user