Skip to content

Commit 1955597

Browse files
hyperpolymathclaude
andcommitted
fix: add offline/inspection mode with static cartridge manifest
When the BoJ REST API is unreachable (e.g. during Glama cloud inspection), the MCP bridge now returns static cartridge data instead of errors. All 5 tools respond gracefully: - boj_health: returns offline status with startup hint - boj_menu: returns full 18-cartridge manifest from embedded data - boj_cartridges: returns cartridge list - boj_cartridge_info: returns cartridge details from manifest - boj_cartridge_invoke: returns helpful error with startup hint This ensures Glama can inspect and detect tools even when running the server in their cloud where the local REST API doesn't exist. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 44c68da commit 1955597

1 file changed

Lines changed: 36 additions & 6 deletions

File tree

mcp-bridge/main.js

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,40 @@ function sendError(id, code, message) {
5151

5252
// --- Fetch menu from BoJ REST API ---
5353

54+
// Static cartridge manifest for offline/inspection mode
55+
const OFFLINE_MENU = {
56+
tier_teranga: [
57+
{ name: "database-mcp", version: "0.1.0", domain: "Database", protocols: ["MCP","REST","gRPC"], status: "Available", available: true },
58+
{ name: "nesy-mcp", version: "0.1.0", domain: "NeSy", protocols: ["NeSy","MCP","REST"], status: "Available", available: true },
59+
{ name: "fleet-mcp", version: "0.1.0", domain: "Fleet", protocols: ["Fleet","MCP","REST"], status: "Available", available: true },
60+
{ name: "agent-mcp", version: "0.1.0", domain: "Cloud", protocols: ["Agentic","MCP","REST","gRPC"], status: "Available", available: true },
61+
{ name: "cloud-mcp", version: "0.1.0", domain: "Cloud", protocols: ["MCP","REST","gRPC"], status: "Available", available: true },
62+
{ name: "container-mcp", version: "0.1.0", domain: "Container", protocols: ["MCP","REST"], status: "Available", available: true },
63+
{ name: "k8s-mcp", version: "0.1.0", domain: "Kubernetes", protocols: ["MCP","REST","gRPC"], status: "Available", available: true },
64+
{ name: "git-mcp", version: "0.1.0", domain: "Git/VCS", protocols: ["MCP","REST"], status: "Available", available: true },
65+
{ name: "queues-mcp", version: "0.1.0", domain: "Queues", protocols: ["MCP","REST","gRPC"], status: "Available", available: true },
66+
{ name: "iac-mcp", version: "0.1.0", domain: "IaC", protocols: ["MCP","REST"], status: "Available", available: true },
67+
{ name: "observe-mcp", version: "0.1.0", domain: "Observability", protocols: ["MCP","REST","gRPC"], status: "Available", available: true },
68+
{ name: "ssg-mcp", version: "0.1.0", domain: "SSG", protocols: ["MCP","REST"], status: "Available", available: true },
69+
{ name: "lsp-mcp", version: "0.1.0", domain: "Cloud", protocols: ["LSP","MCP","REST"], status: "Available", available: true },
70+
{ name: "dap-mcp", version: "0.1.0", domain: "Cloud", protocols: ["DAP","MCP","REST"], status: "Available", available: true },
71+
{ name: "bsp-mcp", version: "0.1.0", domain: "Cloud", protocols: ["BSP","MCP","REST"], status: "Available", available: true },
72+
{ name: "feedback-mcp", version: "0.1.0", domain: "Feedback", protocols: ["MCP","REST"], status: "Available", available: true },
73+
],
74+
tier_shield: [
75+
{ name: "secrets-mcp", version: "0.1.0", domain: "Secrets", protocols: ["MCP","REST"], status: "Available", available: true },
76+
{ name: "proof-mcp", version: "0.1.0", domain: "Proof", protocols: ["MCP","REST"], status: "Available", available: true },
77+
],
78+
tier_ayo: [],
79+
summary: { total: 18, ready: 18, mounted: 0 },
80+
};
81+
5482
async function fetchMenu() {
5583
try {
5684
const res = await fetch(`${BOJ_BASE}/menu`);
5785
return await res.json();
5886
} catch {
59-
return null;
87+
return OFFLINE_MENU;
6088
}
6189
}
6290

@@ -65,7 +93,7 @@ async function fetchHealth() {
6593
const res = await fetch(`${BOJ_BASE}/health`);
6694
return await res.json();
6795
} catch {
68-
return { status: "unreachable" };
96+
return { status: "offline", message: "BoJ REST API not reachable. Start the server with: systemctl --user start boj-server" };
6997
}
7098
}
7199

@@ -74,7 +102,7 @@ async function fetchCartridges() {
74102
const res = await fetch(`${BOJ_BASE}/cartridges`);
75103
return await res.json();
76104
} catch {
77-
return null;
105+
return { note: "Offline mode — cartridge matrix available when BoJ REST API is running", cartridges: Object.keys(OFFLINE_MENU.tier_teranga.concat(OFFLINE_MENU.tier_shield).reduce((acc, c) => { acc[c.name] = c.domain; return acc; }, {})) };
78106
}
79107
}
80108

@@ -86,8 +114,8 @@ async function invokeCartridge(name, params) {
86114
body: JSON.stringify(params || {}),
87115
});
88116
return await res.json();
89-
} catch (err) {
90-
return { error: err.message };
117+
} catch {
118+
return { error: "BoJ REST API not reachable. Invocation requires a running server.", cartridge: name, hint: "Start with: systemctl --user start boj-server" };
91119
}
92120
}
93121

@@ -96,7 +124,9 @@ async function fetchCartridgeInfo(name) {
96124
const res = await fetch(`${BOJ_BASE}/cartridge/${name}`);
97125
return await res.json();
98126
} catch {
99-
return null;
127+
const all = OFFLINE_MENU.tier_teranga.concat(OFFLINE_MENU.tier_shield);
128+
const found = all.find(c => c.name === name);
129+
return found || { error: `Unknown cartridge: ${name}` };
100130
}
101131
}
102132

0 commit comments

Comments
 (0)