Skip to content

Commit 33e5b0c

Browse files
fix(portable): reject drive-relative Windows commands such as C:server
win32.isAbsolute reports them relative, but they resolve against a per-drive working directory on a Windows consumer; treat any nonempty win32 root as rooted.
1 parent 0ec9194 commit 33e5b0c

3 files changed

Lines changed: 9 additions & 2 deletions

File tree

‎packages/agent-bundle/src/adapters/portable-mcp-rules.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ export const containedPortableRelativePath = (relativePath: string): string | un
5858

5959
const staysInsidePosixRoot = (relativePath: string): boolean => containedPortableRelativePath(relativePath) !== undefined;
6060

61-
const isAnyPlatformAbsolute = (value: string): boolean => posix.isAbsolute(value) || win32.isAbsolute(value);
61+
// A nonempty win32 root also catches drive-relative forms such as `C:server`,
62+
// which `win32.isAbsolute` reports as relative yet resolve against a per-drive cwd.
63+
const isAnyPlatformRooted = (value: string): boolean =>
64+
posix.isAbsolute(value) || win32.isAbsolute(value) || win32.parse(value).root.length > 0;
6265

6366
/** §7.2.1: a stdio command is a bare executable name or a plugin-relative `./` path. */
6467
export const portableCommandIssues = (command: unknown): readonly PortableMcpRuleIssue[] => {
@@ -75,7 +78,7 @@ export const portableCommandIssues = (command: unknown): readonly PortableMcpRul
7578
}
7679
return Object.freeze([]);
7780
}
78-
if (command.length === 0 || /[\s/\\\0]/u.test(command) || isAnyPlatformAbsolute(command) || command.startsWith('.')) {
81+
if (command.length === 0 || /[\s/\\\0]/u.test(command) || isAnyPlatformRooted(command) || command.startsWith('.')) {
7982
return Object.freeze([issue(
8083
'command',
8184
`${JSON.stringify(command)} is neither a bare executable name nor a plugin-relative ./ path (Agent Plugins 1.0.0 §7.2.1)`,

‎packages/agent-bundle/tests/portable-adapter.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@ it('fails closed at plan time on Agent Plugins 1.0.0 normative MCP rules the sch
477477
transport: 'stdio',
478478
},
479479
{ command: 'C:\\tools\\server.exe', id: 'mcp:win-abs', name: 'windows-absolute', provenance, targets: ['portable'], transport: 'stdio' },
480+
{ command: 'C:server', id: 'mcp:win-drive', name: 'windows-drive-relative', provenance, targets: ['portable'], transport: 'stdio' },
480481
{
481482
command: 'node',
482483
env: { '${PLUGIN_ROOT}': 'literal' },
@@ -509,6 +510,7 @@ it('fails closed at plan time on Agent Plugins 1.0.0 normative MCP rules the sch
509510
['portable.mcp.command.standard', 'Portable MCP server "anchor-collision" command "./../anchor/server" escapes the plugin root (Agent Plugins 1.0.0 §4.1).'],
510511
['portable.mcp.cwd.standard', 'Portable MCP server "anchor-cwd" cwd "${PLUGIN_ROOT}/../anchor" escapes its plugin root after resolution (Agent Plugins 1.0.0 §7.2.1).'],
511512
['portable.mcp.command.standard', 'Portable MCP server "windows-absolute" command "C:\\\\tools\\\\server.exe" is neither a bare executable name nor a plugin-relative ./ path (Agent Plugins 1.0.0 §7.2.1).'],
513+
['portable.mcp.command.standard', 'Portable MCP server "windows-drive-relative" command "C:server" is neither a bare executable name nor a plugin-relative ./ path (Agent Plugins 1.0.0 §7.2.1).'],
512514
['portable.mcp.env.standard', 'Portable MCP server "placeholder-env-key" env key "${PLUGIN_ROOT}" contains an Agent Plugins placeholder, but expansion never applies to env keys (Agent Plugins 1.0.0 §9.2).'],
513515
['portable.mcp.url.standard', 'Portable MCP server "plain-http" url uses plain HTTP against non-loopback host "mcp.example.test"; non-loopback endpoints must use HTTPS (Agent Plugins 1.0.0 §7.2.1).'],
514516
['portable.mcp.headers.standard', 'Portable MCP server "bad-headers" headers/x-tenant repeats header "X-Tenant" under different casing; header names are case-insensitive (Agent Plugins 1.0.0 §7.2.1).'],

‎packages/agent-bundle/tests/portable-plugin-validation.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ it('applies the normative text where the schemas are silent: commands, cwd, URLs
159159
type: 'streamable-http',
160160
url: 'https://mcp.example.test/mcp',
161161
},
162+
driveRelativeCommand: { command: 'C:server', type: 'stdio' },
162163
missingBundled: { command: './bin/absent', type: 'stdio' },
163164
pathCommand: { command: 'bin/server', type: 'stdio' },
164165
placeholderCommand: { command: '${PLUGIN_ROOT}/bin/launch', type: 'stdio' },
@@ -184,6 +185,7 @@ it('applies the normative text where the schemas are silent: commands, cwd, URLs
184185
'mcp.json/mcpServers/headers/headers/Bad Header is not a valid HTTP header field name (Agent Plugins 1.0.0 §7.2.1).',
185186
'mcp.json/mcpServers/headers/headers/x-tenant repeats header "X-Tenant" under different casing; header names are case-insensitive (Agent Plugins 1.0.0 §7.2.1).',
186187
'mcp.json/mcpServers/headers/headers/X-Token contains an Agent Plugins placeholder, but clients never expand placeholders in headers (Agent Plugins 1.0.0 §7.2.1).',
188+
'mcp.json/mcpServers/driveRelativeCommand/command "C:server" is neither a bare executable name nor a plugin-relative ./ path (Agent Plugins 1.0.0 §7.2.1).',
187189
'mcp.json/mcpServers/missingBundled/command "./bin/absent" does not resolve to a bundled regular file (Agent Plugins 1.0.0 §7.2.1).',
188190
'mcp.json/mcpServers/pathCommand/command "bin/server" is neither a bare executable name nor a plugin-relative ./ path (Agent Plugins 1.0.0 §7.2.1).',
189191
'mcp.json/mcpServers/placeholderCommand/command contains an Agent Plugins placeholder, but clients never expand placeholders in command (Agent Plugins 1.0.0 §7.2.1).',

0 commit comments

Comments
 (0)