fix(web): read modern tasks support from the extension map
SEP-2663 moved task support from `capabilities.tasks` to the negotiated `capabilities.extensions["io.modelcontextprotocol/tasks"]`, but the Connection Info modal's capability grid tested only for the top-level key. A modern server that advertised the extension in `server/discover` therefore showed "Tasks ✗" while the very same extension id was listed under "Server Extensions" two sections below. Server capability rows now fall back to an extension id for the capabilities the modern era expresses that way (tasks is the only one today), gated on the negotiated era so a legacy connection is still judged purely on its `initialize` capabilities. Keyed the same way `InspectorClient.isTasksExtensionNegotiated()` gates the Tasks tab, so the checkmark and the tab can't disagree. Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
8dd0fdf28b
commit
73913ef09f
1 changed files with 161 additions and 2 deletions
|
|
@ -31,6 +31,21 @@ const fullClientCaps: ClientCapabilities = {
|
|||
experimental: {},
|
||||
};
|
||||
|
||||
// The glyphs `CapabilityItem` renders beside each capability label.
|
||||
const SUPPORTED_MARK = "✓";
|
||||
const UNSUPPORTED_MARK = "✗";
|
||||
|
||||
/**
|
||||
* Read the ✓/✗ a capability row is showing. The mark is the label's preceding
|
||||
* sibling inside the row `Group`, so asserting on it — rather than on the
|
||||
* label's mere presence — is what distinguishes "supported" from "listed".
|
||||
*/
|
||||
function capabilityMark(label: string): string | undefined {
|
||||
return (
|
||||
screen.getByText(label).previousElementSibling?.textContent ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
describe("ConnectionInfoContent", () => {
|
||||
it("renders server implementation fields under the heading", () => {
|
||||
renderWithMantine(
|
||||
|
|
@ -181,6 +196,94 @@ describe("ConnectionInfoContent", () => {
|
|||
expect(screen.getByText("Sampling")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("marks Tasks supported on a modern connection that advertises the tasks extension (#1887)", () => {
|
||||
renderWithMantine(
|
||||
<ConnectionInfoContent
|
||||
initializeResult={{
|
||||
...fullResult,
|
||||
capabilities: {
|
||||
...fullResult.capabilities,
|
||||
// SEP-2663: no top-level `tasks` key — support is the extension.
|
||||
extensions: { "io.modelcontextprotocol/tasks": {} },
|
||||
},
|
||||
}}
|
||||
clientCapabilities={fullClientCaps}
|
||||
transport="streamable-http"
|
||||
protocolEra="modern"
|
||||
/>,
|
||||
);
|
||||
expect(capabilityMark("Tasks")).toBe(SUPPORTED_MARK);
|
||||
});
|
||||
|
||||
it("still marks Tasks unsupported on a modern connection without the extension (#1887)", () => {
|
||||
renderWithMantine(
|
||||
<ConnectionInfoContent
|
||||
initializeResult={fullResult}
|
||||
clientCapabilities={fullClientCaps}
|
||||
transport="streamable-http"
|
||||
protocolEra="modern"
|
||||
/>,
|
||||
);
|
||||
expect(capabilityMark("Tasks")).toBe(UNSUPPORTED_MARK);
|
||||
});
|
||||
|
||||
it("does not read the tasks extension on a legacy connection (#1887)", () => {
|
||||
renderWithMantine(
|
||||
<ConnectionInfoContent
|
||||
initializeResult={{
|
||||
...fullResult,
|
||||
capabilities: {
|
||||
...fullResult.capabilities,
|
||||
extensions: { "io.modelcontextprotocol/tasks": {} },
|
||||
},
|
||||
}}
|
||||
clientCapabilities={fullClientCaps}
|
||||
transport="streamable-http"
|
||||
protocolEra="legacy"
|
||||
/>,
|
||||
);
|
||||
// Legacy tasks support is `capabilities.tasks`, which this server omits —
|
||||
// an extension key alone must not turn the row green.
|
||||
expect(capabilityMark("Tasks")).toBe(UNSUPPORTED_MARK);
|
||||
});
|
||||
|
||||
it("marks Tasks supported on a legacy connection from capabilities.tasks", () => {
|
||||
renderWithMantine(
|
||||
<ConnectionInfoContent
|
||||
initializeResult={{
|
||||
...fullResult,
|
||||
capabilities: {
|
||||
...fullResult.capabilities,
|
||||
tasks: { list: true, cancel: true },
|
||||
},
|
||||
}}
|
||||
clientCapabilities={fullClientCaps}
|
||||
transport="streamable-http"
|
||||
protocolEra="legacy"
|
||||
/>,
|
||||
);
|
||||
expect(capabilityMark("Tasks")).toBe(SUPPORTED_MARK);
|
||||
});
|
||||
|
||||
it("does not extension-promote a capability other than tasks (#1887)", () => {
|
||||
renderWithMantine(
|
||||
<ConnectionInfoContent
|
||||
initializeResult={{
|
||||
...fullResult,
|
||||
capabilities: {
|
||||
tools: { listChanged: true },
|
||||
extensions: { "io.modelcontextprotocol/ui": {} },
|
||||
},
|
||||
}}
|
||||
clientCapabilities={fullClientCaps}
|
||||
transport="streamable-http"
|
||||
protocolEra="modern"
|
||||
/>,
|
||||
);
|
||||
expect(capabilityMark("Prompts")).toBe(UNSUPPORTED_MARK);
|
||||
expect(capabilityMark("Tasks")).toBe(UNSUPPORTED_MARK);
|
||||
});
|
||||
|
||||
it("renders instructions when present", () => {
|
||||
renderWithMantine(
|
||||
<ConnectionInfoContent
|
||||
|
|
|
|||
Loading…
Reference in a new issue