fix: preserve spaced stdio paths
This commit is contained in:
parent
7d345bc7db
commit
23d3f9ef8d
@ -2,7 +2,11 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Nothing yet.
|
||||
### Config
|
||||
|
||||
- Preserve existing stdio executable paths that contain spaces instead of
|
||||
splitting them as inline command strings, so app bundle helpers like Hopper's
|
||||
MCP server can be configured directly.
|
||||
|
||||
## [0.10.1] - 2026-05-04
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import type { CommandSpec, RawEntry, ServerDefinition, ServerLoggingOptions, ServerSource } from './config-schema.js';
|
||||
import { expandHome } from './env.js';
|
||||
@ -27,7 +28,7 @@ export function normalizeServerEntry(
|
||||
const headers = buildHeaders(raw);
|
||||
|
||||
const httpUrl = getUrl(raw);
|
||||
const stdio = getCommand(raw);
|
||||
const stdio = getCommand(raw, baseDir);
|
||||
|
||||
let command: CommandSpec;
|
||||
|
||||
@ -114,7 +115,7 @@ function getUrl(raw: RawEntry): string | undefined {
|
||||
return raw.baseUrl ?? raw.base_url ?? raw.url ?? raw.serverUrl ?? raw.server_url ?? undefined;
|
||||
}
|
||||
|
||||
function getCommand(raw: RawEntry): { command: string; args: string[] } | undefined {
|
||||
function getCommand(raw: RawEntry, baseDir: string): { command: string; args: string[] } | undefined {
|
||||
const commandValue = raw.command ?? raw.executable;
|
||||
if (Array.isArray(commandValue)) {
|
||||
if (commandValue.length === 0 || typeof commandValue[0] !== 'string') {
|
||||
@ -127,6 +128,9 @@ function getCommand(raw: RawEntry): { command: string; args: string[] } | undefi
|
||||
if (args.length > 0) {
|
||||
return { command: commandValue, args };
|
||||
}
|
||||
if (isExistingCommandPath(commandValue, baseDir)) {
|
||||
return { command: commandValue, args: [] };
|
||||
}
|
||||
const tokens = parseCommandString(commandValue);
|
||||
if (tokens.length === 0) {
|
||||
return undefined;
|
||||
@ -140,6 +144,27 @@ function getCommand(raw: RawEntry): { command: string; args: string[] } | undefi
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isExistingCommandPath(value: string, baseDir: string): boolean {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed.includes(' ')) {
|
||||
return false;
|
||||
}
|
||||
if (!looksLikePath(trimmed)) {
|
||||
return false;
|
||||
}
|
||||
const expanded = expandHome(trimmed);
|
||||
const resolved = path.isAbsolute(expanded) ? expanded : path.resolve(baseDir, expanded);
|
||||
try {
|
||||
return fs.statSync(resolved).isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function looksLikePath(value: string): boolean {
|
||||
return value.startsWith('/') || value.startsWith('./') || value.startsWith('../') || value.startsWith('~/');
|
||||
}
|
||||
|
||||
function buildHeaders(raw: RawEntry): Record<string, string> | undefined {
|
||||
const headers: Record<string, string> = {};
|
||||
|
||||
|
||||
@ -93,4 +93,44 @@ describe('command string parsing', () => {
|
||||
path: configPath,
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves existing executable paths that contain spaces', async () => {
|
||||
tmpDir = await fs.mkdtemp(TMP_PREFIX);
|
||||
const binDir = path.join(tmpDir, 'Application Bundle.app', 'Contents', 'MacOS');
|
||||
await fs.mkdir(binDir, { recursive: true });
|
||||
const executable = path.join(binDir, 'HopperMCPServer');
|
||||
await fs.writeFile(executable, '#!/bin/sh\nexit 0\n');
|
||||
await fs.chmod(executable, 0o755);
|
||||
|
||||
const configDir = path.join(tmpDir, 'config');
|
||||
await fs.mkdir(configDir, { recursive: true });
|
||||
const configPath = path.join(configDir, 'mcporter.json');
|
||||
await fs.writeFile(
|
||||
configPath,
|
||||
JSON.stringify({
|
||||
mcpServers: {
|
||||
hopper: {
|
||||
command: executable,
|
||||
},
|
||||
},
|
||||
imports: [],
|
||||
})
|
||||
);
|
||||
|
||||
const servers = await loadServerDefinitions({
|
||||
configPath,
|
||||
rootDir: tmpDir,
|
||||
});
|
||||
|
||||
const server = servers[0];
|
||||
if (!server) {
|
||||
throw new Error('expected server definition');
|
||||
}
|
||||
expect(server.command.kind).toBe('stdio');
|
||||
if (server.command.kind !== 'stdio') {
|
||||
throw new Error('expected stdio command');
|
||||
}
|
||||
expect(server.command.command).toBe(executable);
|
||||
expect(server.command.args).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user