jira-mcp

/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable no-constant-binary-expression */ /* eslint-disable @typescript-eslint/no-unused-expressions */ import { Client } from "./index.js"; import { z } from "zod"; import { RequestSchema, NotificationSchema, ResultSchema, LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, InitializeRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, CreateMessageRequestSchema, ListRootsRequestSchema, ErrorCode, } from "../types.js"; import { Server } from "../server/index.js"; import { InMemoryTransport } from "../inMemory.js"; test("should initialize with matching protocol version", async () => { const clientTransport = { start: jest.fn().mockResolvedValue(undefined), close: jest.fn().mockResolvedValue(undefined), send: jest.fn().mockImplementation((message) => { var _a; if (message.method === "initialize") { (_a = clientTransport.onmessage) === null || _a === void 0 ? void 0 : _a.call(clientTransport, { jsonrpc: "2.0", id: message.id, result: { protocolVersion: LATEST_PROTOCOL_VERSION, capabilities: {}, serverInfo: { name: "test", version: "1.0", }, }, }); } return Promise.resolve(); }), }; const client = new Client({ name: "test client", version: "1.0", }, { capabilities: { sampling: {}, }, }); await client.connect(clientTransport); // Should have sent initialize with latest version expect(clientTransport.send).toHaveBeenCalledWith(expect.objectContaining({ method: "initialize", params: expect.objectContaining({ protocolVersion: LATEST_PROTOCOL_VERSION, }), })); }); test("should initialize with supported older protocol version", async () => { const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1]; const clientTransport = { start: jest.fn().mockResolvedValue(undefined), close: jest.fn().mockResolvedValue(undefined), send: jest.fn().mockImplementation((message) => { var _a; if (message.method === "initialize") { (_a = clientTransport.onmessage) === null || _a === void 0 ? void 0 : _a.call(clientTransport, { jsonrpc: "2.0", id: message.id, result: { protocolVersion: OLD_VERSION, capabilities: {}, serverInfo: { name: "test", version: "1.0", }, }, }); } return Promise.resolve(); }), }; const client = new Client({ name: "test client", version: "1.0", }, { capabilities: { sampling: {}, }, }); await client.connect(clientTransport); // Connection should succeed with the older version expect(client.getServerVersion()).toEqual({ name: "test", version: "1.0", }); }); test("should reject unsupported protocol version", async () => { const clientTransport = { start: jest.fn().mockResolvedValue(undefined), close: jest.fn().mockResolvedValue(undefined), send: jest.fn().mockImplementation((message) => { var _a; if (message.method === "initialize") { (_a = clientTransport.onmessage) === null || _a === void 0 ? void 0 : _a.call(clientTransport, { jsonrpc: "2.0", id: message.id, result: { protocolVersion: "invalid-version", capabilities: {}, serverInfo: { name: "test", version: "1.0", }, }, }); } return Promise.resolve(); }), }; const client = new Client({ name: "test client", version: "1.0", }, { capabilities: { sampling: {}, }, }); await expect(client.connect(clientTransport)).rejects.toThrow("Server's protocol version is not supported: invalid-version"); expect(clientTransport.close).toHaveBeenCalled(); }); test("should respect server capabilities", async () => { const server = new Server({ name: "test server", version: "1.0", }, { capabilities: { resources: {}, tools: {}, }, }); server.setRequestHandler(InitializeRequestSchema, (_request) => ({ protocolVersion: LATEST_PROTOCOL_VERSION, capabilities: { resources: {}, tools: {}, }, serverInfo: { name: "test", version: "1.0", }, })); server.setRequestHandler(ListResourcesRequestSchema, () => ({ resources: [], })); server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: [], })); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const client = new Client({ name: "test client", version: "1.0", }, { capabilities: { sampling: {}, }, enforceStrictCapabilities: true, }); await Promise.all([ client.connect(clientTransport), server.connect(serverTransport), ]); // Server supports resources and tools, but not prompts expect(client.getServerCapabilities()).toEqual({ resources: {}, tools: {}, }); // These should work await expect(client.listResources()).resolves.not.toThrow(); await expect(client.listTools()).resolves.not.toThrow(); // This should throw because prompts are not supported await expect(client.listPrompts()).rejects.toThrow("Server does not support prompts"); }); test("should respect client notification capabilities", async () => { const server = new Server({ name: "test server", version: "1.0", }, { capabilities: {}, }); const client = new Client({ name: "test client", version: "1.0", }, { capabilities: { roots: { listChanged: true, }, }, }); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); await Promise.all([ client.connect(clientTransport), server.connect(serverTransport), ]); // This should work because the client has the roots.listChanged capability await expect(client.sendRootsListChanged()).resolves.not.toThrow(); // Create a new client without the roots.listChanged capability const clientWithoutCapability = new Client({ name: "test client without capability", version: "1.0", }, { capabilities: {}, enforceStrictCapabilities: true, }); await clientWithoutCapability.connect(clientTransport); // This should throw because the client doesn't have the roots.listChanged capability await expect(clientWithoutCapability.sendRootsListChanged()).rejects.toThrow(/^Client does not support/); }); test("should respect server notification capabilities", async () => { const server = new Server({ name: "test server", version: "1.0", }, { capabilities: { logging: {}, resources: { listChanged: true, }, }, }); const client = new Client({ name: "test client", version: "1.0", }, { capabilities: {}, }); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); await Promise.all([ client.connect(clientTransport), server.connect(serverTransport), ]); // These should work because the server has the corresponding capabilities await expect(server.sendLoggingMessage({ level: "info", data: "Test" })).resolves.not.toThrow(); await expect(server.sendResourceListChanged()).resolves.not.toThrow(); // This should throw because the server doesn't have the tools capability await expect(server.sendToolListChanged()).rejects.toThrow("Server does not support notifying of tool list changes"); }); test("should only allow setRequestHandler for declared capabilities", () => { const client = new Client({ name: "test client", version: "1.0", }, { capabilities: { sampling: {}, }, }); // This should work because sampling is a declared capability expect(() => { client.setRequestHandler(CreateMessageRequestSchema, () => ({ model: "test-model", role: "assistant", content: { type: "text", text: "Test response", }, })); }).not.toThrow(); // This should throw because roots listing is not a declared capability expect(() => { client.setRequestHandler(ListRootsRequestSchema, () => ({})); }).toThrow("Client does not support roots capability"); }); /* Test that custom request/notification/result schemas can be used with the Client class. */ test("should typecheck", () => { const GetWeatherRequestSchema = RequestSchema.extend({ method: z.literal("weather/get"), params: z.object({ city: z.string(), }), }); const GetForecastRequestSchema = RequestSchema.extend({ method: z.literal("weather/forecast"), params: z.object({ city: z.string(), days: z.number(), }), }); const WeatherForecastNotificationSchema = NotificationSchema.extend({ method: z.literal("weather/alert"), params: z.object({ severity: z.enum(["warning", "watch"]), message: z.string(), }), }); const WeatherRequestSchema = GetWeatherRequestSchema.or(GetForecastRequestSchema); const WeatherNotificationSchema = WeatherForecastNotificationSchema; const WeatherResultSchema = ResultSchema.extend({ temperature: z.number(), conditions: z.string(), }); // Create a typed Client for weather data const weatherClient = new Client({ name: "WeatherClient", version: "1.0.0", }, { capabilities: { sampling: {}, }, }); // Typecheck that only valid weather requests/notifications/results are allowed false && weatherClient.request({ method: "weather/get", params: { city: "Seattle", }, }, WeatherResultSchema); false && weatherClient.notification({ method: "weather/alert", params: { severity: "warning", message: "Storm approaching", }, }); }); test("should handle client cancelling a request", async () => { const server = new Server({ name: "test server", version: "1.0", }, { capabilities: { resources: {}, }, }); // Set up server to delay responding to listResources server.setRequestHandler(ListResourcesRequestSchema, async (request, extra) => { await new Promise((resolve) => setTimeout(resolve, 1000)); return { resources: [], }; }); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const client = new Client({ name: "test client", version: "1.0", }, { capabilities: {}, }); await Promise.all([ client.connect(clientTransport), server.connect(serverTransport), ]); // Set up abort controller const controller = new AbortController(); // Issue request but cancel it immediately const listResourcesPromise = client.listResources(undefined, { signal: controller.signal, }); controller.abort("Cancelled by test"); // Request should be rejected await expect(listResourcesPromise).rejects.toBe("Cancelled by test"); }); test("should handle request timeout", async () => { const server = new Server({ name: "test server", version: "1.0", }, { capabilities: { resources: {}, }, }); // Set up server with a delayed response server.setRequestHandler(ListResourcesRequestSchema, async (_request, extra) => { const timer = new Promise((resolve) => { const timeout = setTimeout(resolve, 100); extra.signal.addEventListener("abort", () => clearTimeout(timeout)); }); await timer; return { resources: [], }; }); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const client = new Client({ name: "test client", version: "1.0", }, { capabilities: {}, }); await Promise.all([ client.connect(clientTransport), server.connect(serverTransport), ]); // Request with 0 msec timeout should fail immediately await expect(client.listResources(undefined, { timeout: 0 })).rejects.toMatchObject({ code: ErrorCode.RequestTimeout, }); }); //# sourceMappingURL=index.test.js.map