137 lines
4.2 KiB
TypeScript
137 lines
4.2 KiB
TypeScript
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';
|
|
|
|
const CLI_ENTRY = fileURLToPath(new URL('../dist/cli.js', import.meta.url));
|
|
|
|
async function ensureDistBuilt(): Promise<void> {
|
|
try {
|
|
await fs.access(CLI_ENTRY);
|
|
} catch {
|
|
throw new Error('dist/cli.js is missing; run `pnpm build` before invoking this integration test directly.');
|
|
}
|
|
}
|
|
|
|
async function runCli(args: string[], configPath: string): Promise<{ stdout: string; stderr: string }> {
|
|
return await new Promise((resolve, reject) => {
|
|
execFile(
|
|
process.execPath,
|
|
[CLI_ENTRY, '--config', configPath, ...args],
|
|
{
|
|
env: { ...process.env, MCPORTER_NO_FORCE_EXIT: '1' },
|
|
},
|
|
(error, stdout, stderr) => {
|
|
if (error) {
|
|
const wrapped = new Error(`${error.message}\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`);
|
|
reject(wrapped);
|
|
return;
|
|
}
|
|
resolve({ stdout, stderr });
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
describe('stdio MCP servers (filesystem + memory)', () => {
|
|
let tempDir: string;
|
|
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-'));
|
|
fsRoot = path.join(tempDir, 'fs-root');
|
|
await fs.mkdir(fsRoot, { recursive: true });
|
|
await fs.writeFile(path.join(fsRoot, 'hello.txt'), 'hello from stdio mcp\n', 'utf8');
|
|
configPath = path.join(tempDir, 'stdio.config.json');
|
|
await fs.writeFile(
|
|
configPath,
|
|
JSON.stringify(
|
|
{
|
|
mcpServers: {
|
|
'fs-test': {
|
|
description: 'Filesystem MCP for stdio e2e tests',
|
|
command: process.execPath,
|
|
args: [filesystemServerScript, fsRoot],
|
|
},
|
|
'memory-test': {
|
|
description: 'Knowledge graph MCP for stdio e2e tests',
|
|
command: process.execPath,
|
|
args: [memoryServerScript],
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
2
|
|
),
|
|
'utf8'
|
|
);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
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',
|
|
'fs-test.read_text_file',
|
|
'--output',
|
|
'json',
|
|
'--args',
|
|
JSON.stringify({ path: path.join(fsRoot, 'hello.txt') }),
|
|
],
|
|
configPath
|
|
);
|
|
expect(callResult.stdout).toContain('hello from stdio mcp');
|
|
}, 20000);
|
|
|
|
const memoryTest = process.platform === 'win32' ? it.skip : it;
|
|
|
|
memoryTest(
|
|
'creates entities with the memory stdio MCP server',
|
|
async () => {
|
|
const callResult = await runCli(
|
|
[
|
|
'call',
|
|
'memory-test.create_entities',
|
|
'--output',
|
|
'json',
|
|
'--args',
|
|
JSON.stringify({ entities: ['alpha', 'beta'] }),
|
|
],
|
|
configPath
|
|
);
|
|
expect(callResult.stderr).toBe('');
|
|
expect(callResult.stdout).not.toContain('Error');
|
|
},
|
|
20000
|
|
);
|
|
|
|
memoryTest(
|
|
'passes multiline @path argument values unchanged to a stdio MCP server',
|
|
async () => {
|
|
const payloadPath = path.join(tempDir, 'multiline.txt');
|
|
const payload = 'first line\nsecond line\n';
|
|
await fs.writeFile(payloadPath, payload, 'utf8');
|
|
const callResult = await runCli(
|
|
['call', 'memory-test.echo_text', '--output', 'json', `text=@${payloadPath}`],
|
|
configPath
|
|
);
|
|
expect(callResult.stderr).toBe('');
|
|
expect(JSON.parse(callResult.stdout)).toMatchObject({ text: payload });
|
|
},
|
|
20000
|
|
);
|
|
});
|