Merge pull request #13 from modelcontextprotocol/ashwin/mcp
Refactor to use MCP on client rather than custom protocol
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.447.0",
|
"lucide-react": "^0.447.0",
|
||||||
|
"mcp-typescript": "file:../packages/mcp-typescript",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"tailwind-merge": "^2.5.3",
|
"tailwind-merge": "^2.5.3",
|
||||||
|
|||||||
@@ -1,4 +1,16 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { Client } from "mcp-typescript/client/index.js";
|
||||||
|
import { SSEClientTransport } from "mcp-typescript/client/sse.js";
|
||||||
|
import {
|
||||||
|
ListResourcesResultSchema,
|
||||||
|
GetPromptResultSchema,
|
||||||
|
ListToolsResultSchema,
|
||||||
|
ReadResourceResultSchema,
|
||||||
|
CallToolResultSchema,
|
||||||
|
ListPromptsResultSchema,
|
||||||
|
Tool,
|
||||||
|
ClientRequest,
|
||||||
|
} from "mcp-typescript/types.js";
|
||||||
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Send,
|
Send,
|
||||||
Bell,
|
Bell,
|
||||||
@@ -18,11 +30,11 @@ import RequestsTab from "./components/RequestsTabs";
|
|||||||
import ResourcesTab, { Resource } from "./components/ResourcesTab";
|
import ResourcesTab, { Resource } from "./components/ResourcesTab";
|
||||||
import NotificationsTab from "./components/NotificationsTab";
|
import NotificationsTab from "./components/NotificationsTab";
|
||||||
import PromptsTab, { Prompt } from "./components/PromptsTab";
|
import PromptsTab, { Prompt } from "./components/PromptsTab";
|
||||||
import ToolsTab, { Tool as ToolType } from "./components/ToolsTab";
|
import ToolsTab from "./components/ToolsTab";
|
||||||
import History from "./components/History";
|
import History from "./components/History";
|
||||||
|
import { AnyZodObject } from "node_modules/zod/lib";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [socket, setSocket] = useState<WebSocket | null>(null);
|
|
||||||
const [connectionStatus, setConnectionStatus] = useState<
|
const [connectionStatus, setConnectionStatus] = useState<
|
||||||
"disconnected" | "connected" | "error"
|
"disconnected" | "connected" | "error"
|
||||||
>("disconnected");
|
>("disconnected");
|
||||||
@@ -30,7 +42,7 @@ const App = () => {
|
|||||||
const [resourceContent, setResourceContent] = useState<string>("");
|
const [resourceContent, setResourceContent] = useState<string>("");
|
||||||
const [prompts, setPrompts] = useState<Prompt[]>([]);
|
const [prompts, setPrompts] = useState<Prompt[]>([]);
|
||||||
const [promptContent, setPromptContent] = useState<string>("");
|
const [promptContent, setPromptContent] = useState<string>("");
|
||||||
const [tools, setTools] = useState<ToolType[]>([]);
|
const [tools, setTools] = useState<Tool[]>([]);
|
||||||
const [toolResult, setToolResult] = useState<string>("");
|
const [toolResult, setToolResult] = useState<string>("");
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [command, setCommand] = useState<string>(
|
const [command, setCommand] = useState<string>(
|
||||||
@@ -39,121 +51,128 @@ const App = () => {
|
|||||||
const [args, setArgs] = useState<string>(
|
const [args, setArgs] = useState<string>(
|
||||||
"/Users/ashwin/code/example-servers/build/everything/index.js",
|
"/Users/ashwin/code/example-servers/build/everything/index.js",
|
||||||
);
|
);
|
||||||
const [mcpConnected, setMcpConnected] = useState<boolean>(false);
|
|
||||||
const [requestHistory, setRequestHistory] = useState<
|
const [requestHistory, setRequestHistory] = useState<
|
||||||
Array<{ request: string; response: string | null }>
|
{ request: string; response: string }[]
|
||||||
>([]);
|
>([]);
|
||||||
|
const [mcpClient, setMcpClient] = useState<Client | null>(null);
|
||||||
useEffect(() => {
|
|
||||||
const ws = new WebSocket("ws://localhost:3000");
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
console.log("Connected to WebSocket server");
|
|
||||||
setConnectionStatus("connected");
|
|
||||||
setSocket(ws);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
|
||||||
const message = JSON.parse(event.data);
|
|
||||||
console.log("Received message:", message);
|
|
||||||
if (message.type === "resources") {
|
|
||||||
setResources(message.data.resources);
|
|
||||||
setError(null);
|
|
||||||
} else if (message.type === "resource") {
|
|
||||||
setResourceContent(JSON.stringify(message.data, null, 2));
|
|
||||||
setError(null);
|
|
||||||
} else if (message.type === "prompts") {
|
|
||||||
setPrompts(message.data.prompts);
|
|
||||||
setError(null);
|
|
||||||
} else if (message.type === "prompt") {
|
|
||||||
setPromptContent(JSON.stringify(message.data, null, 2));
|
|
||||||
setError(null);
|
|
||||||
} else if (message.type === "tools") {
|
|
||||||
setTools(message.data.tools);
|
|
||||||
setError(null);
|
|
||||||
} else if (message.type === "toolResult") {
|
|
||||||
setToolResult(JSON.stringify(message.data, null, 2));
|
|
||||||
setError(null);
|
|
||||||
} else if (message.type === "error") {
|
|
||||||
setError(message.message);
|
|
||||||
} else if (message.type === "connected") {
|
|
||||||
setMcpConnected(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRequestHistory(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onerror = () => {
|
|
||||||
setConnectionStatus("error");
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
setConnectionStatus("disconnected");
|
|
||||||
setMcpConnected(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => ws.close();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updateRequestHistory = (response: unknown) => {
|
|
||||||
setRequestHistory((prev) => {
|
|
||||||
const lastRequest = prev[prev.length - 1];
|
|
||||||
if (lastRequest && lastRequest.response === null) {
|
|
||||||
const updatedHistory = [...prev];
|
|
||||||
updatedHistory[updatedHistory.length - 1] = {
|
|
||||||
...lastRequest,
|
|
||||||
response: JSON.stringify(response),
|
|
||||||
};
|
|
||||||
return updatedHistory;
|
|
||||||
}
|
|
||||||
return prev;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const sendWebSocketMessage = (message: object) => {
|
|
||||||
if (socket) {
|
|
||||||
console.log("Sending WebSocket message:", message);
|
|
||||||
socket.send(JSON.stringify(message));
|
|
||||||
setRequestHistory((prev) => [
|
|
||||||
...prev,
|
|
||||||
{ request: JSON.stringify(message), response: null },
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const [selectedResource, setSelectedResource] = useState<Resource | null>(
|
const [selectedResource, setSelectedResource] = useState<Resource | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
|
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
|
||||||
const [selectedTool, setSelectedTool] = useState<ToolType | null>(null);
|
const [selectedTool, setSelectedTool] = useState<Tool | null>(null);
|
||||||
|
|
||||||
const listResources = () => {
|
const pushHistory = (request: object, response: object) => {
|
||||||
sendWebSocketMessage({ type: "listResources" });
|
setRequestHistory((prev) => [
|
||||||
|
...prev,
|
||||||
|
{ request: JSON.stringify(request), response: JSON.stringify(response) },
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const readResource = (uri: string) => {
|
const makeRequest = async <T extends AnyZodObject>(
|
||||||
sendWebSocketMessage({ type: "readResource", uri });
|
request: ClientRequest,
|
||||||
|
schema: T,
|
||||||
|
) => {
|
||||||
|
if (!mcpClient) {
|
||||||
|
throw new Error("MCP client not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await mcpClient.request(request, schema);
|
||||||
|
pushHistory(request, response);
|
||||||
|
return response;
|
||||||
|
} catch (e: unknown) {
|
||||||
|
setError((e as Error).message);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const listPrompts = () => {
|
const listResources = async () => {
|
||||||
sendWebSocketMessage({ type: "listPrompts" });
|
const response = await makeRequest(
|
||||||
|
{
|
||||||
|
method: "resources/list" as const,
|
||||||
|
},
|
||||||
|
ListResourcesResultSchema,
|
||||||
|
);
|
||||||
|
if (response.resources) {
|
||||||
|
setResources(response.resources);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPrompt = (name: string, args: Record<string, unknown> = {}) => {
|
const readResource = async (uri: string) => {
|
||||||
sendWebSocketMessage({ type: "getPrompt", name, args });
|
const response = await makeRequest(
|
||||||
|
{
|
||||||
|
method: "resources/read" as const,
|
||||||
|
params: { uri },
|
||||||
|
},
|
||||||
|
ReadResourceResultSchema,
|
||||||
|
);
|
||||||
|
setResourceContent(JSON.stringify(response, null, 2));
|
||||||
};
|
};
|
||||||
|
|
||||||
const listTools = () => {
|
const listPrompts = async () => {
|
||||||
sendWebSocketMessage({ type: "listTools" });
|
const response = await makeRequest(
|
||||||
|
{
|
||||||
|
method: "prompts/list" as const,
|
||||||
|
},
|
||||||
|
ListPromptsResultSchema,
|
||||||
|
);
|
||||||
|
setPrompts(response.prompts);
|
||||||
};
|
};
|
||||||
|
|
||||||
const callTool = (name: string, params: Record<string, unknown>) => {
|
const getPrompt = async (name: string, args: Record<string, string> = {}) => {
|
||||||
sendWebSocketMessage({ type: "callTool", name, params });
|
const response = await makeRequest(
|
||||||
|
{
|
||||||
|
method: "prompts/get" as const,
|
||||||
|
params: { name, arguments: args },
|
||||||
|
},
|
||||||
|
GetPromptResultSchema,
|
||||||
|
);
|
||||||
|
setPromptContent(JSON.stringify(response, null, 2));
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectMcpServer = () => {
|
const listTools = async () => {
|
||||||
const argsArray = args.split(" ").filter((arg) => arg.trim() !== "");
|
const response = await makeRequest(
|
||||||
sendWebSocketMessage({ type: "connect", command, args: argsArray });
|
{
|
||||||
|
method: "tools/list" as const,
|
||||||
|
},
|
||||||
|
ListToolsResultSchema,
|
||||||
|
);
|
||||||
|
setTools(response.tools);
|
||||||
|
};
|
||||||
|
|
||||||
|
const callTool = async (name: string, params: Record<string, unknown>) => {
|
||||||
|
const response = await makeRequest(
|
||||||
|
{
|
||||||
|
method: "tools/call" as const,
|
||||||
|
params: { name, arguments: params },
|
||||||
|
},
|
||||||
|
CallToolResultSchema,
|
||||||
|
);
|
||||||
|
setToolResult(JSON.stringify(response.toolResult, null, 2));
|
||||||
|
};
|
||||||
|
|
||||||
|
const connectMcpServer = async () => {
|
||||||
|
try {
|
||||||
|
const client = new Client({
|
||||||
|
name: "mcp-inspector",
|
||||||
|
version: "0.0.1",
|
||||||
|
});
|
||||||
|
|
||||||
|
const clientTransport = new SSEClientTransport();
|
||||||
|
const url = new URL("http://localhost:3000/sse");
|
||||||
|
url.searchParams.append("command", encodeURIComponent(command));
|
||||||
|
url.searchParams.append("args", encodeURIComponent(args));
|
||||||
|
await clientTransport.connect(url);
|
||||||
|
|
||||||
|
await client.connect(clientTransport);
|
||||||
|
|
||||||
|
setMcpClient(client);
|
||||||
|
setConnectionStatus("connected");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setConnectionStatus("error");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -182,7 +201,7 @@ const App = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{mcpConnected ? (
|
{mcpClient ? (
|
||||||
<Tabs defaultValue="resources" className="w-full p-4">
|
<Tabs defaultValue="resources" className="w-full p-4">
|
||||||
<TabsList className="mb-4 p-0">
|
<TabsList className="mb-4 p-0">
|
||||||
<TabsTrigger value="resources">
|
<TabsTrigger value="resources">
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const History = ({
|
|||||||
>
|
>
|
||||||
<span className="font-mono">
|
<span className="font-mono">
|
||||||
{requestHistory.length - index}.{" "}
|
{requestHistory.length - index}.{" "}
|
||||||
{JSON.parse(request.request).type}
|
{JSON.parse(request.request).method}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
{expandedRequests[requestHistory.length - 1 - index]
|
{expandedRequests[requestHistory.length - 1 - index]
|
||||||
|
|||||||
@@ -3,19 +3,11 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Send, AlertCircle } from "lucide-react";
|
import { Send, AlertCircle } from "lucide-react";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
import { Tool } from "mcp-typescript/types.js";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import ListPane from "./ListPane";
|
import ListPane from "./ListPane";
|
||||||
|
|
||||||
export type Tool = {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
inputSchema: {
|
|
||||||
type: string;
|
|
||||||
properties: Record<string, { type: string; description: string }>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const ToolsTab = ({
|
const ToolsTab = ({
|
||||||
tools,
|
tools,
|
||||||
listTools,
|
listTools,
|
||||||
|
|||||||
85
client/yarn.lock
generated
85
client/yarn.lock
generated
@@ -942,6 +942,11 @@ browserslist@^4.23.3, browserslist@^4.24.0:
|
|||||||
node-releases "^2.0.18"
|
node-releases "^2.0.18"
|
||||||
update-browserslist-db "^1.1.0"
|
update-browserslist-db "^1.1.0"
|
||||||
|
|
||||||
|
bytes@3.1.2:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
|
||||||
|
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
|
||||||
|
|
||||||
callsites@^3.0.0:
|
callsites@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||||
@@ -1040,6 +1045,11 @@ concat-map@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||||
|
|
||||||
|
content-type@^1.0.5:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
|
||||||
|
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
|
||||||
|
|
||||||
convert-source-map@^2.0.0:
|
convert-source-map@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||||
@@ -1076,6 +1086,11 @@ deep-is@^0.1.3:
|
|||||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||||
|
|
||||||
|
depd@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||||
|
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||||
|
|
||||||
didyoumean@^1.2.2:
|
didyoumean@^1.2.2:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
||||||
@@ -1411,6 +1426,24 @@ hasown@^2.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.2"
|
function-bind "^1.1.2"
|
||||||
|
|
||||||
|
http-errors@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
|
||||||
|
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
|
||||||
|
dependencies:
|
||||||
|
depd "2.0.0"
|
||||||
|
inherits "2.0.4"
|
||||||
|
setprototypeof "1.2.0"
|
||||||
|
statuses "2.0.1"
|
||||||
|
toidentifier "1.0.1"
|
||||||
|
|
||||||
|
iconv-lite@0.6.3:
|
||||||
|
version "0.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||||
|
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
|
||||||
|
dependencies:
|
||||||
|
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||||
|
|
||||||
ignore@^5.2.0, ignore@^5.3.1:
|
ignore@^5.2.0, ignore@^5.3.1:
|
||||||
version "5.3.2"
|
version "5.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
|
||||||
@@ -1429,6 +1462,11 @@ imurmurhash@^0.1.4:
|
|||||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||||
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
|
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
|
||||||
|
|
||||||
|
inherits@2.0.4:
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||||
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
|
|
||||||
is-binary-path@~2.1.0:
|
is-binary-path@~2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||||
@@ -1587,6 +1625,13 @@ lucide-react@^0.447.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.447.0.tgz#1b2c4044c619517346306d9fae950265aafa76a5"
|
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.447.0.tgz#1b2c4044c619517346306d9fae950265aafa76a5"
|
||||||
integrity sha512-SZ//hQmvi+kDKrNepArVkYK7/jfeZ5uFNEnYmd45RKZcbGD78KLnrcNXmgeg6m+xNHFvTG+CblszXCy4n6DN4w==
|
integrity sha512-SZ//hQmvi+kDKrNepArVkYK7/jfeZ5uFNEnYmd45RKZcbGD78KLnrcNXmgeg6m+xNHFvTG+CblszXCy4n6DN4w==
|
||||||
|
|
||||||
|
"mcp-typescript@file:../packages/mcp-typescript":
|
||||||
|
version "0.1.0"
|
||||||
|
dependencies:
|
||||||
|
content-type "^1.0.5"
|
||||||
|
raw-body "^3.0.0"
|
||||||
|
zod "^3.23.8"
|
||||||
|
|
||||||
merge2@^1.3.0:
|
merge2@^1.3.0:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
@@ -1817,6 +1862,16 @@ queue-microtask@^1.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||||
|
|
||||||
|
raw-body@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f"
|
||||||
|
integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.2"
|
||||||
|
http-errors "2.0.0"
|
||||||
|
iconv-lite "0.6.3"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
react-dom@^18.3.1:
|
react-dom@^18.3.1:
|
||||||
version "18.3.1"
|
version "18.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
|
||||||
@@ -1902,6 +1957,11 @@ run-parallel@^1.1.9:
|
|||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask "^1.2.2"
|
queue-microtask "^1.2.2"
|
||||||
|
|
||||||
|
"safer-buffer@>= 2.1.2 < 3.0.0":
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
scheduler@^0.23.2:
|
scheduler@^0.23.2:
|
||||||
version "0.23.2"
|
version "0.23.2"
|
||||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
|
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
|
||||||
@@ -1919,6 +1979,11 @@ semver@^7.6.0:
|
|||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
|
||||||
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
|
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
|
||||||
|
|
||||||
|
setprototypeof@1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
|
||||||
|
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
|
||||||
|
|
||||||
shebang-command@^2.0.0:
|
shebang-command@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
||||||
@@ -1941,6 +2006,11 @@ source-map-js@^1.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||||
|
|
||||||
|
statuses@2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
|
||||||
|
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
@@ -2095,6 +2165,11 @@ to-regex-range@^5.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-number "^7.0.0"
|
is-number "^7.0.0"
|
||||||
|
|
||||||
|
toidentifier@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
|
||||||
|
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
|
||||||
|
|
||||||
ts-api-utils@^1.3.0:
|
ts-api-utils@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1"
|
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1"
|
||||||
@@ -2131,6 +2206,11 @@ undici-types@~6.19.2:
|
|||||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
|
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
|
||||||
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
|
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
|
||||||
|
|
||||||
|
unpipe@1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
|
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
|
||||||
|
|
||||||
update-browserslist-db@^1.1.0:
|
update-browserslist-db@^1.1.0:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5"
|
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5"
|
||||||
@@ -2206,3 +2286,8 @@ yocto-queue@^0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||||
|
|
||||||
|
zod@^3.23.8:
|
||||||
|
version "3.23.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
|
||||||
|
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"dev": "tsx watch --clear-screen=false src/index.ts"
|
"dev": "tsx watch --clear-screen=false src/index.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.17",
|
||||||
"@types/eventsource": "^1.1.15",
|
"@types/eventsource": "^1.1.15",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/ws": "^8.5.12",
|
"@types/ws": "^8.5.12",
|
||||||
@@ -17,8 +18,10 @@
|
|||||||
"typescript": "^5.6.2"
|
"typescript": "^5.6.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
"mcp-typescript": "file:../packages/mcp-typescript",
|
"mcp-typescript": "file:../packages/mcp-typescript",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0",
|
||||||
|
"zod": "^3.23.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
import { Client } from "mcp-typescript/client/index.js";
|
|
||||||
import { SSEClientTransport } from "mcp-typescript/client/sse.js";
|
|
||||||
import { StdioClientTransport } from "mcp-typescript/client/stdio.js";
|
|
||||||
import {
|
|
||||||
ListResourcesResult,
|
|
||||||
ReadResourceResult,
|
|
||||||
ListResourcesResultSchema,
|
|
||||||
ReadResourceResultSchema,
|
|
||||||
ListPromptsResult,
|
|
||||||
ListPromptsResultSchema,
|
|
||||||
GetPromptResult,
|
|
||||||
GetPromptResultSchema,
|
|
||||||
ListToolsResult,
|
|
||||||
ListToolsResultSchema,
|
|
||||||
CallToolResult,
|
|
||||||
CallToolResultSchema,
|
|
||||||
} from "mcp-typescript/types.js";
|
|
||||||
|
|
||||||
export class McpClient {
|
|
||||||
private client: Client;
|
|
||||||
private transport?: SSEClientTransport | StdioClientTransport;
|
|
||||||
|
|
||||||
constructor(name: string, version: string) {
|
|
||||||
this.client = new Client({
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async connect(url: string | URL) {
|
|
||||||
const urlObj = typeof url === "string" ? new URL(url) : url;
|
|
||||||
|
|
||||||
if (urlObj.protocol === "http:" || urlObj.protocol === "https:") {
|
|
||||||
this.transport = new SSEClientTransport();
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unsupported protocol: ${urlObj.protocol}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.transport.connect(urlObj);
|
|
||||||
await this.client.connect(this.transport);
|
|
||||||
}
|
|
||||||
|
|
||||||
async connectStdio(command: string, args: string[] = []) {
|
|
||||||
this.transport = new StdioClientTransport();
|
|
||||||
await this.transport.spawn({ command, args });
|
|
||||||
await this.client.connect(this.transport);
|
|
||||||
}
|
|
||||||
|
|
||||||
async close() {
|
|
||||||
await this.client.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resource Operations
|
|
||||||
async listResources(): Promise<ListResourcesResult> {
|
|
||||||
return await this.client.request(
|
|
||||||
{
|
|
||||||
method: "resources/list",
|
|
||||||
},
|
|
||||||
ListResourcesResultSchema,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async readResource(uri: string): Promise<ReadResourceResult> {
|
|
||||||
return await this.client.request(
|
|
||||||
{
|
|
||||||
method: "resources/read",
|
|
||||||
params: { uri },
|
|
||||||
},
|
|
||||||
ReadResourceResultSchema,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prompt Operations
|
|
||||||
async listPrompts(): Promise<ListPromptsResult> {
|
|
||||||
return await this.client.request(
|
|
||||||
{
|
|
||||||
method: "prompts/list",
|
|
||||||
},
|
|
||||||
ListPromptsResultSchema,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPrompt(
|
|
||||||
name: string,
|
|
||||||
args?: Record<string, string>,
|
|
||||||
): Promise<GetPromptResult> {
|
|
||||||
return await this.client.request(
|
|
||||||
{
|
|
||||||
method: "prompts/get",
|
|
||||||
params: { name, arguments: args },
|
|
||||||
},
|
|
||||||
GetPromptResultSchema,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Tool Operations
|
|
||||||
async listTools(): Promise<ListToolsResult> {
|
|
||||||
return await this.client.request(
|
|
||||||
{
|
|
||||||
method: "tools/list",
|
|
||||||
},
|
|
||||||
ListToolsResultSchema,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async callTool(
|
|
||||||
name: string,
|
|
||||||
params: Record<string, unknown>,
|
|
||||||
): Promise<CallToolResult> {
|
|
||||||
return await this.client.request(
|
|
||||||
{
|
|
||||||
method: "tools/call",
|
|
||||||
params: { name, arguments: params },
|
|
||||||
},
|
|
||||||
CallToolResultSchema,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getServerCapabilities() {
|
|
||||||
return this.client.getServerCapabilities();
|
|
||||||
}
|
|
||||||
|
|
||||||
getServerVersion() {
|
|
||||||
return this.client.getServerVersion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default McpClient;
|
|
||||||
@@ -1,71 +1,48 @@
|
|||||||
import McpClient from "./client.js";
|
import cors from "cors";
|
||||||
|
|
||||||
|
import { SSEServerTransport } from "mcp-typescript/server/sse.js";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import http from "http";
|
import { StdioClientTransport } from "mcp-typescript/client/stdio.js";
|
||||||
import { WebSocket, WebSocketServer } from "ws";
|
import mcpProxy from "./mcpProxy.js";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = http.createServer(app);
|
app.use(cors());
|
||||||
const wss = new WebSocketServer({ server });
|
|
||||||
|
|
||||||
let mcpClient: McpClient | null = null;
|
let transports: SSEServerTransport[] = [];
|
||||||
|
|
||||||
wss.on("connection", (ws: WebSocket) => {
|
app.get("/sse", async (req, res) => {
|
||||||
ws.on("message", async (message: string) => {
|
console.log("New SSE connection");
|
||||||
try {
|
const command = decodeURIComponent(req.query.command as string);
|
||||||
const command = JSON.parse(message);
|
const args = decodeURIComponent(req.query.args as string).split(",");
|
||||||
|
const backingServerTransport = new StdioClientTransport();
|
||||||
|
await backingServerTransport.spawn({ command, args });
|
||||||
|
|
||||||
if (command.type === "connect" && command.command && command.args) {
|
const webAppTransport = new SSEServerTransport("/message");
|
||||||
mcpClient = new McpClient("MyApp", "1.0.0");
|
transports.push(webAppTransport);
|
||||||
await mcpClient.connectStdio(command.command, command.args);
|
|
||||||
ws.send(JSON.stringify({ type: "connected" }));
|
await webAppTransport.connectSSE(req, res);
|
||||||
} else if (!mcpClient) {
|
|
||||||
ws.send(
|
mcpProxy({
|
||||||
JSON.stringify({
|
transportToClient: webAppTransport,
|
||||||
type: "error",
|
transportToServer: backingServerTransport,
|
||||||
message: "Not connected to MCP server",
|
onerror: (error) => {
|
||||||
}),
|
console.error(error);
|
||||||
);
|
},
|
||||||
} 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 PORT = process.env.PORT || 3000;
|
app.post("/message", async (req, res) => {
|
||||||
server.listen(PORT, () => {
|
console.log("Received message");
|
||||||
console.log(`Server is running on port ${PORT}`);
|
|
||||||
|
const transport = transports.find((t) => true);
|
||||||
|
if (!transport) {
|
||||||
|
res.status(404).send("Session not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await transport.handlePostMessage(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close the client when the server is shutting down
|
const PORT = process.env.PORT || 3000;
|
||||||
process.on("SIGINT", async () => {
|
app.listen(PORT, () => {
|
||||||
if (mcpClient) {
|
console.log(`Server is running on port ${PORT}`);
|
||||||
await mcpClient.close();
|
|
||||||
}
|
|
||||||
process.exit();
|
|
||||||
});
|
});
|
||||||
|
|||||||
30
server/src/mcpProxy.ts
Normal file
30
server/src/mcpProxy.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Transport } from "mcp-typescript/shared/transport.js";
|
||||||
|
|
||||||
|
export default function mcpProxy({
|
||||||
|
transportToClient,
|
||||||
|
transportToServer,
|
||||||
|
onerror,
|
||||||
|
}: {
|
||||||
|
transportToClient: Transport;
|
||||||
|
transportToServer: Transport;
|
||||||
|
onerror: (error: Error) => void;
|
||||||
|
}) {
|
||||||
|
transportToClient.onmessage = (message) => {
|
||||||
|
transportToServer.send(message).catch(onerror);
|
||||||
|
};
|
||||||
|
|
||||||
|
transportToServer.onmessage = (message) => {
|
||||||
|
transportToClient.send(message).catch(onerror);
|
||||||
|
};
|
||||||
|
|
||||||
|
transportToClient.onclose = () => {
|
||||||
|
transportToServer.close().catch(onerror);
|
||||||
|
};
|
||||||
|
|
||||||
|
transportToServer.onclose = () => {
|
||||||
|
transportToClient.close().catch(onerror);
|
||||||
|
};
|
||||||
|
|
||||||
|
transportToClient.onerror = onerror;
|
||||||
|
transportToServer.onerror = onerror;
|
||||||
|
}
|
||||||
22
server/yarn.lock
generated
22
server/yarn.lock
generated
@@ -137,6 +137,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@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":
|
"@types/eventsource@^1.1.15":
|
||||||
version "1.1.15"
|
version "1.1.15"
|
||||||
resolved "https://registry.yarnpkg.com/@types/eventsource/-/eventsource-1.1.15.tgz#949383d3482e20557cbecbf3b038368d94b6be27"
|
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"
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
|
||||||
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
|
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:
|
debug@2.6.9:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
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"
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||||
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
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:
|
object-inspect@^1.13.1:
|
||||||
version "1.13.2"
|
version "1.13.2"
|
||||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff"
|
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"
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
||||||
|
|
||||||
vary@~1.1.2:
|
vary@^1, vary@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||||
|
|||||||
Reference in New Issue
Block a user