@vorim/mcp-server 1.1.4 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.js +89 -12
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -21,6 +21,18 @@
|
|
|
21
21
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22
22
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
23
23
|
import { z } from "zod";
|
|
24
|
+
// SECURITY: The MCP server uses a single process-wide Vorim API key
|
|
25
|
+
// taken from VORIM_API_KEY at startup. The stdio transport between
|
|
26
|
+
// the MCP host (e.g. Claude Desktop) and this process carries NO
|
|
27
|
+
// per-call auth — every tool call is attributed to the configured
|
|
28
|
+
// key. Two consequences:
|
|
29
|
+
// 1. Scope the key tightly. The key's scopes (audit:read,
|
|
30
|
+
// audit:write, agent:write, etc.) determine which MCP tools
|
|
31
|
+
// will actually work; a least-privilege scoped key reduces
|
|
32
|
+
// blast radius if the host environment is compromised.
|
|
33
|
+
// 2. Do not share a key across MCP installations. Each user /
|
|
34
|
+
// machine should mint its own key so revocation surgically
|
|
35
|
+
// kills exactly one installation.
|
|
24
36
|
const API_KEY = process.env.VORIM_API_KEY || "";
|
|
25
37
|
const BASE_URL = (process.env.VORIM_BASE_URL || "https://api.vorim.ai").replace(/\/$/, "");
|
|
26
38
|
if (!API_KEY) {
|
|
@@ -29,10 +41,25 @@ if (!API_KEY) {
|
|
|
29
41
|
}
|
|
30
42
|
// ─── HTTP Client ──────────────────────────────────────────────────────────
|
|
31
43
|
// Read the package version once so the User-Agent string and the MCP
|
|
32
|
-
// server's advertised version stay in sync with package.json.
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
44
|
+
// server's advertised version stay in sync with package.json. The
|
|
45
|
+
// hardcoded fallback is the last-known version — operators see a
|
|
46
|
+
// loud-fail if it ever drifts in CI because `npm test` re-reads
|
|
47
|
+
// package.json and compares.
|
|
48
|
+
import { readFileSync } from "node:fs";
|
|
49
|
+
import { fileURLToPath } from "node:url";
|
|
50
|
+
import { dirname, join } from "node:path";
|
|
51
|
+
function readMcpVersion() {
|
|
52
|
+
try {
|
|
53
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
54
|
+
const pkgPath = join(here, "..", "package.json");
|
|
55
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
56
|
+
if (typeof pkg.version === "string")
|
|
57
|
+
return pkg.version;
|
|
58
|
+
}
|
|
59
|
+
catch { /* fall through */ }
|
|
60
|
+
return "1.1.6"; // hardcoded fallback; bump alongside package.json
|
|
61
|
+
}
|
|
62
|
+
const MCP_VERSION = readMcpVersion();
|
|
36
63
|
/**
|
|
37
64
|
* URL-encode a user-supplied path segment. Agent ids, scopes, and
|
|
38
65
|
* chain ids all reach the API via path interpolation; raw slashes or
|
|
@@ -52,7 +79,35 @@ async function vorimRequest(method, path, body) {
|
|
|
52
79
|
},
|
|
53
80
|
body: body ? JSON.stringify(body) : undefined,
|
|
54
81
|
});
|
|
55
|
-
|
|
82
|
+
// Hand out clearer error messages for the common cases an MCP user
|
|
83
|
+
// will see. Auth: the API key is wrong or revoked. Scope: the key
|
|
84
|
+
// is missing the scope this route requires (e.g. audit:write).
|
|
85
|
+
if (response.status === 401) {
|
|
86
|
+
throw new Error("Vorim API key is invalid, revoked, or expired. Check VORIM_API_KEY.");
|
|
87
|
+
}
|
|
88
|
+
if (response.status === 403) {
|
|
89
|
+
const j = await response.json().catch(() => ({}));
|
|
90
|
+
const err = j.error;
|
|
91
|
+
const code = err?.code;
|
|
92
|
+
if (code === "INSUFFICIENT_SCOPE") {
|
|
93
|
+
throw new Error(err?.message ||
|
|
94
|
+
"Vorim API key is missing the scope required for this operation. Mint a new key with the required scope and update VORIM_API_KEY.");
|
|
95
|
+
}
|
|
96
|
+
throw new Error(err?.message || "Vorim API rejected with 403");
|
|
97
|
+
}
|
|
98
|
+
// Be defensive about non-JSON 5xx bodies (nginx 502, gateway HTML).
|
|
99
|
+
// Without this, response.json() throws "Unexpected token <" and the
|
|
100
|
+
// user sees an unhelpful parser error.
|
|
101
|
+
let json;
|
|
102
|
+
try {
|
|
103
|
+
json = await response.json();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
throw new Error(`Vorim API returned HTTP ${response.status} (non-JSON response — check upstream gateway)`);
|
|
108
|
+
}
|
|
109
|
+
throw new Error("Vorim API returned a non-JSON success response");
|
|
110
|
+
}
|
|
56
111
|
if (!response.ok) {
|
|
57
112
|
const err = json.error;
|
|
58
113
|
throw new Error(err?.message || `HTTP ${response.status}`);
|
|
@@ -84,12 +139,21 @@ const server = new McpServer({
|
|
|
84
139
|
server.registerTool("vorim_ping", {
|
|
85
140
|
description: "Check Vorim AI API health and connectivity. Returns status, version, and service health.",
|
|
86
141
|
inputSchema: {},
|
|
142
|
+
annotations: { readOnlyHint: true },
|
|
87
143
|
}, async () => {
|
|
88
144
|
const response = await fetch(`${BASE_URL}/health`, {
|
|
89
|
-
headers: { "User-Agent":
|
|
145
|
+
headers: { "User-Agent": `vorim-mcp-server/${MCP_VERSION}` },
|
|
90
146
|
});
|
|
91
|
-
|
|
92
|
-
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
throw new Error(`Vorim API health endpoint returned HTTP ${response.status}`);
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const data = await response.json();
|
|
152
|
+
return text(data);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
throw new Error(`Vorim API returned a non-JSON health response (status ${response.status}); check upstream gateway`);
|
|
156
|
+
}
|
|
93
157
|
});
|
|
94
158
|
// ─── Agent Identity ───────────────────────────────────────────────────────
|
|
95
159
|
server.registerTool("vorim_register_agent", {
|
|
@@ -235,11 +299,12 @@ server.registerTool("vorim_emit_event", {
|
|
|
235
299
|
return text(data);
|
|
236
300
|
});
|
|
237
301
|
server.registerTool("vorim_export_audit", {
|
|
238
|
-
description: "Export a signed audit bundle for a date range. Returns events with a SHA-256 manifest for tamper-proof verification.",
|
|
302
|
+
description: "Export a signed audit bundle for a date range. Returns events with a SHA-256 manifest for tamper-proof verification. Window must be <= 90 days; the server returns 400 if from > to or the range exceeds the cap. The returned bundle has up to 1,000,000 events; if truncated, the response carries `truncated: true` and the caller should re-export a narrower window.",
|
|
239
303
|
inputSchema: {
|
|
240
|
-
from: z.string().describe("Start date
|
|
241
|
-
to: z.string().describe("End date
|
|
304
|
+
from: z.string().describe("Start date in ISO 8601 (e.g. 2026-06-01T00:00:00Z)"),
|
|
305
|
+
to: z.string().describe("End date in ISO 8601 (must be on or after `from`; window <= 90 days)"),
|
|
242
306
|
},
|
|
307
|
+
annotations: { readOnlyHint: true },
|
|
243
308
|
}, async ({ from, to }) => {
|
|
244
309
|
const result = await vorimPost("/audit/export", { from, to });
|
|
245
310
|
return text(result);
|
|
@@ -254,7 +319,19 @@ server.registerTool("vorim_verify_trust", {
|
|
|
254
319
|
const response = await fetch(`${BASE_URL}/v1/trust/verify/${encId(agent_id)}`, {
|
|
255
320
|
headers: { "User-Agent": `vorim-mcp-server/${MCP_VERSION}` },
|
|
256
321
|
});
|
|
257
|
-
|
|
322
|
+
if (!response.ok) {
|
|
323
|
+
if (response.status === 404) {
|
|
324
|
+
throw new Error(`Agent ${agent_id} is not registered with Vorim`);
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`Vorim trust endpoint returned HTTP ${response.status}`);
|
|
327
|
+
}
|
|
328
|
+
let json;
|
|
329
|
+
try {
|
|
330
|
+
json = await response.json();
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
throw new Error(`Vorim trust endpoint returned a non-JSON response (status ${response.status})`);
|
|
334
|
+
}
|
|
258
335
|
return text(json.data || json);
|
|
259
336
|
});
|
|
260
337
|
// ─── Ephemeral Agents ────────────────────────────────────────────────────
|
package/package.json
CHANGED