@amerged/ohmyhost-mcp 0.1.20 → 0.1.22

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-NESQZ3BS.js";
5
+ } from "./chunk-NLBFEH6H.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-NESQZ3BS.js";
29
+ } from "./chunk-NLBFEH6H.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 {
@@ -516,7 +516,7 @@ function toError(value) {
516
516
  var failures = {
517
517
  operation_abandoned: [
518
518
  "The operation stopped renewing its execution lease and was ended.",
519
- "Inspect project status and deployment diagnostics before creating a fresh plan. Keep this operation ID; the expired execution cannot resume."
519
+ "Inspect project status and, when this operation names a deployment_id, its diagnostics with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs before creating a fresh plan. Keep this operation ID; the expired execution cannot resume."
520
520
  ],
521
521
  database_write_rejected: [
522
522
  "The database rejected this write; it was not committed.",
@@ -580,7 +580,7 @@ var failures = {
580
580
  ],
581
581
  build_failed: [
582
582
  "The build did not produce a verified deployable artifact.",
583
- "Inspect the deployment build diagnostics and verify the install and build commands for the admitted commit. Correct the reported failure before planning a new deployment."
583
+ "Read the BUILD_FAILED diagnostic of this operation's deployment_id with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs; its excerpt is the tail of the install and build output. Correct that failure and verify the commands for the admitted commit before planning a new deployment."
584
584
  ],
585
585
  insufficient_organization_credits: [
586
586
  "The organization does not have sufficient available credits for this operation.",
@@ -596,7 +596,7 @@ var failures = {
596
596
  ],
597
597
  runtime_candidate_failed: [
598
598
  "The deployed candidate failed its runtime checks.",
599
- "Inspect deployment runtime diagnostics, required secrets and the health-check route before creating a new deployment plan."
599
+ "Read the HEALTH_CHECK_FAILED diagnostic of this operation's deployment_id with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs. Its route must answer 2xx to a cookieless GET without a redirect; status_code and, for owners, excerpt show what it answered. Fix that, often a missing secret or a redirect, then plan a new deployment."
600
600
  ],
601
601
  runtime_candidate_rejected: [
602
602
  "The runtime rejected the built Worker script.",
@@ -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.20",
802
+ version: "0.1.22",
803
803
  private: true,
804
804
  ohmyhost: {
805
805
  deployment: "production",
@@ -2168,6 +2168,11 @@ var PROBLEM_POLICY = {
2168
2168
  retryable: false,
2169
2169
  action: "Finish database provisioning, then request the export again. No backup was accepted."
2170
2170
  },
2171
+ powered_by_flag_required: {
2172
+ status: 409,
2173
+ retryable: false,
2174
+ action: "Keep the flag on, remove the custom domain with 'ohmyhost domain paid delete', or buy Paid, then switch the flag off."
2175
+ },
2171
2176
  rate_limited: { status: 429, retryable: true, action: "Retry after the server delay." },
2172
2177
  insufficient_organization_credits: {
2173
2178
  status: 402,
@@ -2182,7 +2187,7 @@ var PROBLEM_POLICY = {
2182
2187
  paid_plan_required: {
2183
2188
  status: 402,
2184
2189
  retryable: false,
2185
- action: "Ask the organization Owner to activate Paid through billing checkout, then retry. Top-ups and signup credits do not activate Paid."
2190
+ action: "Ask the organization Owner to activate Paid through billing checkout, then retry. Top-ups and signup credits do not activate Paid. For a website domain alone, a Free workspace may instead show the powered-by flag on the project."
2186
2191
  },
2187
2192
  interactive_login_required: {
2188
2193
  status: 403,
@@ -2290,6 +2295,18 @@ var parseDevAccessState = (value) => {
2290
2295
  throw new ResponseContractError();
2291
2296
  return input;
2292
2297
  };
2298
+ var parsePoweredByFlag = (value) => {
2299
+ const input = exactRecord2(value, ["project_id", "enabled", "enabled_at"]);
2300
+ if (!isUlid(input["project_id"]) || typeof input["enabled"] !== "boolean" || (input["enabled"] ? !isIsoInstant(input["enabled_at"]) : input["enabled_at"] !== null))
2301
+ throw new ResponseContractError();
2302
+ return input;
2303
+ };
2304
+ var parseOrganizationReferral = (value) => {
2305
+ const input = exactRecord2(value, ["organization_id", "referral_url"]);
2306
+ if (!isUlid(input["organization_id"]) || input["referral_url"] !== `https://ohmyho.st/?r=ref-${String(input["organization_id"]).toLowerCase()}`)
2307
+ throw new ResponseContractError();
2308
+ return input;
2309
+ };
2293
2310
  var parseDevAccessTicket = (value) => {
2294
2311
  const input = exactRecord2(value, [
2295
2312
  "ticket_id",
@@ -2381,6 +2398,7 @@ var parseSafeProblem = (value) => {
2381
2398
  var parseOperation = (value) => {
2382
2399
  const input = exactRecord2(value, [
2383
2400
  "blocking_operation_id",
2401
+ "deployment_id",
2384
2402
  "id",
2385
2403
  "state",
2386
2404
  "created_at",
@@ -2396,7 +2414,8 @@ var parseOperation = (value) => {
2396
2414
  const updatedAt = input["updated_at"];
2397
2415
  const result = input["result"];
2398
2416
  const blockingOperationId = input["blocking_operation_id"];
2399
- if (blockingOperationId !== void 0 && (state !== "queued" || !isUlid(blockingOperationId) || blockingOperationId === id3))
2417
+ const deploymentId = input["deployment_id"];
2418
+ if (blockingOperationId !== void 0 && (state !== "queued" || !isUlid(blockingOperationId) || blockingOperationId === id3) || deploymentId !== void 0 && !isUlid(deploymentId))
2400
2419
  throw new ResponseContractError();
2401
2420
  if (!isUlid(id3) || !isOperationState(state) || !isIsoInstant(createdAt) || !isIsoInstant(updatedAt) || result !== void 0 && !isRecord2(result)) {
2402
2421
  throw new ResponseContractError();
@@ -2443,6 +2462,7 @@ var parseOperation = (value) => {
2443
2462
  created_at: createdAt,
2444
2463
  updated_at: updatedAt,
2445
2464
  ...blockingOperationId === void 0 ? {} : { blocking_operation_id: blockingOperationId },
2465
+ ...deploymentId === void 0 ? {} : { deployment_id: deploymentId },
2446
2466
  ...progress === void 0 ? {} : { progress },
2447
2467
  ...result === void 0 ? {} : { result },
2448
2468
  ...error === void 0 ? {} : { error },
@@ -2597,6 +2617,7 @@ var HELP_USAGE = Object.freeze({
2597
2617
  "export create": "ohmyhost export create --project ULID --idempotency-key KEY --stdin --json (password on stdin only; one accepted SQL ZIP per project per 24 hours)",
2598
2618
  "export get": "ohmyhost export get EXPORT_ULID --project ULID --json (poll the original job; signed ZIP download lasts 24 hours)",
2599
2619
  "credits account": "ohmyhost credits account --organization ULID --json",
2620
+ "referral link": "ohmyhost referral link --organization ULID --json (the workspace's link to share; a new user who signs up through it starts with a free Paid month and 1,000 credits, and their first payment gives this workspace the same)",
2600
2621
  "credits balance": "ohmyhost credits balance --organization ULID --json",
2601
2622
  "billing recharge get": "ohmyhost billing recharge get --organization ULID --json",
2602
2623
  "billing recharge set": "ohmyhost billing recharge set --organization ULID --enabled true|false --monthly-limit-minor CENTS --revision N --idempotency-key KEY [--consent off_session_v1] --json (explicit Owner consent required before enabling)",
@@ -2626,6 +2647,8 @@ var HELP_USAGE = Object.freeze({
2626
2647
  "project dev-share rotate": "ohmyhost project dev-share rotate --project ULID --idempotency-key KEY --yes --json",
2627
2648
  "project dev-share revoke": "ohmyhost project dev-share revoke --project ULID --idempotency-key KEY --yes --json",
2628
2649
  "project dev-access mode": "ohmyhost project dev-access mode --project ULID --mode protected|public --idempotency-key KEY --yes --json",
2650
+ "project flag status": "ohmyhost project flag status --project ULID --json",
2651
+ "project flag set": "ohmyhost project flag set --project ULID --enabled true|false --idempotency-key KEY --json (shows the small Powered by ohmyho.st flag on the production site; while it shows, a Free workspace may connect its own domain without the domain fee and each Paid period adds 250 credits)",
2629
2652
  "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)",
2630
2653
  "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)",
2631
2654
  "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.",
@@ -3567,6 +3590,23 @@ var getGithubOrganizationConnection = (parameters, options) => {
3567
3590
  ...params
3568
3591
  });
3569
3592
  };
3593
+ var getOrganizationReferral = (parameters, options) => {
3594
+ const params = buildClientParams([parameters], [
3595
+ {
3596
+ args: [
3597
+ { in: "headers", key: "X-Request-Id" },
3598
+ { in: "path", key: "organization_id" }
3599
+ ]
3600
+ }
3601
+ ]);
3602
+ return (options?.client ?? client).get({
3603
+ responseStyle: "data",
3604
+ security: [{ scheme: "bearer", type: "http" }],
3605
+ url: "/v1/organizations/{organization_id}/referral",
3606
+ ...options,
3607
+ ...params
3608
+ });
3609
+ };
3570
3610
  var getOrganizationAccount = (parameters, options) => {
3571
3611
  const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "organization_id" }] }]);
3572
3612
  return (options?.client ?? client).get({
@@ -4171,6 +4211,47 @@ var setProjectDevAccessMode = (parameters, options) => {
4171
4211
  }
4172
4212
  });
4173
4213
  };
4214
+ var getProjectPoweredByFlag = (parameters, options) => {
4215
+ const params = buildClientParams([parameters], [
4216
+ {
4217
+ args: [
4218
+ { in: "headers", key: "X-Request-Id" },
4219
+ { in: "path", key: "project_id" }
4220
+ ]
4221
+ }
4222
+ ]);
4223
+ return (options?.client ?? client).get({
4224
+ responseStyle: "data",
4225
+ security: [{ scheme: "bearer", type: "http" }],
4226
+ url: "/v1/projects/{project_id}/powered-by-flag",
4227
+ ...options,
4228
+ ...params
4229
+ });
4230
+ };
4231
+ var setProjectPoweredByFlag = (parameters, options) => {
4232
+ const params = buildClientParams([parameters], [
4233
+ {
4234
+ args: [
4235
+ { in: "headers", key: "X-Request-Id" },
4236
+ { in: "headers", key: "Idempotency-Key" },
4237
+ { in: "path", key: "project_id" },
4238
+ { key: "setPoweredByFlagRequest", map: "body" }
4239
+ ]
4240
+ }
4241
+ ]);
4242
+ return (options?.client ?? client).put({
4243
+ responseStyle: "data",
4244
+ security: [{ scheme: "bearer", type: "http" }],
4245
+ url: "/v1/projects/{project_id}/powered-by-flag",
4246
+ ...options,
4247
+ ...params,
4248
+ headers: {
4249
+ "Content-Type": "application/json",
4250
+ ...options?.headers,
4251
+ ...params.headers
4252
+ }
4253
+ });
4254
+ };
4174
4255
  var createProjectDevAccessTicket = (parameters, options) => {
4175
4256
  const params = buildClientParams([parameters], [
4176
4257
  {
@@ -5429,6 +5510,9 @@ var GeneratedSdkProductApi = class {
5429
5510
  async getAccountProfile() {
5430
5511
  return parseAccountProfile(await getAccountProfile({ client: this.#client }));
5431
5512
  }
5513
+ getOrganizationReferral(organizationId) {
5514
+ return getOrganizationReferral({ organization_id: organizationId }, { client: this.#client });
5515
+ }
5432
5516
  getOrganizationAccount(organizationId) {
5433
5517
  return getOrganizationAccount({ organization_id: organizationId }, { client: this.#client });
5434
5518
  }
@@ -5602,6 +5686,16 @@ var GeneratedSdkProductApi = class {
5602
5686
  setDevAccessModeRequest: { mode }
5603
5687
  }, { client: this.#client });
5604
5688
  }
5689
+ async getPoweredByFlag(projectId) {
5690
+ return getProjectPoweredByFlag({ project_id: projectId }, { client: this.#client });
5691
+ }
5692
+ async setPoweredByFlag(projectId, enabled, idempotencyKey2) {
5693
+ return setProjectPoweredByFlag({
5694
+ project_id: projectId,
5695
+ "Idempotency-Key": idempotencyKey2,
5696
+ setPoweredByFlagRequest: { enabled }
5697
+ }, { client: this.#client });
5698
+ }
5605
5699
  async createDevAccessTicket(projectId) {
5606
5700
  return createProjectDevAccessTicket({ project_id: projectId }, { client: this.#client });
5607
5701
  }
@@ -6361,6 +6455,9 @@ var GeneratedSdkLocalMcpClient = class {
6361
6455
  assertOrganizationAccount(account);
6362
6456
  return account;
6363
6457
  }
6458
+ async getOrganizationReferral(organizationId) {
6459
+ return parseOrganizationReferral(await getOrganizationReferral({ organization_id: organizationId }, { client: this.#client }));
6460
+ }
6364
6461
  getOrganizationCredits(organizationId) {
6365
6462
  return getOrganizationCredits({ organization_id: organizationId }, { client: this.#client });
6366
6463
  }
@@ -6489,6 +6586,16 @@ var GeneratedSdkLocalMcpClient = class {
6489
6586
  setDevAccessModeRequest: { mode: input.mode }
6490
6587
  }, { client: this.#client }));
6491
6588
  }
6589
+ async getPoweredByFlag(projectId) {
6590
+ return parsePoweredByFlag(await getProjectPoweredByFlag({ project_id: projectId }, { client: this.#client }));
6591
+ }
6592
+ async setPoweredByFlag(input) {
6593
+ return parsePoweredByFlag(await setProjectPoweredByFlag({
6594
+ project_id: input.projectId,
6595
+ "Idempotency-Key": input.idempotencyKey,
6596
+ setPoweredByFlagRequest: { enabled: input.enabled }
6597
+ }, { client: this.#client }));
6598
+ }
6492
6599
  async rotateDevShareLink(input) {
6493
6600
  return parseDevAccessState(await rotateProjectDevShareLink({ project_id: input.projectId, "Idempotency-Key": input.idempotencyKey }, { client: this.#client }));
6494
6601
  }
@@ -6773,9 +6880,10 @@ var PLATFORM_SECRET_NAMES = /* @__PURE__ */ new Set([
6773
6880
  "OHMYHOST_MAIL_KEY",
6774
6881
  "OHMYHOST_MAIL_GATEWAY_URL"
6775
6882
  ]);
6883
+ var LOCAL_SERVER_INSTRUCTIONS = "Operate the user's ohmyho.st hosting account: projects, GitHub deployments, databases, domains, mail, usage and budgets. Start by reading the resource skill://ohmyhost/ohmyhost-get-started/SKILL.md and calling identity_get; read project_context_get before acting on a project. This server is also ohmyho.st support: report a bug, issue or feature request with feedback_submit, give the user the receipt ID and follow up with feedback_status (https://docs.ohmyho.st/support).";
6776
6884
  function createLocalOhmyhostMcpServer(dependencies) {
6777
6885
  const { commandPrefix } = resolveProductCliEnvironment(dependencies.environment);
6778
- const server = createOhmyhostMcpServer("ohmyhost-local");
6886
+ const server = createOhmyhostMcpServer("ohmyhost-local", LOCAL_SERVER_INSTRUCTIONS);
6779
6887
  registerProductTool(server, dependencies, "database_compute_get", "Read current managed database size, memory, region and compute state without running SQL or waking the database. Shared Dev/Prod resolves to one physical database. A null database means no confirmed managed placement. suspend_timeout_seconds=0 uses the provider default; -1 disables scale to zero. A configured value is not proof that an in-progress resize finished. This read never resizes or changes billing.", external_exports.object({ project_id: identifier2, environment: external_exports.enum(["dev", "prod"]).default("dev") }), (client2, input) => client2.getDatabaseCompute({ projectId: input.project_id, environment: input.environment }));
6780
6888
  registerProductTool(server, dependencies, "database_compute_set", "Select standard or performance compute for an existing database: Free 0.25 CU/1 GB/60-second idle suspension, Paid 0.5 CU/2 GB/60-second idle suspension. First read database_compute_get and explain actual size, metered compute cost, possible brief connection interruption and that shared data changes both Dev and Prod. Require the user's decision before confirm=true. SQL data is preserved. Poll the returned operation every 60 seconds; never submit a second change while it runs. Performance requires active Paid access and selects 1 CU/4 GB/300-second idle suspension at 2.5x Paid-standard compute credits per equal active minute; the longer idle window also consumes more active time. This affects only database compute. Read organization_credits_get for the published rate and active meter first. A successful choice is remembered per physical database; automatic Free downgrades preserve it, and an explicit standard choice clears it. Mixed/uncertain transition hours waive the premium; never multiply raw CU usage again.", external_exports.object({
6781
6889
  project_id: identifier2,
@@ -6894,6 +7002,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
6894
7002
  ...input.cursor ? { cursor: input.cursor } : {}
6895
7003
  }));
6896
7004
  registerProductTool(server, dependencies, "organization_account_get", "Owner-only: read the effective Free/Paid plan, its Stripe or granted source, available expiring Free credits and purchased credits that never expire, reservations and next expiry. Granted Paid features do not imply a Stripe subscription or extra monthly Paid allowance. This read grants no access or credits.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getOrganizationAccount(input.organization_id));
7005
+ registerProductTool(server, dependencies, "referral_link_get", "Read the workspace's referral link to share. A new user whose first workspace comes from it starts with a free Paid month and 1,000 credits; that workspace's first payment gives this workspace the same. Any member may read it.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getOrganizationReferral(input.organization_id));
6897
7006
  registerProductTool(server, dependencies, "organization_credits_get", "Read the owner's shared organization credit pool, seven-day grace_started_at/grace_expires_at and published rate_cards. Existing services/domains are not immediately shut down at zero credit; an explicit project stop budget is separate. One credit is 1000000 microcredits. Each rate states credit_micros for units_per_charge; publication alone does not enable billing. active_meters identifies billed sources; provider outages and platform overrun are not customer debt. Works at zero credit.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getOrganizationCredits(input.organization_id));
6898
7007
  registerProductTool(server, dependencies, "project_budget_get", "Read the owner's project UTC-month budget, measured usage and open reservations. A budget never allocates a second wallet.", external_exports.object({ project_id: identifier2 }), (client2, input) => client2.getProjectCreditBudget(input.project_id));
6899
7008
  registerProductTool(server, dependencies, "project_budget_set", "Set an owner's optional monthly project budget in microcredits (1000000 = one credit). continue is the default and uses the org pool after the threshold; stop blocks new billable work. null clears the budget and requires continue. Changing it never resets usage or adds credits. Replay the same key after uncertainty; read the budget again for current usage.", external_exports.object({
@@ -7052,7 +7161,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
7052
7161
  ...input.cursor === void 0 ? {} : { cursor: input.cursor },
7053
7162
  ...input.limit === void 0 ? {} : { limit: input.limit }
7054
7163
  }));
7055
- registerProductTool(server, dependencies, "feedback_submit", "Report a bug, suspected issue or feature request to ohmyho.st. Submit a redacted expected/actual description and minimal reproduction; never credentials, raw logs, environment dumps or personal records. Available at zero credits. Reuse the same key after uncertainty; only a returned receipt ID confirms submission, not triage or a fix.", external_exports.object({
7164
+ registerProductTool(server, dependencies, "feedback_submit", "Report a bug, suspected issue or feature request to ohmyho.st. Submit a redacted expected/actual description and minimal reproduction; never credentials, raw logs, environment dumps or personal records. Available at zero credits. Reuse the same key after uncertainty; only a returned receipt ID confirms submission, not triage or a fix. This is ohmyho.st support for a signed-in agent: give the user the receipt ID and follow up with feedback_status (https://docs.ohmyho.st/support).", external_exports.object({
7056
7165
  organization_id: identifier2,
7057
7166
  project_id: identifier2.optional(),
7058
7167
  environment_id: identifier2.optional(),
@@ -7095,6 +7204,12 @@ function createLocalOhmyhostMcpServer(dependencies) {
7095
7204
  mode: input.mode,
7096
7205
  idempotencyKey: input.idempotency_key
7097
7206
  }), destructiveAnnotations());
7207
+ registerProductTool(server, dependencies, "powered_by_flag_get", 'Read whether the production site shows the opt-in "Powered by ohmyho.st" flag.', external_exports.object({ project_id: identifier2 }), (client2, input) => client2.getPoweredByFlag(input.project_id));
7208
+ registerProductTool(server, dependencies, "powered_by_flag_set", `Owner only, ask the human first: show or hide a small "Powered by ohmyho.st" flag on the right edge of the production site. While it is on, a Free workspace may connect its own domain to this project free of the domain fee, and each Paid period adds 250 credits. Hiding it is refused while a Free workspace's domain depends on it. Replay the same key and value after uncertainty to get the first answer without switching again; every new switch needs a new key.`, external_exports.object({ project_id: identifier2, enabled: external_exports.boolean(), idempotency_key: idempotencyKey }), (client2, input) => client2.setPoweredByFlag({
7209
+ projectId: input.project_id,
7210
+ enabled: input.enabled,
7211
+ idempotencyKey: input.idempotency_key
7212
+ }), mutationAnnotations());
7098
7213
  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({
7099
7214
  projectId: input.project_id,
7100
7215
  idempotencyKey: input.idempotency_key
@@ -7145,7 +7260,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
7145
7260
  ...input.limit === void 0 ? {} : { limit: input.limit }
7146
7261
  }));
7147
7262
  registerProductTool(server, dependencies, "deployment_get", "Get one deployment", external_exports.object({ project_id: identifier2, deployment_id: identifier2 }), (client2, input) => client2.getDeployment({ projectId: input.project_id, deploymentId: input.deployment_id }));
7148
- registerProductTool(server, dependencies, "deployment_logs", "List the newest normalized diagnostics of one deployment (build, control, runtime and function failures with catalog codes). A BUILD_FAILED item carries excerpt: the sanitized tail of the customer's own install/build output, newest lines last, with excerpt_truncated when earlier output was omitted. Read it after build_failed before changing source; scheduled runs are in function_runs_list, not here.", external_exports.object({
7263
+ registerProductTool(server, dependencies, "deployment_logs", "List the newest normalized diagnostics of one deployment (build, control, runtime and function failures with catalog codes). A failed operation_get names the deployment as deployment_id. A BUILD_FAILED item carries excerpt: the sanitized tail of the customer's own install/build output, newest lines last, with excerpt_truncated when earlier output was omitted. A HEALTH_CHECK_FAILED item carries the probed route and status_code and, only for a project owner, excerpt: the head (at most 4096 characters, control characters removed, never a header) of what the app answered. The probe is a cookieless GET that follows no redirect, 5 s per try and up to four tries, so a 3xx or an empty 503 is unhealthy too. Read the item before changing source; scheduled runs are in function_runs_list, not here.", external_exports.object({
7149
7264
  project_id: identifier2,
7150
7265
  deployment_id: identifier2,
7151
7266
  limit: external_exports.number().int().min(1).max(100).default(100)
@@ -7154,7 +7269,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
7154
7269
  deploymentId: input.deployment_id,
7155
7270
  limit: input.limit
7156
7271
  }));
7157
- registerProductTool(server, dependencies, "operation_get", "Get durable operation status and current deployment progress/reconciliation guidance. Follow progress.phase and progress.suggested_action. waiting_for_mail requires mail_domain_status now for the current verification issue and DNS records; publishing means the build completed but runtime preparation/publication is unfinished. Poll this operation after progress.next_poll_after_seconds. A queued/running operation with reconciliation.state=required needs one confirmed operation_reconcile using the same operation; pending means poll this operation after 60 seconds. Never infer deployment success from a completed reconciliation attempt or start another build to recover it.", external_exports.object({ operation_id: identifier2 }), (client2, input) => client2.getOperation(input.operation_id));
7272
+ registerProductTool(server, dependencies, "operation_get", "Get durable operation status and current deployment progress/reconciliation guidance. Follow progress.phase and progress.suggested_action. waiting_for_mail requires mail_domain_status now for the current verification issue and DNS records; publishing means the build completed but runtime preparation/publication is unfinished. Poll this operation after progress.next_poll_after_seconds. A queued/running operation with reconciliation.state=required needs one confirmed operation_reconcile using the same operation; pending means poll this operation after 60 seconds. Never infer deployment success from a completed reconciliation attempt or start another build to recover it. A deploy, promotion or rollback names its deployment_id in every state; when state is failed, read that deployment with deployment_logs and follow error.suggested_action before changing source.", external_exports.object({ operation_id: identifier2 }), (client2, input) => client2.getOperation(input.operation_id));
7158
7273
  registerProductTool(server, dependencies, "operation_logs", "Read available operation events for at most ten seconds, stopping earlier at max_events or a terminal event. This is a bounded snapshot, not a wait for deployment completion. Use operation_get for current progress and keep the same operation. Stream failures are errors, not empty logs.", external_exports.object({
7159
7274
  operation_id: identifier2,
7160
7275
  max_events: external_exports.number().int().min(1).max(100).default(50)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-mcp",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -24,6 +24,13 @@
24
24
  },
25
25
  "license": "Apache-2.0",
26
26
  "homepage": "https://docs.ohmyho.st/",
27
+ "bugs": {
28
+ "url": "https://docs.ohmyho.st/support"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/amerged-org/ohmyhost-mcp.git"
33
+ },
27
34
  "description": "ohmyho.st mcp client",
28
35
  "publishConfig": {
29
36
  "access": "public",
@@ -2,8 +2,11 @@ import mcpPackage from "../package.json" with { type: "json" };
2
2
  import { createMcpHandler, McpServer } from "@modelcontextprotocol/server";
3
3
  import { listOhmyhostSkillResources } from "@ohmyhost/agent-skills";
4
4
 
5
- export function createOhmyhostMcpServer(name = "ohmyhost") {
6
- const server = new McpServer({ name, version: mcpPackage.version });
5
+ export function createOhmyhostMcpServer(name = "ohmyhost", instructions?: string) {
6
+ const server = new McpServer(
7
+ { name, version: mcpPackage.version },
8
+ instructions === undefined ? undefined : { instructions },
9
+ );
7
10
  for (const resource of listOhmyhostSkillResources()) {
8
11
  server.registerResource(
9
12
  `${resource.skillName}:${resource.relativePath}`,
@@ -26,6 +26,8 @@ import {
26
26
  createProjectDevAccessTicket,
27
27
  ensureProjectDevShareLink,
28
28
  setProjectDevAccessMode,
29
+ getProjectPoweredByFlag,
30
+ setProjectPoweredByFlag,
29
31
  rotateProjectDevShareLink,
30
32
  revokeProjectDevShareLink,
31
33
  createCloudflareDnsAuthorization,
@@ -52,6 +54,7 @@ import {
52
54
  deletePaidProjectDomain,
53
55
  getOrganizationCredits,
54
56
  getOrganizationAccount,
57
+ getOrganizationReferral,
55
58
  getOrganizationCreditUsage,
56
59
  getProjectCreditBudget,
57
60
  setProjectCreditBudget,
@@ -93,6 +96,8 @@ import {
93
96
  streamOperationEvents,
94
97
  type DevAccessTicket,
95
98
  type DevAccessState,
99
+ type PoweredByFlag,
100
+ type OrganizationReferral,
96
101
  type CloudflareDnsAuthorization,
97
102
  type CloudflareDnsAuthorizationStatus,
98
103
  type OhMyHostClient,
@@ -113,6 +118,8 @@ import {
113
118
  ProductApiAuthenticationError,
114
119
  parseDevAccessTicket,
115
120
  parseDevAccessState,
121
+ parsePoweredByFlag,
122
+ parseOrganizationReferral,
116
123
  parseCloudflareDnsAuthorization,
117
124
  parseCloudflareDnsAuthorizationStatus,
118
125
  parsePaidDomain,
@@ -419,6 +426,11 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
419
426
  assertOrganizationAccount(account);
420
427
  return account;
421
428
  }
429
+ public async getOrganizationReferral(organizationId: string): Promise<OrganizationReferral> {
430
+ return parseOrganizationReferral(
431
+ await getOrganizationReferral({ organization_id: organizationId }, { client: this.#client }),
432
+ );
433
+ }
422
434
  public getOrganizationCredits(organizationId: string) {
423
435
  return getOrganizationCredits({ organization_id: organizationId }, { client: this.#client });
424
436
  }
@@ -606,6 +618,27 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
606
618
  ),
607
619
  );
608
620
  }
621
+ public async getPoweredByFlag(projectId: string): Promise<PoweredByFlag> {
622
+ return parsePoweredByFlag(
623
+ await getProjectPoweredByFlag({ project_id: projectId }, { client: this.#client }),
624
+ );
625
+ }
626
+ public async setPoweredByFlag(input: {
627
+ projectId: string;
628
+ enabled: boolean;
629
+ idempotencyKey: string;
630
+ }): Promise<PoweredByFlag> {
631
+ return parsePoweredByFlag(
632
+ await setProjectPoweredByFlag(
633
+ {
634
+ project_id: input.projectId,
635
+ "Idempotency-Key": input.idempotencyKey,
636
+ setPoweredByFlagRequest: { enabled: input.enabled },
637
+ },
638
+ { client: this.#client },
639
+ ),
640
+ );
641
+ }
609
642
  public async rotateDevShareLink(input: {
610
643
  projectId: string;
611
644
  idempotencyKey: string;
@@ -70,6 +70,12 @@ export interface LocalMcpProductClient {
70
70
  projectId: string;
71
71
  idempotencyKey: string;
72
72
  }): Promise<import("@ohmyhost/sdk-ts").DevAccessState>;
73
+ getPoweredByFlag(projectId: string): Promise<import("@ohmyhost/sdk-ts").PoweredByFlag>;
74
+ setPoweredByFlag(input: {
75
+ projectId: string;
76
+ enabled: boolean;
77
+ idempotencyKey: string;
78
+ }): Promise<import("@ohmyhost/sdk-ts").PoweredByFlag>;
73
79
  revokeDevShareLink(input: {
74
80
  projectId: string;
75
81
  idempotencyKey: string;
@@ -156,6 +162,9 @@ export interface LocalMcpProductClient {
156
162
  getOrganizationAccount(
157
163
  organizationId: string,
158
164
  ): Promise<import("@ohmyhost/sdk-ts").OrganizationAccount>;
165
+ getOrganizationReferral(
166
+ organizationId: string,
167
+ ): Promise<import("@ohmyhost/sdk-ts").OrganizationReferral>;
159
168
  getOrganizationCredits(
160
169
  organizationId: string,
161
170
  ): Promise<import("@ohmyhost/sdk-ts").OrganizationCredits>;
@@ -419,9 +428,14 @@ const PLATFORM_SECRET_NAMES = new Set([
419
428
  "OHMYHOST_MAIL_GATEWAY_URL",
420
429
  ]);
421
430
 
431
+ const LOCAL_SERVER_INSTRUCTIONS =
432
+ "Operate the user's ohmyho.st hosting account: projects, GitHub deployments, databases, domains, mail, usage and budgets. " +
433
+ "Start by reading the resource skill://ohmyhost/ohmyhost-get-started/SKILL.md and calling identity_get; read project_context_get before acting on a project. " +
434
+ "This server is also ohmyho.st support: report a bug, issue or feature request with feedback_submit, give the user the receipt ID and follow up with feedback_status (https://docs.ohmyho.st/support).";
435
+
422
436
  export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDependencies) {
423
437
  const { commandPrefix } = resolveProductCliEnvironment(dependencies.environment);
424
- const server = createOhmyhostMcpServer("ohmyhost-local");
438
+ const server = createOhmyhostMcpServer("ohmyhost-local", LOCAL_SERVER_INSTRUCTIONS);
425
439
  registerProductTool(
426
440
  server,
427
441
  dependencies,
@@ -706,6 +720,14 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
706
720
  z.object({ organization_id: identifier }),
707
721
  (client, input) => client.getOrganizationAccount(input.organization_id),
708
722
  );
723
+ registerProductTool(
724
+ server,
725
+ dependencies,
726
+ "referral_link_get",
727
+ "Read the workspace's referral link to share. A new user whose first workspace comes from it starts with a free Paid month and 1,000 credits; that workspace's first payment gives this workspace the same. Any member may read it.",
728
+ z.object({ organization_id: identifier }),
729
+ (client, input) => client.getOrganizationReferral(input.organization_id),
730
+ );
709
731
  registerProductTool(
710
732
  server,
711
733
  dependencies,
@@ -1061,7 +1083,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
1061
1083
  server,
1062
1084
  dependencies,
1063
1085
  "feedback_submit",
1064
- "Report a bug, suspected issue or feature request to ohmyho.st. Submit a redacted expected/actual description and minimal reproduction; never credentials, raw logs, environment dumps or personal records. Available at zero credits. Reuse the same key after uncertainty; only a returned receipt ID confirms submission, not triage or a fix.",
1086
+ "Report a bug, suspected issue or feature request to ohmyho.st. Submit a redacted expected/actual description and minimal reproduction; never credentials, raw logs, environment dumps or personal records. Available at zero credits. Reuse the same key after uncertainty; only a returned receipt ID confirms submission, not triage or a fix. This is ohmyho.st support for a signed-in agent: give the user the receipt ID and follow up with feedback_status (https://docs.ohmyho.st/support).",
1065
1087
  z
1066
1088
  .object({
1067
1089
  organization_id: identifier,
@@ -1163,6 +1185,28 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
1163
1185
  }),
1164
1186
  destructiveAnnotations(),
1165
1187
  );
1188
+ registerProductTool(
1189
+ server,
1190
+ dependencies,
1191
+ "powered_by_flag_get",
1192
+ 'Read whether the production site shows the opt-in "Powered by ohmyho.st" flag.',
1193
+ z.object({ project_id: identifier }),
1194
+ (client, input) => client.getPoweredByFlag(input.project_id),
1195
+ );
1196
+ registerProductTool(
1197
+ server,
1198
+ dependencies,
1199
+ "powered_by_flag_set",
1200
+ 'Owner only, ask the human first: show or hide a small "Powered by ohmyho.st" flag on the right edge of the production site. While it is on, a Free workspace may connect its own domain to this project free of the domain fee, and each Paid period adds 250 credits. Hiding it is refused while a Free workspace\'s domain depends on it. Replay the same key and value after uncertainty to get the first answer without switching again; every new switch needs a new key.',
1201
+ z.object({ project_id: identifier, enabled: z.boolean(), idempotency_key: idempotencyKey }),
1202
+ (client, input) =>
1203
+ client.setPoweredByFlag({
1204
+ projectId: input.project_id,
1205
+ enabled: input.enabled,
1206
+ idempotencyKey: input.idempotency_key,
1207
+ }),
1208
+ mutationAnnotations(),
1209
+ );
1166
1210
  registerProductTool(
1167
1211
  server,
1168
1212
  dependencies,
@@ -1308,7 +1352,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
1308
1352
  server,
1309
1353
  dependencies,
1310
1354
  "deployment_logs",
1311
- "List the newest normalized diagnostics of one deployment (build, control, runtime and function failures with catalog codes). A BUILD_FAILED item carries excerpt: the sanitized tail of the customer's own install/build output, newest lines last, with excerpt_truncated when earlier output was omitted. Read it after build_failed before changing source; scheduled runs are in function_runs_list, not here.",
1355
+ "List the newest normalized diagnostics of one deployment (build, control, runtime and function failures with catalog codes). A failed operation_get names the deployment as deployment_id. A BUILD_FAILED item carries excerpt: the sanitized tail of the customer's own install/build output, newest lines last, with excerpt_truncated when earlier output was omitted. A HEALTH_CHECK_FAILED item carries the probed route and status_code and, only for a project owner, excerpt: the head (at most 4096 characters, control characters removed, never a header) of what the app answered. The probe is a cookieless GET that follows no redirect, 5 s per try and up to four tries, so a 3xx or an empty 503 is unhealthy too. Read the item before changing source; scheduled runs are in function_runs_list, not here.",
1312
1356
  z.object({
1313
1357
  project_id: identifier,
1314
1358
  deployment_id: identifier,
@@ -1325,7 +1369,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
1325
1369
  server,
1326
1370
  dependencies,
1327
1371
  "operation_get",
1328
- "Get durable operation status and current deployment progress/reconciliation guidance. Follow progress.phase and progress.suggested_action. waiting_for_mail requires mail_domain_status now for the current verification issue and DNS records; publishing means the build completed but runtime preparation/publication is unfinished. Poll this operation after progress.next_poll_after_seconds. A queued/running operation with reconciliation.state=required needs one confirmed operation_reconcile using the same operation; pending means poll this operation after 60 seconds. Never infer deployment success from a completed reconciliation attempt or start another build to recover it.",
1372
+ "Get durable operation status and current deployment progress/reconciliation guidance. Follow progress.phase and progress.suggested_action. waiting_for_mail requires mail_domain_status now for the current verification issue and DNS records; publishing means the build completed but runtime preparation/publication is unfinished. Poll this operation after progress.next_poll_after_seconds. A queued/running operation with reconciliation.state=required needs one confirmed operation_reconcile using the same operation; pending means poll this operation after 60 seconds. Never infer deployment success from a completed reconciliation attempt or start another build to recover it. A deploy, promotion or rollback names its deployment_id in every state; when state is failed, read that deployment with deployment_logs and follow error.suggested_action before changing source.",
1329
1373
  z.object({ operation_id: identifier }),
1330
1374
  (client, input) => client.getOperation(input.operation_id),
1331
1375
  );
@@ -80,6 +80,8 @@ import {
80
80
  parseProjectCreditBudget,
81
81
  parseDevAccessTicket,
82
82
  parseDevAccessState,
83
+ parsePoweredByFlag,
84
+ parseOrganizationReferral,
83
85
  parseGuardedActionPlan,
84
86
  parseEnvironmentSecret,
85
87
  parseEnvironmentSecretDeletion,
@@ -304,6 +306,8 @@ const HELP_USAGE = Object.freeze({
304
306
  "export get":
305
307
  "ohmyhost export get EXPORT_ULID --project ULID --json (poll the original job; signed ZIP download lasts 24 hours)",
306
308
  "credits account": "ohmyhost credits account --organization ULID --json",
309
+ "referral link":
310
+ "ohmyhost referral link --organization ULID --json (the workspace's link to share; a new user who signs up through it starts with a free Paid month and 1,000 credits, and their first payment gives this workspace the same)",
307
311
  "credits balance": "ohmyhost credits balance --organization ULID --json",
308
312
  "billing recharge get": "ohmyhost billing recharge get --organization ULID --json",
309
313
  "billing recharge set":
@@ -350,6 +354,9 @@ const HELP_USAGE = Object.freeze({
350
354
  "ohmyhost project dev-share revoke --project ULID --idempotency-key KEY --yes --json",
351
355
  "project dev-access mode":
352
356
  "ohmyhost project dev-access mode --project ULID --mode protected|public --idempotency-key KEY --yes --json",
357
+ "project flag status": "ohmyhost project flag status --project ULID --json",
358
+ "project flag set":
359
+ "ohmyhost project flag set --project ULID --enabled true|false --idempotency-key KEY --json (shows the small Powered by ohmyho.st flag on the production site; while it shows, a Free workspace may connect its own domain without the domain fee and each Paid period adds 250 credits)",
353
360
  "project handle check":
354
361
  "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)",
355
362
  "project handle set":
@@ -619,6 +626,13 @@ export class ProductCli {
619
626
  }
620
627
  return success(name, { status: "succeeded", billing: result });
621
628
  }
629
+ if (command.kind === "referral-link") {
630
+ const api = await this.authenticatedApi(command.credentialStore, signal);
631
+ const referral = parseOrganizationReferral(
632
+ await api.getOrganizationReferral(command.organizationId),
633
+ );
634
+ return success("referral link", { status: "succeeded", referral });
635
+ }
622
636
  if (command.kind === "credits-account") {
623
637
  const api = await this.authenticatedApi(command.credentialStore, signal);
624
638
  const account = await api.getOrganizationAccount(command.organizationId);
@@ -809,6 +823,22 @@ export class ProductCli {
809
823
  if (command.kind === "project-status") return await this.getProjectStatus(command, signal);
810
824
  if (command.kind === "project-dev-access-create")
811
825
  return await this.createDevAccessTicket(command, signal);
826
+ if (command.kind === "project-flag-status" || command.kind === "project-flag-set") {
827
+ const api = await this.authenticatedApi(command.credentialStore, signal);
828
+ const flag = parsePoweredByFlag(
829
+ command.kind === "project-flag-status"
830
+ ? await api.getPoweredByFlag(command.projectId)
831
+ : await api.setPoweredByFlag(
832
+ command.projectId,
833
+ command.enabled,
834
+ command.idempotencyKey,
835
+ ),
836
+ );
837
+ return success(
838
+ command.kind === "project-flag-status" ? "project flag status" : "project flag set",
839
+ { status: "succeeded", flag },
840
+ );
841
+ }
812
842
  if (
813
843
  command.kind === "project-dev-share-link" ||
814
844
  command.kind === "project-dev-share-rotate" ||
@@ -2450,6 +2480,7 @@ const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
2450
2480
  if (command.kind === "billing-status") return "billing status";
2451
2481
  if (command.kind === "billing-portal") return "billing portal";
2452
2482
  if (command.kind === "credits-account") return "credits account";
2483
+ if (command.kind === "referral-link") return "referral link";
2453
2484
  if (command.kind === "credits-balance") return "credits balance";
2454
2485
  if (command.kind === "credits-usage") return "credits usage";
2455
2486
  if (command.kind === "budget-get") return "budget get";
@@ -2475,6 +2506,8 @@ const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
2475
2506
  if (command.kind === "project-dev-share-rotate") return "project dev-share rotate";
2476
2507
  if (command.kind === "project-dev-share-revoke") return "project dev-share revoke";
2477
2508
  if (command.kind === "project-dev-access-mode-set") return "project dev-access mode";
2509
+ if (command.kind === "project-flag-status") return "project flag status";
2510
+ if (command.kind === "project-flag-set") return "project flag set";
2478
2511
  if (command.kind === "project-handle-check") return "project handle check";
2479
2512
  if (command.kind === "project-handle-set") return "project handle set";
2480
2513
  if (command.kind === "database-write") return "database write";