Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion packages/mcp-server/src/code-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,27 @@ export function codeTool({
return { metadata, tool, handler };
}

/**
* Grants the Deno subprocess net access to its own control socket, and nothing else.
*
* `@valtown/deno-http-worker` serves its control channel with `Deno.serve({ path })` over a Unix
* socket, and since Deno 2.9 binding one needs net access on top of read/write access:
*
* NotCapable: Requires net access to "unix:/.../<uuid>-deno-http.sock"
*
* The library generates that path itself and never exposes it, so recover it from the argv the
* library built: it is the only bare (non-flag) argument ending in the socket suffix. Deno's
* allowlist matches a Unix socket by full path only — a directory prefix is not accepted — so this
* is as tightly as the permission can be scoped.
*/
function withControlSocketNetAccess(args: string[]): string[] {
const socketPath = args.find((arg) => !arg.startsWith('-') && arg.endsWith('-deno-http.sock'));
if (socketPath === undefined) {
return args;
}
return args.map((arg) => (arg.startsWith('--allow-net=') ? `${arg},unix:${socketPath}` : arg));
}

const localDenoHandler = async ({
reqContext,
args,
Expand All @@ -146,7 +167,7 @@ const localDenoHandler = async ({
const packageNodeModulesPath = path.resolve(packageRoot, 'node_modules');

// Check if deno is in PATH
const { execSync } = await import('node:child_process');
const { execSync, spawn } = await import('node:child_process');
try {
execSync('command -v deno', { stdio: 'ignore' });
denoPath = 'deno';
Expand Down Expand Up @@ -195,6 +216,9 @@ const localDenoHandler = async ({
'--allow-env',
],
printOutput: true,
// The worker library appends its own permission flags for the control socket, so patch the
// final argv rather than the flags above.
spawnFunc: (command, args, options) => spawn(command, withControlSocketNetAccess(args), options),
spawnOptions: {
cwd: path.dirname(workerPath),
// Merge any upstream client envs into the Deno subprocess environment,
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export function parseCLIOptions(): CLIOptions {
description: 'What transport to use; stdio for local servers or http for remote servers',
})
.env('MCP_SERVER')
.version(true)
.version()
.help();

const argv = opts.parseSync();
Expand Down