Skip to content

Commit 7caf6f8

Browse files
authored
Merge pull request modelcontextprotocol#170 from cliffhall/add-subscribe-to-resource
Add subscribe to resource functionality
2 parents 0870a81 + 35a0f46 commit 7caf6f8

File tree

6 files changed

+100
-13
lines changed

6 files changed

+100
-13
lines changed

client/src/App.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ const App = () => {
128128
const [selectedResource, setSelectedResource] = useState<Resource | null>(
129129
null,
130130
);
131+
const [resourceSubscriptions, setResourceSubscriptions] = useState<
132+
Set<string>
133+
>(new Set<string>());
134+
131135
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
132136
const [selectedTool, setSelectedTool] = useState<Tool | null>(null);
133137
const [nextResourceCursor, setNextResourceCursor] = useState<
@@ -308,6 +312,38 @@ const App = () => {
308312
setResourceContent(JSON.stringify(response, null, 2));
309313
};
310314

315+
const subscribeToResource = async (uri: string) => {
316+
if (!resourceSubscriptions.has(uri)) {
317+
await makeRequest(
318+
{
319+
method: "resources/subscribe" as const,
320+
params: { uri },
321+
},
322+
z.object({}),
323+
"resources",
324+
);
325+
const clone = new Set(resourceSubscriptions);
326+
clone.add(uri);
327+
setResourceSubscriptions(clone);
328+
}
329+
};
330+
331+
const unsubscribeFromResource = async (uri: string) => {
332+
if (resourceSubscriptions.has(uri)) {
333+
await makeRequest(
334+
{
335+
method: "resources/unsubscribe" as const,
336+
params: { uri },
337+
},
338+
z.object({}),
339+
"resources",
340+
);
341+
const clone = new Set(resourceSubscriptions);
342+
clone.delete(uri);
343+
setResourceSubscriptions(clone);
344+
}
345+
};
346+
311347
const listPrompts = async () => {
312348
const response = await makeRequest(
313349
{
@@ -485,6 +521,18 @@ const App = () => {
485521
clearError("resources");
486522
setSelectedResource(resource);
487523
}}
524+
resourceSubscriptionsSupported={
525+
serverCapabilities?.resources?.subscribe || false
526+
}
527+
resourceSubscriptions={resourceSubscriptions}
528+
subscribeToResource={(uri) => {
529+
clearError("resources");
530+
subscribeToResource(uri);
531+
}}
532+
unsubscribeFromResource={(uri) => {
533+
clearError("resources");
534+
unsubscribeFromResource(uri);
535+
}}
488536
handleCompletion={handleCompletion}
489537
completionsSupported={completionsSupported}
490538
resourceContent={resourceContent}

client/src/components/ResourcesTab.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ const ResourcesTab = ({
2626
readResource,
2727
selectedResource,
2828
setSelectedResource,
29+
resourceSubscriptionsSupported,
30+
resourceSubscriptions,
31+
subscribeToResource,
32+
unsubscribeFromResource,
2933
handleCompletion,
3034
completionsSupported,
3135
resourceContent,
@@ -52,6 +56,10 @@ const ResourcesTab = ({
5256
nextCursor: ListResourcesResult["nextCursor"];
5357
nextTemplateCursor: ListResourceTemplatesResult["nextCursor"];
5458
error: string | null;
59+
resourceSubscriptionsSupported: boolean;
60+
resourceSubscriptions: Set<string>;
61+
subscribeToResource: (uri: string) => void;
62+
unsubscribeFromResource: (uri: string) => void;
5563
}) => {
5664
const [selectedTemplate, setSelectedTemplate] =
5765
useState<ResourceTemplate | null>(null);
@@ -164,14 +172,38 @@ const ResourcesTab = ({
164172
: "Select a resource or template"}
165173
</h3>
166174
{selectedResource && (
167-
<Button
168-
variant="outline"
169-
size="sm"
170-
onClick={() => readResource(selectedResource.uri)}
171-
>
172-
<RefreshCw className="w-4 h-4 mr-2" />
173-
Refresh
174-
</Button>
175+
<div className="flex row-auto gap-1 justify-end w-2/5">
176+
{resourceSubscriptionsSupported &&
177+
!resourceSubscriptions.has(selectedResource.uri) && (
178+
<Button
179+
variant="outline"
180+
size="sm"
181+
onClick={() => subscribeToResource(selectedResource.uri)}
182+
>
183+
Subscribe
184+
</Button>
185+
)}
186+
{resourceSubscriptionsSupported &&
187+
resourceSubscriptions.has(selectedResource.uri) && (
188+
<Button
189+
variant="outline"
190+
size="sm"
191+
onClick={() =>
192+
unsubscribeFromResource(selectedResource.uri)
193+
}
194+
>
195+
Unsubscribe
196+
</Button>
197+
)}
198+
<Button
199+
variant="outline"
200+
size="sm"
201+
onClick={() => readResource(selectedResource.uri)}
202+
>
203+
<RefreshCw className="w-4 h-4 mr-2" />
204+
Refresh
205+
</Button>
206+
</div>
175207
)}
176208
</div>
177209
<div className="p-4">

client/src/lib/hooks/useConnection.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
CreateMessageRequestSchema,
1010
ListRootsRequestSchema,
1111
ProgressNotificationSchema,
12+
ResourceUpdatedNotificationSchema,
1213
Request,
1314
Result,
1415
ServerCapabilities,
@@ -247,6 +248,11 @@ export function useConnection({
247248
ProgressNotificationSchema,
248249
onNotification,
249250
);
251+
252+
client.setNotificationHandler(
253+
ResourceUpdatedNotificationSchema,
254+
onNotification,
255+
);
250256
}
251257

252258
if (onStdErrNotification) {

client/src/lib/notificationTypes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
NotificationSchema as BaseNotificationSchema,
33
ClientNotificationSchema,
4+
ServerNotificationSchema,
45
} from "@modelcontextprotocol/sdk/types.js";
56
import { z } from "zod";
67

@@ -13,7 +14,7 @@ export const StdErrNotificationSchema = BaseNotificationSchema.extend({
1314

1415
export const NotificationSchema = ClientNotificationSchema.or(
1516
StdErrNotificationSchema,
16-
);
17+
).or(ServerNotificationSchema);
1718

1819
export type StdErrNotification = z.infer<typeof StdErrNotificationSchema>;
1920
export type Notification = z.infer<typeof NotificationSchema>;

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
3535
},
3636
"dependencies": {
37-
"@modelcontextprotocol/inspector-client": "0.4.1",
38-
"@modelcontextprotocol/inspector-server": "0.4.1",
37+
"@modelcontextprotocol/inspector-client": "^0.5.1",
38+
"@modelcontextprotocol/inspector-server": "^0.5.1",
3939
"concurrently": "^9.0.1",
4040
"shell-quote": "^1.8.2",
4141
"spawn-rx": "^5.1.2",

0 commit comments

Comments
 (0)