@amerged/ohmyhost-mcp 0.1.14 → 0.1.17

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
@@ -2,7 +2,7 @@ import { createRequire as __ohmyhostCreateRequire } from "node:module"; const re
2
2
  import {
3
3
  createOhmyhostMcpServer,
4
4
  ohmyhostMcpHandler
5
- } from "./chunk-S2LJAGEP.js";
5
+ } from "./chunk-SVJRXROG.js";
6
6
  export {
7
7
  createOhmyhostMcpServer,
8
8
  ohmyhostMcpHandler
@@ -26,7 +26,7 @@ import {
26
26
  serverIdentityOf,
27
27
  setNegotiatedProtocolVersion,
28
28
  validateEnvelopeMeta
29
- } from "./chunk-S2LJAGEP.js";
29
+ } from "./chunk-SVJRXROG.js";
30
30
 
31
31
  // node_modules/.pnpm/@modelcontextprotocol+server@2.0.0/node_modules/@modelcontextprotocol/server/dist/stdio.mjs
32
32
  var StdioServerTransport = class {
@@ -799,7 +799,7 @@ function exact2(v, keys) {
799
799
  // apps/product-cli/package.json
800
800
  var package_default = {
801
801
  name: "@ohmyhost/product-cli",
802
- version: "0.1.14",
802
+ version: "0.1.17",
803
803
  private: true,
804
804
  ohmyhost: {
805
805
  deployment: "production",
@@ -1571,7 +1571,7 @@ var PROBLEM_POLICY = {
1571
1571
  cloudflare_zone_not_bound: {
1572
1572
  status: 409,
1573
1573
  retryable: false,
1574
- action: "Configure the project domain first with 'ohmyhost domain paid plan' then 'ohmyhost domain paid apply', or configure its sender domain with 'ohmyhost mail domain set'. Then request Cloudflare authorization for that domain's zone."
1574
+ action: "Configure the project domain first with 'ohmyhost domain paid plan' then 'ohmyhost domain paid apply', or configure its sender domain with 'ohmyhost mail setup'. Then request Cloudflare authorization for that domain's zone."
1575
1575
  },
1576
1576
  github_connection_required: {
1577
1577
  status: 409,
@@ -1726,6 +1726,11 @@ var PROBLEM_POLICY = {
1726
1726
  retryable: false,
1727
1727
  action: "Select the current succeeded dev deployment and target prod."
1728
1728
  },
1729
+ mail_capacity_unavailable: {
1730
+ status: 503,
1731
+ retryable: true,
1732
+ action: "Report this request ID through feedback. The stored mail setup continues automatically once ohmyho.st has capacity; follow mail status and keep the original idempotency key."
1733
+ },
1729
1734
  mail_domain_conflict: {
1730
1735
  status: 409,
1731
1736
  retryable: false,
@@ -1734,13 +1739,23 @@ var PROBLEM_POLICY = {
1734
1739
  mail_domain_required: {
1735
1740
  status: 409,
1736
1741
  retryable: false,
1737
- action: "Run 'ohmyhost mail domain set' for this project, then plan again."
1742
+ action: "This app declares mail.enabled. Configure the project's own sender domain with 'ohmyhost mail setup' using its Prod environment ID, or set mail.enabled to false if the app sends no mail, then plan again."
1738
1743
  },
1739
1744
  storage_jurisdiction_conflict: {
1740
1745
  status: 409,
1741
1746
  retryable: false,
1742
1747
  action: "Set storage.jurisdiction in ohmyhost.yaml to the project's hosting region shown by project status, then plan again."
1743
1748
  },
1749
+ shared_data_requires_promotion: {
1750
+ status: 409,
1751
+ retryable: false,
1752
+ action: "Plan and deploy this commit to Dev, then run 'ohmyhost deployment promote plan' to reach Prod."
1753
+ },
1754
+ production_deployment_required: {
1755
+ status: 409,
1756
+ retryable: false,
1757
+ action: "Deploy to Prod first: run 'ohmyhost plan --environment prod' and deploy that plan, or promote the current Dev deployment with 'ohmyhost deployment promote plan'. Then plan the domain again."
1758
+ },
1744
1759
  framework_conversion_required: {
1745
1760
  status: 409,
1746
1761
  retryable: false,
@@ -1888,6 +1903,12 @@ var parseCloudflareDnsAuthorizationStatus = (value) => {
1888
1903
  }
1889
1904
  return input;
1890
1905
  };
1906
+ var parseDevAccessState = (value) => {
1907
+ const input = exactRecord2(value, ["project_id", "mode", "share_url"]);
1908
+ if (!isUlid(input["project_id"]) || input["mode"] !== "protected" && input["mode"] !== "public" || input["mode"] === "public" && input["share_url"] !== null || input["share_url"] !== null && (typeof input["share_url"] !== "string" || !/^https:\/\/dev-[a-z0-9]+(?:-[a-z0-9]+){0,4}\.(?:dev\.)?check\.omh\.st\/\.ohmyhost\/dev-access\/redeem\?stage=dev&ticket=[A-Za-z0-9_-]{43}$/u.test(input["share_url"])))
1909
+ throw new ResponseContractError();
1910
+ return input;
1911
+ };
1891
1912
  var parseDevAccessTicket = (value) => {
1892
1913
  const input = exactRecord2(value, [
1893
1914
  "ticket_id",
@@ -1966,8 +1987,10 @@ var parseSafeProblem = (value) => {
1966
1987
  const retryAfter = input["retry_after_seconds"];
1967
1988
  if (retryAfter !== void 0 && (code !== "rate_limited" || typeof retryAfter !== "number" || !Number.isSafeInteger(retryAfter) || retryAfter < 1 || retryAfter > 86400))
1968
1989
  throw new ResponseContractError();
1990
+ const docsUrl = String(input["type"]);
1969
1991
  return {
1970
1992
  requestId: input["request_id"],
1993
+ ...docsUrl.startsWith("https://docs.ohmyho.st/errors/") ? { docsUrl } : {},
1971
1994
  code,
1972
1995
  retryable: policy.retryable,
1973
1996
  suggestedAction: (code === "framework_conversion_required" ? parseFrameworkConversionDetail(input["detail"]) ?? policy.action : policy.action) + (retryAfter === void 0 ? "" : ` Retry after ${String(retryAfter)} seconds.`),
@@ -2137,12 +2160,16 @@ var HELP_USAGE = Object.freeze({
2137
2160
  "token list": "ohmyhost token list --organization ULID [--after KEY_ID] --json",
2138
2161
  "token revoke": "ohmyhost token revoke --organization ULID --key KEY_ID --yes --json",
2139
2162
  "feedback submit": "ohmyhost feedback submit --organization ULID --kind bug|issue|feature_request --title TITLE --description REDACTED_REPORT [--project ULID] [--environment ULID] [--operation ULID] [--error-code CODE] [--client-version VERSION] --idempotency-key KEY --json",
2140
- "project create": "ohmyhost project create --organization ULID --name NAME [--data-mode shared|isolated] [--region us|eu] --idempotency-key KEY --json (the region is chosen once: us is the default, eu places the database, files and builds in the EU; it cannot be changed later)",
2163
+ "project create": "ohmyhost project create --organization ULID --name NAME [--data-mode shared|isolated] [--dev-access-mode protected|public] [--region us|eu] --idempotency-key KEY --json (the region is chosen once: us is the default, eu places the database, files and builds in the EU; it cannot be changed later)",
2141
2164
  "project list": "ohmyhost project list [--cursor ULID] [--limit LIMIT] --json",
2142
2165
  "project context": "ohmyhost project context --project ULID --json",
2143
2166
  "project notes set": "ohmyhost project notes set --project ULID --version NUMBER --markdown TEXT --idempotency-key KEY --json (no credentials or signed URLs)",
2144
2167
  "project status": "ohmyhost project status --project ULID --json",
2145
2168
  "project dev-access create": "ohmyhost project dev-access create --project ULID --json",
2169
+ "project dev-share link": "ohmyhost project dev-share link --project ULID --json",
2170
+ "project dev-share rotate": "ohmyhost project dev-share rotate --project ULID --idempotency-key KEY --yes --json",
2171
+ "project dev-share revoke": "ohmyhost project dev-share revoke --project ULID --idempotency-key KEY --yes --json",
2172
+ "project dev-access mode": "ohmyhost project dev-access mode --project ULID --mode protected|public --idempotency-key KEY --yes --json",
2146
2173
  "project handle check": "ohmyhost project handle check --handle HANDLE --json (is this address free? answers with a reason and free alternatives; the address becomes HANDLE.check.omh.st)",
2147
2174
  "project handle set": "ohmyhost project handle set --project ULID --handle HANDLE --if-match ETAG --idempotency-key KEY --json (moves the project to a free address; the old one stops working and anyone may claim it)",
2148
2175
  "database compute set": "ohmyhost database compute set --project ULID --environment dev|prod --profile standard|performance --idempotency-key KEY --yes [--wait] --json\n standard: Free 0.25 CU/1 GB/60s idle, Paid 0.5 CU/2 GB/60s idle. performance: Paid 1 CU/4 GB/300s idle, 2.5x database compute credits per equal active minute; longer idle time also consumes credits.",
@@ -2163,8 +2190,8 @@ var HELP_USAGE = Object.freeze({
2163
2190
  "domain paid apply": "ohmyhost domain paid apply --project ULID --hostname HOST --idempotency-key KEY --yes --json",
2164
2191
  "domain paid status": "ohmyhost domain paid status --project ULID --json",
2165
2192
  "domain paid delete": "ohmyhost domain paid delete --project ULID --hostname HOST --idempotency-key KEY --yes --json",
2166
- plan: "ohmyhost plan --project ULID --commit SHA --json",
2167
- deploy: "ohmyhost deploy --project ULID --plan-id ULID --idempotency-key KEY --yes [--wait] --json",
2193
+ plan: "ohmyhost plan --project ULID --commit SHA [--environment dev|prod] --json",
2194
+ deploy: "ohmyhost deploy --project ULID (--plan-id ULID | --commit SHA [--environment dev|prod]) --idempotency-key KEY --yes [--wait] --json",
2168
2195
  logs: "ohmyhost logs OPERATION_ULID --follow --json",
2169
2196
  "deployment logs": "ohmyhost deployment logs --project ULID --deployment ULID --follow --json",
2170
2197
  "rollback plan": "ohmyhost rollback plan --project ULID --deployment DEPLOYMENT_ULID --json",
@@ -2186,7 +2213,8 @@ var HELP_USAGE = Object.freeze({
2186
2213
  "mail messages get": "ohmyhost mail messages get --project ULID --environment ULID --message ULID --json",
2187
2214
  "mail messages retry": "ohmyhost mail messages retry --project ULID --environment ULID --message ULID --idempotency-key KEY --json",
2188
2215
  "mail domain set": "ohmyhost mail domain set --project ULID --environment ULID --domain DOMAIN --sending true --receiving false --idempotency-key KEY --json",
2189
- "mail domain status": "ohmyhost mail domain status --project ULID --environment ULID --json"
2216
+ "mail domain status": "ohmyhost mail domain status --project ULID --environment ULID --json",
2217
+ "mail domain delete": "ohmyhost mail domain delete --project ULID --environment ULID --idempotency-key KEY --yes --json"
2190
2218
  });
2191
2219
  var HELP_COMMANDS = Object.freeze(Object.entries(HELP_USAGE).map(([command, usage]) => Object.freeze({ command, usage })));
2192
2220
  var SIGN_IN_WINDOW_MS = 30 * 60 * 1e3;
@@ -3584,6 +3612,83 @@ var getProjectHandleAvailability = (parameters, options) => {
3584
3612
  ...params
3585
3613
  });
3586
3614
  };
3615
+ var revokeProjectDevShareLink = (parameters, options) => {
3616
+ const params = buildClientParams([parameters], [
3617
+ {
3618
+ args: [
3619
+ { in: "headers", key: "X-Request-Id" },
3620
+ { in: "headers", key: "Idempotency-Key" },
3621
+ { in: "path", key: "project_id" }
3622
+ ]
3623
+ }
3624
+ ]);
3625
+ return (options?.client ?? client).delete({
3626
+ responseStyle: "data",
3627
+ security: [{ scheme: "bearer", type: "http" }],
3628
+ url: "/v1/projects/{project_id}/dev-share-link",
3629
+ ...options,
3630
+ ...params
3631
+ });
3632
+ };
3633
+ var ensureProjectDevShareLink = (parameters, options) => {
3634
+ const params = buildClientParams([parameters], [
3635
+ {
3636
+ args: [
3637
+ { in: "headers", key: "X-Request-Id" },
3638
+ { in: "path", key: "project_id" }
3639
+ ]
3640
+ }
3641
+ ]);
3642
+ return (options?.client ?? client).post({
3643
+ responseStyle: "data",
3644
+ security: [{ scheme: "bearer", type: "http" }],
3645
+ url: "/v1/projects/{project_id}/dev-share-link",
3646
+ ...options,
3647
+ ...params
3648
+ });
3649
+ };
3650
+ var rotateProjectDevShareLink = (parameters, options) => {
3651
+ const params = buildClientParams([parameters], [
3652
+ {
3653
+ args: [
3654
+ { in: "headers", key: "X-Request-Id" },
3655
+ { in: "headers", key: "Idempotency-Key" },
3656
+ { in: "path", key: "project_id" }
3657
+ ]
3658
+ }
3659
+ ]);
3660
+ return (options?.client ?? client).post({
3661
+ responseStyle: "data",
3662
+ security: [{ scheme: "bearer", type: "http" }],
3663
+ url: "/v1/projects/{project_id}/dev-share-link:rotate",
3664
+ ...options,
3665
+ ...params
3666
+ });
3667
+ };
3668
+ var setProjectDevAccessMode = (parameters, options) => {
3669
+ const params = buildClientParams([parameters], [
3670
+ {
3671
+ args: [
3672
+ { in: "headers", key: "X-Request-Id" },
3673
+ { in: "headers", key: "Idempotency-Key" },
3674
+ { in: "path", key: "project_id" },
3675
+ { key: "setDevAccessModeRequest", map: "body" }
3676
+ ]
3677
+ }
3678
+ ]);
3679
+ return (options?.client ?? client).put({
3680
+ responseStyle: "data",
3681
+ security: [{ scheme: "bearer", type: "http" }],
3682
+ url: "/v1/projects/{project_id}/dev-access-mode",
3683
+ ...options,
3684
+ ...params,
3685
+ headers: {
3686
+ "Content-Type": "application/json",
3687
+ ...options?.headers,
3688
+ ...params.headers
3689
+ }
3690
+ });
3691
+ };
3587
3692
  var createProjectDevAccessTicket = (parameters, options) => {
3588
3693
  const params = buildClientParams([parameters], [
3589
3694
  {
@@ -4254,6 +4359,25 @@ var streamOperationEvents = (parameters, options) => {
4254
4359
  ...params
4255
4360
  });
4256
4361
  };
4362
+ var deleteManagedMailDomain = (parameters, options) => {
4363
+ const params = buildClientParams([parameters], [
4364
+ {
4365
+ args: [
4366
+ { in: "headers", key: "X-Request-Id" },
4367
+ { in: "headers", key: "Idempotency-Key" },
4368
+ { in: "path", key: "project_id" },
4369
+ { in: "path", key: "environment_id" }
4370
+ ]
4371
+ }
4372
+ ]);
4373
+ return (options?.client ?? client).delete({
4374
+ responseStyle: "data",
4375
+ security: [{ scheme: "bearer", type: "http" }],
4376
+ url: "/v1/projects/{project_id}/environments/{environment_id}/mail-domain",
4377
+ ...options,
4378
+ ...params
4379
+ });
4380
+ };
4257
4381
  var getManagedMailDomain = (parameters, options) => {
4258
4382
  const params = buildClientParams([parameters], [
4259
4383
  {
@@ -4480,6 +4604,14 @@ function assertManagedMailResponse(value, input) {
4480
4604
  invalid3();
4481
4605
  return;
4482
4606
  }
4607
+ if (input.action === "domain_delete") {
4608
+ exact3(r, ["status", "domain", "dns_records"]);
4609
+ if (r["status"] !== "deleted")
4610
+ invalid3();
4611
+ domain(r["domain"]);
4612
+ records(r["dns_records"]);
4613
+ return;
4614
+ }
4483
4615
  if (input.action === "messages_list") {
4484
4616
  exact3(r, ["items"]);
4485
4617
  if (!Array.isArray(r["items"]) || r["items"].length > 50)
@@ -4608,6 +4740,8 @@ function requestManagedMail(client2, input) {
4608
4740
  return verifyManagedMailWebhook({ ...scope, "Idempotency-Key": input.idempotencyKey }, { client: client2 });
4609
4741
  case "webhook_disable":
4610
4742
  return disableManagedMailReceiving({ ...scope, "Idempotency-Key": input.idempotencyKey }, { client: client2 });
4743
+ case "domain_delete":
4744
+ return deleteManagedMailDomain({ ...scope, "Idempotency-Key": input.idempotencyKey }, { client: client2 });
4611
4745
  case "messages_list":
4612
4746
  return listManagedMailMessages({ ...scope, ...input.after ? { after: input.after } : {} }, { client: client2 });
4613
4747
  case "message_get":
@@ -5204,6 +5338,25 @@ var GeneratedSdkLocalMcpClient = class {
5204
5338
  getProject(projectId) {
5205
5339
  return getProject({ project_id: projectId }, { client: this.#client });
5206
5340
  }
5341
+ async ensureDevShareLink(projectId) {
5342
+ const result = parseDevAccessState(await ensureProjectDevShareLink({ project_id: projectId }, { client: this.#client }));
5343
+ if (result.project_id !== projectId)
5344
+ throw new TypeError("Unexpected Dev share project");
5345
+ return result;
5346
+ }
5347
+ async setDevAccessMode(input) {
5348
+ return parseDevAccessState(await setProjectDevAccessMode({
5349
+ project_id: input.projectId,
5350
+ "Idempotency-Key": input.idempotencyKey,
5351
+ setDevAccessModeRequest: { mode: input.mode }
5352
+ }, { client: this.#client }));
5353
+ }
5354
+ async rotateDevShareLink(input) {
5355
+ return parseDevAccessState(await rotateProjectDevShareLink({ project_id: input.projectId, "Idempotency-Key": input.idempotencyKey }, { client: this.#client }));
5356
+ }
5357
+ async revokeDevShareLink(input) {
5358
+ return parseDevAccessState(await revokeProjectDevShareLink({ project_id: input.projectId, "Idempotency-Key": input.idempotencyKey }, { client: this.#client }));
5359
+ }
5207
5360
  async createDevAccessTicket(projectId) {
5208
5361
  const ticket = parseDevAccessTicket(await createProjectDevAccessTicket({ project_id: projectId }, { client: this.#client }));
5209
5362
  if (ticket.project_id !== projectId)
@@ -5314,6 +5467,7 @@ var GeneratedSdkLocalMcpClient = class {
5314
5467
  organization_id: input.organizationId,
5315
5468
  name: input.name,
5316
5469
  ...input.dataMode === void 0 ? {} : { data_mode: input.dataMode },
5470
+ ...input.devAccessMode === void 0 ? {} : { dev_access_mode: input.devAccessMode },
5317
5471
  ...input.region === void 0 ? {} : { region: input.region }
5318
5472
  }
5319
5473
  }, { client: this.#client });
@@ -5339,7 +5493,10 @@ var GeneratedSdkLocalMcpClient = class {
5339
5493
  }, { client: this.#client });
5340
5494
  }
5341
5495
  planDeployment(input) {
5342
- return planDeployment({ project_id: input.projectId, planDeploymentRequest: { commit_sha: input.commitSha } }, { client: this.#client });
5496
+ return planDeployment({
5497
+ project_id: input.projectId,
5498
+ planDeploymentRequest: { commit_sha: input.commitSha, environment: input.environment }
5499
+ }, { client: this.#client });
5343
5500
  }
5344
5501
  createDeployment(input) {
5345
5502
  return createDeployment({
@@ -5521,8 +5678,8 @@ function createLocalOhmyhostMcpServer(dependencies) {
5521
5678
  idempotencyKey: input.idempotency_key
5522
5679
  }), mutationAnnotations());
5523
5680
  registerProductTool(server, dependencies, "domain_cloudflare_status", "Read the project's customer DNS authorization state, zone, scopes and expiry without credentials. authorized is not DNS/TLS readiness. For a Paid hostname, use domain_paid_apply with the original hostname/key after consent, then domain_paid_status. If DNS/TLS is pending, tell the customer to ask their agent again after 60 minutes; do not repeat correct records or create another deployment.", external_exports.object({ project_id: identifier2 }), (client2, input) => client2.getCloudflareDnsStatus(input.project_id));
5524
- registerProductTool(server, dependencies, "domain_paid_plan", "Plan a customer-owned production hostname and return the manual CNAME/validation instructions. This does not activate or change DNS. Free projects use their platform domain without customer DNS authorization.", external_exports.object({ project_id: identifier2, hostname: domainName }), (client2, input) => client2.planPaidDomain({ projectId: input.project_id, hostname: input.hostname }));
5525
- registerProductTool(server, dependencies, "domain_paid_apply", "Activate the explicitly requested customer hostname. Requires Paid; the backend may reuse only its scoped customer OAuth grant. After domain_cloudflare_status is authorized, replay the original hostname/key to reconcile its exact DNS records. Otherwise return the manual DNS records to the human. Preserve the original hostname/key after uncertainty; no arbitrary DNS or provider credentials are accepted.", external_exports.object({
5681
+ registerProductTool(server, dependencies, "domain_paid_plan", "Plan a customer-owned production hostname and return the manual CNAME/validation instructions. This does not activate or change DNS. Free projects use their platform domain without customer DNS authorization. production_deployment_required means Prod has no active deployment: deploy to Prod or promote the current Dev deployment, then plan again.", external_exports.object({ project_id: identifier2, hostname: domainName }), (client2, input) => client2.planPaidDomain({ projectId: input.project_id, hostname: input.hostname }));
5682
+ registerProductTool(server, dependencies, "domain_paid_apply", "Activate the explicitly requested customer hostname. Requires Paid; the backend may reuse only its scoped customer OAuth grant. After domain_cloudflare_status is authorized, replay the original hostname/key to reconcile its exact DNS records. Otherwise return the manual DNS records to the human. Preserve the original hostname/key after uncertainty; no arbitrary DNS or provider credentials are accepted. production_deployment_required changed nothing: deploy to Prod or promote Dev first, then repeat the same hostname/key.", external_exports.object({
5526
5683
  project_id: identifier2,
5527
5684
  hostname: domainName,
5528
5685
  idempotency_key: idempotencyKey,
@@ -5751,22 +5908,42 @@ function createLocalOhmyhostMcpServer(dependencies) {
5751
5908
  const { idempotency_key, ...report } = input;
5752
5909
  return client2.submitFeedback({ report, idempotencyKey: idempotency_key });
5753
5910
  }, mutationAnnotations());
5754
- registerProductTool(server, dependencies, "project_create", "Create an ohmyho.st project. Recommend isolated Dev/Prod data, but respect the customer's choice and added database consumption. shared is the default and uses one database/file namespace; isolated separates database/Auth records and files. Choose before provisioning; changing the established mode requires a data migration. region defaults to us; eu places the project's database, files and builds in the EU at the same prices. The region cannot be changed later, and ohmyhost.yaml storage.jurisdiction must equal it.", external_exports.object({
5911
+ registerProductTool(server, dependencies, "project_create", "Create an ohmyho.st project. Ask whether Dev should be protected (default) or public before creation; public means anyone with the Dev URL can open it, including when Dev and Prod share data. Recommend isolated Dev/Prod data, but respect the customer's choice and added database consumption. shared is the default and uses one database/file namespace; isolated separates database/Auth records and files. Choose before provisioning; changing the established mode requires a data migration. region defaults to us; eu places the project's database, files and builds in the EU at the same prices. The region cannot be changed later, and ohmyhost.yaml storage.jurisdiction must equal it.", external_exports.object({
5755
5912
  organization_id: identifier2,
5756
5913
  name: external_exports.string().min(1).max(128),
5757
5914
  data_mode: external_exports.enum(["shared", "isolated"]).optional(),
5915
+ dev_access_mode: external_exports.enum(["protected", "public"]).optional(),
5758
5916
  region: external_exports.enum(["us", "eu"]).optional(),
5759
5917
  idempotency_key: idempotencyKey
5760
5918
  }), (client2, input) => client2.createProject({
5761
5919
  organizationId: input.organization_id,
5762
5920
  name: input.name,
5763
5921
  ...input.data_mode === void 0 ? {} : { dataMode: input.data_mode },
5922
+ ...input.dev_access_mode === void 0 ? {} : { devAccessMode: input.dev_access_mode },
5764
5923
  ...input.region === void 0 ? {} : { region: input.region },
5765
5924
  idempotencyKey: input.idempotency_key
5766
5925
  }), mutationAnnotations());
5767
5926
  registerProductTool(server, dependencies, "project_get", "Get one project", external_exports.object({ project_id: identifier2 }), (client2, input) => client2.getProject(input.project_id));
5768
- registerProductTool(server, dependencies, "project_status", "Get source, both Dev/Prod environment IDs, deployment URLs, latest operation and cleanup status. Dev URLs are protected: use project_dev_access_create before browser verification. Select environments by name when configuring secrets; never infer a Prod ID from the default Dev environment.", external_exports.object({ project_id: identifier2 }), (client2, input) => client2.getProjectStatus(input.project_id));
5769
- registerProductTool(server, dependencies, "project_dev_access_create", "Create an owner-only ten-minute single-use access link for the protected Dev app. Anonymous Dev HTTP 404 is expected; it does not prove deployment failure. Open redeem_url once in the intended browser or isolated cookie jar, then use the clean origin with its session cookie. Keep the link and cookie private, out of reports, logs and project notes. A new ticket invalidates earlier Dev access and unused owner tickets; do not blindly retry an uncertain request. This grants no customer application login or Prod access.", external_exports.object({ project_id: identifier2 }), (client2, input) => client2.createDevAccessTicket(input.project_id), { ...mutationAnnotations(), idempotentHint: false });
5927
+ registerProductTool(server, dependencies, "project_status", "Get source, both Dev/Prod environment IDs, deployment URLs, Dev access mode, latest operation and cleanup status. Protected Dev needs project_dev_share_link_get for a persistent share link. Select environments by name when configuring secrets; never infer a Prod ID from the default Dev environment.", external_exports.object({ project_id: identifier2 }), (client2, input) => client2.getProjectStatus(input.project_id));
5928
+ registerProductTool(server, dependencies, "project_dev_share_link_get", "Owner only: get or create the persistent protected Dev link. The same link works for multiple visitors and has no automatic expiry. Keep it out of notes and logs.", external_exports.object({ project_id: identifier2 }), (client2, input) => client2.ensureDevShareLink(input.project_id), mutationAnnotations());
5929
+ registerProductTool(server, dependencies, "project_dev_access_mode_set", "Owner only: choose public Dev (no platform token) or protected Dev (share link required). A switch takes effect on the next request; switching back creates a new link.", external_exports.object({
5930
+ project_id: identifier2,
5931
+ mode: external_exports.enum(["protected", "public"]),
5932
+ idempotency_key: idempotencyKey
5933
+ }), (client2, input) => client2.setDevAccessMode({
5934
+ projectId: input.project_id,
5935
+ mode: input.mode,
5936
+ idempotencyKey: input.idempotency_key
5937
+ }), destructiveAnnotations());
5938
+ registerProductTool(server, dependencies, "project_dev_share_link_rotate", "Owner only: replace the persistent Dev link and immediately revoke old links and sessions. Repeat the same key after uncertainty.", external_exports.object({ project_id: identifier2, idempotency_key: idempotencyKey }), (client2, input) => client2.rotateDevShareLink({
5939
+ projectId: input.project_id,
5940
+ idempotencyKey: input.idempotency_key
5941
+ }), destructiveAnnotations());
5942
+ registerProductTool(server, dependencies, "project_dev_share_link_revoke", "Owner only: revoke the persistent Dev link and active sessions immediately; Dev stays protected until a new link is obtained.", external_exports.object({ project_id: identifier2, idempotency_key: idempotencyKey }), (client2, input) => client2.revokeDevShareLink({
5943
+ projectId: input.project_id,
5944
+ idempotencyKey: input.idempotency_key
5945
+ }), destructiveAnnotations());
5946
+ registerProductTool(server, dependencies, "project_dev_access_create", "Create an owner-only one-hour single-use access link for the protected Dev app. Anonymous Dev HTTP 404 is expected; it does not prove deployment failure. Open redeem_url once in the intended browser or isolated cookie jar, then use the clean origin with its session cookie. Keep the link and cookie private, out of reports, logs and project notes. A new ticket invalidates earlier one-time ticket sessions and unused owner tickets but not the persistent share link; do not blindly retry an uncertain request. This grants no customer application login or Prod access.", external_exports.object({ project_id: identifier2 }), (client2, input) => client2.createDevAccessTicket(input.project_id), { ...mutationAnnotations(), idempotentHint: false });
5770
5947
  registerProductTool(server, dependencies, "github_connect", "Owner or Admin: connect GitHub once for this workspace. Give authorization_url to the customer and repeat identical arguments and key to observe completion. One connection covers subsequent source_link calls; no provider token is returned. Failed or expired attempts require a fresh key for this same workspace, never a new project.", external_exports.object({ organization_id: identifier2, idempotency_key: idempotencyKey }), (client2, input) => client2.connectGithub({
5771
5948
  organizationId: input.organization_id,
5772
5949
  idempotencyKey: input.idempotency_key
@@ -5784,7 +5961,15 @@ function createLocalOhmyhostMcpServer(dependencies) {
5784
5961
  idempotencyKey: input.idempotency_key
5785
5962
  }), mutationAnnotations());
5786
5963
  registerProductTool(server, dependencies, "source_get", "Get linked source status", external_exports.object({ project_id: identifier2 }), (client2, input) => client2.getSource(input.project_id));
5787
- registerProductTool(server, dependencies, "deployment_plan", "Plan an immutable deployment. Preserve the customer's existing authentication integration, such as Better Auth or WorkOS, and use the same generic Next.js/Vite/TanStack runtime contract. ohmyho.st login is separate. Read the app Skill's auth guidance and report actual plan blockers, required secret names/callback configuration and unverified runtime compatibility. Never assume a working platform login proves application login or silently replace its auth provider.", external_exports.object({ project_id: identifier2, commit_sha: commitSha }), (client2, input) => client2.planDeployment({ projectId: input.project_id, commitSha: input.commit_sha }));
5964
+ registerProductTool(server, dependencies, "deployment_plan", "Plan an immutable deployment. Preserve the customer's existing authentication integration, such as Better Auth or WorkOS, and use the same generic Next.js/Vite/TanStack runtime contract. ohmyho.st login is separate. Read the app Skill's auth guidance and report actual plan blockers, required secret names/callback configuration and unverified runtime compatibility. Hosting, including Better Auth, needs no mail domain unless the app declares mail.enabled. Never assume a working platform login proves application login or silently replace its auth provider. The plan builds into Dev unless environment is prod; prod builds straight into Prod without a Dev deployment (a project with shared data and a database must deploy to Dev and promote instead). The plan stays valid for 24 hours.", external_exports.object({
5965
+ project_id: identifier2,
5966
+ commit_sha: commitSha,
5967
+ environment: external_exports.enum(["dev", "prod"]).optional()
5968
+ }), (client2, input) => client2.planDeployment({
5969
+ projectId: input.project_id,
5970
+ commitSha: input.commit_sha,
5971
+ environment: input.environment ?? "dev"
5972
+ }));
5788
5973
  registerProductTool(server, dependencies, "deployment_create", "Start a reviewed deployment plan", external_exports.object({ project_id: identifier2, plan_id: identifier2, idempotency_key: idempotencyKey }), (client2, input) => client2.createDeployment({
5789
5974
  projectId: input.project_id,
5790
5975
  planId: input.plan_id,
@@ -5831,7 +6016,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
5831
6016
  operationId: input.operation_id,
5832
6017
  idempotencyKey: input.idempotency_key
5833
6018
  })), mutationAnnotations());
5834
- registerProductTool(server, dependencies, "mail_setup", "Configure the customer's one production mail domain using the project Prod environment ID. The Dev and Prod application URLs do not create separate mail domains. Ask whether they want sending and receiving. Configure sending first; receiving requires mail_webhook_set, application signature verification and mail_webhook_verify. Preserve existing mailbox MX records. No Resend account or key is needed.", external_exports.object({
6019
+ registerProductTool(server, dependencies, "mail_setup", "Configure the customer's one production mail domain using the project Prod environment ID, only when the customer wants mail or the app declares mail.enabled; hosting needs no mail domain and none is registered automatically. The Dev and Prod application URLs do not create separate mail domains. Ask whether they want sending and receiving. Configure sending first; receiving requires mail_webhook_set, application signature verification and mail_webhook_verify. Preserve existing mailbox MX records. No Resend account or key is needed.", external_exports.object({
5835
6020
  project_id: identifier2,
5836
6021
  environment_id: identifier2,
5837
6022
  domain: external_exports.string(),
@@ -5952,6 +6137,21 @@ function createLocalOhmyhostMcpServer(dependencies) {
5952
6137
  environmentId: input.environment_id
5953
6138
  });
5954
6139
  });
6140
+ registerProductTool(server, dependencies, "mail_domain_delete", "Retire the project's mail domain while the project stays active; use its Prod environment ID. Sending and receiving stop at once, then its provider domain and key are removed. DNS is not changed: remove the returned dns_records from the domain's DNS. Only when the customer no longer wants mail. Requires explicit confirmation; repeat the same key after uncertainty.", external_exports.object({
6141
+ project_id: identifier2,
6142
+ environment_id: identifier2,
6143
+ idempotency_key: idempotencyKey,
6144
+ confirm: external_exports.literal(true)
6145
+ }), async (client2, input) => {
6146
+ if (!client2.managedMail)
6147
+ throw new Error("Mail unavailable");
6148
+ return client2.managedMail({
6149
+ action: "domain_delete",
6150
+ projectId: input.project_id,
6151
+ environmentId: input.environment_id,
6152
+ idempotencyKey: input.idempotency_key
6153
+ });
6154
+ }, destructiveAnnotations());
5955
6155
  registerProductTool(server, dependencies, "secrets_list", "List secret metadata without values", external_exports.object({ project_id: identifier2, environment_id: identifier2 }), (client2, input) => client2.listEnvironmentSecrets({
5956
6156
  projectId: input.project_id,
5957
6157
  environmentId: input.environment_id
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-mcp",
3
- "version": "0.1.14",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -24,6 +24,10 @@ import {
24
24
  } from "@ohmyhost/contracts/billing";
25
25
  import {
26
26
  createProjectDevAccessTicket,
27
+ ensureProjectDevShareLink,
28
+ setProjectDevAccessMode,
29
+ rotateProjectDevShareLink,
30
+ revokeProjectDevShareLink,
27
31
  createCloudflareDnsAuthorization,
28
32
  getAccountProfile,
29
33
  getCloudflareDnsAuthorizationStatus,
@@ -87,6 +91,7 @@ import {
87
91
  reconcileOperation,
88
92
  streamOperationEvents,
89
93
  type DevAccessTicket,
94
+ type DevAccessState,
90
95
  type CloudflareDnsAuthorization,
91
96
  type CloudflareDnsAuthorizationStatus,
92
97
  type OhMyHostClient,
@@ -104,6 +109,7 @@ import {
104
109
  prepareUserTokenFile,
105
110
  ProductApiAuthenticationError,
106
111
  parseDevAccessTicket,
112
+ parseDevAccessState,
107
113
  parseCloudflareDnsAuthorization,
108
114
  parseCloudflareDnsAuthorizationStatus,
109
115
  parsePaidDomain,
@@ -507,6 +513,51 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
507
513
  public getProject(projectId: string) {
508
514
  return getProject({ project_id: projectId }, { client: this.#client });
509
515
  }
516
+ public async ensureDevShareLink(projectId: string): Promise<DevAccessState> {
517
+ const result = parseDevAccessState(
518
+ await ensureProjectDevShareLink({ project_id: projectId }, { client: this.#client }),
519
+ );
520
+ if (result.project_id !== projectId) throw new TypeError("Unexpected Dev share project");
521
+ return result;
522
+ }
523
+ public async setDevAccessMode(input: {
524
+ projectId: string;
525
+ mode: "protected" | "public";
526
+ idempotencyKey: string;
527
+ }): Promise<DevAccessState> {
528
+ return parseDevAccessState(
529
+ await setProjectDevAccessMode(
530
+ {
531
+ project_id: input.projectId,
532
+ "Idempotency-Key": input.idempotencyKey,
533
+ setDevAccessModeRequest: { mode: input.mode },
534
+ },
535
+ { client: this.#client },
536
+ ),
537
+ );
538
+ }
539
+ public async rotateDevShareLink(input: {
540
+ projectId: string;
541
+ idempotencyKey: string;
542
+ }): Promise<DevAccessState> {
543
+ return parseDevAccessState(
544
+ await rotateProjectDevShareLink(
545
+ { project_id: input.projectId, "Idempotency-Key": input.idempotencyKey },
546
+ { client: this.#client },
547
+ ),
548
+ );
549
+ }
550
+ public async revokeDevShareLink(input: {
551
+ projectId: string;
552
+ idempotencyKey: string;
553
+ }): Promise<DevAccessState> {
554
+ return parseDevAccessState(
555
+ await revokeProjectDevShareLink(
556
+ { project_id: input.projectId, "Idempotency-Key": input.idempotencyKey },
557
+ { client: this.#client },
558
+ ),
559
+ );
560
+ }
510
561
  public async createDevAccessTicket(projectId: string): Promise<DevAccessTicket> {
511
562
  const ticket = parseDevAccessTicket(
512
563
  await createProjectDevAccessTicket({ project_id: projectId }, { client: this.#client }),
@@ -675,6 +726,7 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
675
726
  organization_id: input.organizationId,
676
727
  name: input.name,
677
728
  ...(input.dataMode === undefined ? {} : { data_mode: input.dataMode }),
729
+ ...(input.devAccessMode === undefined ? {} : { dev_access_mode: input.devAccessMode }),
678
730
  ...(input.region === undefined ? {} : { region: input.region }),
679
731
  },
680
732
  },
@@ -716,7 +768,10 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
716
768
 
717
769
  public planDeployment(input: Parameters<LocalMcpProductClient["planDeployment"]>[0]) {
718
770
  return planDeployment(
719
- { project_id: input.projectId, planDeploymentRequest: { commit_sha: input.commitSha } },
771
+ {
772
+ project_id: input.projectId,
773
+ planDeploymentRequest: { commit_sha: input.commitSha, environment: input.environment },
774
+ },
720
775
  { client: this.#client },
721
776
  );
722
777
  }