From bf4307c514ec9ec9374081ed4669cbdce9af091c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 18 Nov 2025 03:21:27 +0000 Subject: [PATCH] test(windows): add stdio fixtures --- tests/fixtures/stdio-filesystem-server.mjs | 63 ++++++++++++++++++++++ tests/fixtures/stdio-memory-server.mjs | 58 ++++++++++++++++++++ tests/stdio-servers.integration.test.ts | 34 ++++++------ 3 files changed, 138 insertions(+), 17 deletions(-) create mode 100644 tests/fixtures/stdio-filesystem-server.mjs create mode 100644 tests/fixtures/stdio-memory-server.mjs diff --git a/tests/fixtures/stdio-filesystem-server.mjs b/tests/fixtures/stdio-filesystem-server.mjs new file mode 100644 index 0000000..1c20bac --- /dev/null +++ b/tests/fixtures/stdio-filesystem-server.mjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node + +import fs from 'node:fs/promises'; +import path from 'node:path'; +import process from 'node:process'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { z } from 'zod'; + +const rootDir = path.resolve(process.argv[2] ?? process.cwd()); + +const server = new McpServer({ name: 'fs-fixture', version: '1.0.0' }); + +server.registerTool( + 'list_files', + { + title: 'List Files', + description: 'List the files in the configured root', + inputSchema: {}, + outputSchema: { + files: z.array(z.string()), + }, + }, + async () => { + const entries = await fs.readdir(rootDir); + return { + content: [{ type: 'text', text: entries.join('\n') }], + structuredContent: { files: entries }, + }; + } +); + +server.registerTool( + 'read_text_file', + { + title: 'Read Text File', + description: 'Read a UTF-8 file relative to the MCP root', + inputSchema: { + path: z.string().describe('Relative path inside the root directory'), + }, + outputSchema: { + contents: z.string(), + }, + }, + async ({ path: relativePath }) => { + const targetPath = path.resolve(rootDir, relativePath); + if (!targetPath.startsWith(rootDir)) { + throw new Error('path escapes configured root'); + } + const data = await fs.readFile(targetPath, 'utf8'); + return { + content: [{ type: 'text', text: data }], + structuredContent: { contents: data }, + }; + } +); + +const transport = new StdioServerTransport(); +await server.connect(transport); +await new Promise((resolve, reject) => { + transport.onclose = resolve; + transport.onerror = reject; +}); diff --git a/tests/fixtures/stdio-memory-server.mjs b/tests/fixtures/stdio-memory-server.mjs new file mode 100644 index 0000000..67efaf4 --- /dev/null +++ b/tests/fixtures/stdio-memory-server.mjs @@ -0,0 +1,58 @@ +#!/usr/bin/env node + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { z } from 'zod'; + +const server = new McpServer({ name: 'memory-fixture', version: '1.0.0' }); +const memory = new Set(); + +server.registerTool( + 'create_entities', + { + title: 'Create Entities', + description: 'Insert the provided entity names into the in-memory store', + inputSchema: { + entities: z.array(z.string()), + }, + outputSchema: { + count: z.number(), + }, + }, + async ({ entities }) => { + for (const entity of entities) { + if (entity.trim().length > 0) { + memory.add(entity.trim()); + } + } + return { + content: [{ type: 'text', text: `Stored ${memory.size} entities` }], + structuredContent: { count: memory.size }, + }; + } +); + +server.registerTool( + 'list_entities', + { + title: 'List Entities', + description: 'Return all previously stored entities', + inputSchema: {}, + outputSchema: { + entities: z.array(z.string()), + }, + }, + async () => { + return { + content: [{ type: 'text', text: JSON.stringify(Array.from(memory)) }], + structuredContent: { entities: Array.from(memory) }, + }; + } +); + +const transport = new StdioServerTransport(); +await server.connect(transport); +await new Promise((resolve, reject) => { + transport.onclose = resolve; + transport.onerror = reject; +}); diff --git a/tests/stdio-servers.integration.test.ts b/tests/stdio-servers.integration.test.ts index 2305ee5..0dbfd6a 100644 --- a/tests/stdio-servers.integration.test.ts +++ b/tests/stdio-servers.integration.test.ts @@ -2,6 +2,7 @@ import { execFile } from 'node:child_process'; import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; +import process from 'node:process'; import { fileURLToPath } from 'node:url'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; @@ -48,6 +49,9 @@ describe('stdio MCP servers (filesystem + memory)', () => { let configPath: string; let fsRoot: string; + const filesystemServerScript = fileURLToPath(new URL('./fixtures/stdio-filesystem-server.mjs', import.meta.url)); + const memoryServerScript = fileURLToPath(new URL('./fixtures/stdio-memory-server.mjs', import.meta.url)); + beforeAll(async () => { await ensureDistBuilt(); tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcporter-stdio-e2e-')); @@ -62,13 +66,13 @@ describe('stdio MCP servers (filesystem + memory)', () => { mcpServers: { 'fs-test': { description: 'Filesystem MCP for stdio e2e tests', - command: 'npx', - args: ['-y', '@modelcontextprotocol/server-filesystem', fsRoot], + command: process.execPath, + args: [filesystemServerScript, fsRoot], }, 'memory-test': { description: 'Knowledge graph MCP for stdio e2e tests', - command: 'npx', - args: ['-y', '@modelcontextprotocol/server-memory'], + command: process.execPath, + args: [memoryServerScript], }, }, }, @@ -83,14 +87,12 @@ describe('stdio MCP servers (filesystem + memory)', () => { await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {}); }); - it( - 'lists filesystem tools and reads files via stdio MCP', - async () => { - const listResult = await runCli(['list', 'fs-test'], configPath); - expect(listResult.stdout).toContain('Filesystem MCP for stdio e2e tests'); - const callResult = await runCli( - [ - 'call', + it('lists filesystem tools and reads files via stdio MCP', async () => { + const listResult = await runCli(['list', 'fs-test'], configPath); + expect(listResult.stdout).toContain('Filesystem MCP for stdio e2e tests'); + const callResult = await runCli( + [ + 'call', 'fs-test.read_text_file', '--output', 'json', @@ -98,11 +100,9 @@ describe('stdio MCP servers (filesystem + memory)', () => { JSON.stringify({ path: path.join(fsRoot, 'hello.txt') }), ], configPath - ); - expect(callResult.stdout).toContain('hello from stdio mcp'); - }, - 20000 - ); + ); + expect(callResult.stdout).toContain('hello from stdio mcp'); + }, 20000); const memoryTest = process.platform === 'win32' ? it.skip : it;