@cloudgrid-io/mcp 0.21.5 → 0.21.7
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 +11 -2
- package/src/tools/constants.js +12 -0
- package/src/tools/deploy.js +133 -21
- package/src/tools/register.js +70 -17
- package/src/web.js +37 -0
- package/src/widgets/grid-login/mcp-app.css +57 -0
- package/src/widgets/grid-login/mcp-app.html +26 -0
- package/src/widgets/grid-login/mcp-app.js +131 -0
- package/src/widgets/grid-login.html +150 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file grid_login MCP App — the sign-in card Claude web renders for grid_login.
|
|
3
|
+
*
|
|
4
|
+
* This module is the UI half of the MCP Apps (SEP-1865) mechanism. It talks to
|
|
5
|
+
* the host over postMessage JSON-RPC via the official `App` class — NOT
|
|
6
|
+
* `window.openai`, which does not exist in Claude's host (that global is the
|
|
7
|
+
* ChatGPT Apps-SDK bridge the old org-picker widget targets).
|
|
8
|
+
*
|
|
9
|
+
* It is bundled into a single self-contained src/widgets/grid-login.html by
|
|
10
|
+
* scripts/build-login-widget.mjs (esbuild inlines this file and the whole
|
|
11
|
+
* @modelcontextprotocol/ext-apps App class). The iframe CSP is deny-by-default,
|
|
12
|
+
* so nothing here may reference a remote script, stylesheet, or font.
|
|
13
|
+
*
|
|
14
|
+
* Security invariant (issue #302): the card NEVER receives, displays, or stores
|
|
15
|
+
* a token. Its only job is to open the sign-in URL in the browser; the browser
|
|
16
|
+
* performs the auth. The most we surface after sign-in is the account email,
|
|
17
|
+
* which grid_login_status returns and which is not a credential.
|
|
18
|
+
*/
|
|
19
|
+
import {
|
|
20
|
+
App,
|
|
21
|
+
applyDocumentTheme,
|
|
22
|
+
applyHostFonts,
|
|
23
|
+
applyHostStyleVariables,
|
|
24
|
+
} from "@modelcontextprotocol/ext-apps";
|
|
25
|
+
|
|
26
|
+
const els = {
|
|
27
|
+
root: document.getElementById("card"),
|
|
28
|
+
status: document.getElementById("status"),
|
|
29
|
+
signin: document.getElementById("signin"),
|
|
30
|
+
check: document.getElementById("check"),
|
|
31
|
+
fallback: document.getElementById("fallback"),
|
|
32
|
+
fallbackUrl: document.getElementById("fallback-url"),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// The sign-in URL, lifted from the tool result. Never a token — just the URL
|
|
36
|
+
// the browser opens. Held only to wire the button.
|
|
37
|
+
let loginUrl = null;
|
|
38
|
+
|
|
39
|
+
// Pull the sign-in URL out of a grid_login tool result. structuredContent is
|
|
40
|
+
// the contract (grid_login sets { login_url }); the text content is the
|
|
41
|
+
// text-first fallback that ALSO carries the URL, so we read it if structured is
|
|
42
|
+
// missing. This mirrors the server: the URL is never hidden behind the card.
|
|
43
|
+
function readLoginUrl(result) {
|
|
44
|
+
const fromStructured = result?.structuredContent?.login_url;
|
|
45
|
+
if (typeof fromStructured === "string" && fromStructured) return fromStructured;
|
|
46
|
+
const text = Array.isArray(result?.content)
|
|
47
|
+
? result.content.filter((c) => c?.type === "text").map((c) => c.text).join("\n")
|
|
48
|
+
: "";
|
|
49
|
+
const match = text.match(/https?:\/\/\S+/);
|
|
50
|
+
return match ? match[0] : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function showSignInReady() {
|
|
54
|
+
if (loginUrl) {
|
|
55
|
+
els.fallbackUrl.textContent = loginUrl;
|
|
56
|
+
els.fallbackUrl.setAttribute("href", loginUrl);
|
|
57
|
+
els.fallback.hidden = false;
|
|
58
|
+
}
|
|
59
|
+
els.signin.disabled = !loginUrl;
|
|
60
|
+
els.status.textContent = loginUrl
|
|
61
|
+
? "Sign in to publish to your grid."
|
|
62
|
+
: "Preparing the sign-in link.";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function showAwaitingReturn() {
|
|
66
|
+
els.status.textContent =
|
|
67
|
+
"Finish signing in on the page that opened, then come back and select Check sign-in.";
|
|
68
|
+
els.check.hidden = false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const app = new App({ name: "CloudGrid sign-in", version: "1.0.0" });
|
|
72
|
+
|
|
73
|
+
app.onerror = (err) => console.error("[grid-login card]", err);
|
|
74
|
+
|
|
75
|
+
app.onhostcontextchanged = (ctx) => {
|
|
76
|
+
if (ctx?.theme) applyDocumentTheme(ctx.theme);
|
|
77
|
+
if (ctx?.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
|
|
78
|
+
if (ctx?.styles?.css?.fonts) applyHostFonts(ctx.styles.css.fonts);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// The grid_login result arrives here after connect. It carries the URL, never
|
|
82
|
+
// a token.
|
|
83
|
+
app.ontoolresult = (result) => {
|
|
84
|
+
loginUrl = readLoginUrl(result);
|
|
85
|
+
showSignInReady();
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
els.signin.addEventListener("click", async () => {
|
|
89
|
+
if (!loginUrl) return;
|
|
90
|
+
try {
|
|
91
|
+
await app.openLink({ url: loginUrl });
|
|
92
|
+
showAwaitingReturn();
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error("[grid-login card] openLink failed", err);
|
|
95
|
+
// The link is still on-screen (fallback block); the user can open it
|
|
96
|
+
// manually. Never dead-end.
|
|
97
|
+
showAwaitingReturn();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Check sign-in: ask the server to finish the flow (grid_login_status). It
|
|
102
|
+
// returns { status, email } and NEVER a token, so the card stays token-free.
|
|
103
|
+
els.check.addEventListener("click", async () => {
|
|
104
|
+
els.check.disabled = true;
|
|
105
|
+
els.status.textContent = "Checking sign-in.";
|
|
106
|
+
try {
|
|
107
|
+
const result = await app.callServerTool({ name: "grid_login_status", arguments: {} });
|
|
108
|
+
const status = result?.structuredContent?.status;
|
|
109
|
+
const email = result?.structuredContent?.email;
|
|
110
|
+
if (status === "authenticated") {
|
|
111
|
+
els.status.textContent = email ? `Signed in as ${email}.` : "Signed in.";
|
|
112
|
+
els.signin.hidden = true;
|
|
113
|
+
els.check.hidden = true;
|
|
114
|
+
} else {
|
|
115
|
+
els.status.textContent =
|
|
116
|
+
"Still waiting for sign-in. Finish on the page that opened, then check again.";
|
|
117
|
+
els.check.disabled = false;
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error("[grid-login card] status check failed", err);
|
|
121
|
+
els.status.textContent =
|
|
122
|
+
"Could not check sign-in from here. Complete it in the browser and I will continue.";
|
|
123
|
+
els.check.disabled = false;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
showSignInReady();
|
|
128
|
+
app.connect().then(() => {
|
|
129
|
+
const ctx = app.getHostContext();
|
|
130
|
+
if (ctx) app.onhostcontextchanged(ctx);
|
|
131
|
+
});
|