From 439bdf788252ded553f910af3bf97897be78727f Mon Sep 17 00:00:00 2001 From: jw_ond Date: Wed, 15 Jul 2026 01:07:10 +0800 Subject: [PATCH] test(everything): cover tool annotations during registration --- .../__tests__/registrations.test.ts | 153 ++++++++++++------ 1 file changed, 105 insertions(+), 48 deletions(-) diff --git a/src/everything/__tests__/registrations.test.ts b/src/everything/__tests__/registrations.test.ts index 421d759e07..f622bf3c80 100644 --- a/src/everything/__tests__/registrations.test.ts +++ b/src/everything/__tests__/registrations.test.ts @@ -1,5 +1,5 @@ -import { describe, it, expect, vi } from 'vitest'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { describe, it, expect, vi } from "vitest"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; // Create mock server function createMockServer() { @@ -16,10 +16,21 @@ function createMockServer() { } as unknown as McpServer; } -describe('Registration Index Files', () => { - describe('tools/index.ts', () => { - it('should register all standard tools', async () => { - const { registerTools } = await import('../tools/index.js'); +function expectToolAnnotations(config: any) { + expect(config.annotations).toEqual( + expect.objectContaining({ + readOnlyHint: expect.any(Boolean), + destructiveHint: expect.any(Boolean), + idempotentHint: expect.any(Boolean), + openWorldHint: expect.any(Boolean), + }) + ); +} + +describe("Registration Index Files", () => { + describe("tools/index.ts", () => { + it("should register all standard tools", async () => { + const { registerTools } = await import("../tools/index.js"); const mockServer = createMockServer(); registerTools(mockServer); @@ -31,22 +42,33 @@ describe('Registration Index Files', () => { const registeredTools = (mockServer.registerTool as any).mock.calls.map( (call: any[]) => call[0] ); - expect(registeredTools).toContain('echo'); - expect(registeredTools).toContain('get-sum'); - expect(registeredTools).toContain('get-env'); - expect(registeredTools).toContain('get-tiny-image'); - expect(registeredTools).toContain('get-structured-content'); - expect(registeredTools).toContain('get-annotated-message'); - expect(registeredTools).toContain('trigger-long-running-operation'); - expect(registeredTools).toContain('get-resource-links'); - expect(registeredTools).toContain('get-resource-reference'); - expect(registeredTools).toContain('gzip-file-as-resource'); - expect(registeredTools).toContain('toggle-simulated-logging'); - expect(registeredTools).toContain('toggle-subscriber-updates'); + expect(registeredTools).toContain("echo"); + expect(registeredTools).toContain("get-sum"); + expect(registeredTools).toContain("get-env"); + expect(registeredTools).toContain("get-tiny-image"); + expect(registeredTools).toContain("get-structured-content"); + expect(registeredTools).toContain("get-annotated-message"); + expect(registeredTools).toContain("trigger-long-running-operation"); + expect(registeredTools).toContain("get-resource-links"); + expect(registeredTools).toContain("get-resource-reference"); + expect(registeredTools).toContain("gzip-file-as-resource"); + expect(registeredTools).toContain("toggle-simulated-logging"); + expect(registeredTools).toContain("toggle-subscriber-updates"); }); - it('should register conditional tools based on capabilities', async () => { - const { registerConditionalTools } = await import('../tools/index.js'); + it("should register annotations for all standard tools", async () => { + const { registerTools } = await import("../tools/index.js"); + const mockServer = createMockServer(); + + registerTools(mockServer); + + for (const [, config] of (mockServer.registerTool as any).mock.calls) { + expectToolAnnotations(config); + } + }); + + it("should register conditional tools based on capabilities", async () => { + const { registerConditionalTools } = await import("../tools/index.js"); // Server with all capabilities including experimental tasks API const mockServerWithCapabilities = { @@ -75,17 +97,52 @@ describe('Registration Index Files', () => { const registeredTools = ( mockServerWithCapabilities.registerTool as any ).mock.calls.map((call: any[]) => call[0]); - expect(registeredTools).toContain('get-roots-list'); - expect(registeredTools).toContain('trigger-elicitation-request'); - expect(registeredTools).toContain('trigger-url-elicitation'); - expect(registeredTools).toContain('trigger-sampling-request'); + expect(registeredTools).toContain("get-roots-list"); + expect(registeredTools).toContain("trigger-elicitation-request"); + expect(registeredTools).toContain("trigger-url-elicitation"); + expect(registeredTools).toContain("trigger-sampling-request"); // Task-based tools are registered via experimental.tasks.registerToolTask - expect(mockServerWithCapabilities.experimental.tasks.registerToolTask).toHaveBeenCalled(); + expect( + mockServerWithCapabilities.experimental.tasks.registerToolTask + ).toHaveBeenCalled(); + }); + + it("should register annotations for all conditional tools", async () => { + const { registerConditionalTools } = await import("../tools/index.js"); + + const mockServerWithCapabilities = { + registerTool: vi.fn(), + server: { + getClientCapabilities: vi.fn(() => ({ + roots: {}, + elicitation: { url: {} }, + sampling: {}, + })), + }, + experimental: { + tasks: { + registerToolTask: vi.fn(), + }, + }, + } as unknown as McpServer; + + registerConditionalTools(mockServerWithCapabilities); + + for (const [, config] of (mockServerWithCapabilities.registerTool as any) + .mock.calls) { + expectToolAnnotations(config); + } + + for (const [, config] of ( + mockServerWithCapabilities.experimental.tasks.registerToolTask as any + ).mock.calls) { + expectToolAnnotations(config); + } }); - it('should not register conditional tools when capabilities missing', async () => { - const { registerConditionalTools } = await import('../tools/index.js'); + it("should not register conditional tools when capabilities missing", async () => { + const { registerConditionalTools } = await import("../tools/index.js"); const mockServerNoCapabilities = { registerTool: vi.fn(), @@ -106,9 +163,9 @@ describe('Registration Index Files', () => { }); }); - describe('prompts/index.ts', () => { - it('should register all prompts', async () => { - const { registerPrompts } = await import('../prompts/index.js'); + describe("prompts/index.ts", () => { + it("should register all prompts", async () => { + const { registerPrompts } = await import("../prompts/index.js"); const mockServer = createMockServer(); registerPrompts(mockServer); @@ -116,39 +173,39 @@ describe('Registration Index Files', () => { // Should register 4 prompts expect(mockServer.registerPrompt).toHaveBeenCalledTimes(4); - const registeredPrompts = (mockServer.registerPrompt as any).mock.calls.map( - (call: any[]) => call[0] - ); - expect(registeredPrompts).toContain('simple-prompt'); - expect(registeredPrompts).toContain('args-prompt'); - expect(registeredPrompts).toContain('completable-prompt'); - expect(registeredPrompts).toContain('resource-prompt'); + const registeredPrompts = ( + mockServer.registerPrompt as any + ).mock.calls.map((call: any[]) => call[0]); + expect(registeredPrompts).toContain("simple-prompt"); + expect(registeredPrompts).toContain("args-prompt"); + expect(registeredPrompts).toContain("completable-prompt"); + expect(registeredPrompts).toContain("resource-prompt"); }); }); - describe('resources/index.ts', () => { - it('should register resource templates', async () => { - const { registerResources } = await import('../resources/index.js'); + describe("resources/index.ts", () => { + it("should register resource templates", async () => { + const { registerResources } = await import("../resources/index.js"); const mockServer = createMockServer(); registerResources(mockServer); // Should register at least the 2 resource templates (text and blob) plus file resources expect(mockServer.registerResource).toHaveBeenCalled(); - const registeredResources = (mockServer.registerResource as any).mock.calls.map( - (call: any[]) => call[0] - ); - expect(registeredResources).toContain('Dynamic Text Resource'); - expect(registeredResources).toContain('Dynamic Blob Resource'); + const registeredResources = ( + mockServer.registerResource as any + ).mock.calls.map((call: any[]) => call[0]); + expect(registeredResources).toContain("Dynamic Text Resource"); + expect(registeredResources).toContain("Dynamic Blob Resource"); }); - it('should read instructions from file', async () => { - const { readInstructions } = await import('../resources/index.js'); + it("should read instructions from file", async () => { + const { readInstructions } = await import("../resources/index.js"); const instructions = readInstructions(); // Should return a string (either content or error message) - expect(typeof instructions).toBe('string'); + expect(typeof instructions).toBe("string"); expect(instructions.length).toBeGreaterThan(0); }); });