Add Fortress MCP bridge with fortress_ tool prefix and workspace write dir

This commit is contained in:
EugeneTes
2026-09-23 19:29:10 +00:00
parent 3d4ffde3c9
commit 4dc4b5b557
2 changed files with 91 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
import { mkdtemp, rm, stat } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { createLogger } from "../log.js";
import { _internals, createFortressMcpBridge } from "./fortress-mcp.js";
import type { McpClientLike, SpawnSpec } from "./mcp-bridge.js";
const silentLogger = createLogger("error", () => {});
const client: McpClientLike = {
listTools: async () => ({ tools: [{ name: "read_page" }, { name: "fetch_protected_page" }] }),
callTool: async () => ({ content: [] }),
close: async () => {},
};
describe("createFortressMcpBridge", () => {
it("spawns the configured command with Fortress env, creates the write dir, and prefixes tools", async () => {
const dir = await mkdtemp(path.join(os.tmpdir(), "fortress-"));
try {
const writeDir = path.join(dir, "ws", "fortress");
const seen: SpawnSpec[] = [];
const bridge = await createFortressMcpBridge({
command: ["python", "-m", "tilion.mcp"],
headless: true,
writeDir,
passthroughEnv: { TILION_PROXY: "http://p:1" },
logger: silentLogger,
clientFactory: async (s) => { seen.push(s); return client; },
});
expect(seen).toEqual([{
command: "python",
args: ["-m", "tilion.mcp"],
env: { TILION_PROXY: "http://p:1", TILION_MCP_HEADLESS: "1", TILION_MCP_WRITE_DIR: writeDir },
}]);
expect((await stat(writeDir)).isDirectory()).toBe(true);
expect(bridge.tools.map((t) => t.name)).toEqual(["fortress_read_page", "fortress_fetch_protected_page"]);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it("buildEnv maps headless=false to TILION_MCP_HEADLESS=0", () => {
expect(_internals.buildEnv({ headless: false, writeDir: "/w", passthroughEnv: {} })).toEqual({
TILION_MCP_HEADLESS: "0",
TILION_MCP_WRITE_DIR: "/w",
});
});
});

43
src/agent/fortress-mcp.ts Normal file
View File

@@ -0,0 +1,43 @@
import { mkdir } from "node:fs/promises";
import type { Logger } from "../log.js";
import { createMcpBridge, type McpBridge, type McpBridgeOptions } from "./mcp-bridge.js";
export interface FortressMcpOptions {
// argv that starts the server, e.g. ["tilion-mcp"] or ["python", "-m", "tilion.mcp"].
command: string[];
headless: boolean;
// Absolute dir inside the workspace; screenshots, saved pages, downloads and profiles land here.
writeDir: string;
// TILION_PROXY / TILION_REGION / CAPTCHA_API_KEY when set.
passthroughEnv: Record<string, string>;
logger: Logger;
// Test seam, forwarded to createMcpBridge.
clientFactory?: McpBridgeOptions["clientFactory"];
}
export async function createFortressMcpBridge(opts: FortressMcpOptions): Promise<McpBridge> {
const [command, ...args] = opts.command;
if (!command) throw new Error("fortress command is empty");
// tilion-mcp defaults to <tmpdir>/tilion-mcp, which the send tool can't reach.
await mkdir(opts.writeDir, { recursive: true });
return createMcpBridge({
label: "fortress",
command,
args,
env: buildEnv(opts),
toolPrefix: "fortress_",
logger: opts.logger,
clientFactory: opts.clientFactory,
});
}
function buildEnv(opts: Pick<FortressMcpOptions, "headless" | "writeDir" | "passthroughEnv">): Record<string, string> {
return {
...opts.passthroughEnv,
TILION_MCP_HEADLESS: opts.headless ? "1" : "0",
TILION_MCP_WRITE_DIR: opts.writeDir,
};
}
// exported for tests
export const _internals = { buildEnv };