mcp-test-kits 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +94 -0
  2. package/dist/capabilities/prompts.d.ts +9 -0
  3. package/dist/capabilities/prompts.d.ts.map +1 -0
  4. package/dist/capabilities/prompts.js +76 -0
  5. package/dist/capabilities/prompts.js.map +1 -0
  6. package/dist/capabilities/resources.d.ts +9 -0
  7. package/dist/capabilities/resources.d.ts.map +1 -0
  8. package/dist/capabilities/resources.js +76 -0
  9. package/dist/capabilities/resources.js.map +1 -0
  10. package/dist/capabilities/tools.d.ts +9 -0
  11. package/dist/capabilities/tools.d.ts.map +1 -0
  12. package/dist/capabilities/tools.js +77 -0
  13. package/dist/capabilities/tools.js.map +1 -0
  14. package/dist/config.d.ts +35 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +30 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/extract-spec.d.ts +14 -0
  19. package/dist/extract-spec.d.ts.map +1 -0
  20. package/dist/extract-spec.js +117 -0
  21. package/dist/extract-spec.js.map +1 -0
  22. package/dist/index.d.ts +6 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +97 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/introspection.d.ts +68 -0
  27. package/dist/introspection.d.ts.map +1 -0
  28. package/dist/introspection.js +135 -0
  29. package/dist/introspection.js.map +1 -0
  30. package/dist/server.d.ts +10 -0
  31. package/dist/server.d.ts.map +1 -0
  32. package/dist/server.js +28 -0
  33. package/dist/server.js.map +1 -0
  34. package/dist/transports/http.d.ts +10 -0
  35. package/dist/transports/http.d.ts.map +1 -0
  36. package/dist/transports/http.js +31 -0
  37. package/dist/transports/http.js.map +1 -0
  38. package/dist/transports/index.d.ts +7 -0
  39. package/dist/transports/index.d.ts.map +1 -0
  40. package/dist/transports/index.js +7 -0
  41. package/dist/transports/index.js.map +1 -0
  42. package/dist/transports/sse.d.ts +10 -0
  43. package/dist/transports/sse.d.ts.map +1 -0
  44. package/dist/transports/sse.js +44 -0
  45. package/dist/transports/sse.js.map +1 -0
  46. package/dist/transports/stdio.d.ts +10 -0
  47. package/dist/transports/stdio.d.ts.map +1 -0
  48. package/dist/transports/stdio.js +14 -0
  49. package/dist/transports/stdio.js.map +1 -0
  50. package/eslint.config.js +22 -0
  51. package/package.json +51 -0
  52. package/src/capabilities/prompts.ts +108 -0
  53. package/src/capabilities/resources.ts +108 -0
  54. package/src/capabilities/tools.ts +124 -0
  55. package/src/config.ts +60 -0
  56. package/src/extract-spec.ts +189 -0
  57. package/src/index.ts +110 -0
  58. package/src/introspection.ts +216 -0
  59. package/src/server.ts +34 -0
  60. package/src/transports/http.ts +42 -0
  61. package/src/transports/index.ts +7 -0
  62. package/src/transports/sse.ts +60 -0
  63. package/src/transports/stdio.ts +21 -0
  64. package/tests/fixtures.ts +146 -0
  65. package/tests/http.test.ts +34 -0
  66. package/tests/sse.test.ts +34 -0
  67. package/tests/stdio.test.ts +98 -0
  68. package/tsconfig.json +27 -0
  69. package/vitest.config.ts +8 -0
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Introspection utilities for extracting specifications from MCP Server
3
+ */
4
+
5
+ import {
6
+ McpServer,
7
+ RegisteredTool,
8
+ RegisteredResource,
9
+ RegisteredResourceTemplate,
10
+ RegisteredPrompt,
11
+ } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+
13
+ /**
14
+ * Type representing the internal structure of McpServer with private fields exposed
15
+ */
16
+ interface McpServerInternal {
17
+ _registeredTools: Record<string, RegisteredTool>;
18
+ _registeredResources: Record<string, RegisteredResource>;
19
+ _registeredResourceTemplates: Record<string, RegisteredResourceTemplate>;
20
+ _registeredPrompts: Record<string, RegisteredPrompt>;
21
+ }
22
+
23
+ /**
24
+ * Extended McpServer with introspection capabilities.
25
+ * Provides access to registered tools, resources, and prompts for spec extraction.
26
+ */
27
+ export class IntrospectableMcpServer extends McpServer {
28
+ /**
29
+ * Get all registered tools with their metadata
30
+ */
31
+ getRegisteredTools(): Record<string, RegisteredTool> {
32
+ // Access the private _registeredTools field (it's an object, not a Map)
33
+ return (this as unknown as McpServerInternal)._registeredTools;
34
+ }
35
+
36
+ /**
37
+ * Get all registered resources with their metadata
38
+ */
39
+ getRegisteredResources(): Record<string, RegisteredResource> {
40
+ // Access the private _registeredResources field (it's an object, not a Map)
41
+ return (this as unknown as McpServerInternal)._registeredResources;
42
+ }
43
+
44
+ /**
45
+ * Get all registered resource templates with their metadata
46
+ */
47
+ getRegisteredResourceTemplates(): Record<string, RegisteredResourceTemplate> {
48
+ // Access the private _registeredResourceTemplates field (it's an object, not a Map)
49
+ return (this as unknown as McpServerInternal)._registeredResourceTemplates;
50
+ }
51
+
52
+ /**
53
+ * Get all registered prompts with their metadata
54
+ */
55
+ getRegisteredPrompts(): Record<string, RegisteredPrompt> {
56
+ // Access the private _registeredPrompts field (it's an object, not a Map)
57
+ return (this as unknown as McpServerInternal)._registeredPrompts;
58
+ }
59
+ }
60
+
61
+ interface ToolSpec {
62
+ name: string;
63
+ description: string;
64
+ }
65
+
66
+ interface ResourceSpec {
67
+ name: string;
68
+ uri: string;
69
+ description?: string;
70
+ mimeType?: string;
71
+ }
72
+
73
+ interface PromptArgSpec {
74
+ name: string;
75
+ description?: string;
76
+ required?: boolean;
77
+ }
78
+
79
+ interface PromptSpec {
80
+ name: string;
81
+ description: string;
82
+ arguments?: PromptArgSpec[];
83
+ }
84
+
85
+ /**
86
+ * Extract tool specifications from an introspectable server
87
+ */
88
+ export function extractToolsSpec(server: IntrospectableMcpServer): ToolSpec[] {
89
+ const tools: ToolSpec[] = [];
90
+ const registeredTools = server.getRegisteredTools();
91
+
92
+ for (const [name, tool] of Object.entries(registeredTools)) {
93
+ if (tool.enabled) {
94
+ tools.push({
95
+ name,
96
+ description: tool.description || "",
97
+ });
98
+ }
99
+ }
100
+
101
+ return tools.sort((a, b) => a.name.localeCompare(b.name));
102
+ }
103
+
104
+ /**
105
+ * Extract resource specifications from an introspectable server
106
+ */
107
+ export function extractResourcesSpec(
108
+ server: IntrospectableMcpServer,
109
+ ): ResourceSpec[] {
110
+ const resources: ResourceSpec[] = [];
111
+ const registeredResources = server.getRegisteredResources();
112
+
113
+ for (const [uri, resource] of Object.entries(registeredResources)) {
114
+ if (resource.enabled) {
115
+ resources.push({
116
+ name: resource.name,
117
+ uri,
118
+ mimeType: resource.metadata?.mimeType || "text/plain",
119
+ });
120
+ }
121
+ }
122
+
123
+ // Note: Resource templates are not included in static spec extraction
124
+ // as they represent dynamic URI patterns
125
+
126
+ return resources.sort((a, b) => a.uri.localeCompare(b.uri));
127
+ }
128
+
129
+ /**
130
+ * Type representing Zod schema internal structure
131
+ */
132
+ interface ZodSchemaDef {
133
+ typeName?: string;
134
+ shape?: () => Record<string, ZodSchemaInternal>;
135
+ innerType?: ZodSchemaInternal;
136
+ description?: string;
137
+ }
138
+
139
+ interface ZodSchemaInternal {
140
+ _def: ZodSchemaDef;
141
+ }
142
+
143
+ /**
144
+ * Extract argument specifications from Zod schema
145
+ */
146
+ function extractPromptArgs(argsSchema: ZodSchemaInternal): PromptArgSpec[] {
147
+ if (!argsSchema || !argsSchema._def) {
148
+ return [];
149
+ }
150
+
151
+ const args: PromptArgSpec[] = [];
152
+
153
+ // Handle Zod object schema
154
+ if (argsSchema._def.typeName === "ZodObject") {
155
+ const shape = argsSchema._def.shape?.();
156
+
157
+ if (shape) {
158
+ for (const [argName, argSchema] of Object.entries(shape)) {
159
+ // Check if the argument is optional
160
+ const isOptional = argSchema._def.typeName === "ZodOptional";
161
+ const innerSchema = isOptional ? argSchema._def.innerType : argSchema;
162
+
163
+ // Extract description from the schema
164
+ let description = "";
165
+ if (innerSchema?._def.description) {
166
+ description = innerSchema._def.description;
167
+ }
168
+
169
+ args.push({
170
+ name: argName,
171
+ description,
172
+ required: !isOptional,
173
+ });
174
+ }
175
+ }
176
+ }
177
+
178
+ return args;
179
+ }
180
+
181
+ /**
182
+ * Extract prompt specifications from an introspectable server
183
+ */
184
+ export function extractPromptsSpec(
185
+ server: IntrospectableMcpServer,
186
+ ): PromptSpec[] {
187
+ const prompts: PromptSpec[] = [];
188
+ const registeredPrompts = server.getRegisteredPrompts();
189
+
190
+ for (const [name, prompt] of Object.entries(registeredPrompts)) {
191
+ if (prompt.enabled) {
192
+ const args = prompt.argsSchema
193
+ ? extractPromptArgs(prompt.argsSchema as ZodSchemaInternal)
194
+ : [];
195
+
196
+ prompts.push({
197
+ name,
198
+ description: prompt.description || "",
199
+ arguments: args,
200
+ });
201
+ }
202
+ }
203
+
204
+ return prompts.sort((a, b) => a.name.localeCompare(b.name));
205
+ }
206
+
207
+ /**
208
+ * Extract full specification from an introspectable server
209
+ */
210
+ export function extractSpec(server: IntrospectableMcpServer) {
211
+ return {
212
+ tools: extractToolsSpec(server),
213
+ resources: extractResourcesSpec(server),
214
+ prompts: extractPromptsSpec(server),
215
+ };
216
+ }
package/src/server.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * MCP Server setup for MCP Test Kits
3
+ */
4
+
5
+ import { Config } from "./config.js";
6
+ import { IntrospectableMcpServer } from "./introspection.js";
7
+ import { registerTools } from "./capabilities/tools.js";
8
+ import { registerResources } from "./capabilities/resources.js";
9
+ import { registerPrompts } from "./capabilities/prompts.js";
10
+
11
+ /**
12
+ * Create and configure the MCP server
13
+ */
14
+ export function createServer(config: Config): IntrospectableMcpServer {
15
+ const server = new IntrospectableMcpServer({
16
+ name: config.server.name,
17
+ version: config.server.version,
18
+ });
19
+
20
+ // Register capabilities based on config
21
+ if (config.capabilities.tools) {
22
+ registerTools(server);
23
+ }
24
+
25
+ if (config.capabilities.resources) {
26
+ registerResources(server);
27
+ }
28
+
29
+ if (config.capabilities.prompts) {
30
+ registerPrompts(server);
31
+ }
32
+
33
+ return server;
34
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * HTTP transport for MCP Test Kits (Streamable HTTP)
3
+ */
4
+
5
+ import express from "express";
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8
+ import type { Config } from "../config.js";
9
+
10
+ /**
11
+ * Run the MCP server over HTTP transport (Streamable HTTP)
12
+ */
13
+ export async function runHttpServer(
14
+ server: McpServer,
15
+ config: Config,
16
+ ): Promise<void> {
17
+ const { host, port } = config.transport.network;
18
+
19
+ const app = express();
20
+ app.use(express.json());
21
+
22
+ // MCP endpoint - handle all HTTP methods
23
+ app.all("/mcp", async (req, res) => {
24
+ const transport = new StreamableHTTPServerTransport({
25
+ sessionIdGenerator: undefined,
26
+ enableJsonResponse: true,
27
+ });
28
+
29
+ res.on("close", () => transport.close());
30
+ await server.connect(transport);
31
+ await transport.handleRequest(req, res, req.body);
32
+ });
33
+
34
+ // Start server
35
+ console.error(`Starting MCP HTTP server at http://${host}:${port}/mcp`);
36
+ app.listen(port, host, () => {
37
+ console.error(`MCP HTTP server listening on http://${host}:${port}`);
38
+ });
39
+
40
+ // Keep the process running
41
+ await new Promise(() => {});
42
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Transport modules for MCP Test Kits
3
+ */
4
+
5
+ export { runStdioServer } from "./stdio.js";
6
+ export { runHttpServer } from "./http.js";
7
+ export { runSseServer } from "./sse.js";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * SSE transport for MCP Test Kits
3
+ */
4
+
5
+ import express from "express";
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
8
+ import type { Config } from "../config.js";
9
+
10
+ /**
11
+ * Run the MCP server over SSE transport
12
+ */
13
+ export async function runSseServer(
14
+ server: McpServer,
15
+ config: Config,
16
+ ): Promise<void> {
17
+ const { host, port } = config.transport.network;
18
+
19
+ const app = express();
20
+ app.use(express.json());
21
+
22
+ // Store active transports for session management
23
+ const transports = new Map<string, SSEServerTransport>();
24
+
25
+ // SSE endpoint for server->client messages
26
+ app.get("/sse", async (req, res) => {
27
+ const transport = new SSEServerTransport("/sse", res);
28
+ const sessionId = transport.sessionId;
29
+ transports.set(sessionId, transport);
30
+
31
+ // Cleanup on close
32
+ transport.onclose = () => {
33
+ transports.delete(sessionId);
34
+ };
35
+
36
+ await server.connect(transport);
37
+ });
38
+
39
+ // POST endpoint for client->server messages
40
+ app.post("/sse", async (req, res) => {
41
+ const sessionId = req.query.sessionId as string | undefined;
42
+
43
+ if (!sessionId || !transports.has(sessionId)) {
44
+ res.status(400).json({ error: "Invalid or missing session ID" });
45
+ return;
46
+ }
47
+
48
+ const transport = transports.get(sessionId)!;
49
+ await transport.handlePostMessage(req, res, req.body);
50
+ });
51
+
52
+ // Start server
53
+ console.error(`Starting MCP SSE server at http://${host}:${port}/sse`);
54
+ app.listen(port, host, () => {
55
+ console.error(`MCP SSE server listening on http://${host}:${port}`);
56
+ });
57
+
58
+ // Keep the process running
59
+ await new Promise(() => {});
60
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Stdio transport for MCP Test Kits
3
+ */
4
+
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import type { Config } from "../config.js";
8
+
9
+ /**
10
+ * Run the MCP server over stdio transport
11
+ */
12
+ export async function runStdioServer(
13
+ server: McpServer,
14
+ _config: Config,
15
+ ): Promise<void> {
16
+ const transport = new StdioServerTransport();
17
+ await server.connect(transport);
18
+
19
+ // Keep the process running
20
+ await new Promise(() => {});
21
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Shared test fixtures for integration tests.
3
+ */
4
+
5
+ import { spawn, type ChildProcess } from "node:child_process";
6
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
8
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
9
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
10
+
11
+ /**
12
+ * Create an MCP client connected via stdio transport.
13
+ */
14
+ export async function createStdioClient(): Promise<{
15
+ client: Client;
16
+ close: () => Promise<void>;
17
+ }> {
18
+ const transport = new StdioClientTransport({
19
+ command: "npx",
20
+ args: ["tsx", "src/index.ts"],
21
+ });
22
+
23
+ const client = new Client({
24
+ name: "test-client",
25
+ version: "1.0.0",
26
+ });
27
+
28
+ await client.connect(transport);
29
+
30
+ return {
31
+ client,
32
+ close: async () => {
33
+ await client.close();
34
+ },
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Start an HTTP server and return the base URL.
40
+ */
41
+ export async function startHttpServer(port: number = 3001): Promise<{
42
+ url: string;
43
+ stop: () => void;
44
+ }> {
45
+ const proc = spawn("npx", ["tsx", "src/index.ts", "--transport", "http", "--port", String(port)], {
46
+ stdio: ["pipe", "pipe", "pipe"],
47
+ });
48
+
49
+ // Wait for server to start
50
+ await new Promise<void>((resolve, reject) => {
51
+ const timeout = setTimeout(() => resolve(), 3000);
52
+ proc.stderr?.on("data", (data: Buffer) => {
53
+ if (data.toString().includes("listening")) {
54
+ clearTimeout(timeout);
55
+ resolve();
56
+ }
57
+ });
58
+ proc.on("error", reject);
59
+ });
60
+
61
+ return {
62
+ url: `http://localhost:${port}`,
63
+ stop: () => {
64
+ proc.kill();
65
+ },
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Start an SSE server and return the base URL.
71
+ */
72
+ export async function startSseServer(port: number = 3002): Promise<{
73
+ url: string;
74
+ stop: () => void;
75
+ }> {
76
+ const proc = spawn("npx", ["tsx", "src/index.ts", "--transport", "sse", "--port", String(port)], {
77
+ stdio: ["pipe", "pipe", "pipe"],
78
+ });
79
+
80
+ // Wait for server to start
81
+ await new Promise<void>((resolve, reject) => {
82
+ const timeout = setTimeout(() => resolve(), 3000);
83
+ proc.stderr?.on("data", (data: Buffer) => {
84
+ if (data.toString().includes("listening")) {
85
+ clearTimeout(timeout);
86
+ resolve();
87
+ }
88
+ });
89
+ proc.on("error", reject);
90
+ });
91
+
92
+ return {
93
+ url: `http://localhost:${port}`,
94
+ stop: () => {
95
+ proc.kill();
96
+ },
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Create an MCP client connected via HTTP transport.
102
+ */
103
+ export async function createHttpClient(serverUrl: string): Promise<{
104
+ client: Client;
105
+ close: () => Promise<void>;
106
+ }> {
107
+ const transport = new StreamableHTTPClientTransport(new URL(`${serverUrl}/mcp`));
108
+
109
+ const client = new Client({
110
+ name: "test-client",
111
+ version: "1.0.0",
112
+ });
113
+
114
+ await client.connect(transport);
115
+
116
+ return {
117
+ client,
118
+ close: async () => {
119
+ await client.close();
120
+ },
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Create an MCP client connected via SSE transport.
126
+ */
127
+ export async function createSseClient(serverUrl: string): Promise<{
128
+ client: Client;
129
+ close: () => Promise<void>;
130
+ }> {
131
+ const transport = new SSEClientTransport(new URL(`${serverUrl}/sse`));
132
+
133
+ const client = new Client({
134
+ name: "test-client",
135
+ version: "1.0.0",
136
+ });
137
+
138
+ await client.connect(transport);
139
+
140
+ return {
141
+ client,
142
+ close: async () => {
143
+ await client.close();
144
+ },
145
+ };
146
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Integration tests via HTTP transport.
3
+ */
4
+
5
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
6
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
+ import { startHttpServer, createHttpClient } from "./fixtures.js";
8
+
9
+ describe("HTTP Transport", () => {
10
+ let serverUrl: string;
11
+ let stopServer: () => void;
12
+ let client: Client;
13
+ let closeClient: () => Promise<void>;
14
+
15
+ beforeAll(async () => {
16
+ const server = await startHttpServer(3001);
17
+ serverUrl = server.url;
18
+ stopServer = server.stop;
19
+
20
+ const conn = await createHttpClient(serverUrl);
21
+ client = conn.client;
22
+ closeClient = conn.close;
23
+ });
24
+
25
+ afterAll(async () => {
26
+ await closeClient();
27
+ stopServer();
28
+ });
29
+
30
+ it("should connect and call echo tool", async () => {
31
+ const result = await client.callTool({ name: "echo", arguments: { message: "test" } });
32
+ expect((result.content as Array<{ type: string; text: string }>)[0].text).toBe("test");
33
+ });
34
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Integration tests via SSE transport - smoke test only.
3
+ */
4
+
5
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
6
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
+ import { startSseServer, createSseClient } from "./fixtures.js";
8
+
9
+ describe("SSE Transport", () => {
10
+ let serverUrl: string;
11
+ let stopServer: () => void;
12
+ let client: Client;
13
+ let closeClient: () => Promise<void>;
14
+
15
+ beforeAll(async () => {
16
+ const server = await startSseServer(3002);
17
+ serverUrl = server.url;
18
+ stopServer = server.stop;
19
+
20
+ const conn = await createSseClient(serverUrl);
21
+ client = conn.client;
22
+ closeClient = conn.close;
23
+ });
24
+
25
+ afterAll(async () => {
26
+ await closeClient();
27
+ stopServer();
28
+ });
29
+
30
+ it("should connect and call echo tool", async () => {
31
+ const result = await client.callTool({ name: "echo", arguments: { message: "test" } });
32
+ expect((result.content as Array<{ type: string; text: string }>)[0].text).toBe("test");
33
+ });
34
+ });
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Integration tests via stdio transport.
3
+ */
4
+
5
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
6
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
+ import { createStdioClient } from "./fixtures.js";
8
+
9
+ describe("Stdio Transport", () => {
10
+ let client: Client;
11
+ let close: () => Promise<void>;
12
+
13
+ beforeAll(async () => {
14
+ const conn = await createStdioClient();
15
+ client = conn.client;
16
+ close = conn.close;
17
+ });
18
+
19
+ afterAll(async () => {
20
+ await close();
21
+ });
22
+
23
+ describe("Tools", () => {
24
+ it("should echo message", async () => {
25
+ const result = await client.callTool({ name: "echo", arguments: { message: "hello" } });
26
+ expect((result.content as Array<{ type: string; text: string }>)[0].text).toBe("hello");
27
+ });
28
+
29
+ it("should add numbers", async () => {
30
+ const result = await client.callTool({ name: "add", arguments: { a: 5, b: 3 } });
31
+ expect(Number((result.content as Array<{ type: string; text: string }>)[0].text)).toBe(8);
32
+ });
33
+
34
+ it("should multiply numbers", async () => {
35
+ const result = await client.callTool({ name: "multiply", arguments: { x: 4, y: 7 } });
36
+ expect(Number((result.content as Array<{ type: string; text: string }>)[0].text)).toBe(28);
37
+ });
38
+
39
+ it("should reverse string", async () => {
40
+ const result = await client.callTool({ name: "reverse_string", arguments: { text: "hello" } });
41
+ expect((result.content as Array<{ type: string; text: string }>)[0].text).toBe("olleh");
42
+ });
43
+
44
+ it("should generate valid UUID", async () => {
45
+ const result = await client.callTool({ name: "generate_uuid", arguments: {} });
46
+ const uuid = (result.content as Array<{ type: string; text: string }>)[0].text;
47
+ // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
48
+ expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
49
+ });
50
+
51
+ it("should get timestamp", async () => {
52
+ const result = await client.callTool({ name: "get_timestamp", arguments: { format: "iso" } });
53
+ const timestamp = (result.content as Array<{ type: string; text: string }>)[0].text;
54
+ expect(timestamp).toContain("T");
55
+ });
56
+
57
+ it("should return error for sample_error tool", async () => {
58
+ const result = await client.callTool({ name: "sample_error", arguments: {} });
59
+ expect(result.isError).toBe(true);
60
+ });
61
+ });
62
+
63
+ describe("Resources", () => {
64
+ it("should list resources", async () => {
65
+ const result = await client.listResources();
66
+ const uris = result.resources.map((r) => r.uri);
67
+ expect(uris).toContain("test://static/greeting");
68
+ });
69
+
70
+ it("should read static greeting", async () => {
71
+ const result = await client.readResource({ uri: "test://static/greeting" });
72
+ expect((result.contents[0] as { text: string }).text).toContain("Hello");
73
+ });
74
+
75
+ it("should read dynamic timestamp", async () => {
76
+ const result = await client.readResource({ uri: "test://dynamic/timestamp" });
77
+ expect((result.contents[0] as { text: string }).text).toContain("T");
78
+ });
79
+ });
80
+
81
+ describe("Prompts", () => {
82
+ it("should list prompts", async () => {
83
+ const result = await client.listPrompts();
84
+ const names = result.prompts.map((p) => p.name);
85
+ expect(names).toContain("simple_prompt");
86
+ });
87
+
88
+ it("should get simple prompt", async () => {
89
+ const result = await client.getPrompt({ name: "simple_prompt" });
90
+ expect(result.messages.length).toBeGreaterThanOrEqual(1);
91
+ });
92
+
93
+ it("should get greeting prompt with argument", async () => {
94
+ const result = await client.getPrompt({ name: "greeting_prompt", arguments: { name: "Alice" } });
95
+ expect((result.messages[0].content as { type: string; text: string }).text).toContain("Alice");
96
+ });
97
+ });
98
+ });