@vibetocloud/mcp 1.0.0 → 1.0.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.
package/dist/index.js CHANGED
@@ -57455,25 +57455,24 @@ var ValidateRequestGuard = class {
57455
57455
  }
57456
57456
  async canActivate(context) {
57457
57457
  const req = context.switchToHttp().getRequest();
57458
+ const authHeader = req.headers.authorization;
57459
+ if (authHeader?.startsWith("Bearer ")) {
57460
+ const token = authHeader.slice(7);
57461
+ try {
57462
+ const publicKey = await this.publicKeyCache.get();
57463
+ const payload = import_jsonwebtoken.default.verify(token, publicKey, { algorithms: ["RS256"] });
57464
+ req.auth = { kind: "user", payload };
57465
+ return true;
57466
+ } catch {
57467
+ }
57468
+ }
57458
57469
  const internalServiceKey = process.env["INTERNAL_SERVICE_KEY"];
57459
57470
  const sentKey = req.headers["x-internal-secret"] ?? req.headers["x-service-key"];
57460
57471
  if (internalServiceKey && sentKey === internalServiceKey) {
57461
57472
  req.auth = { kind: "internal" };
57462
57473
  return true;
57463
57474
  }
57464
- const authHeader = req.headers.authorization;
57465
- if (!authHeader?.startsWith("Bearer ")) {
57466
- throw new import_common3.UnauthorizedException("Missing or invalid Authorization header");
57467
- }
57468
- const token = authHeader.slice(7);
57469
- try {
57470
- const publicKey = await this.publicKeyCache.get();
57471
- const payload = import_jsonwebtoken.default.verify(token, publicKey, { algorithms: ["RS256"] });
57472
- req.auth = { kind: "user", payload };
57473
- return true;
57474
- } catch {
57475
- throw new import_common3.UnauthorizedException("Invalid or expired token");
57476
- }
57475
+ throw new import_common3.UnauthorizedException("Missing or invalid Authorization header");
57477
57476
  }
57478
57477
  };
57479
57478
  ValidateRequestGuard = __decorateClass([
@@ -57626,6 +57625,7 @@ var DeployClient = class {
57626
57625
  return null;
57627
57626
  }
57628
57627
  }
57628
+ /** First page only (default page size) — callers needing more should call the history endpoint directly with `take`/`cursor`. */
57629
57629
  async history(appId) {
57630
57630
  try {
57631
57631
  const res = await fetch(`${this.baseUrl}/deploy/history/${encodeURIComponent(appId)}`, {
@@ -57636,7 +57636,7 @@ var DeployClient = class {
57636
57636
  return [];
57637
57637
  }
57638
57638
  const json = await res.json();
57639
- return json.data ?? [];
57639
+ return json.data?.items ?? [];
57640
57640
  } catch {
57641
57641
  return [];
57642
57642
  }
@@ -57672,6 +57672,21 @@ var DeployClient = class {
57672
57672
  return null;
57673
57673
  }
57674
57674
  }
57675
+ async cancel(options) {
57676
+ try {
57677
+ const res = await fetch(`${this.baseUrl}/deploy/cancel/${encodeURIComponent(options.buildId)}`, {
57678
+ method: "POST",
57679
+ headers: this.headers
57680
+ });
57681
+ if (!res.ok) {
57682
+ return null;
57683
+ }
57684
+ const json = await res.json();
57685
+ return json.data;
57686
+ } catch {
57687
+ return null;
57688
+ }
57689
+ }
57675
57690
  async restoreBackup(appId, backupId, environment) {
57676
57691
  try {
57677
57692
  const res = await fetch(`${this.baseUrl}/applications/${encodeURIComponent(appId)}/backups/${encodeURIComponent(backupId)}/restore`, {
@@ -57715,6 +57730,9 @@ var UserClient = class {
57715
57730
  getUser(id) {
57716
57731
  return this.request("GET", `/users/${id}`);
57717
57732
  }
57733
+ getUserByEmail(email2) {
57734
+ return this.request("GET", `/users?email=${encodeURIComponent(email2)}`);
57735
+ }
57718
57736
  createUser(options) {
57719
57737
  return this.request("POST", "/users", options);
57720
57738
  }
@@ -57864,6 +57882,14 @@ var AccountClient = class {
57864
57882
  const env = environments.length ? `&environments=${encodeURIComponent(environments.join(","))}` : "";
57865
57883
  return this.request("GET", `/entitlements/active?orgId=${encodeURIComponent(orgId)}${env}`);
57866
57884
  }
57885
+ /** Per-application capabilities (environments/backup tier scoped to one application). */
57886
+ getAppActiveCapabilities(orgId, appId, environments = []) {
57887
+ const env = environments.length ? `&environments=${encodeURIComponent(environments.join(","))}` : "";
57888
+ return this.request(
57889
+ "GET",
57890
+ `/entitlements/app-active?orgId=${encodeURIComponent(orgId)}&appId=${encodeURIComponent(appId)}${env}`
57891
+ );
57892
+ }
57867
57893
  /** List purchasable environments (staging/production) with their Stripe prices. */
57868
57894
  listCatalogEnvironments() {
57869
57895
  return this.request("GET", "/catalog/environments").then((d) => d ?? []);
@@ -57894,6 +57920,16 @@ var AccountClient = class {
57894
57920
  delta
57895
57921
  }).then((d) => d !== null);
57896
57922
  }
57923
+ /**
57924
+ * Adjust the org's quantity-billed application-count item by ±1.
57925
+ * Called by application-service on application create / delete (Pay-as-you-go only).
57926
+ */
57927
+ adjustApplicationQuantity(orgId, delta) {
57928
+ return this.request("POST", "/entitlements/application-quantity", {
57929
+ orgId,
57930
+ delta
57931
+ }).then((d) => d !== null);
57932
+ }
57897
57933
  };
57898
57934
 
57899
57935
  // libs/shared/src/utils/validate-identifier.ts
@@ -57905,6 +57941,43 @@ var ENVIRONMENT_NAMES = ["dev", "staging", "production"];
57905
57941
  // libs/shared/src/utils/org-ownership.ts
57906
57942
  var import_common6 = __toESM(require_common2());
57907
57943
 
57944
+ // libs/shared/src/utils/jwt-lifetime.ts
57945
+ function decodeJwtLifetime(token) {
57946
+ const parts = token.split(".");
57947
+ if (parts.length !== 3 || !parts[1]) {
57948
+ return null;
57949
+ }
57950
+ try {
57951
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
57952
+ const { iat, exp } = payload;
57953
+ if (typeof iat !== "number" || typeof exp !== "number") {
57954
+ return null;
57955
+ }
57956
+ return { iat, exp };
57957
+ } catch {
57958
+ return null;
57959
+ }
57960
+ }
57961
+ function isTokenDueForRefresh(token, nowMs = Date.now()) {
57962
+ const lifetime = decodeJwtLifetime(token);
57963
+ if (!lifetime) {
57964
+ return false;
57965
+ }
57966
+ const nowSeconds = nowMs / 1e3;
57967
+ const halfLife = lifetime.iat + (lifetime.exp - lifetime.iat) / 2;
57968
+ return nowSeconds > halfLife && nowSeconds < lifetime.exp;
57969
+ }
57970
+
57971
+ // apps/mcp-server/src/lib/token-refresh.ts
57972
+ var authClient = new AuthClient(config2.services.authServiceUrl);
57973
+ async function maybeRefreshToken(token) {
57974
+ if (!isTokenDueForRefresh(token)) {
57975
+ return null;
57976
+ }
57977
+ const result = await authClient.refresh({ token });
57978
+ return result?.token ?? null;
57979
+ }
57980
+
57908
57981
  // apps/mcp-server/src/tools/account.ts
57909
57982
  var client = new AccountClient(config2.services.accountServiceUrl);
57910
57983
  var orgRegisterTool = {
@@ -58092,7 +58165,7 @@ var tools2 = [
58092
58165
  ];
58093
58166
 
58094
58167
  // apps/mcp-server/src/tools/auth.ts
58095
- var authClient = new AuthClient(config2.services.authServiceUrl);
58168
+ var authClient2 = new AuthClient(config2.services.authServiceUrl);
58096
58169
  var orgAuthenticateTool = {
58097
58170
  name: "org_authenticate",
58098
58171
  description: "Verify an existing session token. Returns the authenticated user and organisation. Use this to confirm a stored token is still valid before making other API calls.",
@@ -58112,7 +58185,7 @@ async function handleOrgAuthenticate(args) {
58112
58185
  if (!token) {
58113
58186
  return fail({ status: 401, message: "Please sign in first. Use org_request_otp and org_verify_otp to authenticate." });
58114
58187
  }
58115
- const result = await authClient.verify({ token });
58188
+ const result = await authClient2.verify({ token });
58116
58189
  if (!result) {
58117
58190
  return fail({ status: 401, message: "Your session has expired or is invalid. Please sign in again with org_request_otp." });
58118
58191
  }
@@ -58422,9 +58495,43 @@ ${tail}`;
58422
58495
  }]
58423
58496
  };
58424
58497
  }
58498
+ var orgDeployCancelTool = {
58499
+ name: "org_deploy_cancel",
58500
+ description: 'Cancel a deployment that is currently running. Only works while the build is in the deploy phase; the build is marked failed with reason "Deployment cancelled by user".',
58501
+ inputSchema: {
58502
+ type: "object",
58503
+ properties: {
58504
+ token: { type: "string", description: "JWT session token from org_verify_otp" },
58505
+ buildId: { type: "string", description: "Build ID returned by org_deploy" }
58506
+ },
58507
+ required: ["token", "buildId"]
58508
+ }
58509
+ };
58510
+ async function handleOrgDeployCancel(args) {
58511
+ const token = args["token"];
58512
+ if (!token) {
58513
+ return notAuthenticatedResult();
58514
+ }
58515
+ const client3 = new DeployClient(config2.services.deployServiceUrl, token);
58516
+ const build = await client3.cancel({ buildId: args["buildId"] });
58517
+ if (!build) {
58518
+ return fail({ status: 404, message: "No cancellable deployment found for this build \u2014 it may have already finished, or not reached the deploy phase yet." });
58519
+ }
58520
+ return {
58521
+ content: [{
58522
+ type: "text",
58523
+ text: JSON.stringify({
58524
+ success: true,
58525
+ data: { id: build.id, status: build.status, message: 'Cancellation requested. Poll org_deploy_status \u2014 the build will end as failed with reason "Deployment cancelled by user".' },
58526
+ error: null
58527
+ })
58528
+ }]
58529
+ };
58530
+ }
58425
58531
  var tools5 = [
58426
58532
  { definition: orgDeployTool, handle: handleOrgDeploy },
58427
- { definition: orgDeployStatusTool, handle: handleOrgDeployStatus }
58533
+ { definition: orgDeployStatusTool, handle: handleOrgDeployStatus },
58534
+ { definition: orgDeployCancelTool, handle: handleOrgDeployCancel }
58428
58535
  ];
58429
58536
 
58430
58537
  // apps/mcp-server/src/tools/dev-sync.ts
@@ -58556,37 +58663,35 @@ var orgSetupGitCredentialsTool = {
58556
58663
  inputSchema: {
58557
58664
  type: "object",
58558
58665
  properties: {
58559
- userId: {
58560
- type: "string",
58561
- description: "ID of the user to generate the credential for"
58562
- },
58563
- orgId: {
58666
+ token: {
58564
58667
  type: "string",
58565
- description: "ID of the organisation"
58668
+ description: "JWT session token from org_verify_otp"
58566
58669
  },
58567
58670
  label: {
58568
58671
  type: "string",
58569
58672
  description: 'Label to identify this credential, e.g. "laptop" or "work" (default: "default")'
58570
58673
  }
58571
58674
  },
58572
- required: ["userId", "orgId"]
58675
+ required: ["token"]
58573
58676
  }
58574
58677
  };
58575
58678
  async function handleOrgSetupGitCredentials(args) {
58576
- const userId = args["userId"];
58577
- const orgId = args["orgId"];
58679
+ const sessionToken = args["token"];
58680
+ if (!sessionToken) {
58681
+ return notAuthenticatedResult();
58682
+ }
58578
58683
  const label = args["label"] ?? "default";
58579
58684
  try {
58580
58685
  const response = await fetch(`${config2.services.userServiceUrl}/git-credentials`, {
58581
58686
  method: "POST",
58582
- headers: { "Content-Type": "application/json" },
58583
- body: JSON.stringify({ userId, orgId, label })
58687
+ headers: authHeaders(sessionToken),
58688
+ body: JSON.stringify({ label })
58584
58689
  });
58585
58690
  if (!response.ok) {
58586
58691
  return fail({ status: response.status, message: "Failed to create git credential" });
58587
58692
  }
58588
58693
  const json = await response.json();
58589
- const { token } = json.data;
58694
+ const credentialToken = json.data.token;
58590
58695
  const gitHost = config2.services.applicationServiceUrl.replace(/^https?:\/\//, "");
58591
58696
  const instructions = [
58592
58697
  `Git credentials created (label: "${label}").`,
@@ -58594,7 +58699,7 @@ async function handleOrgSetupGitCredentials(args) {
58594
58699
  `Run these commands to configure git:`,
58595
58700
  ``,
58596
58701
  ` git config --global credential.helper store`,
58597
- ` echo "https://user:${token}@${gitHost}" >> ~/.git-credentials`,
58702
+ ` echo "https://user:${credentialToken}@${gitHost}" >> ~/.git-credentials`,
58598
58703
  ``,
58599
58704
  `After that, clone a repository with:`,
58600
58705
  ``,
@@ -58603,7 +58708,7 @@ async function handleOrgSetupGitCredentials(args) {
58603
58708
  `The token is shown only once. Store it securely. To revoke it, call org_revoke_git_credentials.`
58604
58709
  ].join("\n");
58605
58710
  return {
58606
- content: [{ type: "text", text: JSON.stringify({ success: true, data: { token, instructions }, error: null }) }]
58711
+ content: [{ type: "text", text: JSON.stringify({ success: true, data: { token: credentialToken, instructions }, error: null }) }]
58607
58712
  };
58608
58713
  } catch {
58609
58714
  return fail({ code: "UPSTREAM_ERROR", message: "Failed to connect to user service", hint: "The user service is unreachable. Retry shortly." });
@@ -58615,25 +58720,28 @@ var orgRevokeGitCredentialsTool = {
58615
58720
  inputSchema: {
58616
58721
  type: "object",
58617
58722
  properties: {
58618
- credentialId: {
58723
+ token: {
58619
58724
  type: "string",
58620
- description: "ID of the credential to revoke"
58725
+ description: "JWT session token from org_verify_otp"
58621
58726
  },
58622
- userId: {
58727
+ credentialId: {
58623
58728
  type: "string",
58624
- description: "ID of the user who owns the credential"
58729
+ description: "ID of the credential to revoke"
58625
58730
  }
58626
58731
  },
58627
- required: ["credentialId", "userId"]
58732
+ required: ["token", "credentialId"]
58628
58733
  }
58629
58734
  };
58630
58735
  async function handleOrgRevokeGitCredentials(args) {
58736
+ const sessionToken = args["token"];
58737
+ if (!sessionToken) {
58738
+ return notAuthenticatedResult();
58739
+ }
58631
58740
  const credentialId = args["credentialId"];
58632
- const userId = args["userId"];
58633
58741
  try {
58634
58742
  const response = await fetch(
58635
- `${config2.services.userServiceUrl}/git-credentials/${credentialId}?userId=${encodeURIComponent(userId)}`,
58636
- { method: "DELETE" }
58743
+ `${config2.services.userServiceUrl}/git-credentials/${credentialId}`,
58744
+ { method: "DELETE", headers: authHeaders(sessionToken) }
58637
58745
  );
58638
58746
  if (!response.ok) {
58639
58747
  return fail({ status: response.status, message: "Failed to revoke credential" });
@@ -58647,23 +58755,27 @@ async function handleOrgRevokeGitCredentials(args) {
58647
58755
  }
58648
58756
  var orgListGitCredentialsTool = {
58649
58757
  name: "org_list_git_credentials",
58650
- description: "List all git credentials for a user",
58758
+ description: "List all git credentials for the authenticated user",
58651
58759
  inputSchema: {
58652
58760
  type: "object",
58653
58761
  properties: {
58654
- userId: {
58762
+ token: {
58655
58763
  type: "string",
58656
- description: "ID of the user"
58764
+ description: "JWT session token from org_verify_otp"
58657
58765
  }
58658
58766
  },
58659
- required: ["userId"]
58767
+ required: ["token"]
58660
58768
  }
58661
58769
  };
58662
58770
  async function handleOrgListGitCredentials(args) {
58663
- const userId = args["userId"];
58771
+ const sessionToken = args["token"];
58772
+ if (!sessionToken) {
58773
+ return notAuthenticatedResult();
58774
+ }
58664
58775
  try {
58665
58776
  const response = await fetch(
58666
- `${config2.services.userServiceUrl}/git-credentials?userId=${encodeURIComponent(userId)}`
58777
+ `${config2.services.userServiceUrl}/git-credentials`,
58778
+ { headers: authHeaders(sessionToken) }
58667
58779
  );
58668
58780
  if (!response.ok) {
58669
58781
  return fail({ status: response.status, message: "Failed to list credentials" });
@@ -58786,10 +58898,10 @@ var tools9 = [
58786
58898
  ];
58787
58899
 
58788
58900
  // apps/mcp-server/src/tools/refresh-token.ts
58789
- var authClient2 = new AuthClient(config2.services.authServiceUrl);
58901
+ var authClient3 = new AuthClient(config2.services.authServiceUrl);
58790
58902
  var orgRefreshTokenTool = {
58791
58903
  name: "org_refresh_token",
58792
- description: "Refresh an existing session token to extend its validity for another 8 hours. Call this when the token has less than 1 hour remaining before expiry.",
58904
+ description: "Refresh an existing session token, extending its validity for a full new lifetime (7 days by default). Rarely needed: tokens past half of their lifetime are refreshed automatically on any tool call. Use this only to force a refresh explicitly.",
58793
58905
  inputSchema: {
58794
58906
  type: "object",
58795
58907
  properties: {
@@ -58806,7 +58918,7 @@ async function handleOrgRefreshToken(args) {
58806
58918
  if (!token) {
58807
58919
  return fail({ status: 400, message: "token is required" });
58808
58920
  }
58809
- const result = await authClient2.refresh({ token });
58921
+ const result = await authClient3.refresh({ token });
58810
58922
  if (!result) {
58811
58923
  return fail({ status: 401, message: "Token refresh failed. Please sign in again with org_request_otp." });
58812
58924
  }
@@ -59406,17 +59518,39 @@ function createServer() {
59406
59518
  ListToolsRequestSchema,
59407
59519
  () => Promise.resolve({ tools: allTools.map((t) => t.definition) })
59408
59520
  );
59409
- server.setRequestHandler(CallToolRequestSchema, (request) => {
59410
- const { name, arguments: args } = request.params;
59521
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
59522
+ const { name, arguments: rawArgs } = request.params;
59411
59523
  const handler = toolMap.get(name);
59412
59524
  if (!handler) {
59413
- return Promise.resolve(fail({
59525
+ return fail({
59414
59526
  code: "UNKNOWN_TOOL",
59415
59527
  message: `Unknown tool: ${name}`,
59416
59528
  hint: "This tool does not exist. List available tools and use one of them."
59417
- }));
59529
+ });
59530
+ }
59531
+ const args = { ...rawArgs ?? {} };
59532
+ let refreshedToken = null;
59533
+ if (name !== "org_refresh_token" && typeof args["token"] === "string") {
59534
+ refreshedToken = await maybeRefreshToken(args["token"]).catch(() => null);
59535
+ if (refreshedToken) {
59536
+ args["token"] = refreshedToken;
59537
+ }
59418
59538
  }
59419
- return handler(args ?? {}).catch(fromCatch);
59539
+ const result = await handler(args).catch(fromCatch);
59540
+ if (refreshedToken) {
59541
+ result.content = [
59542
+ ...Array.isArray(result.content) ? result.content : [],
59543
+ {
59544
+ type: "text",
59545
+ text: JSON.stringify({
59546
+ tokenRefreshed: true,
59547
+ token: refreshedToken,
59548
+ note: "Your session token was refreshed automatically. Use this new token for all subsequent tool calls."
59549
+ })
59550
+ }
59551
+ ];
59552
+ }
59553
+ return result;
59420
59554
  });
59421
59555
  return server;
59422
59556
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibetocloud/mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Claude Code MCP server and skills for deploying applications on vibetocloud",
5
5
  "bin": {
6
6
  "vibetocloud-mcp": "./dist/index.js"
@@ -415,3 +415,31 @@ git push --set-upstream <remote from config> <current branch>
415
415
  ```
416
416
 
417
417
  Tell the user: "✅ Your code is on vibetocloud. Run `/vibetocloud-deploy` to trigger your first deployment."
418
+
419
+ ---
420
+
421
+ ## Step 7 — Write CLAUDE.md
422
+
423
+ Check whether `CLAUDE.md` exists in the project root using the Read tool.
424
+
425
+ If it exists, read it and check whether it already contains a "VibeToCloud" section. If it does, skip this step.
426
+
427
+ If it does not exist, or exists but has no VibeToCloud section, append the following block (create the file if needed):
428
+
429
+ ```markdown
430
+ ## VibeToCloud development
431
+
432
+ This project is deployed on VibeToCloud. The `docker-compose.yml` defines how the application runs **in the cloud** — you do not need Docker locally and should never run `docker compose up`.
433
+
434
+ Use the VibeToCloud MCP skills to work with the project:
435
+ - Push code and deploy: `/vibe`
436
+ - Debug running services: `/vibetocloud-debug`
437
+
438
+ All interaction with running containers happens through the MCP tools, not locally.
439
+ ```
440
+
441
+ Commit the CLAUDE.md if it was created or changed:
442
+ ```bash
443
+ git add CLAUDE.md
444
+ git diff --cached --quiet || git commit -m "chore: add VibeToCloud development instructions to CLAUDE.md"
445
+ ```