Skip to content

Commit 18025d7

Browse files
authored
Merge pull request modelcontextprotocol#13 from modelcontextprotocol/ashwin/mcp
Refactor to use MCP on client rather than custom protocol
2 parents 20a2dbe + 50f86eb commit 18025d7

File tree

10 files changed

+296
-296
lines changed

10 files changed

+296
-296
lines changed

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.0",
1818
"clsx": "^2.1.1",
1919
"lucide-react": "^0.447.0",
20+
"mcp-typescript": "file:../packages/mcp-typescript",
2021
"react": "^18.3.1",
2122
"react-dom": "^18.3.1",
2223
"tailwind-merge": "^2.5.3",

client/src/App.tsx

Lines changed: 117 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
import { useState, useEffect } from "react";
1+
import { Client } from "mcp-typescript/client/index.js";
2+
import { SSEClientTransport } from "mcp-typescript/client/sse.js";
3+
import {
4+
ListResourcesResultSchema,
5+
GetPromptResultSchema,
6+
ListToolsResultSchema,
7+
ReadResourceResultSchema,
8+
CallToolResultSchema,
9+
ListPromptsResultSchema,
10+
Tool,
11+
ClientRequest,
12+
} from "mcp-typescript/types.js";
13+
import { useState } from "react";
214
import {
315
Send,
416
Bell,
@@ -18,19 +30,19 @@ import RequestsTab from "./components/RequestsTabs";
1830
import ResourcesTab, { Resource } from "./components/ResourcesTab";
1931
import NotificationsTab from "./components/NotificationsTab";
2032
import PromptsTab, { Prompt } from "./components/PromptsTab";
21-
import ToolsTab, { Tool as ToolType } from "./components/ToolsTab";
33+
import ToolsTab from "./components/ToolsTab";
2234
import History from "./components/History";
35+
import { AnyZodObject } from "node_modules/zod/lib";
2336

2437
const App = () => {
25-
const [socket, setSocket] = useState<WebSocket | null>(null);
2638
const [connectionStatus, setConnectionStatus] = useState<
2739
"disconnected" | "connected" | "error"
2840
>("disconnected");
2941
const [resources, setResources] = useState<Resource[]>([]);
3042
const [resourceContent, setResourceContent] = useState<string>("");
3143
const [prompts, setPrompts] = useState<Prompt[]>([]);
3244
const [promptContent, setPromptContent] = useState<string>("");
33-
const [tools, setTools] = useState<ToolType[]>([]);
45+
const [tools, setTools] = useState<Tool[]>([]);
3446
const [toolResult, setToolResult] = useState<string>("");
3547
const [error, setError] = useState<string | null>(null);
3648
const [command, setCommand] = useState<string>(
@@ -39,121 +51,128 @@ const App = () => {
3951
const [args, setArgs] = useState<string>(
4052
"/Users/ashwin/code/example-servers/build/everything/index.js",
4153
);
42-
const [mcpConnected, setMcpConnected] = useState<boolean>(false);
4354
const [requestHistory, setRequestHistory] = useState<
44-
Array<{ request: string; response: string | null }>
55+
{ request: string; response: string }[]
4556
>([]);
57+
const [mcpClient, setMcpClient] = useState<Client | null>(null);
4658

47-
useEffect(() => {
48-
const ws = new WebSocket("ws://localhost:3000");
49-
50-
ws.onopen = () => {
51-
console.log("Connected to WebSocket server");
52-
setConnectionStatus("connected");
53-
setSocket(ws);
54-
};
55-
56-
ws.onmessage = (event) => {
57-
const message = JSON.parse(event.data);
58-
console.log("Received message:", message);
59-
if (message.type === "resources") {
60-
setResources(message.data.resources);
61-
setError(null);
62-
} else if (message.type === "resource") {
63-
setResourceContent(JSON.stringify(message.data, null, 2));
64-
setError(null);
65-
} else if (message.type === "prompts") {
66-
setPrompts(message.data.prompts);
67-
setError(null);
68-
} else if (message.type === "prompt") {
69-
setPromptContent(JSON.stringify(message.data, null, 2));
70-
setError(null);
71-
} else if (message.type === "tools") {
72-
setTools(message.data.tools);
73-
setError(null);
74-
} else if (message.type === "toolResult") {
75-
setToolResult(JSON.stringify(message.data, null, 2));
76-
setError(null);
77-
} else if (message.type === "error") {
78-
setError(message.message);
79-
} else if (message.type === "connected") {
80-
setMcpConnected(true);
81-
}
82-
83-
updateRequestHistory(message);
84-
};
85-
86-
ws.onerror = () => {
87-
setConnectionStatus("error");
88-
};
59+
const [selectedResource, setSelectedResource] = useState<Resource | null>(
60+
null,
61+
);
62+
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
63+
const [selectedTool, setSelectedTool] = useState<Tool | null>(null);
8964

90-
ws.onclose = () => {
91-
setConnectionStatus("disconnected");
92-
setMcpConnected(false);
93-
};
65+
const pushHistory = (request: object, response: object) => {
66+
setRequestHistory((prev) => [
67+
...prev,
68+
{ request: JSON.stringify(request), response: JSON.stringify(response) },
69+
]);
70+
};
9471

95-
return () => ws.close();
96-
}, []);
72+
const makeRequest = async <T extends AnyZodObject>(
73+
request: ClientRequest,
74+
schema: T,
75+
) => {
76+
if (!mcpClient) {
77+
throw new Error("MCP client not connected");
78+
}
9779

98-
const updateRequestHistory = (response: unknown) => {
99-
setRequestHistory((prev) => {
100-
const lastRequest = prev[prev.length - 1];
101-
if (lastRequest && lastRequest.response === null) {
102-
const updatedHistory = [...prev];
103-
updatedHistory[updatedHistory.length - 1] = {
104-
...lastRequest,
105-
response: JSON.stringify(response),
106-
};
107-
return updatedHistory;
108-
}
109-
return prev;
110-
});
80+
try {
81+
const response = await mcpClient.request(request, schema);
82+
pushHistory(request, response);
83+
return response;
84+
} catch (e: unknown) {
85+
setError((e as Error).message);
86+
throw e;
87+
}
11188
};
11289

113-
const sendWebSocketMessage = (message: object) => {
114-
if (socket) {
115-
console.log("Sending WebSocket message:", message);
116-
socket.send(JSON.stringify(message));
117-
setRequestHistory((prev) => [
118-
...prev,
119-
{ request: JSON.stringify(message), response: null },
120-
]);
90+
const listResources = async () => {
91+
const response = await makeRequest(
92+
{
93+
method: "resources/list" as const,
94+
},
95+
ListResourcesResultSchema,
96+
);
97+
if (response.resources) {
98+
setResources(response.resources);
12199
}
122100
};
123101

124-
const [selectedResource, setSelectedResource] = useState<Resource | null>(
125-
null,
126-
);
127-
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
128-
const [selectedTool, setSelectedTool] = useState<ToolType | null>(null);
129-
130-
const listResources = () => {
131-
sendWebSocketMessage({ type: "listResources" });
102+
const readResource = async (uri: string) => {
103+
const response = await makeRequest(
104+
{
105+
method: "resources/read" as const,
106+
params: { uri },
107+
},
108+
ReadResourceResultSchema,
109+
);
110+
setResourceContent(JSON.stringify(response, null, 2));
132111
};
133112

134-
const readResource = (uri: string) => {
135-
sendWebSocketMessage({ type: "readResource", uri });
113+
const listPrompts = async () => {
114+
const response = await makeRequest(
115+
{
116+
method: "prompts/list" as const,
117+
},
118+
ListPromptsResultSchema,
119+
);
120+
setPrompts(response.prompts);
136121
};
137122

138-
const listPrompts = () => {
139-
sendWebSocketMessage({ type: "listPrompts" });
123+
const getPrompt = async (name: string, args: Record<string, string> = {}) => {
124+
const response = await makeRequest(
125+
{
126+
method: "prompts/get" as const,
127+
params: { name, arguments: args },
128+
},
129+
GetPromptResultSchema,
130+
);
131+
setPromptContent(JSON.stringify(response, null, 2));
140132
};
141133

142-
const getPrompt = (name: string, args: Record<string, unknown> = {}) => {
143-
sendWebSocketMessage({ type: "getPrompt", name, args });
134+
const listTools = async () => {
135+
const response = await makeRequest(
136+
{
137+
method: "tools/list" as const,
138+
},
139+
ListToolsResultSchema,
140+
);
141+
setTools(response.tools);
144142
};
145143

146-
const listTools = () => {
147-
sendWebSocketMessage({ type: "listTools" });
144+
const callTool = async (name: string, params: Record<string, unknown>) => {
145+
const response = await makeRequest(
146+
{
147+
method: "tools/call" as const,
148+
params: { name, arguments: params },
149+
},
150+
CallToolResultSchema,
151+
);
152+
setToolResult(JSON.stringify(response.toolResult, null, 2));
148153
};
149154

150-
const callTool = (name: string, params: Record<string, unknown>) => {
151-
sendWebSocketMessage({ type: "callTool", name, params });
152-
};
155+
const connectMcpServer = async () => {
156+
try {
157+
const client = new Client({
158+
name: "mcp-inspector",
159+
version: "0.0.1",
160+
});
161+
162+
const clientTransport = new SSEClientTransport();
163+
const url = new URL("http://localhost:3000/sse");
164+
url.searchParams.append("command", encodeURIComponent(command));
165+
url.searchParams.append("args", encodeURIComponent(args));
166+
await clientTransport.connect(url);
167+
168+
await client.connect(clientTransport);
153169

154-
const connectMcpServer = () => {
155-
const argsArray = args.split(" ").filter((arg) => arg.trim() !== "");
156-
sendWebSocketMessage({ type: "connect", command, args: argsArray });
170+
setMcpClient(client);
171+
setConnectionStatus("connected");
172+
} catch (e) {
173+
console.error(e);
174+
setConnectionStatus("error");
175+
}
157176
};
158177

159178
return (
@@ -182,7 +201,7 @@ const App = () => {
182201
</Button>
183202
</div>
184203
</div>
185-
{mcpConnected ? (
204+
{mcpClient ? (
186205
<Tabs defaultValue="resources" className="w-full p-4">
187206
<TabsList className="mb-4 p-0">
188207
<TabsTrigger value="resources">

client/src/components/History.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const History = ({
3838
>
3939
<span className="font-mono">
4040
{requestHistory.length - index}.{" "}
41-
{JSON.parse(request.request).type}
41+
{JSON.parse(request.request).method}
4242
</span>
4343
<span>
4444
{expandedRequests[requestHistory.length - 1 - index]

client/src/components/ToolsTab.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,11 @@ import { Button } from "@/components/ui/button";
33
import { Input } from "@/components/ui/input";
44
import { Send, AlertCircle } from "lucide-react";
55
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
6+
import { Tool } from "mcp-typescript/types.js";
67
import { useState } from "react";
78
import { Label } from "@/components/ui/label";
89
import ListPane from "./ListPane";
910

10-
export type Tool = {
11-
name: string;
12-
description: string;
13-
inputSchema: {
14-
type: string;
15-
properties: Record<string, { type: string; description: string }>;
16-
};
17-
};
18-
1911
const ToolsTab = ({
2012
tools,
2113
listTools,

0 commit comments

Comments
 (0)