@kadoa/mcp 0.2.4 → 0.2.5-rc.2

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 (3) hide show
  1. package/README.md +22 -0
  2. package/dist/index.js +200 -21
  3. package/package.json +8 -2
package/README.md CHANGED
@@ -144,6 +144,8 @@ Get your API key from [kadoa.com/settings](https://kadoa.com/settings).
144
144
  | `delete_workflow` | Delete a workflow |
145
145
  | `approve_workflow` | Approve and activate a workflow |
146
146
  | `update_workflow` | Update workflow configuration and schema |
147
+ | `team_list` | List all teams you belong to and see which is active |
148
+ | `team_switch` | Switch the active team by name or ID |
147
149
 
148
150
  ## Usage Examples
149
151
 
@@ -214,6 +216,26 @@ delete_workflow for each, confirming before proceeding.
214
216
  - Verify the MCP server is configured correctly
215
217
  - Restart your MCP client
216
218
 
219
+ ## Deploying the Remote Server
220
+
221
+ The remote MCP server at `mcp.kadoa.com` runs as a Docker container on GKE, deployed from the `kadoa-backend` monorepo.
222
+
223
+ **To deploy a new version:**
224
+
225
+ 1. Publish a new `@kadoa/mcp` version to npm (`npm publish`)
226
+ 2. In `kadoa-backend`, update `infra/docker/mcp/package.json` to the new version and run `bun install` to regenerate the lockfile
227
+ 3. Merge to `main` — the CI pipeline (`main-build-deploy.yml`) builds and pushes the Docker image automatically
228
+ 4. Trigger the **Deploy to Production** workflow (`deploy-prod.yml`) with:
229
+ - **Target cluster:** `gcp`
230
+ - **Deployment scope:** `mcp`
231
+ - **Image tag:** the tag from step 3 (shown in the build summary)
232
+ - **Method:** `kubectl`
233
+
234
+ Infrastructure files in `kadoa-backend`:
235
+ - `infra/docker/mcp/Dockerfile.mcp-server` — Docker image definition
236
+ - `infra/docker/mcp/package.json` — pinned `@kadoa/mcp` version
237
+ - `infra/cdk8s/mcp/` — Kubernetes manifests (cdk8s)
238
+
217
239
  ## Development
218
240
 
219
241
  ```bash
package/dist/index.js CHANGED
@@ -53301,7 +53301,7 @@ async function exchangeSupabaseCode(code, codeVerifier) {
53301
53301
  const data = await res.json();
53302
53302
  return data.access_token;
53303
53303
  }
53304
- async function resolveApiKey2(supabaseJwt) {
53304
+ async function fetchUserTeams(supabaseJwt) {
53305
53305
  const kadoaApiUrl = process.env.KADOA_PUBLIC_API_URI || "https://api.kadoa.com";
53306
53306
  const userRes = await fetch(`${kadoaApiUrl}/v4/user`, {
53307
53307
  headers: { Authorization: `Bearer ${supabaseJwt}` }
@@ -53314,7 +53314,14 @@ async function resolveApiKey2(supabaseJwt) {
53314
53314
  if (!userData.teams?.length) {
53315
53315
  throw new Error("User has no teams");
53316
53316
  }
53317
- const teamId = userData.teams[0].id;
53317
+ return userData.teams.map((t) => ({
53318
+ id: t.id,
53319
+ name: t.name,
53320
+ memberRole: t.memberRole
53321
+ }));
53322
+ }
53323
+ async function resolveTeamApiKey(supabaseJwt, teamId) {
53324
+ const kadoaApiUrl = process.env.KADOA_PUBLIC_API_URI || "https://api.kadoa.com";
53318
53325
  const keyRes = await fetch(`${kadoaApiUrl}/v4/team/${teamId}/api-key`, {
53319
53326
  headers: { Authorization: `Bearer ${supabaseJwt}` }
53320
53327
  });
@@ -53454,21 +53461,20 @@ class KadoaOAuthProvider {
53454
53461
  pendingAuths.delete(state);
53455
53462
  try {
53456
53463
  const supabaseJwt = await exchangeSupabaseCode(code, pending.supabaseCodeVerifier);
53457
- const apiKey = await resolveApiKey2(supabaseJwt);
53458
- const mcpCode = randomToken();
53459
- authCodes.set(mcpCode, {
53460
- apiKey,
53461
- codeChallenge: pending.params.codeChallenge,
53462
- clientId: pending.client.client_id,
53463
- redirectUri: pending.params.redirectUri,
53464
- expiresAt: Date.now() + 10 * 60 * 1000
53465
- });
53466
- const redirectUrl = new URL(pending.params.redirectUri);
53467
- redirectUrl.searchParams.set("code", mcpCode);
53468
- if (pending.params.state) {
53469
- redirectUrl.searchParams.set("state", pending.params.state);
53464
+ const teams = await fetchUserTeams(supabaseJwt);
53465
+ if (teams.length === 1) {
53466
+ const apiKey = await resolveTeamApiKey(supabaseJwt, teams[0].id);
53467
+ this.completeAuthFlow(pending, apiKey, res);
53468
+ return;
53470
53469
  }
53471
- res.redirect(redirectUrl.toString());
53470
+ const selectionToken = randomToken();
53471
+ pendingTeamSelections.set(selectionToken, {
53472
+ supabaseJwt,
53473
+ teams,
53474
+ pending,
53475
+ expiresAt: Date.now() + TEAM_SELECTION_TTL
53476
+ });
53477
+ res.type("html").send(renderTeamSelectionPage(teams, selectionToken));
53472
53478
  } catch (error48) {
53473
53479
  console.error("Auth callback error:", error48);
53474
53480
  const redirectUrl = new URL(pending.params.redirectUri);
@@ -53480,14 +53486,183 @@ class KadoaOAuthProvider {
53480
53486
  res.redirect(redirectUrl.toString());
53481
53487
  }
53482
53488
  }
53489
+ async handleTeamSelection(req, res) {
53490
+ const { token, teamId } = req.body;
53491
+ if (!token || !teamId) {
53492
+ res.status(400).send("Missing token or teamId");
53493
+ return;
53494
+ }
53495
+ const entry = pendingTeamSelections.get(token);
53496
+ if (!entry) {
53497
+ res.status(400).send("Unknown or expired team selection token");
53498
+ return;
53499
+ }
53500
+ if (entry.expiresAt < Date.now()) {
53501
+ pendingTeamSelections.delete(token);
53502
+ res.status(400).send("Team selection expired — please log in again");
53503
+ return;
53504
+ }
53505
+ if (!entry.teams.some((t) => t.id === teamId)) {
53506
+ res.status(403).send("Invalid team selection");
53507
+ return;
53508
+ }
53509
+ pendingTeamSelections.delete(token);
53510
+ try {
53511
+ const apiKey = await resolveTeamApiKey(entry.supabaseJwt, teamId);
53512
+ this.completeAuthFlow(entry.pending, apiKey, res);
53513
+ } catch (error48) {
53514
+ console.error("Team selection error:", error48);
53515
+ const redirectUrl = new URL(entry.pending.params.redirectUri);
53516
+ redirectUrl.searchParams.set("error", "server_error");
53517
+ redirectUrl.searchParams.set("error_description", error48 instanceof Error ? error48.message : "Failed to resolve team API key");
53518
+ if (entry.pending.params.state) {
53519
+ redirectUrl.searchParams.set("state", entry.pending.params.state);
53520
+ }
53521
+ res.redirect(redirectUrl.toString());
53522
+ }
53523
+ }
53524
+ completeAuthFlow(pending, apiKey, res) {
53525
+ const mcpCode = randomToken();
53526
+ authCodes.set(mcpCode, {
53527
+ apiKey,
53528
+ codeChallenge: pending.params.codeChallenge,
53529
+ clientId: pending.client.client_id,
53530
+ redirectUri: pending.params.redirectUri,
53531
+ expiresAt: Date.now() + 10 * 60 * 1000
53532
+ });
53533
+ const redirectUrl = new URL(pending.params.redirectUri);
53534
+ redirectUrl.searchParams.set("code", mcpCode);
53535
+ if (pending.params.state) {
53536
+ redirectUrl.searchParams.set("state", pending.params.state);
53537
+ }
53538
+ res.redirect(redirectUrl.toString());
53539
+ }
53483
53540
  }
53484
- var clients, pendingAuths, authCodes, accessTokens, refreshTokens, ACCESS_TOKEN_TTL = 3600, kadoaClientsStore;
53541
+ function renderTeamSelectionPage(teams, selectionToken) {
53542
+ const teamButtons = teams.map((t) => `
53543
+ <button type="submit" name="teamId" value="${t.id}" class="team-btn">
53544
+ <span class="team-name">${escapeHtml(t.name)}</span>
53545
+ ${t.memberRole ? `<span class="team-role">${escapeHtml(t.memberRole.toLowerCase())}</span>` : ""}
53546
+ </button>`).join(`
53547
+ `);
53548
+ return `<!DOCTYPE html>
53549
+ <html lang="en">
53550
+ <head>
53551
+ <meta charset="utf-8" />
53552
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
53553
+ <title>Select Team - Kadoa</title>
53554
+ <style>
53555
+ * { margin: 0; padding: 0; box-sizing: border-box; }
53556
+
53557
+ body {
53558
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
53559
+ background: oklch(1 0 0);
53560
+ color: oklch(0.17 0.02 228);
53561
+ min-height: 100vh;
53562
+ display: flex;
53563
+ align-items: center;
53564
+ justify-content: center;
53565
+ }
53566
+
53567
+ .container {
53568
+ width: 100%;
53569
+ max-width: 420px;
53570
+ padding: 2rem;
53571
+ }
53572
+
53573
+ .logo {
53574
+ text-align: center;
53575
+ margin-bottom: 2rem;
53576
+ }
53577
+
53578
+ h1 {
53579
+ font-size: 1.25rem;
53580
+ font-weight: 600;
53581
+ text-align: center;
53582
+ margin-bottom: 0.5rem;
53583
+ color: oklch(0.17 0.02 228);
53584
+ }
53585
+
53586
+ .subtitle {
53587
+ text-align: center;
53588
+ color: oklch(0.56 0.02 228);
53589
+ font-size: 0.875rem;
53590
+ margin-bottom: 1.5rem;
53591
+ }
53592
+
53593
+ .team-btn {
53594
+ width: 100%;
53595
+ display: flex;
53596
+ align-items: center;
53597
+ justify-content: space-between;
53598
+ padding: 0.875rem 1rem;
53599
+ margin-bottom: 0.5rem;
53600
+ background: oklch(1 0 0);
53601
+ border: 1px solid oklch(0.5 0.02 228 / 0.4);
53602
+ border-radius: 0.3rem;
53603
+ color: oklch(0.17 0.02 228);
53604
+ font-size: 15px;
53605
+ cursor: pointer;
53606
+ transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
53607
+ box-shadow: 0px 1px 1px 0px oklch(0.68 0.01 60.13 / 0.11);
53608
+ }
53609
+
53610
+ .team-btn:hover {
53611
+ background: oklch(0.96 0 286);
53612
+ border-color: oklch(0.7 0.18 42);
53613
+ }
53614
+
53615
+ .team-btn:active {
53616
+ background: oklch(0.72 0.23 54 / 0.13);
53617
+ }
53618
+
53619
+ .team-name { font-weight: 500; }
53620
+
53621
+ .team-role {
53622
+ font-size: 13px;
53623
+ color: oklch(0.56 0.02 228 / 0.67);
53624
+ text-transform: capitalize;
53625
+ }
53626
+ </style>
53627
+ </head>
53628
+ <body>
53629
+ <div class="container">
53630
+ <div class="logo">
53631
+ <svg width="108" height="32" viewBox="0 0 108 32" fill="none" xmlns="http://www.w3.org/2000/svg">
53632
+ <g clip-path="url(#clip0)">
53633
+ <path d="M4.5 27V20.0059C4.49955 18.6288 3.38499 17.5105 2.00781 17.5059L-0.00585938 17.5V14.5L2.00781 14.4941C3.38499 14.4895 4.49955 13.3712 4.5 11.9941V5C4.5 2.51472 6.51472 0.5 9 0.5H12V3.5H9C8.17157 3.5 7.5 4.17157 7.5 5V11.9941C7.49977 13.5757 6.82719 14.9966 5.75781 16C6.82719 17.0034 7.49977 18.4243 7.5 20.0059V27C7.5 27.8284 8.17157 28.5 9 28.5H12V31.5H9C6.51472 31.5 4.5 29.4853 4.5 27Z" fill="#FD7412"/>
53634
+ <path d="M103.5 27V20.0059C103.5 18.6288 104.615 17.5105 105.992 17.5059L108.006 17.5V14.5L105.992 14.4941C104.615 14.4895 103.5 13.3712 103.5 11.9941V5C103.5 2.51472 101.485 0.5 99 0.5H96V3.5H99C99.8284 3.5 100.5 4.17157 100.5 5V11.9941C100.5 13.5757 101.173 14.9966 102.242 16C101.173 17.0034 100.5 18.4243 100.5 20.0059V27C100.5 27.8284 99.8284 28.5 99 28.5H96V31.5H99C101.485 31.5 103.5 29.4853 103.5 27Z" fill="#FD7412"/>
53635
+ <path d="M85.2346 26.308C84.0026 26.308 82.92 26.0093 81.9866 25.412C81.0533 24.8147 80.3253 23.9653 79.8026 22.864C79.28 21.7627 79.0186 20.4373 79.0186 18.888C79.0186 17.3573 79.28 16.0413 79.8026 14.94C80.3253 13.8387 81.0533 12.9987 81.9866 12.42C82.92 11.8227 84.0026 11.524 85.2346 11.524C86.3733 11.524 87.3906 11.804 88.2866 12.364C89.2013 12.9053 89.7986 13.6427 90.0786 14.576H89.7706L90.1066 11.804H94.1666C94.1106 12.42 94.0546 13.0453 93.9986 13.68C93.9613 14.296 93.9426 14.9027 93.9426 15.5V26H89.7426L89.7146 23.34H90.0506C89.752 24.236 89.1546 24.9547 88.2586 25.496C87.3626 26.0373 86.3546 26.308 85.2346 26.308ZM86.5226 23.116C87.4933 23.116 88.2773 22.7707 88.8746 22.08C89.472 21.3893 89.7706 20.3253 89.7706 18.888C89.7706 17.4507 89.472 16.396 88.8746 15.724C88.2773 15.052 87.4933 14.716 86.5226 14.716C85.552 14.716 84.768 15.052 84.1706 15.724C83.5733 16.396 83.2746 17.4507 83.2746 18.888C83.2746 20.3253 83.564 21.3893 84.1426 22.08C84.74 22.7707 85.5333 23.116 86.5226 23.116Z" fill="#18181B"/>
53636
+ <path d="M70.1002 26.308C68.5882 26.308 67.2722 26.0093 66.1522 25.412C65.0509 24.796 64.1922 23.9373 63.5762 22.836C62.9789 21.7347 62.6802 20.4187 62.6802 18.888C62.6802 17.376 62.9789 16.0693 63.5762 14.968C64.1922 13.8667 65.0509 13.0173 66.1522 12.42C67.2722 11.8227 68.5882 11.524 70.1002 11.524C71.6122 11.524 72.9282 11.8227 74.0482 12.42C75.1682 13.0173 76.0269 13.8667 76.6242 14.968C77.2402 16.0693 77.5482 17.376 77.5482 18.888C77.5482 20.4187 77.2402 21.7347 76.6242 22.836C76.0269 23.9373 75.1682 24.796 74.0482 25.412C72.9282 26.0093 71.6122 26.308 70.1002 26.308ZM70.1002 23.116C71.0709 23.116 71.8362 22.7707 72.3962 22.08C72.9749 21.3893 73.2642 20.3253 73.2642 18.888C73.2642 17.4507 72.9749 16.396 72.3962 15.724C71.8362 15.052 71.0709 14.716 70.1002 14.716C69.1295 14.716 68.3549 15.052 67.7762 15.724C67.2162 16.396 66.9362 17.4507 66.9362 18.888C66.9362 20.3253 67.2162 21.3893 67.7762 22.08C68.3549 22.7707 69.1295 23.116 70.1002 23.116Z" fill="#18181B"/>
53637
+ <path d="M51.8208 26.308C50.5888 26.308 49.4968 26.0093 48.5448 25.412C47.6115 24.8147 46.8741 23.9653 46.3328 22.864C45.8101 21.7627 45.5488 20.4373 45.5488 18.888C45.5488 17.3573 45.8101 16.0413 46.3328 14.94C46.8555 13.8387 47.5928 12.9987 48.5448 12.42C49.4968 11.8227 50.5888 11.524 51.8208 11.524C52.9408 11.524 53.9395 11.7947 54.8168 12.336C55.7128 12.8587 56.3101 13.568 56.6088 14.464H56.2448V5.392H60.4728V26H56.3008V23.228H56.6648C56.3661 24.1613 55.7688 24.908 54.8728 25.468C53.9768 26.028 52.9595 26.308 51.8208 26.308ZM53.0808 23.116C54.0515 23.116 54.8355 22.7707 55.4328 22.08C56.0301 21.3893 56.3288 20.3253 56.3288 18.888C56.3288 17.4507 56.0301 16.396 55.4328 15.724C54.8355 15.052 54.0515 14.716 53.0808 14.716C52.1101 14.716 51.3168 15.052 50.7008 15.724C50.1035 16.396 49.8048 17.4507 49.8048 18.888C49.8048 20.3253 50.1035 21.3893 50.7008 22.08C51.3168 22.7707 52.1101 23.116 53.0808 23.116Z" fill="#18181B"/>
53638
+ <path d="M34.6334 26.308C33.4014 26.308 32.3187 26.0093 31.3854 25.412C30.4521 24.8147 29.7241 23.9653 29.2014 22.864C28.6787 21.7627 28.4174 20.4373 28.4174 18.888C28.4174 17.3573 28.6787 16.0413 29.2014 14.94C29.7241 13.8387 30.4521 12.9987 31.3854 12.42C32.3187 11.8227 33.4014 11.524 34.6334 11.524C35.7721 11.524 36.7894 11.804 37.6854 12.364C38.6001 12.9053 39.1974 13.6427 39.4774 14.576H39.1694L39.5054 11.804H43.5654C43.5094 12.42 43.4534 13.0453 43.3974 13.68C43.3601 14.296 43.3414 14.9027 43.3414 15.5V26H39.1414L39.1134 23.34H39.4494C39.1507 24.236 38.5534 24.9547 37.6574 25.496C36.7614 26.0373 35.7534 26.308 34.6334 26.308ZM35.9214 23.116C36.8921 23.116 37.6761 22.7707 38.2734 22.08C38.8707 21.3893 39.1694 20.3253 39.1694 18.888C39.1694 17.4507 38.8707 16.396 38.2734 15.724C37.6761 15.052 36.8921 14.716 35.9214 14.716C34.9507 14.716 34.1667 15.052 33.5694 15.724C32.9721 16.396 32.6734 17.4507 32.6734 18.888C32.6734 20.3253 32.9627 21.3893 33.5414 22.08C34.1387 22.7707 34.9321 23.116 35.9214 23.116Z" fill="#18181B"/>
53639
+ <path d="M13.736 26V5.392H17.964V17.712H18.02L23.284 11.804H28.324L21.52 19.364V17.824L28.688 26H23.508L18.02 19.84H17.964V26H13.736Z" fill="#18181B"/>
53640
+ </g>
53641
+ <defs><clipPath id="clip0"><rect width="108" height="32" fill="white"/></clipPath></defs>
53642
+ </svg>
53643
+ </div>
53644
+ <h1>Select a team</h1>
53645
+ <p class="subtitle">Choose which team to connect with this MCP session</p>
53646
+ <form method="POST" action="/team-select">
53647
+ <input type="hidden" name="token" value="${selectionToken}" />
53648
+ ${teamButtons}
53649
+ </form>
53650
+ </div>
53651
+ </body>
53652
+ </html>`;
53653
+ }
53654
+ function escapeHtml(str) {
53655
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
53656
+ }
53657
+ var clients, pendingAuths, pendingTeamSelections, authCodes, accessTokens, refreshTokens, TEAM_SELECTION_TTL, ACCESS_TOKEN_TTL = 3600, kadoaClientsStore;
53485
53658
  var init_auth2 = __esm(() => {
53486
53659
  clients = new Map;
53487
53660
  pendingAuths = new Map;
53661
+ pendingTeamSelections = new Map;
53488
53662
  authCodes = new Map;
53489
53663
  accessTokens = new Map;
53490
53664
  refreshTokens = new Map;
53665
+ TEAM_SELECTION_TTL = 10 * 60 * 1000;
53491
53666
  kadoaClientsStore = {
53492
53667
  getClient(clientId) {
53493
53668
  return clients.get(clientId);
@@ -53511,7 +53686,8 @@ __export(exports_http, {
53511
53686
  startHttpServer: () => startHttpServer
53512
53687
  });
53513
53688
  import { randomUUID as randomUUID2 } from "node:crypto";
53514
- function resolveApiKey3(req) {
53689
+ import express8 from "express";
53690
+ function resolveApiKey2(req) {
53515
53691
  return req.auth?.extra?.apiKey;
53516
53692
  }
53517
53693
  async function startHttpServer() {
@@ -53530,13 +53706,16 @@ async function startHttpServer() {
53530
53706
  app.get("/auth/callback", (req, res) => {
53531
53707
  provider.handleAuthCallback(req, res);
53532
53708
  });
53709
+ app.post("/team-select", express8.urlencoded({ extended: false }), (req, res) => {
53710
+ provider.handleTeamSelection(req, res);
53711
+ });
53533
53712
  app.get("/health", (_req, res) => {
53534
53713
  res.json({ status: "ok", sessions: Object.keys(sessions).length });
53535
53714
  });
53536
53715
  const bearerAuth = requireBearerAuth({ verifier: provider });
53537
53716
  app.post("/mcp", bearerAuth, async (req, res) => {
53538
53717
  const sessionId = req.headers["mcp-session-id"];
53539
- const apiKey = resolveApiKey3(req);
53718
+ const apiKey = resolveApiKey2(req);
53540
53719
  if (!apiKey) {
53541
53720
  res.status(401).json({
53542
53721
  jsonrpc: "2.0",
@@ -53593,7 +53772,7 @@ async function startHttpServer() {
53593
53772
  });
53594
53773
  app.get("/mcp", bearerAuth, async (req, res) => {
53595
53774
  const sessionId = req.headers["mcp-session-id"];
53596
- const apiKey = resolveApiKey3(req);
53775
+ const apiKey = resolveApiKey2(req);
53597
53776
  if (!apiKey) {
53598
53777
  res.status(401).send("Unauthorized: Bearer token required");
53599
53778
  return;
@@ -53610,7 +53789,7 @@ async function startHttpServer() {
53610
53789
  });
53611
53790
  app.delete("/mcp", bearerAuth, async (req, res) => {
53612
53791
  const sessionId = req.headers["mcp-session-id"];
53613
- const apiKey = resolveApiKey3(req);
53792
+ const apiKey = resolveApiKey2(req);
53614
53793
  if (!apiKey) {
53615
53794
  res.status(401).send("Unauthorized: Bearer token required");
53616
53795
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadoa/mcp",
3
- "version": "0.2.4",
3
+ "version": "0.2.5-rc.2",
4
4
  "description": "Kadoa MCP Server — manage workflows from Claude Desktop, Cursor, and other MCP clients",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,6 +39,12 @@
39
39
  "bun-types": "^1.3.3",
40
40
  "typescript": "^5.9.3"
41
41
  },
42
- "keywords": ["kadoa", "mcp", "model-context-protocol", "web-scraping", "data-extraction"],
42
+ "keywords": [
43
+ "kadoa",
44
+ "mcp",
45
+ "model-context-protocol",
46
+ "web-scraping",
47
+ "data-extraction"
48
+ ],
43
49
  "license": "MIT"
44
50
  }