Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 171 additions & 33 deletions packages/mcp-shared/__tests__/action-store.test.ts

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions packages/mcp-shared/__tests__/client-pagination.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,24 @@ describe("McpClient.listTools", () => {
expect(calls()).toBe(2);
});

it("does not carry an exact lookup deadline into a later tool call", async () => {
let calls = 0;
vi.stubGlobal("fetch", async (_input: unknown, init?: RequestInit) => {
calls++;
const request = JSON.parse(String(init?.body));
return new Response(JSON.stringify({
jsonrpc: "2.0",
id: request.id,
result: { content: [] },
}), { headers: { "Content-Type": "application/json" } });
});
const client = new McpClient("https://mcp.example.com/mcp", async () => null);

await expect(client.findTool("send", 0)).rejects.toThrow(/timed out|timeout/i);
await expect(client.callTool("send", {})).resolves.toEqual({ content: [] });
expect(calls).toBe(1);
});

it("stops paging when a bounded search has enough matches", async () => {
const calls = stubPages([{
tools: Array.from({ length: 25 }, (_, i) => ({ name: `jira_tool_${i}` })),
Expand Down
29 changes: 26 additions & 3 deletions packages/mcp-shared/__tests__/connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {
McpCallNotDispatchedError,
McpClient,
} from "../src/client.js";
import { withClient, type ConnectionAccount } from "../src/connection.js";
import {
createMcpConnectionChangedRpcError,
McpConnectionChangedError,
withClient,
type ConnectionAccount,
} from "../src/connection.js";

afterEach(() => vi.unstubAllGlobals());

Expand Down Expand Up @@ -62,6 +67,24 @@ it("classifies credential lookup failure as not dispatched", async () => {
expect(callMayHaveTakenEffect(error)).toBe(false);
});

it("preserves a connection change detected during credential lookup", async () => {
const account: ConnectionAccount = {
async getConnection() {
throw createMcpConnectionChangedRpcError("The account was repointed.");
},
async assertConnectionCurrent() {},
async setMcpSessionId() { return true; },
async noteCredentialsExpired() {},
};

const error = await withClient({}, account, "https://mcp.example.com",
client => client.callTool("send", {}), { retryOnExpiry: false }).catch(err => err);

expect(error).toBeInstanceOf(McpConnectionChangedError);
expect(error.message).toBe("The account was repointed.");
expect(callMayHaveTakenEffect(error)).toBe(false);
});

it("classifies initialization failure as not dispatched", async () => {
vi.stubGlobal("fetch", async () => new Response(null, { status: 500 }));
const account: ConnectionAccount = {
Expand Down Expand Up @@ -102,7 +125,7 @@ it("rechecks account generation immediately before the tool request", async () =
return { authorization: "token", sessionId: "session", generation: 1 };
},
async assertConnectionCurrent() {
if (!current) throw new Error("connection replaced");
if (!current) throw createMcpConnectionChangedRpcError("connection replaced");
},
async setMcpSessionId() { return true; },
async noteCredentialsExpired() {},
Expand All @@ -113,7 +136,7 @@ it("rechecks account generation immediately before the tool request", async () =
return client.callTool("send", {});
}, { retryOnExpiry: false }).catch(err => err);

expect(error).toBeInstanceOf(McpCallNotDispatchedError);
expect(error).toBeInstanceOf(McpConnectionChangedError);
expect(requests).toBe(0);
});

Expand Down
Loading
Loading