@minicor/mcp-server 3.9.1 → 4.1.0

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 (102) hide show
  1. package/README.md +145 -100
  2. package/dist/__tests__/dev-mode-draft.test.js +27 -19
  3. package/dist/__tests__/dev-mode-draft.test.js.map +1 -1
  4. package/dist/__tests__/discovery.test.d.ts +2 -0
  5. package/dist/__tests__/discovery.test.d.ts.map +1 -0
  6. package/dist/__tests__/{builder-discovery.test.js → discovery.test.js} +3 -3
  7. package/dist/__tests__/discovery.test.js.map +1 -0
  8. package/dist/__tests__/http-seal.test.d.ts +2 -0
  9. package/dist/__tests__/http-seal.test.d.ts.map +1 -0
  10. package/dist/__tests__/http-seal.test.js +40 -0
  11. package/dist/__tests__/http-seal.test.js.map +1 -0
  12. package/dist/__tests__/http-server.test.d.ts +7 -0
  13. package/dist/__tests__/http-server.test.d.ts.map +1 -0
  14. package/dist/__tests__/http-server.test.js +317 -0
  15. package/dist/__tests__/http-server.test.js.map +1 -0
  16. package/dist/__tests__/jobs-tools.test.d.ts +2 -0
  17. package/dist/__tests__/jobs-tools.test.d.ts.map +1 -0
  18. package/dist/__tests__/jobs-tools.test.js +337 -0
  19. package/dist/__tests__/jobs-tools.test.js.map +1 -0
  20. package/dist/__tests__/laminar-client.test.js +31 -31
  21. package/dist/__tests__/laminar-client.test.js.map +1 -1
  22. package/dist/__tests__/lib-dev-mode.test.js +2 -2
  23. package/dist/__tests__/lib-dev-mode.test.js.map +1 -1
  24. package/dist/__tests__/middleware-service-client.test.js +70 -0
  25. package/dist/__tests__/middleware-service-client.test.js.map +1 -1
  26. package/dist/__tests__/server-surface.test.d.ts +2 -0
  27. package/dist/__tests__/server-surface.test.d.ts.map +1 -0
  28. package/dist/__tests__/{server-profile.test.js → server-surface.test.js} +120 -100
  29. package/dist/__tests__/server-surface.test.js.map +1 -0
  30. package/dist/bootstrap.js +29 -0
  31. package/dist/bootstrap.js.map +1 -1
  32. package/dist/http-oauth.d.ts +60 -0
  33. package/dist/http-oauth.d.ts.map +1 -0
  34. package/dist/http-oauth.js +380 -0
  35. package/dist/http-oauth.js.map +1 -0
  36. package/dist/http-seal.d.ts +17 -0
  37. package/dist/http-seal.d.ts.map +1 -0
  38. package/dist/http-seal.js +63 -0
  39. package/dist/http-seal.js.map +1 -0
  40. package/dist/http-server.d.ts +29 -0
  41. package/dist/http-server.d.ts.map +1 -0
  42. package/dist/http-server.js +229 -0
  43. package/dist/http-server.js.map +1 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +12 -15
  46. package/dist/index.js.map +1 -1
  47. package/dist/laminar-client.d.ts +11 -14
  48. package/dist/laminar-client.d.ts.map +1 -1
  49. package/dist/laminar-client.js +25 -25
  50. package/dist/laminar-client.js.map +1 -1
  51. package/dist/lib.d.ts +6 -4
  52. package/dist/lib.d.ts.map +1 -1
  53. package/dist/lib.js +10 -10
  54. package/dist/lib.js.map +1 -1
  55. package/dist/middleware-service-client.d.ts +37 -2
  56. package/dist/middleware-service-client.d.ts.map +1 -1
  57. package/dist/middleware-service-client.js +43 -2
  58. package/dist/middleware-service-client.js.map +1 -1
  59. package/dist/prompts/build-rpa.d.ts.map +1 -1
  60. package/dist/prompts/build-rpa.js +2 -0
  61. package/dist/prompts/build-rpa.js.map +1 -1
  62. package/dist/server-surface.d.ts +23 -0
  63. package/dist/server-surface.d.ts.map +1 -0
  64. package/dist/server-surface.js +113 -0
  65. package/dist/server-surface.js.map +1 -0
  66. package/dist/setup.d.ts +1 -1
  67. package/dist/setup.js +3 -9
  68. package/dist/setup.js.map +1 -1
  69. package/dist/sync.js +34 -6
  70. package/dist/sync.js.map +1 -1
  71. package/dist/tools/core.js +1 -1
  72. package/dist/tools/core.js.map +1 -1
  73. package/dist/tools/dev-mode-draft.d.ts.map +1 -1
  74. package/dist/tools/dev-mode-draft.js +63 -49
  75. package/dist/tools/dev-mode-draft.js.map +1 -1
  76. package/dist/tools/{builder-discovery.d.ts → discovery.d.ts} +3 -3
  77. package/dist/tools/discovery.d.ts.map +1 -0
  78. package/dist/tools/{builder-discovery.js → discovery.js} +3 -3
  79. package/dist/tools/discovery.js.map +1 -0
  80. package/dist/tools/jobs.d.ts.map +1 -1
  81. package/dist/tools/jobs.js +296 -3
  82. package/dist/tools/jobs.js.map +1 -1
  83. package/dist/tools/vm.js +1 -1
  84. package/dist/tools/vm.js.map +1 -1
  85. package/package.json +3 -2
  86. package/skills/general/job-build-loop.md +166 -0
  87. package/dist/__tests__/builder-discovery.test.d.ts +0 -2
  88. package/dist/__tests__/builder-discovery.test.d.ts.map +0 -1
  89. package/dist/__tests__/builder-discovery.test.js.map +0 -1
  90. package/dist/__tests__/server-profile.test.d.ts +0 -2
  91. package/dist/__tests__/server-profile.test.d.ts.map +0 -1
  92. package/dist/__tests__/server-profile.test.js.map +0 -1
  93. package/dist/builder-index.d.ts +0 -3
  94. package/dist/builder-index.d.ts.map +0 -1
  95. package/dist/builder-index.js +0 -5
  96. package/dist/builder-index.js.map +0 -1
  97. package/dist/server-profile.d.ts +0 -11
  98. package/dist/server-profile.d.ts.map +0 -1
  99. package/dist/server-profile.js +0 -104
  100. package/dist/server-profile.js.map +0 -1
  101. package/dist/tools/builder-discovery.d.ts.map +0 -1
  102. package/dist/tools/builder-discovery.js.map +0 -1
@@ -0,0 +1,60 @@
1
+ /**
2
+ * OAuth 2.1 authorization bridge for the hosted HTTP server.
3
+ *
4
+ * Implements the MCP authorization spec (metadata discovery, dynamic
5
+ * client registration, authorization code + PKCE, refresh tokens) on top
6
+ * of the Minicor /auth/signin API. The /authorize endpoint serves a login
7
+ * form; submitting valid Minicor credentials mints an auth code that the
8
+ * client exchanges for sealed tokens wrapping the upstream credentials.
9
+ *
10
+ * Everything is stateless: client IDs, auth codes, and tokens are all
11
+ * TokenSealer blobs, so this works across multiple Cloud Run instances.
12
+ */
13
+ import { type Region } from "./paths.js";
14
+ import type { TokenSealer } from "./http-seal.js";
15
+ export interface UpstreamCredentials {
16
+ region: Region;
17
+ apiBase: string;
18
+ access_token: string;
19
+ refresh_token: string | null;
20
+ expires_at: number;
21
+ }
22
+ export interface ClientPayload {
23
+ redirect_uris: string[];
24
+ }
25
+ export interface CodePayload {
26
+ creds: UpstreamCredentials;
27
+ client_id: string;
28
+ redirect_uri: string;
29
+ code_challenge: string;
30
+ }
31
+ export interface AccessPayload {
32
+ creds: UpstreamCredentials;
33
+ }
34
+ export interface RefreshPayload {
35
+ creds: UpstreamCredentials;
36
+ client_id: string;
37
+ }
38
+ export interface OauthConfig {
39
+ sealer: TokenSealer;
40
+ /** Empty list = any email accepted by the upstream API may sign in. */
41
+ allowedEmailDomains: string[];
42
+ }
43
+ export interface HandlerResult {
44
+ status: number;
45
+ headers: Record<string, string>;
46
+ body: string;
47
+ }
48
+ export declare function authorizationServerMetadata(issuer: string): HandlerResult;
49
+ export declare function protectedResourceMetadata(issuer: string): HandlerResult;
50
+ export declare function handleRegister(config: OauthConfig, body: unknown): HandlerResult;
51
+ export interface AuthorizeParams {
52
+ client_id: string;
53
+ redirect_uri: string;
54
+ state: string;
55
+ code_challenge: string;
56
+ }
57
+ export declare function handleAuthorizeGet(config: OauthConfig, query: URLSearchParams): HandlerResult;
58
+ export declare function handleAuthorizePost(config: OauthConfig, form: URLSearchParams): Promise<HandlerResult>;
59
+ export declare function handleToken(config: OauthConfig, form: URLSearchParams): Promise<HandlerResult>;
60
+ //# sourceMappingURL=http-oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-oauth.d.ts","sourceRoot":"","sources":["../src/http-oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAYlD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,mBAAmB,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,uEAAuE;IACvE,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAoBD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAYzE;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAOvE;AAgBD,wBAAgB,cAAc,CAC5B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,OAAO,GACZ,aAAa,CA0Bf;AAID,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AA4ID,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,eAAe,GACrB,aAAa,CAQf;AA6DD,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,aAAa,CAAC,CA0DxB;AAkCD,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,aAAa,CAAC,CAgExB"}
@@ -0,0 +1,380 @@
1
+ /**
2
+ * OAuth 2.1 authorization bridge for the hosted HTTP server.
3
+ *
4
+ * Implements the MCP authorization spec (metadata discovery, dynamic
5
+ * client registration, authorization code + PKCE, refresh tokens) on top
6
+ * of the Minicor /auth/signin API. The /authorize endpoint serves a login
7
+ * form; submitting valid Minicor credentials mints an auth code that the
8
+ * client exchanges for sealed tokens wrapping the upstream credentials.
9
+ *
10
+ * Everything is stateless: client IDs, auth codes, and tokens are all
11
+ * TokenSealer blobs, so this works across multiple Cloud Run instances.
12
+ */
13
+ import crypto from "node:crypto";
14
+ import { resolveDefaultApiBase } from "./paths.js";
15
+ // ─── TTLs ────────────────────────────────────────────────────
16
+ const CLIENT_TTL_MS = 365 * 24 * 60 * 60 * 1000;
17
+ const CODE_TTL_MS = 10 * 60 * 1000;
18
+ const ACCESS_TTL_MAX_MS = 60 * 60 * 1000;
19
+ const ACCESS_TTL_MIN_MS = 60 * 1000;
20
+ const REFRESH_TTL_MS = 30 * 24 * 60 * 60 * 1000;
21
+ function json(status, body) {
22
+ return {
23
+ status,
24
+ headers: { "Content-Type": "application/json" },
25
+ body: JSON.stringify(body),
26
+ };
27
+ }
28
+ function oauthError(status, error, description) {
29
+ return json(status, { error, error_description: description });
30
+ }
31
+ // ─── Discovery metadata ──────────────────────────────────────
32
+ export function authorizationServerMetadata(issuer) {
33
+ return json(200, {
34
+ issuer,
35
+ authorization_endpoint: `${issuer}/authorize`,
36
+ token_endpoint: `${issuer}/token`,
37
+ registration_endpoint: `${issuer}/register`,
38
+ response_types_supported: ["code"],
39
+ grant_types_supported: ["authorization_code", "refresh_token"],
40
+ code_challenge_methods_supported: ["S256"],
41
+ token_endpoint_auth_methods_supported: ["none"],
42
+ scopes_supported: ["minicor"],
43
+ });
44
+ }
45
+ export function protectedResourceMetadata(issuer) {
46
+ return json(200, {
47
+ resource: `${issuer}/mcp`,
48
+ authorization_servers: [issuer],
49
+ bearer_methods_supported: ["header"],
50
+ scopes_supported: ["minicor"],
51
+ });
52
+ }
53
+ // ─── Dynamic client registration ─────────────────────────────
54
+ function isAllowedRedirectUri(uri) {
55
+ let parsed;
56
+ try {
57
+ parsed = new URL(uri);
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ if (parsed.protocol === "https:")
63
+ return true;
64
+ if (parsed.protocol !== "http:")
65
+ return false;
66
+ return ["localhost", "127.0.0.1", "[::1]"].includes(parsed.hostname);
67
+ }
68
+ export function handleRegister(config, body) {
69
+ const redirectUris = body?.redirect_uris;
70
+ if (!Array.isArray(redirectUris) ||
71
+ redirectUris.length === 0 ||
72
+ !redirectUris.every((u) => typeof u === "string" && isAllowedRedirectUri(u))) {
73
+ return oauthError(400, "invalid_redirect_uri", "redirect_uris must be a non-empty array of https URLs (or http on localhost).");
74
+ }
75
+ const payload = { redirect_uris: redirectUris };
76
+ const clientId = config.sealer.seal("client", payload, CLIENT_TTL_MS);
77
+ return json(201, {
78
+ client_id: clientId,
79
+ client_id_issued_at: Math.floor(Date.now() / 1000),
80
+ redirect_uris: redirectUris,
81
+ token_endpoint_auth_method: "none",
82
+ grant_types: ["authorization_code", "refresh_token"],
83
+ response_types: ["code"],
84
+ });
85
+ }
86
+ function escapeHtml(value) {
87
+ return value
88
+ .replace(/&/g, "&amp;")
89
+ .replace(/</g, "&lt;")
90
+ .replace(/>/g, "&gt;")
91
+ .replace(/"/g, "&quot;");
92
+ }
93
+ /**
94
+ * Hostname the auth code will be sent to, shown on the login form so users
95
+ * can spot a phishing attempt: dynamic client registration is open, so the
96
+ * redirect target — not the form's own URL — is what identifies the app.
97
+ */
98
+ function redirectHost(redirectUri) {
99
+ try {
100
+ return new URL(redirectUri).hostname;
101
+ }
102
+ catch {
103
+ return "";
104
+ }
105
+ }
106
+ function loginPage(params, error) {
107
+ const hidden = (name, value) => `<input type="hidden" name="${name}" value="${escapeHtml(value)}" />`;
108
+ const host = redirectHost(params.redirect_uri);
109
+ const desc = host
110
+ ? `The application at <strong>${escapeHtml(host)}</strong> is requesting access to your Minicor account. After signing in you will be redirected there.`
111
+ : "An application is requesting access to your Minicor account.";
112
+ return `<!DOCTYPE html>
113
+ <html lang="en">
114
+ <head>
115
+ <meta charset="utf-8" />
116
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
117
+ <title>Minicor — Sign in</title>
118
+ <style>
119
+ *,:before,:after{box-sizing:border-box;margin:0;padding:0}
120
+ body{font-family:'Inter',system-ui,sans-serif;background:#f8f9fc;color:#111;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
121
+ .card{background:#fff;border:1px solid #e2e5f1;border-radius:10px;padding:24px;width:100%;max-width:400px}
122
+ .logo{display:flex;align-items:center;gap:10px;margin-bottom:16px}
123
+ .logo .wordmark{font-size:18px;font-weight:700}
124
+ h2{font-size:16px;font-weight:600;margin-bottom:4px}
125
+ p.desc{font-size:13px;color:#4b5563;margin-bottom:20px}
126
+ .field{margin-bottom:14px}
127
+ .field label{display:block;font-size:12px;font-weight:500;color:#4b5563;margin-bottom:5px}
128
+ .field input[type=email],.field input[type=password]{width:100%;height:36px;padding:0 10px;background:#f3f4f8;border:1px solid #e2e5f1;border-radius:7px;font-size:13px;outline:none}
129
+ .field input:focus{border-color:#646ecb}
130
+ .seg{display:flex;background:#f3f4f8;border-radius:7px;padding:3px;gap:2px}
131
+ .seg label{flex:1;text-align:center;font-size:12px;font-weight:500;border-radius:5px;cursor:pointer;color:#4b5563;user-select:none}
132
+ .seg input{display:none}
133
+ .seg input:checked+span{background:#fff;color:#111;box-shadow:0 1px 3px rgba(0,0,0,.08)}
134
+ .seg label span{display:block;padding:6px 0;border-radius:5px}
135
+ button{width:100%;height:36px;margin-top:6px;background:#646ecb;color:#fff;border:none;border-radius:7px;font-size:13px;font-weight:600;cursor:pointer}
136
+ button:hover{background:#5560c0}
137
+ .error{padding:10px 12px;border-radius:7px;font-size:12px;background:hsl(355 78% 60%/.07);border:1px solid hsl(355 78% 60%/.18);color:hsl(355 78% 60%);margin-bottom:14px}
138
+ </style>
139
+ </head>
140
+ <body>
141
+ <div class="card">
142
+ <div class="logo">
143
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 118 98" width="30" height="25"><rect width="118" height="98" rx="10" ry="10" fill="#646ecb"/><g transform="translate(5.2,-4.6) scale(1.39)" fill="#fff"><path d="M27.4 59L10.6 38.5 27.4 17.7l4.6 3.7L18.2 38.4l13.8 16.9z"/><path d="M49.6 59l-4.6-3.7L58.9 38.3 45.1 21.4l4.6-3.7L66.5 38.3z"/></g></svg>
144
+ <span class="wordmark">Minicor</span>
145
+ </div>
146
+ <h2>Sign in to continue</h2>
147
+ <p class="desc">${desc}</p>
148
+ ${error ? `<div class="error">${escapeHtml(error)}</div>` : ""}
149
+ <form method="POST" action="/authorize">
150
+ ${hidden("client_id", params.client_id)}
151
+ ${hidden("redirect_uri", params.redirect_uri)}
152
+ ${hidden("state", params.state)}
153
+ ${hidden("code_challenge", params.code_challenge)}
154
+ <div class="field">
155
+ <label for="email">Email</label>
156
+ <input id="email" name="email" type="email" placeholder="you@company.com" autocomplete="email" required autofocus />
157
+ </div>
158
+ <div class="field">
159
+ <label for="password">Password</label>
160
+ <input id="password" name="password" type="password" placeholder="••••••••" autocomplete="current-password" required />
161
+ </div>
162
+ <div class="field">
163
+ <label>Region</label>
164
+ <div class="seg">
165
+ <label><input type="radio" name="region" value="us" checked /><span>US</span></label>
166
+ <label><input type="radio" name="region" value="ca" /><span>Canada</span></label>
167
+ </div>
168
+ </div>
169
+ <button type="submit">Sign in</button>
170
+ </form>
171
+ </div>
172
+ </body>
173
+ </html>`;
174
+ }
175
+ function validateAuthorizeParams(config, params) {
176
+ const clientId = params.get("client_id") ?? "";
177
+ const redirectUri = params.get("redirect_uri") ?? "";
178
+ const client = config.sealer.unseal("client", clientId);
179
+ if (!client) {
180
+ return {
181
+ err: oauthError(400, "invalid_client", "Unknown or expired client_id."),
182
+ };
183
+ }
184
+ if (!client.redirect_uris.includes(redirectUri)) {
185
+ return {
186
+ err: oauthError(400, "invalid_request", "redirect_uri does not match the registered redirect URIs."),
187
+ };
188
+ }
189
+ const responseType = params.get("response_type");
190
+ const codeChallenge = params.get("code_challenge") ?? "";
191
+ const challengeMethod = params.get("code_challenge_method") ?? "S256";
192
+ if (responseType !== "code") {
193
+ return {
194
+ err: oauthError(400, "unsupported_response_type", "Only response_type=code is supported."),
195
+ };
196
+ }
197
+ if (!codeChallenge || challengeMethod !== "S256") {
198
+ return {
199
+ err: oauthError(400, "invalid_request", "PKCE with code_challenge_method=S256 is required."),
200
+ };
201
+ }
202
+ return {
203
+ ok: {
204
+ client_id: clientId,
205
+ redirect_uri: redirectUri,
206
+ state: params.get("state") ?? "",
207
+ code_challenge: codeChallenge,
208
+ },
209
+ };
210
+ }
211
+ export function handleAuthorizeGet(config, query) {
212
+ const validated = validateAuthorizeParams(config, query);
213
+ if ("err" in validated)
214
+ return validated.err;
215
+ return {
216
+ status: 200,
217
+ headers: { "Content-Type": "text/html; charset=utf-8" },
218
+ body: loginPage(validated.ok),
219
+ };
220
+ }
221
+ async function upstreamSignIn(apiBase, email, password) {
222
+ const res = await fetch(`${apiBase}/auth/signin`, {
223
+ method: "POST",
224
+ headers: { "Content-Type": "application/json" },
225
+ body: JSON.stringify({ username: email, password }),
226
+ });
227
+ if (!res.ok)
228
+ throw new Error("Invalid email or password.");
229
+ return (await res.json());
230
+ }
231
+ async function upstreamRefresh(apiBase, refreshToken) {
232
+ try {
233
+ const res = await fetch(`${apiBase}/auth/refresh`, {
234
+ method: "POST",
235
+ headers: { "Content-Type": "application/json" },
236
+ body: JSON.stringify({ refreshToken }),
237
+ });
238
+ if (!res.ok)
239
+ return null;
240
+ return (await res.json());
241
+ }
242
+ catch {
243
+ return null;
244
+ }
245
+ }
246
+ function toCredentials(result, region, apiBase) {
247
+ return {
248
+ region,
249
+ apiBase,
250
+ access_token: result.access_token,
251
+ refresh_token: result.refresh_token || null,
252
+ expires_at: Date.now() + result.expires_in * 1000,
253
+ };
254
+ }
255
+ function emailDomainAllowed(config, email) {
256
+ if (config.allowedEmailDomains.length === 0)
257
+ return true;
258
+ const domain = email.split("@")[1]?.toLowerCase() ?? "";
259
+ return config.allowedEmailDomains.includes(domain);
260
+ }
261
+ export async function handleAuthorizePost(config, form) {
262
+ const validated = validateAuthorizeParams(config, new URLSearchParams({
263
+ client_id: form.get("client_id") ?? "",
264
+ redirect_uri: form.get("redirect_uri") ?? "",
265
+ state: form.get("state") ?? "",
266
+ code_challenge: form.get("code_challenge") ?? "",
267
+ response_type: "code",
268
+ }));
269
+ if ("err" in validated)
270
+ return validated.err;
271
+ const params = validated.ok;
272
+ const retry = (message) => ({
273
+ status: 200,
274
+ headers: { "Content-Type": "text/html; charset=utf-8" },
275
+ body: loginPage(params, message),
276
+ });
277
+ const email = (form.get("email") ?? "").trim();
278
+ const password = form.get("password") ?? "";
279
+ const region = form.get("region") === "ca" ? "ca" : "us";
280
+ if (!email || !password) {
281
+ return retry("Email and password are required.");
282
+ }
283
+ if (!emailDomainAllowed(config, email)) {
284
+ return retry("This email domain is not allowed on this server.");
285
+ }
286
+ const apiBase = resolveDefaultApiBase(region);
287
+ let creds;
288
+ try {
289
+ creds = toCredentials(await upstreamSignIn(apiBase, email, password), region, apiBase);
290
+ }
291
+ catch {
292
+ return retry("Sign-in failed. Check your email, password, and region.");
293
+ }
294
+ const codePayload = {
295
+ creds,
296
+ client_id: params.client_id,
297
+ redirect_uri: params.redirect_uri,
298
+ code_challenge: params.code_challenge,
299
+ };
300
+ const code = config.sealer.seal("code", codePayload, CODE_TTL_MS);
301
+ const redirect = new URL(params.redirect_uri);
302
+ redirect.searchParams.set("code", code);
303
+ if (params.state)
304
+ redirect.searchParams.set("state", params.state);
305
+ return {
306
+ status: 302,
307
+ headers: { Location: redirect.toString() },
308
+ body: "",
309
+ };
310
+ }
311
+ // ─── Token endpoint ──────────────────────────────────────────
312
+ function verifyPkce(verifier, challenge) {
313
+ const computed = crypto
314
+ .createHash("sha256")
315
+ .update(verifier, "utf8")
316
+ .digest("base64url");
317
+ const a = Buffer.from(computed);
318
+ const b = Buffer.from(challenge);
319
+ return a.length === b.length && crypto.timingSafeEqual(a, b);
320
+ }
321
+ function issueTokens(config, creds, clientId) {
322
+ const accessTtlMs = Math.min(ACCESS_TTL_MAX_MS, Math.max(ACCESS_TTL_MIN_MS, creds.expires_at - Date.now()));
323
+ const accessPayload = { creds };
324
+ const refreshPayload = { creds, client_id: clientId };
325
+ return json(200, {
326
+ access_token: config.sealer.seal("access", accessPayload, accessTtlMs),
327
+ token_type: "bearer",
328
+ expires_in: Math.floor(accessTtlMs / 1000),
329
+ refresh_token: config.sealer.seal("refresh", refreshPayload, REFRESH_TTL_MS),
330
+ scope: "minicor",
331
+ });
332
+ }
333
+ export async function handleToken(config, form) {
334
+ const grantType = form.get("grant_type");
335
+ if (grantType === "authorization_code") {
336
+ const code = config.sealer.unseal("code", form.get("code") ?? "");
337
+ if (!code) {
338
+ return oauthError(400, "invalid_grant", "Invalid or expired authorization code.");
339
+ }
340
+ const clientId = form.get("client_id") ?? "";
341
+ if (clientId !== code.client_id) {
342
+ return oauthError(400, "invalid_grant", "client_id does not match the authorization code.");
343
+ }
344
+ const redirectUri = form.get("redirect_uri");
345
+ if (redirectUri && redirectUri !== code.redirect_uri) {
346
+ return oauthError(400, "invalid_grant", "redirect_uri does not match the authorization request.");
347
+ }
348
+ const verifier = form.get("code_verifier") ?? "";
349
+ if (!verifier || !verifyPkce(verifier, code.code_challenge)) {
350
+ return oauthError(400, "invalid_grant", "PKCE verification failed.");
351
+ }
352
+ return issueTokens(config, code.creds, code.client_id);
353
+ }
354
+ if (grantType === "refresh_token") {
355
+ const payload = config.sealer.unseal("refresh", form.get("refresh_token") ?? "");
356
+ if (!payload) {
357
+ return oauthError(400, "invalid_grant", "Invalid or expired refresh token.");
358
+ }
359
+ const clientId = form.get("client_id");
360
+ if (clientId && clientId !== payload.client_id) {
361
+ return oauthError(400, "invalid_grant", "client_id does not match the refresh token.");
362
+ }
363
+ const { creds } = payload;
364
+ if (creds.refresh_token) {
365
+ const refreshed = await upstreamRefresh(creds.apiBase, creds.refresh_token);
366
+ if (refreshed) {
367
+ return issueTokens(config, toCredentials({
368
+ ...refreshed,
369
+ refresh_token: refreshed.refresh_token || creds.refresh_token,
370
+ }, creds.region, creds.apiBase), payload.client_id);
371
+ }
372
+ }
373
+ if (Date.now() < creds.expires_at) {
374
+ return issueTokens(config, creds, payload.client_id);
375
+ }
376
+ return oauthError(400, "invalid_grant", "Upstream session expired. Sign in again.");
377
+ }
378
+ return oauthError(400, "unsupported_grant_type", "Use authorization_code or refresh_token.");
379
+ }
380
+ //# sourceMappingURL=http-oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-oauth.js","sourceRoot":"","sources":["../src/http-oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Stateless token sealing for the hosted HTTP server.
3
+ *
4
+ * OAuth artifacts (auth codes, access/refresh tokens, client IDs) are
5
+ * self-contained AES-256-GCM blobs so any Cloud Run instance can verify
6
+ * them without shared storage. The payload carries the upstream Minicor
7
+ * credentials; nothing is persisted server-side.
8
+ */
9
+ export type SealedType = "access" | "refresh" | "code" | "client";
10
+ export declare class TokenSealer {
11
+ private key;
12
+ constructor(secret: string);
13
+ seal(type: SealedType, payload: unknown, ttlMs: number): string;
14
+ /** Returns the payload, or null if the blob is invalid, expired, or of the wrong type. */
15
+ unseal<T>(type: SealedType, blob: string): T | null;
16
+ }
17
+ //# sourceMappingURL=http-seal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-seal.d.ts","sourceRoot":"","sources":["../src/http-seal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAWlE,qBAAa,WAAW;IACtB,OAAO,CAAC,GAAG,CAAS;gBAER,MAAM,EAAE,MAAM;IAS1B,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAgB/D,0FAA0F;IAC1F,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;CAuBpD"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Stateless token sealing for the hosted HTTP server.
3
+ *
4
+ * OAuth artifacts (auth codes, access/refresh tokens, client IDs) are
5
+ * self-contained AES-256-GCM blobs so any Cloud Run instance can verify
6
+ * them without shared storage. The payload carries the upstream Minicor
7
+ * credentials; nothing is persisted server-side.
8
+ */
9
+ import crypto from "node:crypto";
10
+ const IV_LENGTH = 12;
11
+ const TAG_LENGTH = 16;
12
+ export class TokenSealer {
13
+ key;
14
+ constructor(secret) {
15
+ if (!secret || secret.length < 32) {
16
+ throw new Error("MCP_TOKEN_SECRET must be set to a random string of at least 32 characters. Generate one with: openssl rand -base64 48");
17
+ }
18
+ this.key = crypto.createHash("sha256").update(secret, "utf8").digest();
19
+ }
20
+ seal(type, payload, ttlMs) {
21
+ const envelope = {
22
+ t: type,
23
+ exp: Date.now() + ttlMs,
24
+ p: payload,
25
+ };
26
+ const iv = crypto.randomBytes(IV_LENGTH);
27
+ const cipher = crypto.createCipheriv("aes-256-gcm", this.key, iv);
28
+ const ciphertext = Buffer.concat([
29
+ cipher.update(JSON.stringify(envelope), "utf8"),
30
+ cipher.final(),
31
+ ]);
32
+ const tag = cipher.getAuthTag();
33
+ return Buffer.concat([iv, tag, ciphertext]).toString("base64url");
34
+ }
35
+ /** Returns the payload, or null if the blob is invalid, expired, or of the wrong type. */
36
+ unseal(type, blob) {
37
+ try {
38
+ const raw = Buffer.from(blob, "base64url");
39
+ if (raw.length <= IV_LENGTH + TAG_LENGTH)
40
+ return null;
41
+ const iv = raw.subarray(0, IV_LENGTH);
42
+ const tag = raw.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
43
+ const ciphertext = raw.subarray(IV_LENGTH + TAG_LENGTH);
44
+ const decipher = crypto.createDecipheriv("aes-256-gcm", this.key, iv);
45
+ decipher.setAuthTag(tag);
46
+ const plaintext = Buffer.concat([
47
+ decipher.update(ciphertext),
48
+ decipher.final(),
49
+ ]).toString("utf8");
50
+ const envelope = JSON.parse(plaintext);
51
+ if (envelope.t !== type)
52
+ return null;
53
+ if (typeof envelope.exp !== "number" || Date.now() > envelope.exp) {
54
+ return null;
55
+ }
56
+ return envelope.p;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ }
63
+ //# sourceMappingURL=http-seal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-seal.js","sourceRoot":"","sources":["../src/http-seal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AAUjC,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,MAAM,OAAO,WAAW;IACd,GAAG,CAAS;IAEpB,YAAY,MAAc;QACxB,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACb,uHAAuH,CACxH,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;IACzE,CAAC;IAED,IAAI,CAAC,IAAgB,EAAE,OAAgB,EAAE,KAAa;QACpD,MAAM,QAAQ,GAAmB;YAC/B,CAAC,EAAE,IAAI;YACP,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;YACvB,CAAC,EAAE,OAAO;SACX,CAAC;QACF,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAClE,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;YAC/B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;YAC/C,MAAM,CAAC,KAAK,EAAE;SACf,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACpE,CAAC;IAED,0FAA0F;IAC1F,MAAM,CAAI,IAAgB,EAAE,IAAY;QACtC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YAC3C,IAAI,GAAG,CAAC,MAAM,IAAI,SAAS,GAAG,UAAU;gBAAE,OAAO,IAAI,CAAC;YACtD,MAAM,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;YACtC,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC,CAAC;YAC5D,MAAM,UAAU,GAAG,GAAG,CAAC,QAAQ,CAAC,SAAS,GAAG,UAAU,CAAC,CAAC;YACxD,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACtE,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YACzB,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;gBAC9B,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC;gBAC3B,QAAQ,CAAC,KAAK,EAAE;aACjB,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAmB,CAAC;YACzD,IAAI,QAAQ,CAAC,CAAC,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YACrC,IAAI,OAAO,QAAQ,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC;gBAClE,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,QAAQ,CAAC,CAAM,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Minicor MCP — Hosted HTTP server
4
+ *
5
+ * Serves the MCP server over the Streamable HTTP transport with an
6
+ * OAuth 2.1 flow bridged to Minicor account sign-in, so it can be added
7
+ * to Claude (custom connectors) or any remote-MCP client by URL.
8
+ *
9
+ * Stateless by design for Cloud Run: every /mcp request builds a fresh
10
+ * server bound to the credentials sealed inside the bearer token, and no
11
+ * OAuth state is kept in memory, so any instance can serve any request.
12
+ */
13
+ export interface HttpServerOptions {
14
+ port?: number;
15
+ host?: string;
16
+ /** Public base URL (issuer). Defaults to MCP_PUBLIC_URL, else derived per request from Host / X-Forwarded-Proto. */
17
+ publicUrl?: string;
18
+ /** Secret for sealing tokens. Defaults to MCP_TOKEN_SECRET. */
19
+ tokenSecret?: string;
20
+ /** Comma-separated in env (ALLOWED_EMAIL_DOMAINS); empty = rely on upstream auth only. */
21
+ allowedEmailDomains?: string[];
22
+ }
23
+ export interface RunningHttpServer {
24
+ port: number;
25
+ url: string;
26
+ close: () => Promise<void>;
27
+ }
28
+ export declare function startHttpServer(opts?: HttpServerOptions): Promise<RunningHttpServer>;
29
+ //# sourceMappingURL=http-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;GAUG;AAoBH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oHAAoH;IACpH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0FAA0F;IAC1F,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AA0DD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,wBAAsB,eAAe,CACnC,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAkL5B"}