fix: support Windows agent command shims

This commit is contained in:
2026-08-20 17:34:30 +08:00
parent 4387cedf76
commit 7416dfea1a
8 changed files with 43 additions and 16 deletions
Vendored
+5 -5
View File
@@ -5,7 +5,6 @@ import { fileURLToPath } from "node:url";
import { Command } from "commander";
// src/agents.ts
import { spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { constants } from "node:fs";
import { copyFile, cp, mkdir as mkdir2, readFile as readFile2, readdir, rename as rename2, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
@@ -13,6 +12,7 @@ import os2 from "node:os";
import path2 from "node:path";
import { createInterface } from "node:readline/promises";
import { applyEdits, modify, parse } from "jsonc-parser";
import spawn from "cross-spawn";
// src/config.ts
import { createHash } from "node:crypto";
@@ -397,10 +397,10 @@ var SpawnCommandRunner = class {
const child = spawn(command, args, { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8").on("data", (chunk) => {
child.stdout?.setEncoding("utf8").on("data", (chunk) => {
stdout += String(chunk);
});
child.stderr.setEncoding("utf8").on("data", (chunk) => {
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
stderr += String(chunk);
});
child.once("error", (error) => reject(new Error(`Could not run ${command}: ${errorMessage(error)}`)));
@@ -737,7 +737,7 @@ async function serveBridge(options, tokenStore, discoveryStore, serviceToken = p
}
}
function createProxyServer(upstream) {
const server = new Server({ name: "crafttable-mcp-stdio-bridge", version: "0.1.2" }, {
const server = new Server({ name: "crafttable-mcp-stdio-bridge", version: "0.1.3" }, {
capabilities: {
tools: {},
resources: {}
@@ -880,7 +880,7 @@ function keyringError(error) {
}
// src/cli.ts
var program = new Command().name("crafttable-mcp").description("OAuth client and stdio bridge for CraftTable MCP").version("0.1.2");
var program = new Command().name("crafttable-mcp").description("OAuth client and stdio bridge for CraftTable MCP").version("0.1.3");
withConnection(program.command("login").description("Log in through the system browser")).option("--no-browser", "print the authorization URL instead of opening it").option("--timeout <milliseconds>", "OAuth callback timeout", "600000").action(async (flags) => {
const options = connectionOptions(flags);
const timeoutMs = positiveInteger(flags.timeout, "OAuth callback timeout");
+2 -2
View File
File diff suppressed because one or more lines are too long
+14 -2
View File
@@ -1,17 +1,18 @@
{
"name": "@game-crafttable/mcp-client",
"version": "0.1.2",
"version": "0.1.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@game-crafttable/mcp-client",
"version": "0.1.2",
"version": "0.1.3",
"license": "UNLICENSED",
"dependencies": {
"@modelcontextprotocol/sdk": "latest",
"@napi-rs/keyring": "^1.3.0",
"commander": "^14.0.0",
"cross-spawn": "^7.0.6",
"jsonc-parser": "^3.3.1",
"open": "^10.2.0"
},
@@ -20,6 +21,7 @@
"crafttable-mcp-client": "dist/cli.js"
},
"devDependencies": {
"@types/cross-spawn": "^6.0.6",
"@types/node": "latest",
"esbuild": "latest",
"tsx": "latest",
@@ -757,6 +759,16 @@
"node": ">= 10"
}
},
"node_modules/@types/cross-spawn": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz",
"integrity": "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "26.2.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@game-crafttable/mcp-client",
"version": "0.1.2",
"version": "0.1.3",
"description": "OAuth CLI and stdio bridge for the CraftTable MCP server",
"repository": {
"type": "git",
@@ -29,11 +29,13 @@
"@modelcontextprotocol/sdk": "latest",
"@napi-rs/keyring": "^1.3.0",
"commander": "^14.0.0",
"cross-spawn": "^7.0.6",
"jsonc-parser": "^3.3.1",
"open": "^10.2.0"
},
"devDependencies": {
"@types/node": "latest",
"@types/cross-spawn": "^6.0.6",
"esbuild": "latest",
"tsx": "latest",
"typescript": "latest"
+3 -3
View File
@@ -1,4 +1,3 @@
import { spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { constants } from "node:fs";
import { copyFile, cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
@@ -6,6 +5,7 @@ import os from "node:os";
import path from "node:path";
import { createInterface } from "node:readline/promises";
import { applyEdits, modify, parse } from "jsonc-parser";
import spawn from "cross-spawn";
import type { ClientOptions } from "./config.js";
import { errorMessage } from "./config.js";
@@ -383,8 +383,8 @@ export class SpawnCommandRunner implements CommandRunner {
const child = spawn(command, args, { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8").on("data", (chunk) => { stdout += String(chunk); });
child.stderr.setEncoding("utf8").on("data", (chunk) => { stderr += String(chunk); });
child.stdout?.setEncoding("utf8").on("data", (chunk) => { stdout += String(chunk); });
child.stderr?.setEncoding("utf8").on("data", (chunk) => { stderr += String(chunk); });
child.once("error", (error) => reject(new Error(`Could not run ${command}: ${errorMessage(error)}`)));
child.once("close", (code) => resolve({ code: code ?? 1, stdout, stderr }));
});
+1 -1
View File
@@ -45,7 +45,7 @@ export function createProxyServer(upstream: {
listResourceTemplates: (params?: { cursor?: string }) => Promise<unknown>;
readResource: (params: { uri: string }) => Promise<unknown>;
}): Server {
const server = new Server({ name: "crafttable-mcp-stdio-bridge", version: "0.1.2" }, {
const server = new Server({ name: "crafttable-mcp-stdio-bridge", version: "0.1.3" }, {
capabilities: {
tools: {},
resources: {},
+1 -1
View File
@@ -13,7 +13,7 @@ type ConnectionFlags = { url?: string; clientId?: string; callbackPort?: string
const program = new Command()
.name("crafttable-mcp")
.description("OAuth client and stdio bridge for CraftTable MCP")
.version("0.1.2");
.version("0.1.3");
withConnection(program.command("login").description("Log in through the system browser"))
.option("--no-browser", "print the authorization URL instead of opening it")
+14 -1
View File
@@ -4,7 +4,7 @@ import os from "node:os";
import path from "node:path";
import test from "node:test";
import { parse } from "jsonc-parser";
import { configureAgents, type CommandResult, type CommandRunner, defaultSkillPath, launchCommand, unconfigureAgents } from "../src/agents.js";
import { configureAgents, type CommandResult, type CommandRunner, defaultSkillPath, launchCommand, SpawnCommandRunner, unconfigureAgents } from "../src/agents.js";
import { resolveClientOptions } from "../src/config.js";
class FakeRunner implements CommandRunner {
@@ -45,6 +45,19 @@ test("Agent Skill paths use each client's user-level discovery directory", () =>
assert.equal(defaultSkillPath("opencode", env), path.join("C:\\Config", "opencode", "skills", "crafttable"));
});
test("command runner launches Windows command shims", { skip: process.platform !== "win32" }, async () => {
const directory = await mkdtemp(path.join(os.tmpdir(), "crafttable-mcp-shim-test-"));
const shim = path.join(directory, "fixture.cmd");
await writeFile(shim, "@echo off\r\necho shim:%1\r\n", "utf8");
try {
const result = await new SpawnCommandRunner().run(shim, ["ok"]);
assert.equal(result.code, 0);
assert.match(result.stdout, /shim:"?ok"?/);
} finally {
await rm(directory, { recursive: true, force: true });
}
});
test("Codex and Claude adapters add, detect idempotency, replace conflicts, remove, and dry-run", async () => {
const directory = await mkdtemp(path.join(os.tmpdir(), "crafttable-mcp-cli-agent-test-"));
const skillSource = await createSkillFixture(directory);