Skip to content

Commit 95c803a

Browse files
fix: resolve symlink in isDirectRun check for npx compatibility
The server failed to start when invoked via npx because npm creates a symlink at .bin/validkit-mcp → dist/index.js. path.resolve() does not follow symlinks, so process.argv[1] (symlink path) never matched fileURLToPath(import.meta.url) (real path). Switch to realpathSync() which resolves symlinks. Add integration tests that spawn the server via direct node and via symlink, verifying it responds to MCP initialize in both cases. Bump version to 1.1.1.
1 parent 666f982 commit 95c803a

3 files changed

Lines changed: 77 additions & 5 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@validkit/mcp-server",
3-
"version": "1.1.0",
3+
"version": "1.1.1",
44
"description": "Model Context Protocol (MCP) server for ValidKit email validation - works with Claude Code, Cursor, Windsurf, and any MCP-compatible AI assistant",
55
"main": "dist/index.js",
66
"type": "module",

src/index.test.ts

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ describe('ValidKit MCP Server', () => {
485485
expect.objectContaining({
486486
headers: expect.objectContaining({
487487
'X-API-Key': 'vk_test_mykey',
488-
'User-Agent': 'validkit-mcp/1.1.0',
488+
'User-Agent': 'validkit-mcp/1.1.1',
489489
'Content-Type': 'application/json',
490490
}),
491491
})
@@ -727,4 +727,75 @@ describe('ValidKit MCP Server', () => {
727727
);
728728
});
729729
});
730+
731+
describe('integration: entrypoint', () => {
732+
const MCP_INIT = JSON.stringify({
733+
jsonrpc: '2.0',
734+
id: 1,
735+
method: 'initialize',
736+
params: {
737+
protocolVersion: '2024-11-05',
738+
capabilities: {},
739+
clientInfo: { name: 'test', version: '1.0.0' },
740+
},
741+
});
742+
743+
async function spawnAndSend(entrypoint: string): Promise<string> {
744+
const { spawn } = await import('node:child_process');
745+
return new Promise((resolve, reject) => {
746+
const child = spawn('node', [entrypoint], {
747+
env: { ...process.env, VALIDKIT_API_KEY: 'vk_test_integration' },
748+
stdio: ['pipe', 'pipe', 'pipe'],
749+
});
750+
751+
let stdout = '';
752+
child.stdout.on('data', (chunk: Buffer) => {
753+
stdout += chunk.toString();
754+
// Got a response — kill the server
755+
child.kill();
756+
});
757+
758+
child.on('close', () => resolve(stdout));
759+
child.on('error', reject);
760+
761+
const timer = setTimeout(() => {
762+
child.kill();
763+
reject(new Error('Timed out'));
764+
}, 10_000);
765+
child.on('close', () => clearTimeout(timer));
766+
767+
child.stdin.write(MCP_INIT + '\n');
768+
child.stdin.end();
769+
});
770+
}
771+
772+
it('starts and responds to MCP initialize when invoked directly', async () => {
773+
const { join } = await import('node:path');
774+
const distIndex = join(process.cwd(), 'dist', 'index.js');
775+
776+
const output = await spawnAndSend(distIndex);
777+
const response = JSON.parse(output.trim());
778+
expect(response.result.serverInfo.name).toBe('validkit');
779+
expect(response.result.serverInfo.version).toBe('1.1.1');
780+
});
781+
782+
it('starts and responds when invoked via symlink (npx simulation)', async () => {
783+
const { join } = await import('node:path');
784+
const { symlinkSync, unlinkSync } = await import('node:fs');
785+
const { tmpdir } = await import('node:os');
786+
787+
const distIndex = join(process.cwd(), 'dist', 'index.js');
788+
const symlink = join(tmpdir(), `validkit-mcp-test-${Date.now()}`);
789+
symlinkSync(distIndex, symlink);
790+
791+
try {
792+
const output = await spawnAndSend(symlink);
793+
const response = JSON.parse(output.trim());
794+
expect(response.result.serverInfo.name).toBe('validkit');
795+
expect(response.result.serverInfo.version).toBe('1.1.1');
796+
} finally {
797+
unlinkSync(symlink);
798+
}
799+
});
800+
});
730801
});

src/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#!/usr/bin/env node
2-
import { resolve } from 'node:path';
2+
import { realpathSync } from 'node:fs';
33
import { fileURLToPath } from 'node:url';
44
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
55
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
66
import { z } from 'zod';
77

8-
export const VERSION = '1.1.0';
8+
export const VERSION = '1.1.1';
99
const REQUEST_TIMEOUT_MS = 30_000;
1010

1111
export function formatCatchError(error: unknown, prefix: string): string {
@@ -280,7 +280,8 @@ async function main() {
280280
}
281281

282282
const currentFile = fileURLToPath(import.meta.url);
283-
const isDirectRun = process.argv[1] && resolve(process.argv[1]) === currentFile;
283+
const isDirectRun =
284+
process.argv[1] && realpathSync(process.argv[1]) === currentFile;
284285

285286
if (isDirectRun) {
286287
main().catch((error) => {

0 commit comments

Comments
 (0)