@cloudgrid-io/mcp 0.2.0 → 0.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "MCP server for CloudGrid. Two editions: a local stdio server (full toolset) and a hosted web server (light, CLI-free toolset) over MCP Streamable HTTP.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/auth.js CHANGED
@@ -14,8 +14,16 @@ import { mkdir, writeFile, chmod, readFile } from "node:fs/promises";
14
14
  import { homedir } from "node:os";
15
15
  import { join } from "node:path";
16
16
 
17
+ // Base for server-to-API calls. On a hosted deployment this is the in-cluster
18
+ // service address (CLOUDGRID_API_URL), which is fast but NOT reachable by a browser.
17
19
  const API_BASE = (process.env.CLOUDGRID_API_URL || "https://api.cloudgrid.io").replace(/\/+$/, "");
18
20
 
21
+ // Base for URLs handed to the user's browser (the sign-in link). Must be public,
22
+ // regardless of the internal API address. Defaults to the public host.
23
+ const PUBLIC_API_BASE = (
24
+ process.env.CLOUDGRID_PUBLIC_API_URL || "https://api.cloudgrid.io"
25
+ ).replace(/\/+$/, "");
26
+
19
27
  export function cloudgridHome() {
20
28
  return process.env.CLOUDGRID_HOME || join(homedir(), ".cloudgrid");
21
29
  }
@@ -28,8 +36,9 @@ export function newLoginCode() {
28
36
  return randomUUID();
29
37
  }
30
38
 
39
+ // The sign-in URL the user opens in a browser — always the public host.
31
40
  export function buildLoginUrl(code) {
32
- return `${API_BASE}/auth/login?code=${encodeURIComponent(code)}`;
41
+ return `${PUBLIC_API_BASE}/auth/login?code=${encodeURIComponent(code)}`;
33
42
  }
34
43
 
35
44
  // One poll. Returns { status: 'not_started' | 'pending' | 'authenticated' | 'expired', jwt? }.
package/src/index.js CHANGED
@@ -25,7 +25,7 @@ const ctx = {
25
25
  savedLocationNote: () => `Credentials saved to ${credentialsPath()}.`,
26
26
  };
27
27
 
28
- const server = new McpServer({ name: "cloudgrid-mcp", version: "0.2.0" });
28
+ const server = new McpServer({ name: "cloudgrid-mcp", version: "0.2.2" });
29
29
  registerTools(server, ctx);
30
30
 
31
31
  const transport = new StdioServerTransport();
package/src/tools.js CHANGED
@@ -120,6 +120,15 @@ async function runDrop(ctx, { html, path: filePath, filename, anonymous, org })
120
120
  }
121
121
  }
122
122
 
123
+ // Anonymous path on a hosted server: present the trusted-server credential so the
124
+ // platform keys the anon-drop cap on the per-user id instead of the shared cluster
125
+ // egress IP. A missing/bad secret silently falls back to the IP cap server-side, so
126
+ // this is safe to send whenever configured. Only the web edition sets ctx.trustedServer.
127
+ if (!headers["Authorization"] && ctx.trustedServer?.secret && ctx.trustedServer?.endUserId) {
128
+ headers["X-CloudGrid-Trusted-Server-Auth"] = ctx.trustedServer.secret;
129
+ headers["X-CloudGrid-Trusted-Server-End-User"] = ctx.trustedServer.endUserId;
130
+ }
131
+
123
132
  const form = new FormData();
124
133
  form.append("artifact", new Blob([bytes], { type }), name);
125
134
  if (orgSlug) form.append("org_slug", orgSlug);
package/src/web.js CHANGED
@@ -17,8 +17,15 @@ import { registerTools, decodeJwt } from "./tools.js";
17
17
 
18
18
  const PORT = Number(process.env.PORT || 8080);
19
19
 
20
- // A web session: identity lives in memory for the session's lifetime only.
21
- function makeWebContext() {
20
+ // The trusted-server credential, if this host is provisioned as one. Sent on
21
+ // anonymous drops so the platform keys the anon-drop cap on the per-user id rather
22
+ // than the shared cluster egress IP. Missing/bad secret falls back to the IP cap
23
+ // server-side, so it is safe to leave unset.
24
+ const TRUSTED_SERVER_SECRET = process.env.MCP_TRUSTED_SERVER_SECRET || null;
25
+
26
+ // A web session: identity lives in memory for the session's lifetime only. The
27
+ // session id doubles as the stable, opaque end-user id for the trusted-server cap.
28
+ function makeWebContext(sessionId) {
22
29
  let sessionToken = null;
23
30
  return {
24
31
  edition: "web",
@@ -33,6 +40,9 @@ function makeWebContext() {
33
40
  return decodeJwt(jwt);
34
41
  },
35
42
  savedLocationNote: () => "You are signed in for this session.",
43
+ trustedServer: TRUSTED_SERVER_SECRET
44
+ ? { secret: TRUSTED_SERVER_SECRET, endUserId: sessionId }
45
+ : null,
36
46
  };
37
47
  }
38
48
 
@@ -57,9 +67,12 @@ app.post("/mcp", async (req, res) => {
57
67
  });
58
68
  return;
59
69
  }
60
- // New session: fresh server + per-session identity context.
70
+ // New session: fresh server + per-session identity context. Generate the session
71
+ // id up front so it is also the trusted-server end-user id. (Distinct name from the
72
+ // incoming `sessionId` header above.)
73
+ const newSessionId = randomUUID();
61
74
  transport = new StreamableHTTPServerTransport({
62
- sessionIdGenerator: () => randomUUID(),
75
+ sessionIdGenerator: () => newSessionId,
63
76
  onsessioninitialized: (sid) => {
64
77
  transports[sid] = transport;
65
78
  },
@@ -67,8 +80,8 @@ app.post("/mcp", async (req, res) => {
67
80
  transport.onclose = () => {
68
81
  if (transport.sessionId) delete transports[transport.sessionId];
69
82
  };
70
- const server = new McpServer({ name: "cloudgrid-mcp-web", version: "0.2.0" });
71
- registerTools(server, makeWebContext());
83
+ const server = new McpServer({ name: "cloudgrid-mcp-web", version: "0.2.2" });
84
+ registerTools(server, makeWebContext(newSessionId));
72
85
  await server.connect(transport);
73
86
  }
74
87