Merge pull request #170 from cliffhall/add-subscribe-to-resource
Add subscribe to resource functionality
This commit is contained in:
@@ -128,6 +128,10 @@ const App = () => {
|
|||||||
const [selectedResource, setSelectedResource] = useState<Resource | null>(
|
const [selectedResource, setSelectedResource] = useState<Resource | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
const [resourceSubscriptions, setResourceSubscriptions] = useState<
|
||||||
|
Set<string>
|
||||||
|
>(new Set<string>());
|
||||||
|
|
||||||
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
|
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
|
||||||
const [selectedTool, setSelectedTool] = useState<Tool | null>(null);
|
const [selectedTool, setSelectedTool] = useState<Tool | null>(null);
|
||||||
const [nextResourceCursor, setNextResourceCursor] = useState<
|
const [nextResourceCursor, setNextResourceCursor] = useState<
|
||||||
@@ -308,6 +312,38 @@ const App = () => {
|
|||||||
setResourceContent(JSON.stringify(response, null, 2));
|
setResourceContent(JSON.stringify(response, null, 2));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const subscribeToResource = async (uri: string) => {
|
||||||
|
if (!resourceSubscriptions.has(uri)) {
|
||||||
|
await makeRequest(
|
||||||
|
{
|
||||||
|
method: "resources/subscribe" as const,
|
||||||
|
params: { uri },
|
||||||
|
},
|
||||||
|
z.object({}),
|
||||||
|
"resources",
|
||||||
|
);
|
||||||
|
const clone = new Set(resourceSubscriptions);
|
||||||
|
clone.add(uri);
|
||||||
|
setResourceSubscriptions(clone);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsubscribeFromResource = async (uri: string) => {
|
||||||
|
if (resourceSubscriptions.has(uri)) {
|
||||||
|
await makeRequest(
|
||||||
|
{
|
||||||
|
method: "resources/unsubscribe" as const,
|
||||||
|
params: { uri },
|
||||||
|
},
|
||||||
|
z.object({}),
|
||||||
|
"resources",
|
||||||
|
);
|
||||||
|
const clone = new Set(resourceSubscriptions);
|
||||||
|
clone.delete(uri);
|
||||||
|
setResourceSubscriptions(clone);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const listPrompts = async () => {
|
const listPrompts = async () => {
|
||||||
const response = await makeRequest(
|
const response = await makeRequest(
|
||||||
{
|
{
|
||||||
@@ -485,6 +521,18 @@ const App = () => {
|
|||||||
clearError("resources");
|
clearError("resources");
|
||||||
setSelectedResource(resource);
|
setSelectedResource(resource);
|
||||||
}}
|
}}
|
||||||
|
resourceSubscriptionsSupported={
|
||||||
|
serverCapabilities?.resources?.subscribe || false
|
||||||
|
}
|
||||||
|
resourceSubscriptions={resourceSubscriptions}
|
||||||
|
subscribeToResource={(uri) => {
|
||||||
|
clearError("resources");
|
||||||
|
subscribeToResource(uri);
|
||||||
|
}}
|
||||||
|
unsubscribeFromResource={(uri) => {
|
||||||
|
clearError("resources");
|
||||||
|
unsubscribeFromResource(uri);
|
||||||
|
}}
|
||||||
handleCompletion={handleCompletion}
|
handleCompletion={handleCompletion}
|
||||||
completionsSupported={completionsSupported}
|
completionsSupported={completionsSupported}
|
||||||
resourceContent={resourceContent}
|
resourceContent={resourceContent}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ const ResourcesTab = ({
|
|||||||
readResource,
|
readResource,
|
||||||
selectedResource,
|
selectedResource,
|
||||||
setSelectedResource,
|
setSelectedResource,
|
||||||
|
resourceSubscriptionsSupported,
|
||||||
|
resourceSubscriptions,
|
||||||
|
subscribeToResource,
|
||||||
|
unsubscribeFromResource,
|
||||||
handleCompletion,
|
handleCompletion,
|
||||||
completionsSupported,
|
completionsSupported,
|
||||||
resourceContent,
|
resourceContent,
|
||||||
@@ -52,6 +56,10 @@ const ResourcesTab = ({
|
|||||||
nextCursor: ListResourcesResult["nextCursor"];
|
nextCursor: ListResourcesResult["nextCursor"];
|
||||||
nextTemplateCursor: ListResourceTemplatesResult["nextCursor"];
|
nextTemplateCursor: ListResourceTemplatesResult["nextCursor"];
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
resourceSubscriptionsSupported: boolean;
|
||||||
|
resourceSubscriptions: Set<string>;
|
||||||
|
subscribeToResource: (uri: string) => void;
|
||||||
|
unsubscribeFromResource: (uri: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedTemplate, setSelectedTemplate] =
|
const [selectedTemplate, setSelectedTemplate] =
|
||||||
useState<ResourceTemplate | null>(null);
|
useState<ResourceTemplate | null>(null);
|
||||||
@@ -164,14 +172,38 @@ const ResourcesTab = ({
|
|||||||
: "Select a resource or template"}
|
: "Select a resource or template"}
|
||||||
</h3>
|
</h3>
|
||||||
{selectedResource && (
|
{selectedResource && (
|
||||||
<Button
|
<div className="flex row-auto gap-1 justify-end w-2/5">
|
||||||
variant="outline"
|
{resourceSubscriptionsSupported &&
|
||||||
size="sm"
|
!resourceSubscriptions.has(selectedResource.uri) && (
|
||||||
onClick={() => readResource(selectedResource.uri)}
|
<Button
|
||||||
>
|
variant="outline"
|
||||||
<RefreshCw className="w-4 h-4 mr-2" />
|
size="sm"
|
||||||
Refresh
|
onClick={() => subscribeToResource(selectedResource.uri)}
|
||||||
</Button>
|
>
|
||||||
|
Subscribe
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{resourceSubscriptionsSupported &&
|
||||||
|
resourceSubscriptions.has(selectedResource.uri) && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
unsubscribeFromResource(selectedResource.uri)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Unsubscribe
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => readResource(selectedResource.uri)}
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-4 h-4 mr-2" />
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
CreateMessageRequestSchema,
|
CreateMessageRequestSchema,
|
||||||
ListRootsRequestSchema,
|
ListRootsRequestSchema,
|
||||||
ProgressNotificationSchema,
|
ProgressNotificationSchema,
|
||||||
|
ResourceUpdatedNotificationSchema,
|
||||||
Request,
|
Request,
|
||||||
Result,
|
Result,
|
||||||
ServerCapabilities,
|
ServerCapabilities,
|
||||||
@@ -247,6 +248,11 @@ export function useConnection({
|
|||||||
ProgressNotificationSchema,
|
ProgressNotificationSchema,
|
||||||
onNotification,
|
onNotification,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
client.setNotificationHandler(
|
||||||
|
ResourceUpdatedNotificationSchema,
|
||||||
|
onNotification,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onStdErrNotification) {
|
if (onStdErrNotification) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
NotificationSchema as BaseNotificationSchema,
|
NotificationSchema as BaseNotificationSchema,
|
||||||
ClientNotificationSchema,
|
ClientNotificationSchema,
|
||||||
|
ServerNotificationSchema,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ export const StdErrNotificationSchema = BaseNotificationSchema.extend({
|
|||||||
|
|
||||||
export const NotificationSchema = ClientNotificationSchema.or(
|
export const NotificationSchema = ClientNotificationSchema.or(
|
||||||
StdErrNotificationSchema,
|
StdErrNotificationSchema,
|
||||||
);
|
).or(ServerNotificationSchema);
|
||||||
|
|
||||||
export type StdErrNotification = z.infer<typeof StdErrNotificationSchema>;
|
export type StdErrNotification = z.infer<typeof StdErrNotificationSchema>;
|
||||||
export type Notification = z.infer<typeof NotificationSchema>;
|
export type Notification = z.infer<typeof NotificationSchema>;
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -13,8 +13,8 @@
|
|||||||
"server"
|
"server"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/inspector-client": "0.4.1",
|
"@modelcontextprotocol/inspector-client": "^0.5.1",
|
||||||
"@modelcontextprotocol/inspector-server": "0.4.1",
|
"@modelcontextprotocol/inspector-server": "^0.5.1",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"shell-quote": "^1.8.2",
|
"shell-quote": "^1.8.2",
|
||||||
"spawn-rx": "^5.1.2",
|
"spawn-rx": "^5.1.2",
|
||||||
|
|||||||
@@ -34,8 +34,8 @@
|
|||||||
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/inspector-client": "0.4.1",
|
"@modelcontextprotocol/inspector-client": "^0.5.1",
|
||||||
"@modelcontextprotocol/inspector-server": "0.4.1",
|
"@modelcontextprotocol/inspector-server": "^0.5.1",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"shell-quote": "^1.8.2",
|
"shell-quote": "^1.8.2",
|
||||||
"spawn-rx": "^5.1.2",
|
"spawn-rx": "^5.1.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user