[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
316
electron/bridges/externalMcp/clientSetup.test.cjs
Normal file
316
electron/bridges/externalMcp/clientSetup.test.cjs
Normal file
@@ -0,0 +1,316 @@
|
||||
"use strict";
|
||||
|
||||
const { describe, it } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const {
|
||||
EXTERNAL_MCP_CODEX_NAME,
|
||||
classifyCodexExternalMcpStatus,
|
||||
parseCodexMcpList,
|
||||
} = require("./codexSetup.cjs");
|
||||
const {
|
||||
EXTERNAL_MCP_CLAUDE_NAME,
|
||||
classifyClaudeExternalMcpStatus,
|
||||
} = require("./claudeSetup.cjs");
|
||||
const {
|
||||
EXTERNAL_MCP_GROK_NAME,
|
||||
classifyGrokExternalMcpStatus,
|
||||
parseGrokMcpList,
|
||||
} = require("./grokSetup.cjs");
|
||||
|
||||
describe("external MCP client setup classifiers", () => {
|
||||
it("parses Codex MCP list and detects configured launcher", () => {
|
||||
const entries = parseCodexMcpList(JSON.stringify([
|
||||
{
|
||||
name: EXTERNAL_MCP_CODEX_NAME,
|
||||
enabled: true,
|
||||
transport: {
|
||||
type: "stdio",
|
||||
command: "/path/to/netcatty-external-mcp",
|
||||
args: [],
|
||||
env: { NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE: "/tmp/discovery.json" },
|
||||
},
|
||||
},
|
||||
]));
|
||||
const status = classifyCodexExternalMcpStatus({
|
||||
entries,
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
codexPath: "/usr/bin/codex",
|
||||
discoveryEnv: { NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE: "/tmp/discovery.json" },
|
||||
});
|
||||
assert.equal(status.state, "configured");
|
||||
});
|
||||
|
||||
it("treats Codex launcher without discovery env as not_configured", () => {
|
||||
const status = classifyCodexExternalMcpStatus({
|
||||
entries: [{
|
||||
name: EXTERNAL_MCP_CODEX_NAME,
|
||||
transport: { type: "stdio", command: "/path/to/netcatty-external-mcp", args: [], env: null },
|
||||
}],
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
codexPath: "/usr/bin/codex",
|
||||
discoveryEnv: { NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE: "/tmp/discovery.json" },
|
||||
});
|
||||
assert.equal(status.state, "not_configured");
|
||||
assert.equal(status.existingCommand, "/path/to/netcatty-external-mcp");
|
||||
});
|
||||
|
||||
it("treats disabled Codex entries as not_configured with existingCommand", () => {
|
||||
const status = classifyCodexExternalMcpStatus({
|
||||
entries: [{
|
||||
name: EXTERNAL_MCP_CODEX_NAME,
|
||||
enabled: false,
|
||||
transport: { type: "stdio", command: "/path/to/netcatty-external-mcp", args: [] },
|
||||
}],
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
codexPath: "/usr/bin/codex",
|
||||
discoveryEnv: { NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE: "/tmp/discovery.json" },
|
||||
});
|
||||
assert.equal(status.state, "not_configured");
|
||||
assert.ok(status.existingCommand);
|
||||
});
|
||||
|
||||
it("flags Codex conflict when command differs", () => {
|
||||
const status = classifyCodexExternalMcpStatus({
|
||||
entries: [{
|
||||
name: EXTERNAL_MCP_CODEX_NAME,
|
||||
transport: { type: "stdio", command: "/other/path", args: [] },
|
||||
}],
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
codexPath: "/usr/bin/codex",
|
||||
});
|
||||
assert.equal(status.state, "conflict");
|
||||
});
|
||||
|
||||
it("embeds desktop-managed Codex path in the copyable setup command", () => {
|
||||
const desktopPath = "/Applications/ChatGPT.app/Contents/Resources/codex";
|
||||
const status = classifyCodexExternalMcpStatus({
|
||||
entries: [],
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
codexPath: desktopPath,
|
||||
commandExecutable: desktopPath,
|
||||
});
|
||||
assert.equal(status.state, "not_configured");
|
||||
assert.ok(status.command.startsWith(`${desktopPath} `));
|
||||
assert.equal(status.command.startsWith("codex "), false);
|
||||
});
|
||||
|
||||
it("keeps bare codex for PATH installs even when codexPath is absolute", () => {
|
||||
const status = classifyCodexExternalMcpStatus({
|
||||
entries: [],
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
codexPath: "C:\\Program Files\\Codex\\codex.exe",
|
||||
commandExecutable: "codex",
|
||||
});
|
||||
assert.equal(status.state, "not_configured");
|
||||
assert.ok(status.command.startsWith("codex "));
|
||||
});
|
||||
|
||||
it("embeds desktop-managed Claude path in the copyable setup command", () => {
|
||||
const desktopPath = "/Users/test/Library/Application Support/Claude/claude-code/2.10.0/claude.app/Contents/MacOS/claude";
|
||||
const status = classifyClaudeExternalMcpStatus({
|
||||
getResult: {
|
||||
exitCode: 1,
|
||||
stdout: "",
|
||||
stderr: 'No MCP server found with name: "netcatty-external"',
|
||||
},
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: desktopPath,
|
||||
commandExecutable: desktopPath,
|
||||
});
|
||||
assert.equal(status.state, "not_configured");
|
||||
assert.ok(status.command.startsWith(`"${desktopPath}" `));
|
||||
assert.equal(status.command.startsWith("claude "), false);
|
||||
});
|
||||
|
||||
it("quotes launcher paths with apostrophes in the copyable setup command", () => {
|
||||
const launcherPath = "/Applications/Bob's/Netcatty.app/Contents/MacOS/netcatty-external-mcp";
|
||||
const status = classifyCodexExternalMcpStatus({
|
||||
entries: [],
|
||||
launcherPath,
|
||||
codexPath: "/usr/bin/codex",
|
||||
commandExecutable: "codex",
|
||||
});
|
||||
assert.equal(status.state, "not_configured");
|
||||
assert.ok(status.command.includes(`"${launcherPath}"`));
|
||||
});
|
||||
|
||||
it("classifies Claude configured and missing states", () => {
|
||||
const configured = classifyClaudeExternalMcpStatus({
|
||||
getResult: { exitCode: 0, stdout: `${EXTERNAL_MCP_CLAUDE_NAME}: /path/to/netcatty-external-mcp - connected`, stderr: "" },
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: "/usr/bin/claude",
|
||||
});
|
||||
assert.equal(configured.state, "configured");
|
||||
|
||||
const quoted = classifyClaudeExternalMcpStatus({
|
||||
getResult: {
|
||||
exitCode: 0,
|
||||
stdout: `Command: "/path/to/netcatty-external-mcp"\nStatus: connected`,
|
||||
stderr: "",
|
||||
},
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: "/usr/bin/claude",
|
||||
});
|
||||
assert.equal(quoted.state, "configured");
|
||||
|
||||
const withEnvHeader = classifyClaudeExternalMcpStatus({
|
||||
getResult: {
|
||||
exitCode: 0,
|
||||
stdout: `Command: /path/to/netcatty-external-mcp\nEnvironment:\n NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE=/tmp/d.json\nScope: User config (available in all your projects)`,
|
||||
stderr: "",
|
||||
},
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: "/usr/bin/claude",
|
||||
discoveryEnv: { NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE: "/tmp/d.json" },
|
||||
});
|
||||
assert.equal(withEnvHeader.state, "configured");
|
||||
|
||||
const withEnvFlags = classifyClaudeExternalMcpStatus({
|
||||
getResult: {
|
||||
exitCode: 0,
|
||||
stdout: `Command: -e NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE=/tmp/d.json -- /path/to/netcatty-external-mcp`,
|
||||
stderr: "",
|
||||
},
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: "/usr/bin/claude",
|
||||
discoveryEnv: { NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE: "/tmp/d.json" },
|
||||
});
|
||||
assert.equal(withEnvFlags.state, "configured");
|
||||
|
||||
const missingEnv = classifyClaudeExternalMcpStatus({
|
||||
getResult: {
|
||||
exitCode: 0,
|
||||
stdout: `Command: /path/to/netcatty-external-mcp`,
|
||||
stderr: "",
|
||||
},
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: "/usr/bin/claude",
|
||||
discoveryEnv: { NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE: "/tmp/d.json" },
|
||||
});
|
||||
assert.equal(missingEnv.state, "not_configured");
|
||||
|
||||
const withExtraArgs = classifyClaudeExternalMcpStatus({
|
||||
getResult: {
|
||||
exitCode: 0,
|
||||
stdout: `Command: /path/to/netcatty-external-mcp --evil`,
|
||||
stderr: "",
|
||||
},
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: "/usr/bin/claude",
|
||||
});
|
||||
assert.equal(withExtraArgs.state, "conflict");
|
||||
|
||||
const withArgsField = classifyClaudeExternalMcpStatus({
|
||||
getResult: {
|
||||
exitCode: 0,
|
||||
stdout: `Command: /path/to/netcatty-external-mcp\nArgs: --evil\nScope: Local config (private to you in this project)`,
|
||||
stderr: "",
|
||||
},
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: "/usr/bin/claude",
|
||||
});
|
||||
assert.equal(withArgsField.state, "conflict");
|
||||
assert.equal(withArgsField.existingScope, "local");
|
||||
|
||||
const userScope = classifyClaudeExternalMcpStatus({
|
||||
getResult: {
|
||||
exitCode: 0,
|
||||
stdout: `Command: -e NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE=/tmp/d.json -- /path/to/netcatty-external-mcp\nScope: User config (available in all your projects)`,
|
||||
stderr: "",
|
||||
},
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: "/usr/bin/claude",
|
||||
discoveryEnv: { NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE: "/tmp/d.json" },
|
||||
});
|
||||
assert.equal(userScope.state, "configured");
|
||||
assert.equal(userScope.existingScope, "user");
|
||||
|
||||
const localScopeNeedsUpgrade = classifyClaudeExternalMcpStatus({
|
||||
getResult: {
|
||||
exitCode: 0,
|
||||
stdout: `Command: -e NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE=/tmp/d.json -- /path/to/netcatty-external-mcp\nScope: Local config (private to you in this project)`,
|
||||
stderr: "",
|
||||
},
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: "/usr/bin/claude",
|
||||
discoveryEnv: { NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE: "/tmp/d.json" },
|
||||
});
|
||||
assert.equal(localScopeNeedsUpgrade.state, "not_configured");
|
||||
assert.equal(localScopeNeedsUpgrade.existingScope, "local");
|
||||
assert.ok(localScopeNeedsUpgrade.existingCommand);
|
||||
|
||||
const missing = classifyClaudeExternalMcpStatus({
|
||||
getResult: {
|
||||
exitCode: 1,
|
||||
stdout: "",
|
||||
stderr: `No MCP server found with name: "${EXTERNAL_MCP_CLAUDE_NAME}"`,
|
||||
},
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: "/usr/bin/claude",
|
||||
});
|
||||
assert.equal(missing.state, "not_configured");
|
||||
|
||||
const missingNamed = classifyClaudeExternalMcpStatus({
|
||||
getResult: {
|
||||
exitCode: 1,
|
||||
stdout: "",
|
||||
stderr: `No MCP server named ${EXTERNAL_MCP_CLAUDE_NAME}`,
|
||||
},
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
claudePath: "/usr/bin/claude",
|
||||
});
|
||||
assert.equal(missingNamed.state, "not_configured");
|
||||
});
|
||||
|
||||
it("parses Grok MCP list and detects configured launcher", () => {
|
||||
const entries = parseGrokMcpList(JSON.stringify([
|
||||
{
|
||||
name: EXTERNAL_MCP_GROK_NAME,
|
||||
enabled: true,
|
||||
transport: { type: "stdio", command: "/path/to/netcatty-external-mcp", args: [] },
|
||||
env: { NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE: "/tmp/discovery.json" },
|
||||
},
|
||||
]));
|
||||
const status = classifyGrokExternalMcpStatus({
|
||||
entries,
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
grokPath: "/usr/bin/grok",
|
||||
discoveryEnv: { NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE: "/tmp/discovery.json" },
|
||||
});
|
||||
assert.equal(status.state, "configured");
|
||||
});
|
||||
|
||||
it("flags Grok conflict when command differs", () => {
|
||||
const status = classifyGrokExternalMcpStatus({
|
||||
entries: [{
|
||||
name: EXTERNAL_MCP_GROK_NAME,
|
||||
transport: { type: "stdio", command: "/other/path", args: [] },
|
||||
}],
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
grokPath: "/usr/bin/grok",
|
||||
});
|
||||
assert.equal(status.state, "conflict");
|
||||
});
|
||||
|
||||
it("flags Grok conflict when launcher has extra args", () => {
|
||||
const status = classifyGrokExternalMcpStatus({
|
||||
entries: [{
|
||||
name: EXTERNAL_MCP_GROK_NAME,
|
||||
transport: { type: "stdio", command: "/path/to/netcatty-external-mcp", args: ["--evil"] },
|
||||
}],
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
grokPath: "/usr/bin/grok",
|
||||
});
|
||||
assert.equal(status.state, "conflict");
|
||||
});
|
||||
|
||||
it("classifies Grok missing when CLI is absent", () => {
|
||||
const status = classifyGrokExternalMcpStatus({
|
||||
entries: [],
|
||||
launcherPath: "/path/to/netcatty-external-mcp",
|
||||
grokPath: null,
|
||||
});
|
||||
assert.equal(status.state, "grok_not_found");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user