fix: support Windows agent command shims
This commit is contained in:
Vendored
+5
-5
@@ -5,7 +5,6 @@ import { fileURLToPath } from "node:url";
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
|
|
||||||
// src/agents.ts
|
// src/agents.ts
|
||||||
import { spawn } from "node:child_process";
|
|
||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import { constants } from "node:fs";
|
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";
|
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 path2 from "node:path";
|
||||||
import { createInterface } from "node:readline/promises";
|
import { createInterface } from "node:readline/promises";
|
||||||
import { applyEdits, modify, parse } from "jsonc-parser";
|
import { applyEdits, modify, parse } from "jsonc-parser";
|
||||||
|
import spawn from "cross-spawn";
|
||||||
|
|
||||||
// src/config.ts
|
// src/config.ts
|
||||||
import { createHash } from "node:crypto";
|
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"] });
|
const child = spawn(command, args, { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
||||||
let stdout = "";
|
let stdout = "";
|
||||||
let stderr = "";
|
let stderr = "";
|
||||||
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
child.stdout?.setEncoding("utf8").on("data", (chunk) => {
|
||||||
stdout += String(chunk);
|
stdout += String(chunk);
|
||||||
});
|
});
|
||||||
child.stderr.setEncoding("utf8").on("data", (chunk) => {
|
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
||||||
stderr += String(chunk);
|
stderr += String(chunk);
|
||||||
});
|
});
|
||||||
child.once("error", (error) => reject(new Error(`Could not run ${command}: ${errorMessage(error)}`)));
|
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) {
|
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: {
|
capabilities: {
|
||||||
tools: {},
|
tools: {},
|
||||||
resources: {}
|
resources: {}
|
||||||
@@ -880,7 +880,7 @@ function keyringError(error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// src/cli.ts
|
// 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) => {
|
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 options = connectionOptions(flags);
|
||||||
const timeoutMs = positiveInteger(flags.timeout, "OAuth callback timeout");
|
const timeoutMs = positiveInteger(flags.timeout, "OAuth callback timeout");
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Generated
+14
-2
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "@game-crafttable/mcp-client",
|
"name": "@game-crafttable/mcp-client",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@game-crafttable/mcp-client",
|
"name": "@game-crafttable/mcp-client",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "latest",
|
"@modelcontextprotocol/sdk": "latest",
|
||||||
"@napi-rs/keyring": "^1.3.0",
|
"@napi-rs/keyring": "^1.3.0",
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
|
"cross-spawn": "^7.0.6",
|
||||||
"jsonc-parser": "^3.3.1",
|
"jsonc-parser": "^3.3.1",
|
||||||
"open": "^10.2.0"
|
"open": "^10.2.0"
|
||||||
},
|
},
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"crafttable-mcp-client": "dist/cli.js"
|
"crafttable-mcp-client": "dist/cli.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/cross-spawn": "^6.0.6",
|
||||||
"@types/node": "latest",
|
"@types/node": "latest",
|
||||||
"esbuild": "latest",
|
"esbuild": "latest",
|
||||||
"tsx": "latest",
|
"tsx": "latest",
|
||||||
@@ -757,6 +759,16 @@
|
|||||||
"node": ">= 10"
|
"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": {
|
"node_modules/@types/node": {
|
||||||
"version": "26.2.0",
|
"version": "26.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@game-crafttable/mcp-client",
|
"name": "@game-crafttable/mcp-client",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"description": "OAuth CLI and stdio bridge for the CraftTable MCP server",
|
"description": "OAuth CLI and stdio bridge for the CraftTable MCP server",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -29,11 +29,13 @@
|
|||||||
"@modelcontextprotocol/sdk": "latest",
|
"@modelcontextprotocol/sdk": "latest",
|
||||||
"@napi-rs/keyring": "^1.3.0",
|
"@napi-rs/keyring": "^1.3.0",
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
|
"cross-spawn": "^7.0.6",
|
||||||
"jsonc-parser": "^3.3.1",
|
"jsonc-parser": "^3.3.1",
|
||||||
"open": "^10.2.0"
|
"open": "^10.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "latest",
|
"@types/node": "latest",
|
||||||
|
"@types/cross-spawn": "^6.0.6",
|
||||||
"esbuild": "latest",
|
"esbuild": "latest",
|
||||||
"tsx": "latest",
|
"tsx": "latest",
|
||||||
"typescript": "latest"
|
"typescript": "latest"
|
||||||
|
|||||||
+3
-3
@@ -1,4 +1,3 @@
|
|||||||
import { spawn } from "node:child_process";
|
|
||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import { constants } from "node:fs";
|
import { constants } from "node:fs";
|
||||||
import { copyFile, cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
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 path from "node:path";
|
||||||
import { createInterface } from "node:readline/promises";
|
import { createInterface } from "node:readline/promises";
|
||||||
import { applyEdits, modify, parse } from "jsonc-parser";
|
import { applyEdits, modify, parse } from "jsonc-parser";
|
||||||
|
import spawn from "cross-spawn";
|
||||||
import type { ClientOptions } from "./config.js";
|
import type { ClientOptions } from "./config.js";
|
||||||
import { errorMessage } 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"] });
|
const child = spawn(command, args, { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
||||||
let stdout = "";
|
let stdout = "";
|
||||||
let stderr = "";
|
let stderr = "";
|
||||||
child.stdout.setEncoding("utf8").on("data", (chunk) => { stdout += String(chunk); });
|
child.stdout?.setEncoding("utf8").on("data", (chunk) => { stdout += String(chunk); });
|
||||||
child.stderr.setEncoding("utf8").on("data", (chunk) => { stderr += 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("error", (error) => reject(new Error(`Could not run ${command}: ${errorMessage(error)}`)));
|
||||||
child.once("close", (code) => resolve({ code: code ?? 1, stdout, stderr }));
|
child.once("close", (code) => resolve({ code: code ?? 1, stdout, stderr }));
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ export function createProxyServer(upstream: {
|
|||||||
listResourceTemplates: (params?: { cursor?: string }) => Promise<unknown>;
|
listResourceTemplates: (params?: { cursor?: string }) => Promise<unknown>;
|
||||||
readResource: (params: { uri: string }) => Promise<unknown>;
|
readResource: (params: { uri: string }) => Promise<unknown>;
|
||||||
}): Server {
|
}): 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: {
|
capabilities: {
|
||||||
tools: {},
|
tools: {},
|
||||||
resources: {},
|
resources: {},
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ type ConnectionFlags = { url?: string; clientId?: string; callbackPort?: string
|
|||||||
const program = new Command()
|
const program = new Command()
|
||||||
.name("crafttable-mcp")
|
.name("crafttable-mcp")
|
||||||
.description("OAuth client and stdio bridge for 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"))
|
withConnection(program.command("login").description("Log in through the system browser"))
|
||||||
.option("--no-browser", "print the authorization URL instead of opening it")
|
.option("--no-browser", "print the authorization URL instead of opening it")
|
||||||
|
|||||||
+14
-1
@@ -4,7 +4,7 @@ import os from "node:os";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
import { parse } from "jsonc-parser";
|
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";
|
import { resolveClientOptions } from "../src/config.js";
|
||||||
|
|
||||||
class FakeRunner implements CommandRunner {
|
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"));
|
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 () => {
|
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 directory = await mkdtemp(path.join(os.tmpdir(), "crafttable-mcp-cli-agent-test-"));
|
||||||
const skillSource = await createSkillFixture(directory);
|
const skillSource = await createSkillFixture(directory);
|
||||||
|
|||||||
Reference in New Issue
Block a user