@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.
Files changed (2) hide show
  1. package/build/index.js +89 -12
  2. 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
- // require() works in the CommonJS build path; for the ESM-only build
34
- // we fall back to a constant that must match package.json.
35
- const MCP_VERSION = "1.1.4";
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
- const json = await response.json();
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": "vorim-mcp-server/1.0.0" },
145
+ headers: { "User-Agent": `vorim-mcp-server/${MCP_VERSION}` },
90
146
  });
91
- const data = await response.json();
92
- return text(data);
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 (ISO 8601)"),
241
- to: z.string().describe("End date (ISO 8601)"),
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
- const json = await response.json();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vorim/mcp-server",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "mcpName": "io.github.Kzino/vorim-mcp-server",
5
5
  "description": "MCP server for Vorim AI — AI agent identity, permissions, and audit trails",
6
6
  "type": "module",