Skip to content

Commit 7bc622c

Browse files
authored
Merge pull request modelcontextprotocol#14 from modelcontextprotocol/ashwin/sse
Add support for SSE transport
2 parents 18025d7 + 17456ad commit 7bc622c

File tree

8 files changed

+512
-32
lines changed

8 files changed

+512
-32
lines changed

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"dependencies": {
1313
"@radix-ui/react-icons": "^1.3.0",
1414
"@radix-ui/react-label": "^2.1.0",
15+
"@radix-ui/react-select": "^2.1.2",
1516
"@radix-ui/react-slot": "^1.1.0",
1617
"@radix-ui/react-tabs": "^1.1.1",
1718
"class-variance-authority": "^0.7.0",

client/src/App.tsx

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ import {
2323
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
2424
import { Input } from "@/components/ui/input";
2525
import { Button } from "@/components/ui/button";
26+
import {
27+
Select,
28+
SelectContent,
29+
SelectItem,
30+
SelectTrigger,
31+
SelectValue,
32+
} from "@/components/ui/select";
2633

2734
import ConsoleTab from "./components/ConsoleTab";
2835
import Sidebar from "./components/Sidebar";
@@ -49,8 +56,10 @@ const App = () => {
4956
"/Users/ashwin/.nvm/versions/node/v18.20.4/bin/node",
5057
);
5158
const [args, setArgs] = useState<string>(
52-
"/Users/ashwin/code/example-servers/build/everything/index.js",
59+
"/Users/ashwin/code/example-servers/build/everything/stdio.js",
5360
);
61+
const [url, setUrl] = useState<string>("http://localhost:3001/sse");
62+
const [transportType, setTransportType] = useState<"stdio" | "sse">("stdio");
5463
const [requestHistory, setRequestHistory] = useState<
5564
{ request: string; response: string }[]
5665
>([]);
@@ -160,11 +169,17 @@ const App = () => {
160169
});
161170

162171
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);
172+
const backendUrl = new URL("http://localhost:3000/sse");
173+
174+
backendUrl.searchParams.append("transportType", transportType);
175+
if (transportType === "stdio") {
176+
backendUrl.searchParams.append("command", command);
177+
backendUrl.searchParams.append("args", args);
178+
} else {
179+
backendUrl.searchParams.append("url", url);
180+
}
167181

182+
await clientTransport.connect(backendUrl);
168183
await client.connect(clientTransport);
169184

170185
setMcpClient(client);
@@ -185,16 +200,40 @@ const App = () => {
185200
<div className="p-4 bg-white shadow-md m-4 rounded-md">
186201
<h2 className="text-lg font-semibold mb-2">Connect MCP Server</h2>
187202
<div className="flex space-x-2 mb-2">
188-
<Input
189-
placeholder="Command"
190-
value={command}
191-
onChange={(e) => setCommand(e.target.value)}
192-
/>
193-
<Input
194-
placeholder="Arguments (space-separated)"
195-
value={args}
196-
onChange={(e) => setArgs(e.target.value)}
197-
/>
203+
<Select
204+
value={transportType}
205+
onValueChange={(value: "stdio" | "sse") =>
206+
setTransportType(value)
207+
}
208+
>
209+
<SelectTrigger className="w-[180px]">
210+
<SelectValue placeholder="Select transport type" />
211+
</SelectTrigger>
212+
<SelectContent>
213+
<SelectItem value="stdio">STDIO</SelectItem>
214+
<SelectItem value="sse">SSE</SelectItem>
215+
</SelectContent>
216+
</Select>
217+
{transportType === "stdio" ? (
218+
<>
219+
<Input
220+
placeholder="Command"
221+
value={command}
222+
onChange={(e) => setCommand(e.target.value)}
223+
/>
224+
<Input
225+
placeholder="Arguments (space-separated)"
226+
value={args}
227+
onChange={(e) => setArgs(e.target.value)}
228+
/>
229+
</>
230+
) : (
231+
<Input
232+
placeholder="URL"
233+
value={url}
234+
onChange={(e) => setUrl(e.target.value)}
235+
/>
236+
)}
198237
<Button onClick={connectMcpServer}>
199238
<Play className="w-4 h-4 mr-2" />
200239
Connect

client/src/components/ResourcesTab.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ListPane from "./ListPane";
66

77
export type Resource = {
88
uri: string;
9+
name: string;
910
};
1011

1112
const ResourcesTab = ({
@@ -34,20 +35,22 @@ const ResourcesTab = ({
3435
readResource(resource.uri);
3536
}}
3637
renderItem={(resource) => (
37-
<>
38-
<FileText className="w-4 h-4 mr-2 text-gray-500" />
39-
<span className="flex-1">{resource.uri}</span>
40-
<ChevronRight className="w-4 h-4 text-gray-400" />
41-
</>
38+
<div className="flex items-center w-full">
39+
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
40+
<span className="flex-1 truncate" title={resource.uri}>
41+
{resource.name}
42+
</span>
43+
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
44+
</div>
4245
)}
4346
title="Resources"
4447
buttonText="List Resources"
4548
/>
4649

4750
<div className="bg-white rounded-lg shadow">
4851
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
49-
<h3 className="font-semibold">
50-
{selectedResource ? selectedResource.uri : "Select a resource"}
52+
<h3 className="font-semibold truncate" title={selectedResource?.name}>
53+
{selectedResource ? selectedResource.name : "Select a resource"}
5154
</h3>
5255
{selectedResource && (
5356
<Button
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import * as React from "react"
2+
import {
3+
CaretSortIcon,
4+
CheckIcon,
5+
ChevronDownIcon,
6+
ChevronUpIcon,
7+
} from "@radix-ui/react-icons"
8+
import * as SelectPrimitive from "@radix-ui/react-select"
9+
10+
import { cn } from "@/lib/utils"
11+
12+
const Select = SelectPrimitive.Root
13+
14+
const SelectGroup = SelectPrimitive.Group
15+
16+
const SelectValue = SelectPrimitive.Value
17+
18+
const SelectTrigger = React.forwardRef<
19+
React.ElementRef<typeof SelectPrimitive.Trigger>,
20+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
21+
>(({ className, children, ...props }, ref) => (
22+
<SelectPrimitive.Trigger
23+
ref={ref}
24+
className={cn(
25+
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
26+
className
27+
)}
28+
{...props}
29+
>
30+
{children}
31+
<SelectPrimitive.Icon asChild>
32+
<CaretSortIcon className="h-4 w-4 opacity-50" />
33+
</SelectPrimitive.Icon>
34+
</SelectPrimitive.Trigger>
35+
))
36+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
37+
38+
const SelectScrollUpButton = React.forwardRef<
39+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
40+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
41+
>(({ className, ...props }, ref) => (
42+
<SelectPrimitive.ScrollUpButton
43+
ref={ref}
44+
className={cn(
45+
"flex cursor-default items-center justify-center py-1",
46+
className
47+
)}
48+
{...props}
49+
>
50+
<ChevronUpIcon />
51+
</SelectPrimitive.ScrollUpButton>
52+
))
53+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
54+
55+
const SelectScrollDownButton = React.forwardRef<
56+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
57+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
58+
>(({ className, ...props }, ref) => (
59+
<SelectPrimitive.ScrollDownButton
60+
ref={ref}
61+
className={cn(
62+
"flex cursor-default items-center justify-center py-1",
63+
className
64+
)}
65+
{...props}
66+
>
67+
<ChevronDownIcon />
68+
</SelectPrimitive.ScrollDownButton>
69+
))
70+
SelectScrollDownButton.displayName =
71+
SelectPrimitive.ScrollDownButton.displayName
72+
73+
const SelectContent = React.forwardRef<
74+
React.ElementRef<typeof SelectPrimitive.Content>,
75+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
76+
>(({ className, children, position = "popper", ...props }, ref) => (
77+
<SelectPrimitive.Portal>
78+
<SelectPrimitive.Content
79+
ref={ref}
80+
className={cn(
81+
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
82+
position === "popper" &&
83+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
84+
className
85+
)}
86+
position={position}
87+
{...props}
88+
>
89+
<SelectScrollUpButton />
90+
<SelectPrimitive.Viewport
91+
className={cn(
92+
"p-1",
93+
position === "popper" &&
94+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
95+
)}
96+
>
97+
{children}
98+
</SelectPrimitive.Viewport>
99+
<SelectScrollDownButton />
100+
</SelectPrimitive.Content>
101+
</SelectPrimitive.Portal>
102+
))
103+
SelectContent.displayName = SelectPrimitive.Content.displayName
104+
105+
const SelectLabel = React.forwardRef<
106+
React.ElementRef<typeof SelectPrimitive.Label>,
107+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
108+
>(({ className, ...props }, ref) => (
109+
<SelectPrimitive.Label
110+
ref={ref}
111+
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
112+
{...props}
113+
/>
114+
))
115+
SelectLabel.displayName = SelectPrimitive.Label.displayName
116+
117+
const SelectItem = React.forwardRef<
118+
React.ElementRef<typeof SelectPrimitive.Item>,
119+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
120+
>(({ className, children, ...props }, ref) => (
121+
<SelectPrimitive.Item
122+
ref={ref}
123+
className={cn(
124+
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
125+
className
126+
)}
127+
{...props}
128+
>
129+
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
130+
<SelectPrimitive.ItemIndicator>
131+
<CheckIcon className="h-4 w-4" />
132+
</SelectPrimitive.ItemIndicator>
133+
</span>
134+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
135+
</SelectPrimitive.Item>
136+
))
137+
SelectItem.displayName = SelectPrimitive.Item.displayName
138+
139+
const SelectSeparator = React.forwardRef<
140+
React.ElementRef<typeof SelectPrimitive.Separator>,
141+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
142+
>(({ className, ...props }, ref) => (
143+
<SelectPrimitive.Separator
144+
ref={ref}
145+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
146+
{...props}
147+
/>
148+
))
149+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
150+
151+
export {
152+
Select,
153+
SelectGroup,
154+
SelectValue,
155+
SelectTrigger,
156+
SelectContent,
157+
SelectLabel,
158+
SelectItem,
159+
SelectSeparator,
160+
SelectScrollUpButton,
161+
SelectScrollDownButton,
162+
}

0 commit comments

Comments
 (0)