Track subscribed resources and show the appropriate subscribe or unsubscribe button on selected resource panel.

If the server does not support resource subscriptions, do not show any subscription buttons.

* In App.tsx
  - useState for resourceSubscriptions, setResourceSubscriptions a Set of type string.
  - in subscribeToResource()
    - only make the request to subscribe if the uri is not in the resourceSubscriptions set
  - in unsubscribeFromResource()
    - only make the request to unsubscribe if the uri is in the resourceSubscriptions set
  - in ResourceTab element,
    - pass a boolean resourceSubscriptionsSupported as serverCapabilities.resources.subscribe
    - pass resourceSubscriptions as a prop
* In ResourcesTab.tsx
  - deconstruct resourceSubscriptions and resourceSubscriptionsSupported from props and add prop type
  - in selected resource panel
    - don't show subscribe or unsubscribe buttons unless resourceSubscriptionsSupported is true
    - only show subscribe button if selected resource uri is not in resourceSubscriptions set
    - only show unsubscribe button if selected resource uri is in resourceSubscriptions set
    - wrap buttons in a flex div that is
      - justified right
      - has a minimal gap between
      - 2/5 wide (just big enough to contain two buttons and leave the h3 text 3/5 of the row to render and not overflow.
This commit is contained in:
cliffhall
2025-03-08 13:40:37 -05:00
parent 747c0154c5
commit a669272fda
4 changed files with 45 additions and 23 deletions

View File

@@ -128,6 +128,8 @@ const App = () => {
const [selectedResource, setSelectedResource] = useState<Resource | null>(
null,
);
const [resourceSubscriptions, setResourceSubscriptions] = useState<Set<string>>(new Set<string>());
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
const [selectedTool, setSelectedTool] = useState<Tool | null>(null);
const [nextResourceCursor, setNextResourceCursor] = useState<
@@ -310,26 +312,37 @@ const App = () => {
const subscribeToResource = async (uri: string) => {
await makeRequest(
{
method: "resources/subscribe" as const,
params: { uri },
},
z.object({}),
"resources",
);
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) => {
await makeRequest(
{
method: "resources/unsubscribe" as const,
params: { uri },
},
z.object({}),
"resources",
);
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);
}
};
@@ -510,6 +523,8 @@ const App = () => {
clearError("resources");
setSelectedResource(resource);
}}
resourceSubscriptionsSupported={serverCapabilities?.resources?.subscribe || false}
resourceSubscriptions={resourceSubscriptions}
subscribeToResource={(uri) => {
clearError("resources");
subscribeToResource(uri);

View File

@@ -26,6 +26,8 @@ const ResourcesTab = ({
readResource,
selectedResource,
setSelectedResource,
resourceSubscriptionsSupported,
resourceSubscriptions,
subscribeToResource,
unsubscribeFromResource,
handleCompletion,
@@ -54,6 +56,8 @@ const ResourcesTab = ({
nextCursor: ListResourcesResult["nextCursor"];
nextTemplateCursor: ListResourceTemplatesResult["nextCursor"];
error: string | null;
resourceSubscriptionsSupported: boolean;
resourceSubscriptions: Set<string>;
subscribeToResource: (uri: string) => void;
unsubscribeFromResource: (uri: string) => void;
}) => {
@@ -168,14 +172,16 @@ const ResourcesTab = ({
: "Select a resource or template"}
</h3>
{selectedResource && (
<>
<Button
<div className="flex row-auto gap-1 justify-end w-2/5">
{ resourceSubscriptionsSupported && !resourceSubscriptions.has(selectedResource.uri) && <Button
variant="outline"
size="sm"
onClick={() => subscribeToResource(selectedResource.uri)}
>
Subscribe
</Button>
}
{ resourceSubscriptionsSupported && resourceSubscriptions.has(selectedResource.uri) &&
<Button
variant="outline"
size="sm"
@@ -183,6 +189,7 @@ const ResourcesTab = ({
>
Unsubscribe
</Button>
}
<Button
variant="outline"
size="sm"
@@ -191,7 +198,7 @@ const ResourcesTab = ({
<RefreshCw className="w-4 h-4 mr-2" />
Refresh
</Button>
</>
</div>
)}
</div>
<div className="p-4">

4
package-lock.json generated
View File

@@ -13,8 +13,8 @@
"server"
],
"dependencies": {
"@modelcontextprotocol/inspector-client": "0.4.1",
"@modelcontextprotocol/inspector-server": "0.4.1",
"@modelcontextprotocol/inspector-client": "^0.5.1",
"@modelcontextprotocol/inspector-server": "^0.5.1",
"concurrently": "^9.0.1",
"shell-quote": "^1.8.2",
"spawn-rx": "^5.1.2",

View File

@@ -34,8 +34,8 @@
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
},
"dependencies": {
"@modelcontextprotocol/inspector-client": "0.4.1",
"@modelcontextprotocol/inspector-server": "0.4.1",
"@modelcontextprotocol/inspector-client": "^0.5.1",
"@modelcontextprotocol/inspector-server": "^0.5.1",
"concurrently": "^9.0.1",
"shell-quote": "^1.8.2",
"spawn-rx": "^5.1.2",