@manifest-network/manifest-mcp-fred 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,10 +22,10 @@ function registerPrompts(mcpServer) {
22
22
  `- size: ${size ?? "(unspecified — call browse_catalog to enumerate sizes and ask the user)"}`,
23
23
  ``,
24
24
  `Workflow:`,
25
- `1. Pre-flight: call \`check_deployment_readiness\` with { size, image }. If \`ready: false\`, surface the \`missing_steps\` list to the user and stop.`,
25
+ `1. Pre-flight: call \`check_deployment_readiness\` with { size, image }. If \`ready: false\` and the only issue is that \`sku_candidates\` has more than one entry, the name is ambiguous — show the user each candidate (provider_uuid + price), ask which provider to use, and carry the chosen \`provider_uuid\` (or \`sku_uuid\`) into deploy_app. Otherwise, if \`ready: false\`, surface the \`missing_steps\` list to the user and STOP.`,
26
26
  `2. Build a manifest preview: call \`build_manifest_preview\` with \`image\` and \`port\` parsed as an integer (the prompt arg is a string; the tool's port schema is z.number().int().min(1).max(65535)). Show the user the resulting \`manifest_json\`, \`format\`, and \`meta_hash_hex\`. If \`validation.valid: false\`, surface every \`validation.errors\` entry verbatim and stop.`,
27
- `3. Print a deployment plan: image, manifest summary, SKU, provider (from \`check_deployment_readiness.sku\`), and the meta_hash. Wait for an explicit "yes" before continuing.`,
28
- `4. Call \`deploy_app\` (this broadcasts a chain TX and incurs fees). Pass any progressToken the host provides so the user sees provisioning progress.`,
27
+ `3. Print a deployment plan: image, manifest summary, SKU (name + chosen provider_uuid), and the meta_hash. Wait for an explicit "yes" before continuing.`,
28
+ `4. Call \`deploy_app\` (this broadcasts a chain TX and incurs fees), passing the chosen \`provider_uuid\`/\`sku_uuid\` when the size was ambiguous. If deploy_app returns a SKU_AMBIGUOUS error, surface the listed candidates and ask the user to pick, then retry with the disambiguator. Pass any progressToken the host provides.`,
29
29
  `5. Call \`wait_for_app_ready\` with the returned \`lease_uuid\`. On success, print the lease UUID, provider URL, and any \`status.endpoints\`. On failure, surface diagnostics and offer \`close_lease\` to reclaim the orphaned lease.`,
30
30
  ``,
31
31
  `Never skip the confirmation step in (3). If anything in (1) or (2) fails, do NOT proceed to (4).`
@@ -1 +1 @@
1
- {"version":3,"file":"register-prompts.js","names":[],"sources":["../../src/server/register-prompts.ts"],"sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nexport function registerPrompts(mcpServer: McpServer): void {\n // -- deploy-containerized-app --\n mcpServer.registerPrompt(\n 'deploy-containerized-app',\n {\n title: 'Deploy a containerized app to Manifest',\n description:\n 'Walks through the full deploy lifecycle for a single containerized app: pre-flight check, manifest preview, deploy_app, and wait_for_app_ready. Emits a user-facing plan that asks for confirmation before broadcasting any transaction.',\n argsSchema: {\n image: z\n .string()\n .describe('Public Docker image to deploy (e.g. nginx:1.25)'),\n port: z\n .string()\n .optional()\n .describe(\n 'TCP port the container exposes, as a string-encoded integer (e.g. \"80\"). MCP prompt arguments are always strings on the wire — the agent must parse this to a number before passing it to build_manifest_preview or deploy_app, whose port fields are typed as integers (1-65535). Required for single-service deployments.',\n ),\n size: z\n .string()\n .optional()\n .describe(\n 'SKU tier name (e.g. \"docker-micro\"). Use browse_catalog or check_deployment_readiness to enumerate available sizes.',\n ),\n },\n },\n ({ image, port, size }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: [\n `Deploy this containerized app to the Manifest network end-to-end. Use ONLY the manifest-mcp-fred tools listed below; never broadcast a transaction without explicit confirmation from the user.`,\n ``,\n `Inputs:`,\n `- image: ${image}`,\n `- port: ${port ?? '(unspecified — ask the user)'}`,\n `- size: ${size ?? '(unspecified — call browse_catalog to enumerate sizes and ask the user)'}`,\n ``,\n `Workflow:`,\n `1. Pre-flight: call \\`check_deployment_readiness\\` with { size, image }. If \\`ready: false\\`, surface the \\`missing_steps\\` list to the user and stop.`,\n `2. Build a manifest preview: call \\`build_manifest_preview\\` with \\`image\\` and \\`port\\` parsed as an integer (the prompt arg is a string; the tool's port schema is z.number().int().min(1).max(65535)). Show the user the resulting \\`manifest_json\\`, \\`format\\`, and \\`meta_hash_hex\\`. If \\`validation.valid: false\\`, surface every \\`validation.errors\\` entry verbatim and stop.`,\n `3. Print a deployment plan: image, manifest summary, SKU, provider (from \\`check_deployment_readiness.sku\\`), and the meta_hash. Wait for an explicit \"yes\" before continuing.`,\n `4. Call \\`deploy_app\\` (this broadcasts a chain TX and incurs fees). Pass any progressToken the host provides so the user sees provisioning progress.`,\n `5. Call \\`wait_for_app_ready\\` with the returned \\`lease_uuid\\`. On success, print the lease UUID, provider URL, and any \\`status.endpoints\\`. On failure, surface diagnostics and offer \\`close_lease\\` to reclaim the orphaned lease.`,\n ``,\n `Never skip the confirmation step in (3). If anything in (1) or (2) fails, do NOT proceed to (4).`,\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n\n // -- diagnose-failing-app --\n mcpServer.registerPrompt(\n 'diagnose-failing-app',\n {\n title: 'Diagnose a failing or stuck deployed app',\n description:\n 'Bundles app_status, app_diagnostics, and get_logs into a structured triage flow for a misbehaving lease.',\n argsSchema: {\n lease_uuid: z\n .string()\n .describe('Lease UUID of the app to diagnose (uuid format)'),\n },\n },\n ({ lease_uuid }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: [\n `Diagnose lease ${lease_uuid} on Manifest. Surface a short triage report — do not propose fixes until the data is collected.`,\n ``,\n `1. \\`app_status({ lease_uuid })\\` — record the chainState and (if present) fredStatus.`,\n `2. \\`app_diagnostics({ lease_uuid })\\` — record provision_status, fail_count, and last_error.`,\n `3. \\`get_logs({ lease_uuid, tail: 200 })\\` — capture the most recent logs.`,\n ``,\n `Then summarize:`,\n `- Lease state on chain.`,\n `- Provider state (provision_status / phase / fail_count / last_error).`,\n `- Most relevant log lines (last error or repeated failures).`,\n `- One concrete next step (e.g. \"image pull is failing — try a public registry\", \"container is restarting — inspect crash trace\", \"lease is closed — open a new deployment\").`,\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n\n // -- shutdown-all-leases --\n mcpServer.registerPrompt(\n 'shutdown-all-leases',\n {\n title: \"Close all of the caller's active leases\",\n description:\n \"Lists the caller's active and pending leases and walks through closing each one. Always confirms with the user before broadcasting close_lease transactions.\",\n },\n () => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: [\n `Shut down every active or pending lease for the current wallet. Each \\`close_lease\\` is a chain transaction with fees, and is destructive — never call it without an explicit \"yes\" from the user.`,\n ``,\n `1. Read \\`manifest://leases/active\\` to list current leases. If the list is empty, report that and stop.`,\n `2. Print a numbered table: { uuid, state, provider_uuid, created_at }. Ask the user \"close all (N), some, or none?\".`,\n `3. For each UUID the user approves, call \\`close_lease({ lease_uuid })\\`.`,\n `4. After each close, print \\`{ lease_uuid, status }\\` so the user can confirm progress.`,\n `5. End with a summary: closed count, skipped count, any errors.`,\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n}\n"],"mappings":";;AAGA,SAAgB,gBAAgB,WAA4B;CAE1D,UAAU,eACR,4BACA;EACE,OAAO;EACP,aACE;EACF,YAAY;GACV,OAAO,EACJ,OAAO,EACP,SAAS,iDAAiD;GAC7D,MAAM,EACH,OAAO,EACP,SAAS,EACT,SACC,+TACF;GACF,MAAM,EACH,OAAO,EACP,SAAS,EACT,SACC,uHACF;EACJ;CACF,IACC,EAAE,OAAO,MAAM,YAAY,EAC1B,UAAU,CACR;EACE,MAAM;EACN,SAAS;GACP,MAAM;GACN,MAAM;IACJ;IACA;IACA;IACA,YAAY;IACZ,WAAW,QAAQ;IACnB,WAAW,QAAQ;IACnB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,EAAE,KAAK,IAAI;EACb;CACF,CACF,EACF,EACF;CAGA,UAAU,eACR,wBACA;EACE,OAAO;EACP,aACE;EACF,YAAY,EACV,YAAY,EACT,OAAO,EACP,SAAS,iDAAiD,EAC/D;CACF,IACC,EAAE,kBAAkB,EACnB,UAAU,CACR;EACE,MAAM;EACN,SAAS;GACP,MAAM;GACN,MAAM;IACJ,kBAAkB,WAAW;IAC7B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,EAAE,KAAK,IAAI;EACb;CACF,CACF,EACF,EACF;CAGA,UAAU,eACR,uBACA;EACE,OAAO;EACP,aACE;CACJ,UACO,EACL,UAAU,CACR;EACE,MAAM;EACN,SAAS;GACP,MAAM;GACN,MAAM;IACJ;IACA;IACA;IACA;IACA;IACA;IACA;GACF,EAAE,KAAK,IAAI;EACb;CACF,CACF,EACF,EACF;AACF"}
1
+ {"version":3,"file":"register-prompts.js","names":[],"sources":["../../src/server/register-prompts.ts"],"sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nexport function registerPrompts(mcpServer: McpServer): void {\n // -- deploy-containerized-app --\n mcpServer.registerPrompt(\n 'deploy-containerized-app',\n {\n title: 'Deploy a containerized app to Manifest',\n description:\n 'Walks through the full deploy lifecycle for a single containerized app: pre-flight check, manifest preview, deploy_app, and wait_for_app_ready. Emits a user-facing plan that asks for confirmation before broadcasting any transaction.',\n argsSchema: {\n image: z\n .string()\n .describe('Public Docker image to deploy (e.g. nginx:1.25)'),\n port: z\n .string()\n .optional()\n .describe(\n 'TCP port the container exposes, as a string-encoded integer (e.g. \"80\"). MCP prompt arguments are always strings on the wire — the agent must parse this to a number before passing it to build_manifest_preview or deploy_app, whose port fields are typed as integers (1-65535). Required for single-service deployments.',\n ),\n size: z\n .string()\n .optional()\n .describe(\n 'SKU tier name (e.g. \"docker-micro\"). Use browse_catalog or check_deployment_readiness to enumerate available sizes.',\n ),\n },\n },\n ({ image, port, size }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: [\n `Deploy this containerized app to the Manifest network end-to-end. Use ONLY the manifest-mcp-fred tools listed below; never broadcast a transaction without explicit confirmation from the user.`,\n ``,\n `Inputs:`,\n `- image: ${image}`,\n `- port: ${port ?? '(unspecified — ask the user)'}`,\n `- size: ${size ?? '(unspecified — call browse_catalog to enumerate sizes and ask the user)'}`,\n ``,\n `Workflow:`,\n `1. Pre-flight: call \\`check_deployment_readiness\\` with { size, image }. If \\`ready: false\\` and the only issue is that \\`sku_candidates\\` has more than one entry, the name is ambiguous — show the user each candidate (provider_uuid + price), ask which provider to use, and carry the chosen \\`provider_uuid\\` (or \\`sku_uuid\\`) into deploy_app. Otherwise, if \\`ready: false\\`, surface the \\`missing_steps\\` list to the user and STOP.`,\n `2. Build a manifest preview: call \\`build_manifest_preview\\` with \\`image\\` and \\`port\\` parsed as an integer (the prompt arg is a string; the tool's port schema is z.number().int().min(1).max(65535)). Show the user the resulting \\`manifest_json\\`, \\`format\\`, and \\`meta_hash_hex\\`. If \\`validation.valid: false\\`, surface every \\`validation.errors\\` entry verbatim and stop.`,\n `3. Print a deployment plan: image, manifest summary, SKU (name + chosen provider_uuid), and the meta_hash. Wait for an explicit \"yes\" before continuing.`,\n `4. Call \\`deploy_app\\` (this broadcasts a chain TX and incurs fees), passing the chosen \\`provider_uuid\\`/\\`sku_uuid\\` when the size was ambiguous. If deploy_app returns a SKU_AMBIGUOUS error, surface the listed candidates and ask the user to pick, then retry with the disambiguator. Pass any progressToken the host provides.`,\n `5. Call \\`wait_for_app_ready\\` with the returned \\`lease_uuid\\`. On success, print the lease UUID, provider URL, and any \\`status.endpoints\\`. On failure, surface diagnostics and offer \\`close_lease\\` to reclaim the orphaned lease.`,\n ``,\n `Never skip the confirmation step in (3). If anything in (1) or (2) fails, do NOT proceed to (4).`,\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n\n // -- diagnose-failing-app --\n mcpServer.registerPrompt(\n 'diagnose-failing-app',\n {\n title: 'Diagnose a failing or stuck deployed app',\n description:\n 'Bundles app_status, app_diagnostics, and get_logs into a structured triage flow for a misbehaving lease.',\n argsSchema: {\n lease_uuid: z\n .string()\n .describe('Lease UUID of the app to diagnose (uuid format)'),\n },\n },\n ({ lease_uuid }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: [\n `Diagnose lease ${lease_uuid} on Manifest. Surface a short triage report — do not propose fixes until the data is collected.`,\n ``,\n `1. \\`app_status({ lease_uuid })\\` — record the chainState and (if present) fredStatus.`,\n `2. \\`app_diagnostics({ lease_uuid })\\` — record provision_status, fail_count, and last_error.`,\n `3. \\`get_logs({ lease_uuid, tail: 200 })\\` — capture the most recent logs.`,\n ``,\n `Then summarize:`,\n `- Lease state on chain.`,\n `- Provider state (provision_status / phase / fail_count / last_error).`,\n `- Most relevant log lines (last error or repeated failures).`,\n `- One concrete next step (e.g. \"image pull is failing — try a public registry\", \"container is restarting — inspect crash trace\", \"lease is closed — open a new deployment\").`,\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n\n // -- shutdown-all-leases --\n mcpServer.registerPrompt(\n 'shutdown-all-leases',\n {\n title: \"Close all of the caller's active leases\",\n description:\n \"Lists the caller's active and pending leases and walks through closing each one. Always confirms with the user before broadcasting close_lease transactions.\",\n },\n () => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: [\n `Shut down every active or pending lease for the current wallet. Each \\`close_lease\\` is a chain transaction with fees, and is destructive — never call it without an explicit \"yes\" from the user.`,\n ``,\n `1. Read \\`manifest://leases/active\\` to list current leases. If the list is empty, report that and stop.`,\n `2. Print a numbered table: { uuid, state, provider_uuid, created_at }. Ask the user \"close all (N), some, or none?\".`,\n `3. For each UUID the user approves, call \\`close_lease({ lease_uuid })\\`.`,\n `4. After each close, print \\`{ lease_uuid, status }\\` so the user can confirm progress.`,\n `5. End with a summary: closed count, skipped count, any errors.`,\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n}\n"],"mappings":";;AAGA,SAAgB,gBAAgB,WAA4B;CAE1D,UAAU,eACR,4BACA;EACE,OAAO;EACP,aACE;EACF,YAAY;GACV,OAAO,EACJ,OAAO,EACP,SAAS,iDAAiD;GAC7D,MAAM,EACH,OAAO,EACP,SAAS,EACT,SACC,+TACF;GACF,MAAM,EACH,OAAO,EACP,SAAS,EACT,SACC,uHACF;EACJ;CACF,IACC,EAAE,OAAO,MAAM,YAAY,EAC1B,UAAU,CACR;EACE,MAAM;EACN,SAAS;GACP,MAAM;GACN,MAAM;IACJ;IACA;IACA;IACA,YAAY;IACZ,WAAW,QAAQ;IACnB,WAAW,QAAQ;IACnB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,EAAE,KAAK,IAAI;EACb;CACF,CACF,EACF,EACF;CAGA,UAAU,eACR,wBACA;EACE,OAAO;EACP,aACE;EACF,YAAY,EACV,YAAY,EACT,OAAO,EACP,SAAS,iDAAiD,EAC/D;CACF,IACC,EAAE,kBAAkB,EACnB,UAAU,CACR;EACE,MAAM;EACN,SAAS;GACP,MAAM;GACN,MAAM;IACJ,kBAAkB,WAAW;IAC7B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,EAAE,KAAK,IAAI;EACb;CACF,CACF,EACF,EACF;CAGA,UAAU,eACR,uBACA;EACE,OAAO;EACP,aACE;CACJ,UACO,EACL,UAAU,CACR;EACE,MAAM;EACN,SAAS;GACP,MAAM;GACN,MAAM;IACJ;IACA;IACA;IACA;IACA;IACA;IACA;GACF,EAAE,KAAK,IAAI;EACb;CACF,CACF,EACF,EACF;AACF"}
@@ -17,10 +17,18 @@ import { z } from "zod";
17
17
  function registerTools(deps) {
18
18
  const { mcpServer, clientManager, walletProvider, authTokens, fetchFn } = deps;
19
19
  mcpServer.registerTool("browse_catalog", {
20
- description: "Browse available cloud providers and service tiers with live health checks. Use this before deploy_app to see which providers are online and what SKU sizes (e.g. docker-micro, docker-small) are available with pricing.",
20
+ description: "Browse available cloud providers and SKUs with live health checks. Use this before deploy_app to see which providers are online and what SKU sizes (e.g. docker-micro, docker-small) are available with pricing.",
21
21
  outputSchema: {
22
22
  providers: z.array(z.looseObject({})),
23
- tiers: z.record(z.string(), z.array(z.looseObject({})))
23
+ skus: z.array(z.object({
24
+ name: z.string(),
25
+ sku_uuid: z.string(),
26
+ provider_uuid: z.string(),
27
+ provider_url: z.string().nullable(),
28
+ price: z.string().nullable(),
29
+ unit: z.string().nullable(),
30
+ active: z.boolean()
31
+ }))
24
32
  },
25
33
  annotations: readOnlyAnnotations("Browse providers and SKUs"),
26
34
  _meta: manifestMeta({
@@ -111,7 +119,9 @@ function registerTools(deps) {
111
119
  description: "Pre-flight check for deploy_app: surfaces the caller's wallet balances, credit account, requested SKU availability, and a human-readable list of missing steps. Use this before deploy_app to decide whether to fund credits, switch SKU, or top up the wallet. Note: the chain does not expose provider allowed_registries, so a `ready: true` does not guarantee the registry of `image` is allowed — that is checked at upload time.",
112
120
  inputSchema: {
113
121
  size: z.string().optional().describe("SKU tier to verify availability for (e.g. \"docker-micro\"). Omit to skip the SKU check."),
114
- image: z.string().optional().describe("Image planned for deployment. Recorded on the result for downstream display; not validated.")
122
+ image: z.string().optional().describe("Image planned for deployment. Recorded on the result for downstream display; not validated."),
123
+ provider_uuid: z.string().optional().describe("Narrow a duplicate SKU `size` to one provider."),
124
+ sku_uuid: z.string().optional().describe("Resolve the SKU by uuid, bypassing the `size` name filter. When set, `size` is ignored for candidate selection. Pair with `provider_uuid` to further narrow to a specific provider.")
115
125
  },
116
126
  outputSchema: {
117
127
  tenant: z.string(),
@@ -137,7 +147,21 @@ function registerTools(deps) {
137
147
  }).optional(),
138
148
  active: z.boolean()
139
149
  }).nullable(),
140
- available_sku_names: z.array(z.string()),
150
+ sku_candidates: z.array(z.object({
151
+ name: z.string(),
152
+ uuid: z.string(),
153
+ provider_uuid: z.string(),
154
+ price: z.object({
155
+ amount: z.string(),
156
+ denom: z.string()
157
+ }).optional(),
158
+ active: z.boolean()
159
+ })),
160
+ available_skus: z.array(z.object({
161
+ name: z.string(),
162
+ uuid: z.string(),
163
+ provider_uuid: z.string()
164
+ })),
141
165
  ready: z.boolean(),
142
166
  missing_steps: z.array(z.string())
143
167
  },
@@ -151,7 +175,9 @@ function registerTools(deps) {
151
175
  await clientManager.acquireRateLimit();
152
176
  return structuredResponse(await checkDeploymentReadiness(await clientManager.getQueryClient(), address, {
153
177
  size: args.size,
154
- image: args.image
178
+ image: args.image,
179
+ providerUuid: args.provider_uuid,
180
+ skuUuid: args.sku_uuid
155
181
  }), bigIntReplacer);
156
182
  }));
157
183
  mcpServer.registerTool("build_manifest_preview", {
@@ -238,6 +264,8 @@ function registerTools(deps) {
238
264
  image: z.string().optional().describe("Docker image to deploy. Required unless services is provided."),
239
265
  port: z.number().int().min(1).max(65535).optional().describe("Container port to expose. Required unless services is provided."),
240
266
  size: z.string().describe("SKU tier name (e.g. \"docker-micro\", \"docker-small\")"),
267
+ provider_uuid: z.string().optional().describe("Disambiguate when multiple providers publish a SKU with the same `size` name. Get candidates from browse_catalog or check_deployment_readiness. If a name is ambiguous and this is omitted, deploy_app fails with a SKU_AMBIGUOUS error listing the candidates."),
268
+ sku_uuid: z.string().optional().describe("Pin a specific SKU by its uuid. If provider_uuid is also given, the on-chain lookup is fully bypassed; otherwise the chain is still queried to resolve the SKU's provider. Takes precedence over size."),
241
269
  env: z.record(z.string(), z.string()).optional().describe("Environment variables as key-value pairs"),
242
270
  command: z.array(z.string()).optional().describe("Override container command (entrypoint)"),
243
271
  args: z.array(z.string()).optional().describe("Arguments to the container command"),
@@ -315,6 +343,8 @@ function registerTools(deps) {
315
343
  storage: args.storage,
316
344
  depends_on: args.depends_on,
317
345
  services: args.services,
346
+ providerUuid: args.provider_uuid,
347
+ skuUuid: args.sku_uuid,
318
348
  gasMultiplier: args.gas_multiplier,
319
349
  customDomain: args.custom_domain,
320
350
  serviceName: args.service_name,
@@ -1 +1 @@
1
- {"version":3,"file":"register-tools.js","names":[],"sources":["../../src/server/register-tools.ts"],"sourcesContent":["import {\n bigIntReplacer,\n type CosmosClientManager,\n DNS_LABEL_RE,\n jsonResponse,\n leaseStateToJSON,\n ManifestMCPError,\n ManifestMCPErrorCode,\n manifestMeta,\n mutatingAnnotations,\n readOnlyAnnotations,\n structuredResponse,\n type WalletProvider,\n withErrorHandling,\n} from '@manifest-network/manifest-mcp-core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';\nimport type {\n ServerNotification,\n ServerRequest,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport type { AuthTokenService } from '../http/auth-token-service.js';\nimport { getLeaseProvision, getLeaseReleases, MAX_TAIL } from '../http/fred.js';\nimport { appStatus } from '../tools/appStatus.js';\nimport { browseCatalog } from '../tools/browseCatalog.js';\nimport { buildManifestPreview } from '../tools/buildManifestPreview.js';\nimport { checkDeploymentReadiness } from '../tools/checkDeploymentReadiness.js';\nimport { deployApp } from '../tools/deployApp.js';\nimport { fetchActiveLease } from '../tools/fetchActiveLease.js';\nimport { getAppLogs } from '../tools/getLogs.js';\nimport { resolveProviderUrl } from '../tools/resolveLeaseProvider.js';\nimport { restartApp } from '../tools/restartApp.js';\nimport { updateApp } from '../tools/updateApp.js';\nimport { waitForAppReady } from '../tools/waitForAppReady.js';\nimport { createProgressEmitter } from './progress.js';\n\ninterface RegisterToolsDeps {\n mcpServer: McpServer;\n clientManager: CosmosClientManager;\n walletProvider: WalletProvider;\n authTokens: AuthTokenService;\n /**\n * Fetch implementation for all outbound provider/Fred HTTP calls. When\n * omitted (e.g. external library consumers), the HTTP layer falls back to\n * `globalThis.fetch`. `FredMCPServer` injects an SSRF-guarded fetch here by\n * default so on-chain-sourced provider URLs cannot reach internal hosts\n * (ENG-268).\n */\n fetchFn?: typeof globalThis.fetch;\n}\n\nexport function registerTools(deps: RegisterToolsDeps): void {\n const { mcpServer, clientManager, walletProvider, authTokens, fetchFn } =\n deps;\n\n // -- browse_catalog --\n mcpServer.registerTool(\n 'browse_catalog',\n {\n description:\n 'Browse available cloud providers and service tiers with live health checks. Use this before deploy_app to see which providers are online and what SKU sizes (e.g. docker-micro, docker-small) are available with pricing.',\n outputSchema: {\n providers: z.array(z.looseObject({})),\n tiers: z.record(z.string(), z.array(z.looseObject({}))),\n },\n annotations: readOnlyAnnotations('Browse providers and SKUs'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('browse_catalog', async () => {\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await browseCatalog(queryClient, fetchFn);\n return structuredResponse(result, bigIntReplacer);\n }),\n );\n\n // -- app_status --\n mcpServer.registerTool(\n 'app_status',\n {\n description:\n 'Get detailed status and connection info for a deployed app. Use this after deploy_app to check if an app is running and get its URL.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to check'),\n },\n outputSchema: {\n lease_uuid: z.string(),\n chainState: z.looseObject({\n state: z.number(),\n providerUuid: z.string(),\n }),\n connection: z.looseObject({}).optional(),\n fredStatus: z.looseObject({}).optional(),\n providerError: z.string().optional(),\n connectionError: z.string().optional(),\n },\n annotations: readOnlyAnnotations('Get deployed app status'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('app_status', async (args) => {\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await appStatus(\n queryClient,\n address,\n leaseUuid,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n fetchFn,\n );\n return structuredResponse(result, bigIntReplacer);\n }),\n );\n\n // -- wait_for_app_ready --\n mcpServer.registerTool(\n 'wait_for_app_ready',\n {\n description:\n 'Wait for a deployed app to reach the ACTIVE state on the provider, polling at the configured interval. Use this after deploy_app instead of looping app_status manually. Throws on timeout or terminal lease state.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to wait on'),\n timeout_seconds: z\n .number()\n .int()\n .min(1)\n .max(600)\n .optional()\n .describe(\n 'Maximum seconds to wait before throwing. Defaults to 120s.',\n ),\n interval_seconds: z\n .number()\n .int()\n .min(1)\n .max(60)\n .optional()\n .describe('Seconds between status polls. Defaults to 3s.'),\n },\n outputSchema: {\n lease_uuid: z.string().describe('The lease UUID that was waited on'),\n provider_uuid: z.string().describe('Provider hosting the lease'),\n provider_url: z.string().describe('Provider API URL'),\n state: z\n .string()\n .describe(\n 'Final lease state, JSON-encoded LeaseState (e.g. LEASE_STATE_ACTIVE)',\n ),\n status: z\n .looseObject({})\n .describe(\n 'Raw provider status payload (instances, endpoints, services, etc.)',\n ),\n },\n annotations: readOnlyAnnotations('Wait for deployed app readiness'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling(\n 'wait_for_app_ready',\n async (\n args,\n extra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n ) => {\n const emit = createProgressEmitter('wait_for_app_ready', extra);\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await waitForAppReady(\n queryClient,\n address,\n leaseUuid,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n {\n timeoutMs:\n args.timeout_seconds !== undefined\n ? args.timeout_seconds * 1_000\n : undefined,\n intervalMs:\n args.interval_seconds !== undefined\n ? args.interval_seconds * 1_000\n : undefined,\n abortSignal: extra.signal,\n onProgress: emit\n ? (status) => {\n const state = leaseStateToJSON(status.state);\n const provision = status.provision_status\n ? `, provision=${status.provision_status}`\n : '';\n emit(`Polling lease: state=${state}${provision}`);\n }\n : undefined,\n },\n fetchFn,\n );\n return structuredResponse(result, bigIntReplacer);\n },\n ),\n );\n\n // -- get_logs --\n mcpServer.registerTool(\n 'get_logs',\n {\n description:\n 'Get recent container logs for a deployed app. Use this to debug apps that are failing or to verify an app started correctly after deploy_app.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to get logs for'),\n tail: z\n .number()\n .int()\n .min(1)\n .max(MAX_TAIL)\n .optional()\n .describe('Number of recent log lines to retrieve'),\n },\n annotations: readOnlyAnnotations('Get container logs'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('get_logs', async (args) => {\n const leaseUuid = args.lease_uuid;\n const tail = args.tail;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await getAppLogs(\n queryClient,\n address,\n leaseUuid,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n tail,\n fetchFn,\n );\n return jsonResponse(result, bigIntReplacer);\n }),\n );\n\n // -- check_deployment_readiness --\n mcpServer.registerTool(\n 'check_deployment_readiness',\n {\n description:\n \"Pre-flight check for deploy_app: surfaces the caller's wallet balances, credit account, requested SKU availability, and a human-readable list of missing steps. Use this before deploy_app to decide whether to fund credits, switch SKU, or top up the wallet. Note: the chain does not expose provider allowed_registries, so a `ready: true` does not guarantee the registry of `image` is allowed — that is checked at upload time.\",\n inputSchema: {\n size: z\n .string()\n .optional()\n .describe(\n 'SKU tier to verify availability for (e.g. \"docker-micro\"). Omit to skip the SKU check.',\n ),\n image: z\n .string()\n .optional()\n .describe(\n 'Image planned for deployment. Recorded on the result for downstream display; not validated.',\n ),\n },\n outputSchema: {\n tenant: z.string(),\n image: z.string().nullable(),\n size: z.string().nullable(),\n wallet_balances: z.array(\n z.object({ denom: z.string(), amount: z.string() }),\n ),\n credits: z.looseObject({}).nullable(),\n current_balance: z\n .array(z.object({ denom: z.string(), amount: z.string() }))\n .optional(),\n hours_remaining: z.string().optional(),\n sku: z\n .object({\n name: z.string(),\n uuid: z.string(),\n provider_uuid: z.string(),\n price: z\n .object({ amount: z.string(), denom: z.string() })\n .optional(),\n active: z.boolean(),\n })\n .nullable(),\n available_sku_names: z.array(z.string()),\n ready: z.boolean(),\n missing_steps: z.array(z.string()),\n },\n annotations: readOnlyAnnotations('Check deploy pre-flight readiness'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('check_deployment_readiness', async (args) => {\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await checkDeploymentReadiness(queryClient, address, {\n size: args.size,\n image: args.image,\n });\n return structuredResponse(result, bigIntReplacer);\n }),\n );\n\n // -- build_manifest_preview --\n mcpServer.registerTool(\n 'build_manifest_preview',\n {\n description:\n 'Build a deployment manifest, validate it against the documented Fred rules, and compute the SHA-256 meta_hash that would be recorded on-chain. Use this BEFORE deploy_app to catch invalid manifests without paying for a lease. Two modes: raw `manifest` JSON string, or structured fields (image+port, or services for stacks).',\n inputSchema: {\n manifest: z\n .string()\n .optional()\n .describe(\n 'Raw manifest JSON string. Mutually exclusive with structured fields below.',\n ),\n image: z\n .string()\n .optional()\n .describe(\n 'Single-service image. Required (with port) when not using manifest or services.',\n ),\n port: z\n .number()\n .int()\n .min(1)\n .max(65535)\n .optional()\n .describe('Container port to expose. Required with image.'),\n env: z\n .record(z.string(), z.string())\n .optional()\n .describe('Environment variables as key-value pairs.'),\n command: z.array(z.string()).optional(),\n args: z.array(z.string()).optional(),\n user: z.string().optional(),\n tmpfs: z.array(z.string()).optional(),\n health_check: z\n .object({\n test: z.array(z.string()),\n interval: z.string().optional(),\n timeout: z.string().optional(),\n retries: z.number().int().optional(),\n start_period: z.string().optional(),\n })\n .optional(),\n stop_grace_period: z.string().optional(),\n init: z.boolean().optional(),\n expose: z.array(z.string()).optional(),\n labels: z.record(z.string(), z.string()).optional(),\n depends_on: z\n .record(z.string(), z.object({ condition: z.string() }))\n .optional(),\n services: z\n .record(\n z.string(),\n z.object({\n image: z.string(),\n ports: z.record(z.string(), z.object({})).optional(),\n env: z.record(z.string(), z.string()).optional(),\n command: z.array(z.string()).optional(),\n args: z.array(z.string()).optional(),\n user: z.string().optional(),\n tmpfs: z.array(z.string()).optional(),\n health_check: z\n .object({\n test: z.array(z.string()),\n interval: z.string().optional(),\n timeout: z.string().optional(),\n retries: z.number().int().optional(),\n start_period: z.string().optional(),\n })\n .optional(),\n stop_grace_period: z.string().optional(),\n depends_on: z\n .record(z.string(), z.object({ condition: z.string() }))\n .optional(),\n expose: z.array(z.string()).optional(),\n labels: z.record(z.string(), z.string()).optional(),\n }),\n )\n .optional()\n .describe(\n 'Multi-service stack. Mutually exclusive with image/port and manifest.',\n ),\n },\n outputSchema: {\n manifest_json: z\n .string()\n .describe('Canonical manifest JSON that would be uploaded'),\n manifest: z\n .looseObject({})\n .describe('Parsed manifest object (same content as manifest_json)'),\n format: z\n .enum(['single', 'stack'])\n .describe('Detected manifest format'),\n meta_hash_hex: z\n .string()\n .describe('SHA-256 of manifest_json, lowercase hex'),\n validation: z.object({\n valid: z.boolean(),\n errors: z.array(z.string()),\n }),\n },\n annotations: readOnlyAnnotations('Preview and validate a manifest'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('build_manifest_preview', async (args) => {\n const result = await buildManifestPreview({\n manifest: args.manifest,\n image: args.image,\n port: args.port,\n env: args.env,\n command: args.command,\n args: args.args,\n user: args.user,\n tmpfs: args.tmpfs,\n health_check: args.health_check,\n stop_grace_period: args.stop_grace_period,\n init: args.init,\n expose: args.expose,\n labels: args.labels,\n depends_on: args.depends_on,\n services: args.services,\n });\n return structuredResponse(result, bigIntReplacer);\n }),\n );\n\n // -- deploy_app --\n mcpServer.registerTool(\n 'deploy_app',\n {\n description:\n 'Deploy a new containerized application. Requires funded credits (use fund_credit if needed). Creates a lease on-chain, optionally attaches a custom domain (FQDN) to the lease item, uploads the container manifest to a provider, and polls until ready. Use browse_catalog first to see available SKU sizes.',\n inputSchema: {\n image: z\n .string()\n .optional()\n .describe(\n 'Docker image to deploy. Required unless services is provided.',\n ),\n port: z\n .number()\n .int()\n .min(1)\n .max(65535)\n .optional()\n .describe(\n 'Container port to expose. Required unless services is provided.',\n ),\n size: z\n .string()\n .describe('SKU tier name (e.g. \"docker-micro\", \"docker-small\")'),\n env: z\n .record(z.string(), z.string())\n .optional()\n .describe('Environment variables as key-value pairs'),\n command: z\n .array(z.string())\n .optional()\n .describe('Override container command (entrypoint)'),\n args: z\n .array(z.string())\n .optional()\n .describe('Arguments to the container command'),\n user: z\n .string()\n .optional()\n .describe('User to run the container as (e.g. \"1000:1000\")'),\n tmpfs: z\n .array(z.string())\n .optional()\n .describe('tmpfs mounts (e.g. [\"/tmp:size=64M\"])'),\n health_check: z\n .object({\n test: z.array(z.string()),\n interval: z.string().optional(),\n timeout: z.string().optional(),\n retries: z.number().int().optional(),\n start_period: z.string().optional(),\n })\n .optional()\n .describe('Container health check configuration'),\n stop_grace_period: z\n .string()\n .optional()\n .describe('Grace period before force-killing (e.g. \"30s\")'),\n init: z\n .boolean()\n .optional()\n .describe('Run an init process inside the container'),\n expose: z\n .array(z.string())\n .optional()\n .describe('Expose ports without publishing (e.g. [\"8080/tcp\"])'),\n labels: z\n .record(z.string(), z.string())\n .optional()\n .describe('Container labels as key-value pairs'),\n storage: z\n .string()\n .optional()\n .describe(\n 'Storage SKU name for persistent disk (adds a second lease item)',\n ),\n depends_on: z\n .record(z.string(), z.object({ condition: z.string() }))\n .optional()\n .describe('Service dependencies'),\n services: z\n .record(\n z.string(),\n z.object({\n image: z.string(),\n ports: z.record(z.string(), z.object({})).optional(),\n env: z.record(z.string(), z.string()).optional(),\n command: z.array(z.string()).optional(),\n args: z.array(z.string()).optional(),\n user: z.string().optional(),\n tmpfs: z.array(z.string()).optional(),\n health_check: z\n .object({\n test: z.array(z.string()),\n interval: z.string().optional(),\n timeout: z.string().optional(),\n retries: z.number().int().optional(),\n start_period: z.string().optional(),\n })\n .optional(),\n stop_grace_period: z.string().optional(),\n depends_on: z\n .record(z.string(), z.object({ condition: z.string() }))\n .optional(),\n expose: z.array(z.string()).optional(),\n labels: z.record(z.string(), z.string()).optional(),\n }),\n )\n .optional()\n .describe(\n 'Multi-service stack. Mutually exclusive with image/port. Keys are service names (RFC 1123 DNS labels).',\n ),\n gas_multiplier: z\n .number()\n .finite()\n .min(1)\n .optional()\n .describe(\n 'Gas simulation multiplier override for this transaction. Defaults to the server-configured value (typically 1.5). Increase if a transaction fails with out-of-gas errors.',\n ),\n custom_domain: z\n .string()\n .max(253)\n .optional()\n .describe(\n 'Optional FQDN to attach to the lease item once the create-lease tx confirms (e.g. \"app.example.com\"). Must be lowercase with a non-numeric TLD label and not match a reserved suffix; the chain validates the format. On a stack lease (`services`), pair with `service_name` to pick which item to attach the domain to.',\n ),\n service_name: z\n .string()\n .regex(DNS_LABEL_RE)\n .optional()\n .describe(\n 'Required when `custom_domain` is set on a stack lease (`services`). Must match one of the keys in `services` and be a valid RFC 1123 DNS label (1-63 lowercase alphanumeric chars + hyphens, no leading/trailing hyphen). Omit for image+port (single-item legacy) leases.',\n ),\n },\n outputSchema: {\n lease_uuid: z.string(),\n provider_uuid: z.string(),\n provider_url: z.string(),\n state: z.number(),\n url: z.string().optional(),\n connection: z.looseObject({}).optional(),\n connectionError: z.string().optional(),\n custom_domain: z.string().optional(),\n service_name: z.string().optional(),\n },\n // Additive: creates a new lease and uploads a manifest. Does not\n // replace any existing app's state.\n annotations: mutatingAnnotations('Deploy a containerized app', {\n destructive: false,\n }),\n _meta: manifestMeta({\n broadcasts: true,\n estimable: false,\n }),\n },\n withErrorHandling(\n 'deploy_app',\n async (\n args,\n extra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n ) => {\n const emit = createProgressEmitter('deploy_app', extra);\n const result = await deployApp(\n clientManager,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n (addr, uuid, metaHashHex) =>\n authTokens.leaseDataToken(addr, uuid, metaHashHex),\n {\n image: args.image,\n port: args.port,\n size: args.size,\n env: args.env,\n command: args.command,\n args: args.args,\n user: args.user,\n tmpfs: args.tmpfs,\n health_check: args.health_check,\n stop_grace_period: args.stop_grace_period,\n init: args.init,\n expose: args.expose,\n labels: args.labels,\n storage: args.storage,\n depends_on: args.depends_on,\n services: args.services,\n gasMultiplier: args.gas_multiplier,\n customDomain: args.custom_domain,\n serviceName: args.service_name,\n abortSignal: extra.signal,\n onLeaseCreated: emit\n ? (leaseUuid, providerUrl) => {\n emit(\n `Lease ${leaseUuid} created on chain at ${providerUrl}; uploading manifest`,\n );\n }\n : undefined,\n pollOptions: emit\n ? {\n onProgress: (status) => {\n const state = leaseStateToJSON(status.state);\n const provision = status.provision_status\n ? `, provision=${status.provision_status}`\n : '';\n emit(`Polling lease: state=${state}${provision}`);\n },\n }\n : undefined,\n },\n fetchFn,\n );\n return structuredResponse(result, bigIntReplacer);\n },\n ),\n );\n\n // -- restart_app --\n mcpServer.registerTool(\n 'restart_app',\n {\n description:\n 'Restart a running app via the provider without closing its lease. Use this to apply configuration changes or recover from a crash.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to restart'),\n },\n // Additive: triggers a restart cycle without replacing config.\n // Not idempotent — each call triggers a fresh restart even when\n // the app is already running (relies on the helper's default).\n annotations: mutatingAnnotations('Restart a deployed app', {\n destructive: false,\n }),\n _meta: manifestMeta({\n broadcasts: true,\n estimable: false,\n }),\n },\n withErrorHandling('restart_app', async (args) => {\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await restartApp(\n queryClient,\n address,\n leaseUuid,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n fetchFn,\n );\n return jsonResponse(result, bigIntReplacer);\n }),\n );\n\n // -- update_app --\n mcpServer.registerTool(\n 'update_app',\n {\n description:\n 'Update a deployed app with a new container manifest. Use this to change the Docker image, ports, or environment variables of a running app without closing the lease.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to update'),\n manifest: z\n .string()\n .describe('The full manifest JSON string to deploy'),\n existing_manifest: z\n .string()\n .optional()\n .describe(\n 'The current manifest JSON. When provided, the new manifest is merged over the existing one (env, ports, labels merged; other fields carried forward if not in new).',\n ),\n },\n outputSchema: {\n lease_uuid: z.string(),\n status: z.string(),\n },\n // Destructive: replaces the running app's manifest. Even with the\n // merge mode, prior config can be overwritten.\n annotations: mutatingAnnotations('Update a deployed app manifest', {\n destructive: true,\n }),\n _meta: manifestMeta({\n broadcasts: true,\n estimable: false,\n }),\n },\n withErrorHandling('update_app', async (args) => {\n const manifest = args.manifest;\n\n try {\n const parsed = JSON.parse(manifest);\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n throw new Error('must be a JSON object');\n }\n } catch (err) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid manifest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n\n const result = await updateApp(\n queryClient,\n address,\n leaseUuid,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n manifest,\n args.existing_manifest,\n fetchFn,\n );\n return structuredResponse(result, bigIntReplacer);\n }),\n );\n\n // -- app_diagnostics --\n mcpServer.registerTool(\n 'app_diagnostics',\n {\n description:\n 'Get provision diagnostics for a deployed app. Use this to debug apps stuck in provisioning or that failed to start. Returns provision status, failure count, and last error message.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to diagnose'),\n },\n outputSchema: {\n lease_uuid: z.string(),\n provision_status: z.string(),\n fail_count: z.number(),\n // The provider omits last_error when there's no recent failure.\n // structuredResponse's JSON.stringify round-trip drops undefined\n // keys, so the parsed structuredContent has no `last_error` at\n // all in the success case — declare optional to match.\n last_error: z.string().optional(),\n },\n annotations: readOnlyAnnotations('Get app provision diagnostics'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('app_diagnostics', async (args) => {\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n\n const lease = await fetchActiveLease(\n queryClient,\n leaseUuid,\n 'cannot be diagnosed',\n );\n const providerUrl = await resolveProviderUrl(\n queryClient,\n lease.providerUuid,\n );\n const authToken = await authTokens.providerToken(address, leaseUuid);\n const provision = await getLeaseProvision(\n providerUrl,\n leaseUuid,\n authToken,\n fetchFn,\n );\n\n return structuredResponse(\n {\n lease_uuid: leaseUuid,\n provision_status: provision.status,\n fail_count: provision.fail_count,\n last_error: provision.last_error,\n },\n bigIntReplacer,\n );\n }),\n );\n\n // -- app_releases --\n mcpServer.registerTool(\n 'app_releases',\n {\n description:\n 'Get release/version history for a deployed app. Use this to see what versions have been deployed, when they were created, and their status.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to get release history for'),\n },\n outputSchema: {\n lease_uuid: z.string(),\n releases: z.array(\n z.looseObject({\n version: z.number(),\n image: z.string(),\n status: z.string(),\n created_at: z.string(),\n }),\n ),\n },\n annotations: readOnlyAnnotations('Get app release history'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('app_releases', async (args) => {\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n\n const lease = await fetchActiveLease(\n queryClient,\n leaseUuid,\n 'releases are not available',\n );\n const providerUrl = await resolveProviderUrl(\n queryClient,\n lease.providerUuid,\n );\n const authToken = await authTokens.providerToken(address, leaseUuid);\n const result = await getLeaseReleases(\n providerUrl,\n leaseUuid,\n authToken,\n fetchFn,\n );\n\n return structuredResponse(\n {\n lease_uuid: leaseUuid,\n releases: result.releases,\n },\n bigIntReplacer,\n );\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"register-tools.js","names":[],"sources":["../../src/server/register-tools.ts"],"sourcesContent":["import {\n bigIntReplacer,\n type CosmosClientManager,\n DNS_LABEL_RE,\n jsonResponse,\n leaseStateToJSON,\n ManifestMCPError,\n ManifestMCPErrorCode,\n manifestMeta,\n mutatingAnnotations,\n readOnlyAnnotations,\n structuredResponse,\n type WalletProvider,\n withErrorHandling,\n} from '@manifest-network/manifest-mcp-core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';\nimport type {\n ServerNotification,\n ServerRequest,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport type { AuthTokenService } from '../http/auth-token-service.js';\nimport { getLeaseProvision, getLeaseReleases, MAX_TAIL } from '../http/fred.js';\nimport { appStatus } from '../tools/appStatus.js';\nimport { browseCatalog } from '../tools/browseCatalog.js';\nimport { buildManifestPreview } from '../tools/buildManifestPreview.js';\nimport { checkDeploymentReadiness } from '../tools/checkDeploymentReadiness.js';\nimport { deployApp } from '../tools/deployApp.js';\nimport { fetchActiveLease } from '../tools/fetchActiveLease.js';\nimport { getAppLogs } from '../tools/getLogs.js';\nimport { resolveProviderUrl } from '../tools/resolveLeaseProvider.js';\nimport { restartApp } from '../tools/restartApp.js';\nimport { updateApp } from '../tools/updateApp.js';\nimport { waitForAppReady } from '../tools/waitForAppReady.js';\nimport { createProgressEmitter } from './progress.js';\n\ninterface RegisterToolsDeps {\n mcpServer: McpServer;\n clientManager: CosmosClientManager;\n walletProvider: WalletProvider;\n authTokens: AuthTokenService;\n /**\n * Fetch implementation for all outbound provider/Fred HTTP calls. When\n * omitted (e.g. external library consumers), the HTTP layer falls back to\n * `globalThis.fetch`. `FredMCPServer` injects an SSRF-guarded fetch here by\n * default so on-chain-sourced provider URLs cannot reach internal hosts\n * (ENG-268).\n */\n fetchFn?: typeof globalThis.fetch;\n}\n\nexport function registerTools(deps: RegisterToolsDeps): void {\n const { mcpServer, clientManager, walletProvider, authTokens, fetchFn } =\n deps;\n\n // -- browse_catalog --\n mcpServer.registerTool(\n 'browse_catalog',\n {\n description:\n 'Browse available cloud providers and SKUs with live health checks. Use this before deploy_app to see which providers are online and what SKU sizes (e.g. docker-micro, docker-small) are available with pricing.',\n outputSchema: {\n providers: z.array(z.looseObject({})),\n skus: z.array(\n z.object({\n name: z.string(),\n sku_uuid: z.string(),\n provider_uuid: z.string(),\n provider_url: z.string().nullable(),\n price: z.string().nullable(),\n unit: z.string().nullable(),\n active: z.boolean(),\n }),\n ),\n },\n annotations: readOnlyAnnotations('Browse providers and SKUs'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('browse_catalog', async () => {\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await browseCatalog(queryClient, fetchFn);\n return structuredResponse(result, bigIntReplacer);\n }),\n );\n\n // -- app_status --\n mcpServer.registerTool(\n 'app_status',\n {\n description:\n 'Get detailed status and connection info for a deployed app. Use this after deploy_app to check if an app is running and get its URL.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to check'),\n },\n outputSchema: {\n lease_uuid: z.string(),\n chainState: z.looseObject({\n state: z.number(),\n providerUuid: z.string(),\n }),\n connection: z.looseObject({}).optional(),\n fredStatus: z.looseObject({}).optional(),\n providerError: z.string().optional(),\n connectionError: z.string().optional(),\n },\n annotations: readOnlyAnnotations('Get deployed app status'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('app_status', async (args) => {\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await appStatus(\n queryClient,\n address,\n leaseUuid,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n fetchFn,\n );\n return structuredResponse(result, bigIntReplacer);\n }),\n );\n\n // -- wait_for_app_ready --\n mcpServer.registerTool(\n 'wait_for_app_ready',\n {\n description:\n 'Wait for a deployed app to reach the ACTIVE state on the provider, polling at the configured interval. Use this after deploy_app instead of looping app_status manually. Throws on timeout or terminal lease state.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to wait on'),\n timeout_seconds: z\n .number()\n .int()\n .min(1)\n .max(600)\n .optional()\n .describe(\n 'Maximum seconds to wait before throwing. Defaults to 120s.',\n ),\n interval_seconds: z\n .number()\n .int()\n .min(1)\n .max(60)\n .optional()\n .describe('Seconds between status polls. Defaults to 3s.'),\n },\n outputSchema: {\n lease_uuid: z.string().describe('The lease UUID that was waited on'),\n provider_uuid: z.string().describe('Provider hosting the lease'),\n provider_url: z.string().describe('Provider API URL'),\n state: z\n .string()\n .describe(\n 'Final lease state, JSON-encoded LeaseState (e.g. LEASE_STATE_ACTIVE)',\n ),\n status: z\n .looseObject({})\n .describe(\n 'Raw provider status payload (instances, endpoints, services, etc.)',\n ),\n },\n annotations: readOnlyAnnotations('Wait for deployed app readiness'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling(\n 'wait_for_app_ready',\n async (\n args,\n extra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n ) => {\n const emit = createProgressEmitter('wait_for_app_ready', extra);\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await waitForAppReady(\n queryClient,\n address,\n leaseUuid,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n {\n timeoutMs:\n args.timeout_seconds !== undefined\n ? args.timeout_seconds * 1_000\n : undefined,\n intervalMs:\n args.interval_seconds !== undefined\n ? args.interval_seconds * 1_000\n : undefined,\n abortSignal: extra.signal,\n onProgress: emit\n ? (status) => {\n const state = leaseStateToJSON(status.state);\n const provision = status.provision_status\n ? `, provision=${status.provision_status}`\n : '';\n emit(`Polling lease: state=${state}${provision}`);\n }\n : undefined,\n },\n fetchFn,\n );\n return structuredResponse(result, bigIntReplacer);\n },\n ),\n );\n\n // -- get_logs --\n mcpServer.registerTool(\n 'get_logs',\n {\n description:\n 'Get recent container logs for a deployed app. Use this to debug apps that are failing or to verify an app started correctly after deploy_app.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to get logs for'),\n tail: z\n .number()\n .int()\n .min(1)\n .max(MAX_TAIL)\n .optional()\n .describe('Number of recent log lines to retrieve'),\n },\n annotations: readOnlyAnnotations('Get container logs'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('get_logs', async (args) => {\n const leaseUuid = args.lease_uuid;\n const tail = args.tail;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await getAppLogs(\n queryClient,\n address,\n leaseUuid,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n tail,\n fetchFn,\n );\n return jsonResponse(result, bigIntReplacer);\n }),\n );\n\n // -- check_deployment_readiness --\n mcpServer.registerTool(\n 'check_deployment_readiness',\n {\n description:\n \"Pre-flight check for deploy_app: surfaces the caller's wallet balances, credit account, requested SKU availability, and a human-readable list of missing steps. Use this before deploy_app to decide whether to fund credits, switch SKU, or top up the wallet. Note: the chain does not expose provider allowed_registries, so a `ready: true` does not guarantee the registry of `image` is allowed — that is checked at upload time.\",\n inputSchema: {\n size: z\n .string()\n .optional()\n .describe(\n 'SKU tier to verify availability for (e.g. \"docker-micro\"). Omit to skip the SKU check.',\n ),\n image: z\n .string()\n .optional()\n .describe(\n 'Image planned for deployment. Recorded on the result for downstream display; not validated.',\n ),\n provider_uuid: z\n .string()\n .optional()\n .describe('Narrow a duplicate SKU `size` to one provider.'),\n sku_uuid: z\n .string()\n .optional()\n .describe(\n 'Resolve the SKU by uuid, bypassing the `size` name filter. When set, `size` is ignored for candidate selection. Pair with `provider_uuid` to further narrow to a specific provider.',\n ),\n },\n outputSchema: {\n tenant: z.string(),\n image: z.string().nullable(),\n size: z.string().nullable(),\n wallet_balances: z.array(\n z.object({ denom: z.string(), amount: z.string() }),\n ),\n credits: z.looseObject({}).nullable(),\n current_balance: z\n .array(z.object({ denom: z.string(), amount: z.string() }))\n .optional(),\n hours_remaining: z.string().optional(),\n sku: z\n .object({\n name: z.string(),\n uuid: z.string(),\n provider_uuid: z.string(),\n price: z\n .object({ amount: z.string(), denom: z.string() })\n .optional(),\n active: z.boolean(),\n })\n .nullable(),\n sku_candidates: z.array(\n z.object({\n name: z.string(),\n uuid: z.string(),\n provider_uuid: z.string(),\n price: z\n .object({ amount: z.string(), denom: z.string() })\n .optional(),\n active: z.boolean(),\n }),\n ),\n available_skus: z.array(\n z.object({\n name: z.string(),\n uuid: z.string(),\n provider_uuid: z.string(),\n }),\n ),\n ready: z.boolean(),\n missing_steps: z.array(z.string()),\n },\n annotations: readOnlyAnnotations('Check deploy pre-flight readiness'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('check_deployment_readiness', async (args) => {\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await checkDeploymentReadiness(queryClient, address, {\n size: args.size,\n image: args.image,\n providerUuid: args.provider_uuid,\n skuUuid: args.sku_uuid,\n });\n return structuredResponse(result, bigIntReplacer);\n }),\n );\n\n // -- build_manifest_preview --\n mcpServer.registerTool(\n 'build_manifest_preview',\n {\n description:\n 'Build a deployment manifest, validate it against the documented Fred rules, and compute the SHA-256 meta_hash that would be recorded on-chain. Use this BEFORE deploy_app to catch invalid manifests without paying for a lease. Two modes: raw `manifest` JSON string, or structured fields (image+port, or services for stacks).',\n inputSchema: {\n manifest: z\n .string()\n .optional()\n .describe(\n 'Raw manifest JSON string. Mutually exclusive with structured fields below.',\n ),\n image: z\n .string()\n .optional()\n .describe(\n 'Single-service image. Required (with port) when not using manifest or services.',\n ),\n port: z\n .number()\n .int()\n .min(1)\n .max(65535)\n .optional()\n .describe('Container port to expose. Required with image.'),\n env: z\n .record(z.string(), z.string())\n .optional()\n .describe('Environment variables as key-value pairs.'),\n command: z.array(z.string()).optional(),\n args: z.array(z.string()).optional(),\n user: z.string().optional(),\n tmpfs: z.array(z.string()).optional(),\n health_check: z\n .object({\n test: z.array(z.string()),\n interval: z.string().optional(),\n timeout: z.string().optional(),\n retries: z.number().int().optional(),\n start_period: z.string().optional(),\n })\n .optional(),\n stop_grace_period: z.string().optional(),\n init: z.boolean().optional(),\n expose: z.array(z.string()).optional(),\n labels: z.record(z.string(), z.string()).optional(),\n depends_on: z\n .record(z.string(), z.object({ condition: z.string() }))\n .optional(),\n services: z\n .record(\n z.string(),\n z.object({\n image: z.string(),\n ports: z.record(z.string(), z.object({})).optional(),\n env: z.record(z.string(), z.string()).optional(),\n command: z.array(z.string()).optional(),\n args: z.array(z.string()).optional(),\n user: z.string().optional(),\n tmpfs: z.array(z.string()).optional(),\n health_check: z\n .object({\n test: z.array(z.string()),\n interval: z.string().optional(),\n timeout: z.string().optional(),\n retries: z.number().int().optional(),\n start_period: z.string().optional(),\n })\n .optional(),\n stop_grace_period: z.string().optional(),\n depends_on: z\n .record(z.string(), z.object({ condition: z.string() }))\n .optional(),\n expose: z.array(z.string()).optional(),\n labels: z.record(z.string(), z.string()).optional(),\n }),\n )\n .optional()\n .describe(\n 'Multi-service stack. Mutually exclusive with image/port and manifest.',\n ),\n },\n outputSchema: {\n manifest_json: z\n .string()\n .describe('Canonical manifest JSON that would be uploaded'),\n manifest: z\n .looseObject({})\n .describe('Parsed manifest object (same content as manifest_json)'),\n format: z\n .enum(['single', 'stack'])\n .describe('Detected manifest format'),\n meta_hash_hex: z\n .string()\n .describe('SHA-256 of manifest_json, lowercase hex'),\n validation: z.object({\n valid: z.boolean(),\n errors: z.array(z.string()),\n }),\n },\n annotations: readOnlyAnnotations('Preview and validate a manifest'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('build_manifest_preview', async (args) => {\n const result = await buildManifestPreview({\n manifest: args.manifest,\n image: args.image,\n port: args.port,\n env: args.env,\n command: args.command,\n args: args.args,\n user: args.user,\n tmpfs: args.tmpfs,\n health_check: args.health_check,\n stop_grace_period: args.stop_grace_period,\n init: args.init,\n expose: args.expose,\n labels: args.labels,\n depends_on: args.depends_on,\n services: args.services,\n });\n return structuredResponse(result, bigIntReplacer);\n }),\n );\n\n // -- deploy_app --\n mcpServer.registerTool(\n 'deploy_app',\n {\n description:\n 'Deploy a new containerized application. Requires funded credits (use fund_credit if needed). Creates a lease on-chain, optionally attaches a custom domain (FQDN) to the lease item, uploads the container manifest to a provider, and polls until ready. Use browse_catalog first to see available SKU sizes.',\n inputSchema: {\n image: z\n .string()\n .optional()\n .describe(\n 'Docker image to deploy. Required unless services is provided.',\n ),\n port: z\n .number()\n .int()\n .min(1)\n .max(65535)\n .optional()\n .describe(\n 'Container port to expose. Required unless services is provided.',\n ),\n size: z\n .string()\n .describe('SKU tier name (e.g. \"docker-micro\", \"docker-small\")'),\n provider_uuid: z\n .string()\n .optional()\n .describe(\n 'Disambiguate when multiple providers publish a SKU with the same `size` name. ' +\n 'Get candidates from browse_catalog or check_deployment_readiness. If a name ' +\n 'is ambiguous and this is omitted, deploy_app fails with a SKU_AMBIGUOUS error ' +\n 'listing the candidates.',\n ),\n sku_uuid: z\n .string()\n .optional()\n .describe(\n 'Pin a specific SKU by its uuid. If provider_uuid is also given, the ' +\n \"on-chain lookup is fully bypassed; otherwise the chain is still queried to resolve the SKU's \" +\n 'provider. Takes precedence over size.',\n ),\n env: z\n .record(z.string(), z.string())\n .optional()\n .describe('Environment variables as key-value pairs'),\n command: z\n .array(z.string())\n .optional()\n .describe('Override container command (entrypoint)'),\n args: z\n .array(z.string())\n .optional()\n .describe('Arguments to the container command'),\n user: z\n .string()\n .optional()\n .describe('User to run the container as (e.g. \"1000:1000\")'),\n tmpfs: z\n .array(z.string())\n .optional()\n .describe('tmpfs mounts (e.g. [\"/tmp:size=64M\"])'),\n health_check: z\n .object({\n test: z.array(z.string()),\n interval: z.string().optional(),\n timeout: z.string().optional(),\n retries: z.number().int().optional(),\n start_period: z.string().optional(),\n })\n .optional()\n .describe('Container health check configuration'),\n stop_grace_period: z\n .string()\n .optional()\n .describe('Grace period before force-killing (e.g. \"30s\")'),\n init: z\n .boolean()\n .optional()\n .describe('Run an init process inside the container'),\n expose: z\n .array(z.string())\n .optional()\n .describe('Expose ports without publishing (e.g. [\"8080/tcp\"])'),\n labels: z\n .record(z.string(), z.string())\n .optional()\n .describe('Container labels as key-value pairs'),\n storage: z\n .string()\n .optional()\n .describe(\n 'Storage SKU name for persistent disk (adds a second lease item)',\n ),\n depends_on: z\n .record(z.string(), z.object({ condition: z.string() }))\n .optional()\n .describe('Service dependencies'),\n services: z\n .record(\n z.string(),\n z.object({\n image: z.string(),\n ports: z.record(z.string(), z.object({})).optional(),\n env: z.record(z.string(), z.string()).optional(),\n command: z.array(z.string()).optional(),\n args: z.array(z.string()).optional(),\n user: z.string().optional(),\n tmpfs: z.array(z.string()).optional(),\n health_check: z\n .object({\n test: z.array(z.string()),\n interval: z.string().optional(),\n timeout: z.string().optional(),\n retries: z.number().int().optional(),\n start_period: z.string().optional(),\n })\n .optional(),\n stop_grace_period: z.string().optional(),\n depends_on: z\n .record(z.string(), z.object({ condition: z.string() }))\n .optional(),\n expose: z.array(z.string()).optional(),\n labels: z.record(z.string(), z.string()).optional(),\n }),\n )\n .optional()\n .describe(\n 'Multi-service stack. Mutually exclusive with image/port. Keys are service names (RFC 1123 DNS labels).',\n ),\n gas_multiplier: z\n .number()\n .finite()\n .min(1)\n .optional()\n .describe(\n 'Gas simulation multiplier override for this transaction. Defaults to the server-configured value (typically 1.5). Increase if a transaction fails with out-of-gas errors.',\n ),\n custom_domain: z\n .string()\n .max(253)\n .optional()\n .describe(\n 'Optional FQDN to attach to the lease item once the create-lease tx confirms (e.g. \"app.example.com\"). Must be lowercase with a non-numeric TLD label and not match a reserved suffix; the chain validates the format. On a stack lease (`services`), pair with `service_name` to pick which item to attach the domain to.',\n ),\n service_name: z\n .string()\n .regex(DNS_LABEL_RE)\n .optional()\n .describe(\n 'Required when `custom_domain` is set on a stack lease (`services`). Must match one of the keys in `services` and be a valid RFC 1123 DNS label (1-63 lowercase alphanumeric chars + hyphens, no leading/trailing hyphen). Omit for image+port (single-item legacy) leases.',\n ),\n },\n outputSchema: {\n lease_uuid: z.string(),\n provider_uuid: z.string(),\n provider_url: z.string(),\n state: z.number(),\n url: z.string().optional(),\n connection: z.looseObject({}).optional(),\n connectionError: z.string().optional(),\n custom_domain: z.string().optional(),\n service_name: z.string().optional(),\n },\n // Additive: creates a new lease and uploads a manifest. Does not\n // replace any existing app's state.\n annotations: mutatingAnnotations('Deploy a containerized app', {\n destructive: false,\n }),\n _meta: manifestMeta({\n broadcasts: true,\n estimable: false,\n }),\n },\n withErrorHandling(\n 'deploy_app',\n async (\n args,\n extra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n ) => {\n const emit = createProgressEmitter('deploy_app', extra);\n const result = await deployApp(\n clientManager,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n (addr, uuid, metaHashHex) =>\n authTokens.leaseDataToken(addr, uuid, metaHashHex),\n {\n image: args.image,\n port: args.port,\n size: args.size,\n env: args.env,\n command: args.command,\n args: args.args,\n user: args.user,\n tmpfs: args.tmpfs,\n health_check: args.health_check,\n stop_grace_period: args.stop_grace_period,\n init: args.init,\n expose: args.expose,\n labels: args.labels,\n storage: args.storage,\n depends_on: args.depends_on,\n services: args.services,\n providerUuid: args.provider_uuid,\n skuUuid: args.sku_uuid,\n gasMultiplier: args.gas_multiplier,\n customDomain: args.custom_domain,\n serviceName: args.service_name,\n abortSignal: extra.signal,\n onLeaseCreated: emit\n ? (leaseUuid, providerUrl) => {\n emit(\n `Lease ${leaseUuid} created on chain at ${providerUrl}; uploading manifest`,\n );\n }\n : undefined,\n pollOptions: emit\n ? {\n onProgress: (status) => {\n const state = leaseStateToJSON(status.state);\n const provision = status.provision_status\n ? `, provision=${status.provision_status}`\n : '';\n emit(`Polling lease: state=${state}${provision}`);\n },\n }\n : undefined,\n },\n fetchFn,\n );\n return structuredResponse(result, bigIntReplacer);\n },\n ),\n );\n\n // -- restart_app --\n mcpServer.registerTool(\n 'restart_app',\n {\n description:\n 'Restart a running app via the provider without closing its lease. Use this to apply configuration changes or recover from a crash.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to restart'),\n },\n // Additive: triggers a restart cycle without replacing config.\n // Not idempotent — each call triggers a fresh restart even when\n // the app is already running (relies on the helper's default).\n annotations: mutatingAnnotations('Restart a deployed app', {\n destructive: false,\n }),\n _meta: manifestMeta({\n broadcasts: true,\n estimable: false,\n }),\n },\n withErrorHandling('restart_app', async (args) => {\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const result = await restartApp(\n queryClient,\n address,\n leaseUuid,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n fetchFn,\n );\n return jsonResponse(result, bigIntReplacer);\n }),\n );\n\n // -- update_app --\n mcpServer.registerTool(\n 'update_app',\n {\n description:\n 'Update a deployed app with a new container manifest. Use this to change the Docker image, ports, or environment variables of a running app without closing the lease.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to update'),\n manifest: z\n .string()\n .describe('The full manifest JSON string to deploy'),\n existing_manifest: z\n .string()\n .optional()\n .describe(\n 'The current manifest JSON. When provided, the new manifest is merged over the existing one (env, ports, labels merged; other fields carried forward if not in new).',\n ),\n },\n outputSchema: {\n lease_uuid: z.string(),\n status: z.string(),\n },\n // Destructive: replaces the running app's manifest. Even with the\n // merge mode, prior config can be overwritten.\n annotations: mutatingAnnotations('Update a deployed app manifest', {\n destructive: true,\n }),\n _meta: manifestMeta({\n broadcasts: true,\n estimable: false,\n }),\n },\n withErrorHandling('update_app', async (args) => {\n const manifest = args.manifest;\n\n try {\n const parsed = JSON.parse(manifest);\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n throw new Error('must be a JSON object');\n }\n } catch (err) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid manifest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n\n const result = await updateApp(\n queryClient,\n address,\n leaseUuid,\n (addr, uuid) => authTokens.providerToken(addr, uuid),\n manifest,\n args.existing_manifest,\n fetchFn,\n );\n return structuredResponse(result, bigIntReplacer);\n }),\n );\n\n // -- app_diagnostics --\n mcpServer.registerTool(\n 'app_diagnostics',\n {\n description:\n 'Get provision diagnostics for a deployed app. Use this to debug apps stuck in provisioning or that failed to start. Returns provision status, failure count, and last error message.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to diagnose'),\n },\n outputSchema: {\n lease_uuid: z.string(),\n provision_status: z.string(),\n fail_count: z.number(),\n // The provider omits last_error when there's no recent failure.\n // structuredResponse's JSON.stringify round-trip drops undefined\n // keys, so the parsed structuredContent has no `last_error` at\n // all in the success case — declare optional to match.\n last_error: z.string().optional(),\n },\n annotations: readOnlyAnnotations('Get app provision diagnostics'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('app_diagnostics', async (args) => {\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n\n const lease = await fetchActiveLease(\n queryClient,\n leaseUuid,\n 'cannot be diagnosed',\n );\n const providerUrl = await resolveProviderUrl(\n queryClient,\n lease.providerUuid,\n );\n const authToken = await authTokens.providerToken(address, leaseUuid);\n const provision = await getLeaseProvision(\n providerUrl,\n leaseUuid,\n authToken,\n fetchFn,\n );\n\n return structuredResponse(\n {\n lease_uuid: leaseUuid,\n provision_status: provision.status,\n fail_count: provision.fail_count,\n last_error: provision.last_error,\n },\n bigIntReplacer,\n );\n }),\n );\n\n // -- app_releases --\n mcpServer.registerTool(\n 'app_releases',\n {\n description:\n 'Get release/version history for a deployed app. Use this to see what versions have been deployed, when they were created, and their status.',\n inputSchema: {\n lease_uuid: z\n .string()\n .uuid()\n .describe('The lease UUID of the app to get release history for'),\n },\n outputSchema: {\n lease_uuid: z.string(),\n releases: z.array(\n z.looseObject({\n version: z.number(),\n image: z.string(),\n status: z.string(),\n created_at: z.string(),\n }),\n ),\n },\n annotations: readOnlyAnnotations('Get app release history'),\n _meta: manifestMeta({\n broadcasts: false,\n estimable: false,\n }),\n },\n withErrorHandling('app_releases', async (args) => {\n const leaseUuid = args.lease_uuid;\n const address = await walletProvider.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n\n const lease = await fetchActiveLease(\n queryClient,\n leaseUuid,\n 'releases are not available',\n );\n const providerUrl = await resolveProviderUrl(\n queryClient,\n lease.providerUuid,\n );\n const authToken = await authTokens.providerToken(address, leaseUuid);\n const result = await getLeaseReleases(\n providerUrl,\n leaseUuid,\n authToken,\n fetchFn,\n );\n\n return structuredResponse(\n {\n lease_uuid: leaseUuid,\n releases: result.releases,\n },\n bigIntReplacer,\n );\n }),\n );\n}\n"],"mappings":"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"}
@@ -16,11 +16,15 @@ declare function browseCatalog(queryClient: ManifestQueryClient, fetchFn?: typeo
16
16
  healthy: boolean;
17
17
  providerUuid: string | undefined;
18
18
  }[];
19
- tiers: Record<string, {
20
- provider: string;
21
- price: string | null;
22
- unit: string | null;
23
- }[]>;
19
+ skus: {
20
+ name: string;
21
+ sku_uuid: string;
22
+ provider_uuid: string;
23
+ provider_url: string | null;
24
+ price: string;
25
+ unit: string;
26
+ active: boolean;
27
+ }[];
24
28
  }>;
25
29
  //#endregion
26
30
  export { browseCatalog, mapWithConcurrency };
@@ -55,19 +55,17 @@ async function browseCatalog(queryClient, fetchFn) {
55
55
  };
56
56
  });
57
57
  const providerByUuid = new Map(providersResult.providers.map((p) => [p.uuid, p]));
58
- const tiers = {};
59
- for (const s of skusResult.skus) {
60
- const entry = {
61
- provider: providerByUuid.get(s.providerUuid)?.apiUrl ?? s.providerUuid,
62
- price: s.basePrice?.amount ?? null,
63
- unit: s.basePrice?.denom ?? null
64
- };
65
- if (!tiers[s.name]) tiers[s.name] = [];
66
- tiers[s.name].push(entry);
67
- }
68
58
  return {
69
59
  providers,
70
- tiers
60
+ skus: skusResult.skus.map((s) => ({
61
+ name: s.name,
62
+ sku_uuid: s.uuid,
63
+ provider_uuid: s.providerUuid,
64
+ provider_url: providerByUuid.get(s.providerUuid)?.apiUrl ?? null,
65
+ price: s.basePrice?.amount ?? null,
66
+ unit: s.basePrice?.denom ?? null,
67
+ active: s.active
68
+ }))
71
69
  };
72
70
  }
73
71
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"browseCatalog.js","names":[],"sources":["../../src/tools/browseCatalog.ts"],"sourcesContent":["import type { ManifestQueryClient } from '@manifest-network/manifest-mcp-core';\nimport {\n createPagination,\n INFRASTRUCTURE_ERROR_CODES,\n MAX_PAGE_LIMIT,\n ManifestMCPError,\n} from '@manifest-network/manifest-mcp-core';\nimport { getProviderHealth, ProviderApiError } from '../http/provider.js';\n\n/** Maximum concurrent outgoing health check requests to provider APIs */\nconst MAX_CONCURRENT_HEALTH_CHECKS = 5;\n\n/**\n * Run an array of async functions with a concurrency limit.\n * Returns results in the same order as the input.\n */\nexport async function mapWithConcurrency<T, R>(\n items: T[],\n limit: number,\n fn: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = new Array(items.length);\n let nextIndex = 0;\n\n async function worker(): Promise<void> {\n while (nextIndex < items.length) {\n const idx = nextIndex++;\n results[idx] = await fn(items[idx]);\n }\n }\n\n const workerCount = Math.min(Math.max(limit, 1), items.length);\n const workers = Array.from({ length: workerCount }, () => worker());\n await Promise.all(workers);\n return results;\n}\n\nexport async function browseCatalog(\n queryClient: ManifestQueryClient,\n fetchFn?: typeof globalThis.fetch,\n) {\n const sku = queryClient.liftedinit.sku.v1;\n\n const pagination = createPagination(MAX_PAGE_LIMIT);\n\n const [providersResult, skusResult] = await Promise.all([\n sku.providers({ activeOnly: true, pagination }),\n sku.sKUs({ activeOnly: true, pagination }),\n ]);\n\n const providers = await mapWithConcurrency(\n providersResult.providers,\n MAX_CONCURRENT_HEALTH_CHECKS,\n async (p) => {\n let healthy = false;\n let providerUuid: string | undefined;\n let healthError: string | undefined;\n try {\n const health = await getProviderHealth(p.apiUrl, undefined, fetchFn);\n healthy = health.status === 'ok' || health.status === 'healthy';\n providerUuid = health.provider_uuid;\n } catch (err) {\n if (\n err instanceof ManifestMCPError &&\n INFRASTRUCTURE_ERROR_CODES.has(err.code)\n )\n throw err;\n if (err instanceof ProviderApiError) {\n healthError = `HTTP ${err.status}: ${err.message}`;\n } else {\n healthError = `Health check failed: ${err instanceof Error ? err.message : String(err)}`;\n }\n }\n return {\n uuid: p.uuid,\n address: p.address,\n apiUrl: p.apiUrl,\n active: p.active,\n healthy,\n providerUuid,\n ...(healthError && { healthError }),\n };\n },\n );\n\n const providerByUuid = new Map(\n providersResult.providers.map((p) => [p.uuid, p]),\n );\n\n const tiers: Record<\n string,\n Array<{ provider: string; price: string | null; unit: string | null }>\n > = {};\n for (const s of skusResult.skus) {\n const provider = providerByUuid.get(s.providerUuid);\n const entry = {\n provider: provider?.apiUrl ?? s.providerUuid,\n price: s.basePrice?.amount ?? null,\n unit: s.basePrice?.denom ?? null,\n };\n if (!tiers[s.name]) {\n tiers[s.name] = [];\n }\n tiers[s.name].push(entry);\n }\n\n return { providers, tiers };\n}\n"],"mappings":";;;;AAUA,MAAM,+BAA+B;;;;;AAMrC,eAAsB,mBACpB,OACA,OACA,IACc;CACd,MAAM,UAAe,IAAI,MAAM,MAAM,MAAM;CAC3C,IAAI,YAAY;CAEhB,eAAe,SAAwB;EACrC,OAAO,YAAY,MAAM,QAAQ;GAC/B,MAAM,MAAM;GACZ,QAAQ,OAAO,MAAM,GAAG,MAAM,IAAI;EACpC;CACF;CAEA,MAAM,cAAc,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,MAAM,MAAM;CAC7D,MAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,YAAY,SAAS,OAAO,CAAC;CAClE,MAAM,QAAQ,IAAI,OAAO;CACzB,OAAO;AACT;AAEA,eAAsB,cACpB,aACA,SACA;CACA,MAAM,MAAM,YAAY,WAAW,IAAI;CAEvC,MAAM,aAAa,iBAAiB,cAAc;CAElD,MAAM,CAAC,iBAAiB,cAAc,MAAM,QAAQ,IAAI,CACtD,IAAI,UAAU;EAAE,YAAY;EAAM;CAAW,CAAC,GAC9C,IAAI,KAAK;EAAE,YAAY;EAAM;CAAW,CAAC,CAC3C,CAAC;CAED,MAAM,YAAY,MAAM,mBACtB,gBAAgB,WAChB,8BACA,OAAO,MAAM;EACX,IAAI,UAAU;EACd,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,MAAM,kBAAkB,EAAE,QAAQ,KAAA,GAAW,OAAO;GACnE,UAAU,OAAO,WAAW,QAAQ,OAAO,WAAW;GACtD,eAAe,OAAO;EACxB,SAAS,KAAK;GACZ,IACE,eAAe,oBACf,2BAA2B,IAAI,IAAI,IAAI,GAEvC,MAAM;GACR,IAAI,eAAe,kBACjB,cAAc,QAAQ,IAAI,OAAO,IAAI,IAAI;QAEzC,cAAc,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;EAEzF;EACA,OAAO;GACL,MAAM,EAAE;GACR,SAAS,EAAE;GACX,QAAQ,EAAE;GACV,QAAQ,EAAE;GACV;GACA;GACA,GAAI,eAAe,EAAE,YAAY;EACnC;CACF,CACF;CAEA,MAAM,iBAAiB,IAAI,IACzB,gBAAgB,UAAU,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAClD;CAEA,MAAM,QAGF,CAAC;CACL,KAAK,MAAM,KAAK,WAAW,MAAM;EAE/B,MAAM,QAAQ;GACZ,UAFe,eAAe,IAAI,EAAE,YAEnB,GAAG,UAAU,EAAE;GAChC,OAAO,EAAE,WAAW,UAAU;GAC9B,MAAM,EAAE,WAAW,SAAS;EAC9B;EACA,IAAI,CAAC,MAAM,EAAE,OACX,MAAM,EAAE,QAAQ,CAAC;EAEnB,MAAM,EAAE,MAAM,KAAK,KAAK;CAC1B;CAEA,OAAO;EAAE;EAAW;CAAM;AAC5B"}
1
+ {"version":3,"file":"browseCatalog.js","names":[],"sources":["../../src/tools/browseCatalog.ts"],"sourcesContent":["import type { ManifestQueryClient } from '@manifest-network/manifest-mcp-core';\nimport {\n createPagination,\n INFRASTRUCTURE_ERROR_CODES,\n MAX_PAGE_LIMIT,\n ManifestMCPError,\n} from '@manifest-network/manifest-mcp-core';\nimport { getProviderHealth, ProviderApiError } from '../http/provider.js';\n\n/** Maximum concurrent outgoing health check requests to provider APIs */\nconst MAX_CONCURRENT_HEALTH_CHECKS = 5;\n\n/**\n * Run an array of async functions with a concurrency limit.\n * Returns results in the same order as the input.\n */\nexport async function mapWithConcurrency<T, R>(\n items: T[],\n limit: number,\n fn: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = new Array(items.length);\n let nextIndex = 0;\n\n async function worker(): Promise<void> {\n while (nextIndex < items.length) {\n const idx = nextIndex++;\n results[idx] = await fn(items[idx]);\n }\n }\n\n const workerCount = Math.min(Math.max(limit, 1), items.length);\n const workers = Array.from({ length: workerCount }, () => worker());\n await Promise.all(workers);\n return results;\n}\n\nexport async function browseCatalog(\n queryClient: ManifestQueryClient,\n fetchFn?: typeof globalThis.fetch,\n) {\n const sku = queryClient.liftedinit.sku.v1;\n\n const pagination = createPagination(MAX_PAGE_LIMIT);\n\n const [providersResult, skusResult] = await Promise.all([\n sku.providers({ activeOnly: true, pagination }),\n sku.sKUs({ activeOnly: true, pagination }),\n ]);\n\n const providers = await mapWithConcurrency(\n providersResult.providers,\n MAX_CONCURRENT_HEALTH_CHECKS,\n async (p) => {\n let healthy = false;\n let providerUuid: string | undefined;\n let healthError: string | undefined;\n try {\n const health = await getProviderHealth(p.apiUrl, undefined, fetchFn);\n healthy = health.status === 'ok' || health.status === 'healthy';\n providerUuid = health.provider_uuid;\n } catch (err) {\n if (\n err instanceof ManifestMCPError &&\n INFRASTRUCTURE_ERROR_CODES.has(err.code)\n )\n throw err;\n if (err instanceof ProviderApiError) {\n healthError = `HTTP ${err.status}: ${err.message}`;\n } else {\n healthError = `Health check failed: ${err instanceof Error ? err.message : String(err)}`;\n }\n }\n return {\n uuid: p.uuid,\n address: p.address,\n apiUrl: p.apiUrl,\n active: p.active,\n healthy,\n providerUuid,\n ...(healthError && { healthError }),\n };\n },\n );\n\n const providerByUuid = new Map(\n providersResult.providers.map((p) => [p.uuid, p]),\n );\n\n const skus = skusResult.skus.map((s) => ({\n name: s.name,\n sku_uuid: s.uuid,\n provider_uuid: s.providerUuid,\n provider_url: providerByUuid.get(s.providerUuid)?.apiUrl ?? null,\n price: s.basePrice?.amount ?? null,\n unit: s.basePrice?.denom ?? null,\n active: s.active,\n }));\n\n return { providers, skus };\n}\n"],"mappings":";;;;AAUA,MAAM,+BAA+B;;;;;AAMrC,eAAsB,mBACpB,OACA,OACA,IACc;CACd,MAAM,UAAe,IAAI,MAAM,MAAM,MAAM;CAC3C,IAAI,YAAY;CAEhB,eAAe,SAAwB;EACrC,OAAO,YAAY,MAAM,QAAQ;GAC/B,MAAM,MAAM;GACZ,QAAQ,OAAO,MAAM,GAAG,MAAM,IAAI;EACpC;CACF;CAEA,MAAM,cAAc,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,MAAM,MAAM;CAC7D,MAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,YAAY,SAAS,OAAO,CAAC;CAClE,MAAM,QAAQ,IAAI,OAAO;CACzB,OAAO;AACT;AAEA,eAAsB,cACpB,aACA,SACA;CACA,MAAM,MAAM,YAAY,WAAW,IAAI;CAEvC,MAAM,aAAa,iBAAiB,cAAc;CAElD,MAAM,CAAC,iBAAiB,cAAc,MAAM,QAAQ,IAAI,CACtD,IAAI,UAAU;EAAE,YAAY;EAAM;CAAW,CAAC,GAC9C,IAAI,KAAK;EAAE,YAAY;EAAM;CAAW,CAAC,CAC3C,CAAC;CAED,MAAM,YAAY,MAAM,mBACtB,gBAAgB,WAChB,8BACA,OAAO,MAAM;EACX,IAAI,UAAU;EACd,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,MAAM,kBAAkB,EAAE,QAAQ,KAAA,GAAW,OAAO;GACnE,UAAU,OAAO,WAAW,QAAQ,OAAO,WAAW;GACtD,eAAe,OAAO;EACxB,SAAS,KAAK;GACZ,IACE,eAAe,oBACf,2BAA2B,IAAI,IAAI,IAAI,GAEvC,MAAM;GACR,IAAI,eAAe,kBACjB,cAAc,QAAQ,IAAI,OAAO,IAAI,IAAI;QAEzC,cAAc,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;EAEzF;EACA,OAAO;GACL,MAAM,EAAE;GACR,SAAS,EAAE;GACX,QAAQ,EAAE;GACV,QAAQ,EAAE;GACV;GACA;GACA,GAAI,eAAe,EAAE,YAAY;EACnC;CACF,CACF;CAEA,MAAM,iBAAiB,IAAI,IACzB,gBAAgB,UAAU,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAClD;CAYA,OAAO;EAAE;EAAW,MAVP,WAAW,KAAK,KAAK,OAAO;GACvC,MAAM,EAAE;GACR,UAAU,EAAE;GACZ,eAAe,EAAE;GACjB,cAAc,eAAe,IAAI,EAAE,YAAY,GAAG,UAAU;GAC5D,OAAO,EAAE,WAAW,UAAU;GAC9B,MAAM,EAAE,WAAW,SAAS;GAC5B,QAAQ,EAAE;EACZ,EAEuB;CAAE;AAC3B"}
@@ -12,6 +12,17 @@ interface CheckDeploymentReadinessInput {
12
12
  * caller can carry it through to a deployment plan.
13
13
  */
14
14
  readonly image?: string;
15
+ /**
16
+ * Narrow a duplicate SKU `size` name to one provider (ENG-258).
17
+ * Get candidates from `browse_catalog` or `check_deployment_readiness`.
18
+ */
19
+ readonly providerUuid?: string;
20
+ /**
21
+ * Resolve the SKU by uuid, bypassing the `size` name filter (ENG-258).
22
+ * When set, `size` is ignored for candidate selection (consistent with
23
+ * core `resolveSku`). Narrow to one provider by also supplying `providerUuid`.
24
+ */
25
+ readonly skuUuid?: string;
15
26
  }
16
27
  interface SkuSummary {
17
28
  readonly name: string;
@@ -37,8 +48,19 @@ interface CheckDeploymentReadinessResult {
37
48
  readonly amount: string;
38
49
  }>;
39
50
  readonly hours_remaining?: string;
51
+ /** Determinate SKU pick (exactly 1 candidate) or null when ambiguous. */
40
52
  readonly sku: SkuSummary | null;
41
- readonly available_sku_names: readonly string[];
53
+ /** Active SKU candidates: uuid-resolved (skuUuid path) or name-filtered (size path), narrowed by providerUuid if given. (ENG-258) */
54
+ readonly sku_candidates: readonly SkuSummary[];
55
+ /**
56
+ * All active SKUs with their uuid and provider_uuid for disambiguation. (ENG-258)
57
+ * Capped at MAX_SKU_NAMES_RETURNED to avoid bloating LLM context.
58
+ */
59
+ readonly available_skus: ReadonlyArray<{
60
+ readonly name: string;
61
+ readonly uuid: string;
62
+ readonly provider_uuid: string;
63
+ }>;
42
64
  readonly ready: boolean;
43
65
  readonly missing_steps: readonly string[];
44
66
  }
@@ -57,6 +79,10 @@ interface CheckDeploymentReadinessResult {
57
79
  * deploy upload will reject disallowed registries at runtime; document
58
80
  * that to the user when the readiness check is "ready: true" but the
59
81
  * registry is suspect.
82
+ *
83
+ * ENG-258: When `size` matches multiple active SKUs (duplicate names across
84
+ * providers), `sku` is null and `sku_candidates` lists all matches. Supply
85
+ * `providerUuid` or `skuUuid` to narrow to a single candidate.
60
86
  */
61
87
  declare function checkDeploymentReadiness(queryClient: ManifestQueryClient, address: string, input?: CheckDeploymentReadinessInput): Promise<CheckDeploymentReadinessResult>;
62
88
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"checkDeploymentReadiness.d.ts","names":[],"sources":["../../src/tools/checkDeploymentReadiness.ts"],"mappings":";;;UAgBiB,6BAAA;;WAEN,IAAA;EAFmC;;;AAU9B;AAGhB;;;EAb8C,SAUnC,KAAK;AAAA;AAAA,UAGC,UAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,aAAA;EAAA,SACA,KAAA;IAAA,SAAmB,MAAA;IAAA,SAAyB,KAAA;EAAA;EAAA,SAC5C,MAAA;AAAA;AAAA,UAGM,8BAAA;EAAA,SACN,MAAA;EAAA,SACA,KAAA;EAAA,SACA,IAAA;EAAA,SACA,eAAA,EAAiB,aAAA;IAAA,SACf,KAAA;IAAA,SACA,MAAA;EAAA;EAAA,SAEF,OAAA,EAAS,OAAA,CAAQ,UAAA,QAAkB,UAAA;EAAA,SACnC,eAAA,GAAkB,aAAA;IAAA,SAChB,KAAA;IAAA,SACA,MAAA;EAAA;EAAA,SAEF,eAAA;EAAA,SACA,GAAA,EAAK,UAAA;EAAA,SACL,mBAAA;EAAA,SACA,KAAA;EAAA,SACA,aAAA;AAAA;;;;;;;;;;;;;AAAa;AAmBxB;;;iBAAsB,wBAAA,CACpB,WAAA,EAAa,mBAAA,EACb,OAAA,UACA,KAAA,GAAO,6BAAA,GACN,OAAA,CAAQ,8BAAA"}
1
+ {"version":3,"file":"checkDeploymentReadiness.d.ts","names":[],"sources":["../../src/tools/checkDeploymentReadiness.ts"],"mappings":";;;UAgBiB,6BAAA;;WAEN,IAAA;EAFmC;;;;;;;EAAA,SAUnC,KAAA;EAWO;AAGlB;;;EAHkB,SANP,YAAA;EAUA;;;;;EAAA,SAJA,OAAA;AAAA;AAAA,UAGM,UAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,aAAA;EAAA,SACA,KAAA;IAAA,SAAmB,MAAA;IAAA,SAAyB,KAAA;EAAA;EAAA,SAC5C,MAAA;AAAA;AAAA,UAGM,8BAAA;EAAA,SACN,MAAA;EAAA,SACA,KAAA;EAAA,SACA,IAAA;EAAA,SACA,eAAA,EAAiB,aAAA;IAAA,SACf,KAAA;IAAA,SACA,MAAA;EAAA;EAAA,SAEF,OAAA,EAAS,OAAA,CAAQ,UAAA,QAAkB,UAAA;EAAA,SACnC,eAAA,GAAkB,aAAA;IAAA,SAChB,KAAA;IAAA,SACA,MAAA;EAAA;EAAA,SAEF,eAAA;EALiB;EAAA,SAOjB,GAAA,EAAK,UAAA;EANL;EAAA,SAQA,cAAA,WAAyB,UAAA;EAPvB;;;;EAAA,SAYF,cAAA,EAAgB,aAAA;IAAA,SACd,IAAA;IAAA,SACA,IAAA;IAAA,SACA,aAAA;EAAA;EAAA,SAEF,KAAA;EAAA,SACA,aAAA;AAAA;;;;AAAa;AAuBxB;;;;;;;;;;;;;;;;iBAAsB,wBAAA,CACpB,WAAA,EAAa,mBAAA,EACb,OAAA,UACA,KAAA,GAAO,6BAAA,GACN,OAAA,CAAQ,8BAAA"}
@@ -1,11 +1,11 @@
1
1
  import { MAX_PAGE_LIMIT, createPagination, getBalance } from "@manifest-network/manifest-mcp-core";
2
2
  //#region src/tools/checkDeploymentReadiness.ts
3
3
  /**
4
- * `available_sku_names` is bounded so the response never bloats an LLM
5
- * context if a provider lists many SKUs. The chain query itself is bounded
6
- * by `MAX_PAGE_LIMIT` (1000), but agents only need a hint of what's
7
- * available — the lookup against `size` is exact. 50 mirrors the spirit of
8
- * the 10-name slice already used in `missing_steps`.
4
+ * `available_skus` is bounded so the response never bloats an LLM context
5
+ * if a provider lists many SKUs. The chain query itself is bounded by
6
+ * `MAX_PAGE_LIMIT` (1000), but agents only need a hint of what's available —
7
+ * the lookup against `size` is exact. 50 mirrors the spirit of the 10-name
8
+ * slice already used in `missing_steps`.
9
9
  */
10
10
  const MAX_SKU_NAMES_RETURNED = 50;
11
11
  /**
@@ -23,6 +23,10 @@ const MAX_SKU_NAMES_RETURNED = 50;
23
23
  * deploy upload will reject disallowed registries at runtime; document
24
24
  * that to the user when the readiness check is "ready: true" but the
25
25
  * registry is suspect.
26
+ *
27
+ * ENG-258: When `size` matches multiple active SKUs (duplicate names across
28
+ * providers), `sku` is null and `sku_candidates` lists all matches. Supply
29
+ * `providerUuid` or `skuUuid` to narrow to a single candidate.
26
30
  */
27
31
  async function checkDeploymentReadiness(queryClient, address, input = {}) {
28
32
  const pagination = createPagination(MAX_PAGE_LIMIT);
@@ -30,36 +34,52 @@ async function checkDeploymentReadiness(queryClient, address, input = {}) {
30
34
  activeOnly: true,
31
35
  pagination
32
36
  })]);
33
- const skuByName = new Map(skusResult.skus.map((s) => [s.name, s]));
34
- const sku = input.size ? skuByName.get(input.size) ?? null : null;
37
+ const size = input.size?.trim() || void 0;
38
+ const providerUuid = input.providerUuid?.trim() || void 0;
39
+ const skuUuid = input.skuUuid?.trim() || void 0;
40
+ const allActive = skusResult.skus;
41
+ const toSummary = (s) => ({
42
+ name: s.name,
43
+ uuid: s.uuid,
44
+ provider_uuid: s.providerUuid,
45
+ ...s.basePrice ? { price: {
46
+ amount: s.basePrice.amount,
47
+ denom: s.basePrice.denom
48
+ } } : {},
49
+ active: s.active
50
+ });
51
+ let candidates = [];
52
+ if (skuUuid) candidates = allActive.filter((s) => s.uuid === skuUuid).filter((s) => providerUuid ? s.providerUuid === providerUuid : true).map(toSummary);
53
+ else if (size) candidates = allActive.filter((s) => s.name === size).filter((s) => providerUuid ? s.providerUuid === providerUuid : true).map(toSummary);
54
+ const sku = candidates.length === 1 ? candidates[0] : null;
35
55
  const missing = [];
36
- if (input.size && !sku) {
37
- const available = Array.from(skuByName.keys()).slice(0, 10).join(", ");
38
- missing.push(`Requested SKU "${input.size}" is not available. Pick one of: ${available || "(none active)"}`);
56
+ if (skuUuid && candidates.length === 0) missing.push(`SKU uuid "${skuUuid}" not found among active SKUs${providerUuid ? ` on provider ${providerUuid}` : ""}.`);
57
+ else if (!skuUuid && size && candidates.length === 0) {
58
+ const available = [...new Set(allActive.map((s) => s.name))].slice(0, 10).join(", ");
59
+ missing.push(`Requested SKU "${size}" is not available. Pick one of: ${available || "(none active)"}`);
60
+ } else if (!skuUuid && size && candidates.length > 1) {
61
+ const providers = [...new Set(candidates.map((c) => c.provider_uuid))];
62
+ missing.push(`SKU "${size}" matches ${candidates.length} active SKUs (provider(s): ${providers.join(", ")}). Specify provider_uuid or sku_uuid to disambiguate.`);
39
63
  }
40
64
  if (!balance.credits) missing.push("Credit account does not exist for this tenant. Call `fund_credit` (manifest-mcp-lease server) to create and fund it.");
41
65
  else if (balance.credits.available_balances.length === 0) missing.push("Credit account exists but has no available balance. Call `fund_credit` (manifest-mcp-lease server) to top it up.");
42
66
  if (balance.balances.length === 0) missing.push("Wallet has no balance — cannot pay for the create-lease transaction. Use the faucet (testnet) or top up the wallet.");
43
- const skuSummary = sku ? {
44
- name: sku.name,
45
- uuid: sku.uuid,
46
- provider_uuid: sku.providerUuid,
47
- price: sku.basePrice ? {
48
- amount: sku.basePrice.amount,
49
- denom: sku.basePrice.denom
50
- } : void 0,
51
- active: sku.active
52
- } : null;
67
+ const available_skus = allActive.map((s) => ({
68
+ name: s.name,
69
+ uuid: s.uuid,
70
+ provider_uuid: s.providerUuid
71
+ })).slice(0, MAX_SKU_NAMES_RETURNED);
53
72
  return {
54
73
  tenant: address,
55
74
  image: input.image ?? null,
56
- size: input.size ?? null,
75
+ size: sku?.name ?? size ?? null,
57
76
  wallet_balances: balance.balances,
58
77
  credits: balance.credits,
59
78
  ...balance.current_balance && { current_balance: balance.current_balance },
60
79
  ...balance.hours_remaining && { hours_remaining: balance.hours_remaining },
61
- sku: skuSummary,
62
- available_sku_names: Array.from(skuByName.keys()).slice(0, MAX_SKU_NAMES_RETURNED),
80
+ sku,
81
+ sku_candidates: candidates,
82
+ available_skus,
63
83
  ready: missing.length === 0,
64
84
  missing_steps: missing
65
85
  };
@@ -1 +1 @@
1
- {"version":3,"file":"checkDeploymentReadiness.js","names":[],"sources":["../../src/tools/checkDeploymentReadiness.ts"],"sourcesContent":["import {\n createPagination,\n getBalance,\n MAX_PAGE_LIMIT,\n type ManifestQueryClient,\n} from '@manifest-network/manifest-mcp-core';\n\n/**\n * `available_sku_names` is bounded so the response never bloats an LLM\n * context if a provider lists many SKUs. The chain query itself is bounded\n * by `MAX_PAGE_LIMIT` (1000), but agents only need a hint of what's\n * available — the lookup against `size` is exact. 50 mirrors the spirit of\n * the 10-name slice already used in `missing_steps`.\n */\nconst MAX_SKU_NAMES_RETURNED = 50;\n\nexport interface CheckDeploymentReadinessInput {\n /** SKU tier to verify availability for (e.g. \"docker-micro\"). Optional. */\n readonly size?: string;\n /**\n * Image to consider. Currently informational — the chain does not expose\n * provider `allowed_registries`, so the agent must accept a runtime\n * \"registry not allowed\" error from the upload step if the registry\n * is not in the operator's allowlist. Recorded in the result so the\n * caller can carry it through to a deployment plan.\n */\n readonly image?: string;\n}\n\nexport interface SkuSummary {\n readonly name: string;\n readonly uuid: string;\n readonly provider_uuid: string;\n readonly price?: { readonly amount: string; readonly denom: string };\n readonly active: boolean;\n}\n\nexport interface CheckDeploymentReadinessResult {\n readonly tenant: string;\n readonly image: string | null;\n readonly size: string | null;\n readonly wallet_balances: ReadonlyArray<{\n readonly denom: string;\n readonly amount: string;\n }>;\n readonly credits: Awaited<ReturnType<typeof getBalance>>['credits'];\n readonly current_balance?: ReadonlyArray<{\n readonly denom: string;\n readonly amount: string;\n }>;\n readonly hours_remaining?: string;\n readonly sku: SkuSummary | null;\n readonly available_sku_names: readonly string[];\n readonly ready: boolean;\n readonly missing_steps: readonly string[];\n}\n\n/**\n * Combined pre-flight check for a deploy: wallet balances, credit account,\n * and SKU availability in a single round trip. The agent uses this before\n * `deploy_app` to decide whether to fund credits, switch SKU, or top up\n * the wallet.\n *\n * The `ready` flag is conservative — `false` whenever a clearly missing\n * prerequisite is detected. `missing_steps` is the actionable bullet list\n * the agent can surface to the user verbatim.\n *\n * Note: provider `allowed_registries` is operator config not exposed via\n * chain or public API, so this helper cannot pre-validate `image`. The\n * deploy upload will reject disallowed registries at runtime; document\n * that to the user when the readiness check is \"ready: true\" but the\n * registry is suspect.\n */\nexport async function checkDeploymentReadiness(\n queryClient: ManifestQueryClient,\n address: string,\n input: CheckDeploymentReadinessInput = {},\n): Promise<CheckDeploymentReadinessResult> {\n const pagination = createPagination(MAX_PAGE_LIMIT);\n const [balance, skusResult] = await Promise.all([\n getBalance(queryClient, address),\n queryClient.liftedinit.sku.v1.sKUs({ activeOnly: true, pagination }),\n ]);\n\n const skuByName = new Map(skusResult.skus.map((s) => [s.name, s]));\n const sku = input.size ? (skuByName.get(input.size) ?? null) : null;\n\n const missing: string[] = [];\n if (input.size && !sku) {\n const available = Array.from(skuByName.keys()).slice(0, 10).join(', ');\n missing.push(\n `Requested SKU \"${input.size}\" is not available. Pick one of: ${available || '(none active)'}`,\n );\n }\n if (!balance.credits) {\n missing.push(\n 'Credit account does not exist for this tenant. Call `fund_credit` (manifest-mcp-lease server) to create and fund it.',\n );\n } else if (balance.credits.available_balances.length === 0) {\n missing.push(\n 'Credit account exists but has no available balance. Call `fund_credit` (manifest-mcp-lease server) to top it up.',\n );\n }\n if (balance.balances.length === 0) {\n missing.push(\n 'Wallet has no balance — cannot pay for the create-lease transaction. Use the faucet (testnet) or top up the wallet.',\n );\n }\n\n const skuSummary: SkuSummary | null = sku\n ? {\n name: sku.name,\n uuid: sku.uuid,\n provider_uuid: sku.providerUuid,\n price: sku.basePrice\n ? { amount: sku.basePrice.amount, denom: sku.basePrice.denom }\n : undefined,\n active: sku.active,\n }\n : null;\n\n return {\n tenant: address,\n image: input.image ?? null,\n size: input.size ?? null,\n wallet_balances: balance.balances,\n credits: balance.credits,\n ...(balance.current_balance && {\n current_balance: balance.current_balance,\n }),\n ...(balance.hours_remaining && {\n hours_remaining: balance.hours_remaining,\n }),\n sku: skuSummary,\n available_sku_names: Array.from(skuByName.keys()).slice(\n 0,\n MAX_SKU_NAMES_RETURNED,\n ),\n ready: missing.length === 0,\n missing_steps: missing,\n };\n}\n"],"mappings":";;;;;;;;;AAcA,MAAM,yBAAyB;;;;;;;;;;;;;;;;;AA2D/B,eAAsB,yBACpB,aACA,SACA,QAAuC,CAAC,GACC;CACzC,MAAM,aAAa,iBAAiB,cAAc;CAClD,MAAM,CAAC,SAAS,cAAc,MAAM,QAAQ,IAAI,CAC9C,WAAW,aAAa,OAAO,GAC/B,YAAY,WAAW,IAAI,GAAG,KAAK;EAAE,YAAY;EAAM;CAAW,CAAC,CACrE,CAAC;CAED,MAAM,YAAY,IAAI,IAAI,WAAW,KAAK,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CACjE,MAAM,MAAM,MAAM,OAAQ,UAAU,IAAI,MAAM,IAAI,KAAK,OAAQ;CAE/D,MAAM,UAAoB,CAAC;CAC3B,IAAI,MAAM,QAAQ,CAAC,KAAK;EACtB,MAAM,YAAY,MAAM,KAAK,UAAU,KAAK,CAAC,EAAE,MAAM,GAAG,EAAE,EAAE,KAAK,IAAI;EACrE,QAAQ,KACN,kBAAkB,MAAM,KAAK,mCAAmC,aAAa,iBAC/E;CACF;CACA,IAAI,CAAC,QAAQ,SACX,QAAQ,KACN,sHACF;MACK,IAAI,QAAQ,QAAQ,mBAAmB,WAAW,GACvD,QAAQ,KACN,kHACF;CAEF,IAAI,QAAQ,SAAS,WAAW,GAC9B,QAAQ,KACN,qHACF;CAGF,MAAM,aAAgC,MAClC;EACE,MAAM,IAAI;EACV,MAAM,IAAI;EACV,eAAe,IAAI;EACnB,OAAO,IAAI,YACP;GAAE,QAAQ,IAAI,UAAU;GAAQ,OAAO,IAAI,UAAU;EAAM,IAC3D,KAAA;EACJ,QAAQ,IAAI;CACd,IACA;CAEJ,OAAO;EACL,QAAQ;EACR,OAAO,MAAM,SAAS;EACtB,MAAM,MAAM,QAAQ;EACpB,iBAAiB,QAAQ;EACzB,SAAS,QAAQ;EACjB,GAAI,QAAQ,mBAAmB,EAC7B,iBAAiB,QAAQ,gBAC3B;EACA,GAAI,QAAQ,mBAAmB,EAC7B,iBAAiB,QAAQ,gBAC3B;EACA,KAAK;EACL,qBAAqB,MAAM,KAAK,UAAU,KAAK,CAAC,EAAE,MAChD,GACA,sBACF;EACA,OAAO,QAAQ,WAAW;EAC1B,eAAe;CACjB;AACF"}
1
+ {"version":3,"file":"checkDeploymentReadiness.js","names":[],"sources":["../../src/tools/checkDeploymentReadiness.ts"],"sourcesContent":["import {\n createPagination,\n getBalance,\n MAX_PAGE_LIMIT,\n type ManifestQueryClient,\n} from '@manifest-network/manifest-mcp-core';\n\n/**\n * `available_skus` is bounded so the response never bloats an LLM context\n * if a provider lists many SKUs. The chain query itself is bounded by\n * `MAX_PAGE_LIMIT` (1000), but agents only need a hint of what's available —\n * the lookup against `size` is exact. 50 mirrors the spirit of the 10-name\n * slice already used in `missing_steps`.\n */\nconst MAX_SKU_NAMES_RETURNED = 50;\n\nexport interface CheckDeploymentReadinessInput {\n /** SKU tier to verify availability for (e.g. \"docker-micro\"). Optional. */\n readonly size?: string;\n /**\n * Image to consider. Currently informational — the chain does not expose\n * provider `allowed_registries`, so the agent must accept a runtime\n * \"registry not allowed\" error from the upload step if the registry\n * is not in the operator's allowlist. Recorded in the result so the\n * caller can carry it through to a deployment plan.\n */\n readonly image?: string;\n /**\n * Narrow a duplicate SKU `size` name to one provider (ENG-258).\n * Get candidates from `browse_catalog` or `check_deployment_readiness`.\n */\n readonly providerUuid?: string;\n /**\n * Resolve the SKU by uuid, bypassing the `size` name filter (ENG-258).\n * When set, `size` is ignored for candidate selection (consistent with\n * core `resolveSku`). Narrow to one provider by also supplying `providerUuid`.\n */\n readonly skuUuid?: string;\n}\n\nexport interface SkuSummary {\n readonly name: string;\n readonly uuid: string;\n readonly provider_uuid: string;\n readonly price?: { readonly amount: string; readonly denom: string };\n readonly active: boolean;\n}\n\nexport interface CheckDeploymentReadinessResult {\n readonly tenant: string;\n readonly image: string | null;\n readonly size: string | null;\n readonly wallet_balances: ReadonlyArray<{\n readonly denom: string;\n readonly amount: string;\n }>;\n readonly credits: Awaited<ReturnType<typeof getBalance>>['credits'];\n readonly current_balance?: ReadonlyArray<{\n readonly denom: string;\n readonly amount: string;\n }>;\n readonly hours_remaining?: string;\n /** Determinate SKU pick (exactly 1 candidate) or null when ambiguous. */\n readonly sku: SkuSummary | null;\n /** Active SKU candidates: uuid-resolved (skuUuid path) or name-filtered (size path), narrowed by providerUuid if given. (ENG-258) */\n readonly sku_candidates: readonly SkuSummary[];\n /**\n * All active SKUs with their uuid and provider_uuid for disambiguation. (ENG-258)\n * Capped at MAX_SKU_NAMES_RETURNED to avoid bloating LLM context.\n */\n readonly available_skus: ReadonlyArray<{\n readonly name: string;\n readonly uuid: string;\n readonly provider_uuid: string;\n }>;\n readonly ready: boolean;\n readonly missing_steps: readonly string[];\n}\n\n/**\n * Combined pre-flight check for a deploy: wallet balances, credit account,\n * and SKU availability in a single round trip. The agent uses this before\n * `deploy_app` to decide whether to fund credits, switch SKU, or top up\n * the wallet.\n *\n * The `ready` flag is conservative — `false` whenever a clearly missing\n * prerequisite is detected. `missing_steps` is the actionable bullet list\n * the agent can surface to the user verbatim.\n *\n * Note: provider `allowed_registries` is operator config not exposed via\n * chain or public API, so this helper cannot pre-validate `image`. The\n * deploy upload will reject disallowed registries at runtime; document\n * that to the user when the readiness check is \"ready: true\" but the\n * registry is suspect.\n *\n * ENG-258: When `size` matches multiple active SKUs (duplicate names across\n * providers), `sku` is null and `sku_candidates` lists all matches. Supply\n * `providerUuid` or `skuUuid` to narrow to a single candidate.\n */\nexport async function checkDeploymentReadiness(\n queryClient: ManifestQueryClient,\n address: string,\n input: CheckDeploymentReadinessInput = {},\n): Promise<CheckDeploymentReadinessResult> {\n const pagination = createPagination(MAX_PAGE_LIMIT);\n const [balance, skusResult] = await Promise.all([\n getBalance(queryClient, address),\n queryClient.liftedinit.sku.v1.sKUs({ activeOnly: true, pagination }),\n ]);\n\n // Normalize selector inputs once: trim whitespace, treat whitespace-only as absent.\n // This mirrors core resolveSku and the deploy paths — a value copied with surrounding\n // spaces must not false-miss, and a whitespace-only value must not act like a real selector.\n const size = input.size?.trim() || undefined;\n const providerUuid = input.providerUuid?.trim() || undefined;\n const skuUuid = input.skuUuid?.trim() || undefined;\n\n const allActive = skusResult.skus;\n\n // Build a SkuSummary from a raw SKU record.\n const toSummary = (s: (typeof allActive)[number]): SkuSummary => ({\n name: s.name,\n uuid: s.uuid,\n provider_uuid: s.providerUuid,\n ...(s.basePrice\n ? { price: { amount: s.basePrice.amount, denom: s.basePrice.denom } }\n : {}),\n active: s.active,\n });\n\n // Build candidate list (ENG-258 review: skuUuid bypasses the name filter,\n // consistent with core resolveSku — a caller pinning skuUuid whose SKU name\n // differs from `size` must still get that SKU as the single candidate).\n let candidates: SkuSummary[] = [];\n if (skuUuid) {\n // UUID path: resolve by identity; size is ignored.\n candidates = allActive\n .filter((s) => s.uuid === skuUuid)\n .filter((s) => (providerUuid ? s.providerUuid === providerUuid : true))\n .map(toSummary);\n } else if (size) {\n // Name path: filter by name, optionally narrow by provider.\n candidates = allActive\n .filter((s) => s.name === size)\n .filter((s) => (providerUuid ? s.providerUuid === providerUuid : true))\n .map(toSummary);\n }\n // Determinate pick only when exactly one candidate (unambiguous).\n const sku = candidates.length === 1 ? candidates[0] : null;\n\n const missing: string[] = [];\n if (skuUuid && candidates.length === 0) {\n missing.push(\n `SKU uuid \"${skuUuid}\" not found among active SKUs${providerUuid ? ` on provider ${providerUuid}` : ''}.`,\n );\n } else if (!skuUuid && size && candidates.length === 0) {\n const available = [...new Set(allActive.map((s) => s.name))]\n .slice(0, 10)\n .join(', ');\n missing.push(\n `Requested SKU \"${size}\" is not available. Pick one of: ${available || '(none active)'}`,\n );\n } else if (!skuUuid && size && candidates.length > 1) {\n // Ambiguous: the name matches >1 active SKU, across one or more providers\n // (a single provider can publish duplicate names too) (ENG-258).\n const providers = [...new Set(candidates.map((c) => c.provider_uuid))];\n missing.push(\n `SKU \"${size}\" matches ${candidates.length} active SKUs (provider(s): ${providers.join(', ')}). ` +\n `Specify provider_uuid or sku_uuid to disambiguate.`,\n );\n }\n\n if (!balance.credits) {\n missing.push(\n 'Credit account does not exist for this tenant. Call `fund_credit` (manifest-mcp-lease server) to create and fund it.',\n );\n } else if (balance.credits.available_balances.length === 0) {\n missing.push(\n 'Credit account exists but has no available balance. Call `fund_credit` (manifest-mcp-lease server) to top it up.',\n );\n }\n if (balance.balances.length === 0) {\n missing.push(\n 'Wallet has no balance — cannot pay for the create-lease transaction. Use the faucet (testnet) or top up the wallet.',\n );\n }\n\n // available_skus: full flat list with identity fields for disambiguation.\n // Capped to avoid bloating LLM context.\n const available_skus = allActive\n .map((s) => ({ name: s.name, uuid: s.uuid, provider_uuid: s.providerUuid }))\n .slice(0, MAX_SKU_NAMES_RETURNED);\n\n return {\n tenant: address,\n image: input.image ?? null,\n // When exactly one SKU candidate resolved, echo its real name so the result\n // is internally consistent (size === sku.name). On the name path with one\n // candidate, sku.name === size anyway. Ambiguous/none → falls back to\n // the trimmed size (ENG-258 r2).\n size: sku?.name ?? size ?? null,\n wallet_balances: balance.balances,\n credits: balance.credits,\n ...(balance.current_balance && {\n current_balance: balance.current_balance,\n }),\n ...(balance.hours_remaining && {\n hours_remaining: balance.hours_remaining,\n }),\n sku,\n sku_candidates: candidates,\n available_skus,\n ready: missing.length === 0,\n missing_steps: missing,\n };\n}\n"],"mappings":";;;;;;;;;AAcA,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;AAqF/B,eAAsB,yBACpB,aACA,SACA,QAAuC,CAAC,GACC;CACzC,MAAM,aAAa,iBAAiB,cAAc;CAClD,MAAM,CAAC,SAAS,cAAc,MAAM,QAAQ,IAAI,CAC9C,WAAW,aAAa,OAAO,GAC/B,YAAY,WAAW,IAAI,GAAG,KAAK;EAAE,YAAY;EAAM;CAAW,CAAC,CACrE,CAAC;CAKD,MAAM,OAAO,MAAM,MAAM,KAAK,KAAK,KAAA;CACnC,MAAM,eAAe,MAAM,cAAc,KAAK,KAAK,KAAA;CACnD,MAAM,UAAU,MAAM,SAAS,KAAK,KAAK,KAAA;CAEzC,MAAM,YAAY,WAAW;CAG7B,MAAM,aAAa,OAA+C;EAChE,MAAM,EAAE;EACR,MAAM,EAAE;EACR,eAAe,EAAE;EACjB,GAAI,EAAE,YACF,EAAE,OAAO;GAAE,QAAQ,EAAE,UAAU;GAAQ,OAAO,EAAE,UAAU;EAAM,EAAE,IAClE,CAAC;EACL,QAAQ,EAAE;CACZ;CAKA,IAAI,aAA2B,CAAC;CAChC,IAAI,SAEF,aAAa,UACV,QAAQ,MAAM,EAAE,SAAS,OAAO,EAChC,QAAQ,MAAO,eAAe,EAAE,iBAAiB,eAAe,IAAK,EACrE,IAAI,SAAS;MACX,IAAI,MAET,aAAa,UACV,QAAQ,MAAM,EAAE,SAAS,IAAI,EAC7B,QAAQ,MAAO,eAAe,EAAE,iBAAiB,eAAe,IAAK,EACrE,IAAI,SAAS;CAGlB,MAAM,MAAM,WAAW,WAAW,IAAI,WAAW,KAAK;CAEtD,MAAM,UAAoB,CAAC;CAC3B,IAAI,WAAW,WAAW,WAAW,GACnC,QAAQ,KACN,aAAa,QAAQ,+BAA+B,eAAe,gBAAgB,iBAAiB,GAAG,EACzG;MACK,IAAI,CAAC,WAAW,QAAQ,WAAW,WAAW,GAAG;EACtD,MAAM,YAAY,CAAC,GAAG,IAAI,IAAI,UAAU,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,EACxD,MAAM,GAAG,EAAE,EACX,KAAK,IAAI;EACZ,QAAQ,KACN,kBAAkB,KAAK,mCAAmC,aAAa,iBACzE;CACF,OAAO,IAAI,CAAC,WAAW,QAAQ,WAAW,SAAS,GAAG;EAGpD,MAAM,YAAY,CAAC,GAAG,IAAI,IAAI,WAAW,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC;EACrE,QAAQ,KACN,QAAQ,KAAK,YAAY,WAAW,OAAO,6BAA6B,UAAU,KAAK,IAAI,EAAE,sDAE/F;CACF;CAEA,IAAI,CAAC,QAAQ,SACX,QAAQ,KACN,sHACF;MACK,IAAI,QAAQ,QAAQ,mBAAmB,WAAW,GACvD,QAAQ,KACN,kHACF;CAEF,IAAI,QAAQ,SAAS,WAAW,GAC9B,QAAQ,KACN,qHACF;CAKF,MAAM,iBAAiB,UACpB,KAAK,OAAO;EAAE,MAAM,EAAE;EAAM,MAAM,EAAE;EAAM,eAAe,EAAE;CAAa,EAAE,EAC1E,MAAM,GAAG,sBAAsB;CAElC,OAAO;EACL,QAAQ;EACR,OAAO,MAAM,SAAS;EAKtB,MAAM,KAAK,QAAQ,QAAQ;EAC3B,iBAAiB,QAAQ;EACzB,SAAS,QAAQ;EACjB,GAAI,QAAQ,mBAAmB,EAC7B,iBAAiB,QAAQ,gBAC3B;EACA,GAAI,QAAQ,mBAAmB,EAC7B,iBAAiB,QAAQ,gBAC3B;EACA;EACA,gBAAgB;EAChB;EACA,OAAO,QAAQ,WAAW;EAC1B,eAAe;CACjB;AACF"}
@@ -29,6 +29,10 @@ interface DeployAppInput {
29
29
  image?: string;
30
30
  port?: number;
31
31
  size: string;
32
+ /** Disambiguate a duplicate SKU name to one provider (ENG-258). */
33
+ providerUuid?: string;
34
+ /** Pin a specific SKU by uuid, bypassing name resolution (ENG-258). Wins over size/providerUuid. */
35
+ skuUuid?: string;
32
36
  env?: Record<string, string>;
33
37
  command?: string[];
34
38
  args?: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"deployApp.d.ts","names":[],"sources":["../../src/tools/deployApp.ts"],"mappings":";;;;;UAiBiB,aAAA;EACf,KAAA;EACA,KAAA,GAAQ,MAAA,SAAe,MAAA;EACvB,GAAA,GAAM,MAAA;EACN,OAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,YAAA;IACE,IAAA;IACA,QAAA;IACA,OAAA;IACA,OAAA;IACA,YAAA;EAAA;EAEF,iBAAA;EACA,UAAA,GAAa,MAAA;IAAiB,SAAA;EAAA;EAC9B,MAAA;EACA,MAAA,GAAS,MAAA;AAAA;AAAA,UAGM,cAAA;EACf,KAAA;EACA,IAAA;EACA,IAAA;EACA,GAAA,GAAM,MAAA;EACN,OAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,YAAA;IACE,IAAA;IACA,QAAA;IACA,OAAA;IACA,OAAA;IACA,YAAA;EAAA;EAEF,iBAAA;EACA,IAAA;EACA,MAAA;EACA,MAAA,GAAS,MAAA;EACT,OAAA;EACA,UAAA,GAAa,MAAA;IAAiB,SAAA;EAAA;EAC9B,QAAA,GAAW,MAAA,SAAe,aAAA;EAC1B,aAAA;EAyBkB;;;;;;;;;EAflB,YAAA;EAzBA;;;;;EA+BA,WAAA;EAzBE;EA2BF,cAAA,IACE,SAAA,UACA,WAAA,oBACU,OAAA;EA3BZ;EA6BA,WAAA,GAAc,WAAA;EA3Bd;EA6BA,WAAA,GAAc,IAAA,CAAK,WAAA;AAAA;AAAA,iBAGC,SAAA,CACpB,aAAA,EAAe,mBAAA,EACf,YAAA,GAAe,OAAA,UAAiB,SAAA,aAAsB,OAAA,UACtD,qBAAA,GACE,OAAA,UACA,SAAA,UACA,WAAA,aACG,OAAA,UACL,KAAA,EAAO,cAAA,EACP,OAAA,UAAiB,UAAA,CAAW,KAAA,GAC3B,OAAA,CAAQ,eAAA"}
1
+ {"version":3,"file":"deployApp.d.ts","names":[],"sources":["../../src/tools/deployApp.ts"],"mappings":";;;;;UAiBiB,aAAA;EACf,KAAA;EACA,KAAA,GAAQ,MAAA,SAAe,MAAA;EACvB,GAAA,GAAM,MAAA;EACN,OAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,YAAA;IACE,IAAA;IACA,QAAA;IACA,OAAA;IACA,OAAA;IACA,YAAA;EAAA;EAEF,iBAAA;EACA,UAAA,GAAa,MAAA;IAAiB,SAAA;EAAA;EAC9B,MAAA;EACA,MAAA,GAAS,MAAA;AAAA;AAAA,UAGM,cAAA;EACf,KAAA;EACA,IAAA;EACA,IAAA;EAXE;EAaF,YAAA;EAVA;EAYA,OAAA;EACA,GAAA,GAAM,MAAA;EACN,OAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,YAAA;IACE,IAAA;IACA,QAAA;IACA,OAAA;IACA,OAAA;IACA,YAAA;EAAA;EAEF,iBAAA;EACA,IAAA;EACA,MAAA;EACA,MAAA,GAAS,MAAA;EACT,OAAA;EACA,UAAA,GAAa,MAAA;IAAiB,SAAA;EAAA;EAC9B,QAAA,GAAW,MAAA,SAAe,aAAA;EAC1B,aAAA;EAxBA;;;;;;;;;EAkCA,YAAA;EAvBE;;;;;EA6BF,WAAA;EAtBA;EAwBA,cAAA,IACE,SAAA,UACA,WAAA,oBACU,OAAA;EAzBZ;EA2BA,WAAA,GAAc,WAAA;EA1Bd;EA4BA,WAAA,GAAc,IAAA,CAAK,WAAA;AAAA;AAAA,iBAoBC,SAAA,CACpB,aAAA,EAAe,mBAAA,EACf,YAAA,GAAe,OAAA,UAAiB,SAAA,aAAsB,OAAA,UACtD,qBAAA,GACE,OAAA,UACA,SAAA,UACA,WAAA,aACG,OAAA,UACL,KAAA,EAAO,cAAA,EACP,OAAA,UAAiB,UAAA,CAAW,KAAA,GAC3B,OAAA,CAAQ,eAAA"}
@@ -2,6 +2,21 @@ import { buildManifest, buildStackManifest, validateServiceName } from "../manif
2
2
  import { deployManifest } from "./deployManifest.js";
3
3
  import { ManifestMCPError, ManifestMCPErrorCode } from "@manifest-network/manifest-mcp-core";
4
4
  //#region src/tools/deployApp.ts
5
+ function skuSelectorFromInput(input) {
6
+ const skuUuid = input.skuUuid?.trim();
7
+ const providerUuid = input.providerUuid?.trim();
8
+ if (skuUuid && providerUuid) return {
9
+ kind: "resolved",
10
+ skuUuid,
11
+ providerUuid
12
+ };
13
+ return {
14
+ kind: "byName",
15
+ size: input.size,
16
+ ...providerUuid ? { providerUuid } : {},
17
+ ...skuUuid ? { skuUuid } : {}
18
+ };
19
+ }
5
20
  async function deployApp(clientManager, getAuthToken, getLeaseDataAuthToken, input, fetchFn) {
6
21
  if (input.image && input.services) throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, "image and services are mutually exclusive");
7
22
  if (!input.image && !input.services) throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, "either image or services is required");
@@ -45,10 +60,7 @@ async function deployApp(clientManager, getAuthToken, getLeaseDataAuthToken, inp
45
60
  }
46
61
  return deployManifest({
47
62
  manifest: manifestJson,
48
- sku: {
49
- kind: "byName",
50
- size: input.size
51
- },
63
+ sku: skuSelectorFromInput(input),
52
64
  storage: input.storage,
53
65
  customDomain: input.customDomain,
54
66
  serviceName: input.serviceName,
@@ -1 +1 @@
1
- {"version":3,"file":"deployApp.js","names":[],"sources":["../../src/tools/deployApp.ts"],"sourcesContent":["import type { CosmosClientManager } from '@manifest-network/manifest-mcp-core';\nimport {\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\nimport type { PollOptions } from '../http/fred.js';\nimport {\n type BuildManifestOptions,\n buildManifest,\n buildStackManifest,\n validateServiceName,\n} from '../manifest.js';\nimport type { DeployAppResult } from './deployManifest.js';\nimport { deployManifest } from './deployManifest.js';\n\nexport type { DeployAppResult } from './deployManifest.js';\n\nexport interface ServiceConfig {\n image: string;\n ports?: Record<string, Record<string, never>>;\n env?: Record<string, string>;\n command?: string[];\n args?: string[];\n user?: string;\n tmpfs?: string[];\n health_check?: {\n test: string[];\n interval?: string;\n timeout?: string;\n retries?: number;\n start_period?: string;\n };\n stop_grace_period?: string;\n depends_on?: Record<string, { condition: string }>;\n expose?: string[];\n labels?: Record<string, string>;\n}\n\nexport interface DeployAppInput {\n image?: string;\n port?: number;\n size: string;\n env?: Record<string, string>;\n command?: string[];\n args?: string[];\n user?: string;\n tmpfs?: string[];\n health_check?: {\n test: string[];\n interval?: string;\n timeout?: string;\n retries?: number;\n start_period?: string;\n };\n stop_grace_period?: string;\n init?: boolean;\n expose?: string[];\n labels?: Record<string, string>;\n storage?: string;\n depends_on?: Record<string, { condition: string }>;\n services?: Record<string, ServiceConfig>;\n gasMultiplier?: number;\n /**\n * Optional FQDN to attach to the lease item once the create-lease tx\n * confirms. The set-domain tx is submitted after `onLeaseCreated` fires\n * and before the manifest upload, so providerd has the domain available\n * when it provisions the container. Failures here surface as the same\n * \"Deploy partially succeeded: lease X was created...\" error shape as\n * upload/poll failures — the caller can `close_lease` to clean up or\n * retry `set_item_custom_domain` standalone.\n */\n customDomain?: string;\n /**\n * Required when `customDomain` is set on a stack lease (i.e., `services`\n * is provided). Must match one of the keys in `services`. Omit for an\n * image+port single-item legacy lease.\n */\n serviceName?: string;\n /** Fires once after the create-lease TX confirms, before upload/poll. Awaited. Errors propagate. */\n onLeaseCreated?: (\n leaseUuid: string,\n providerUrl: string,\n ) => void | Promise<void>;\n /** Aborts upload and poll (not the already-submitted chain TX). */\n abortSignal?: AbortSignal;\n /** Forwarded to the internal pollLeaseUntilReady call. abortSignal is the top-level field above. */\n pollOptions?: Omit<PollOptions, 'abortSignal'>;\n}\n\nexport async function deployApp(\n clientManager: CosmosClientManager,\n getAuthToken: (address: string, leaseUuid: string) => Promise<string>,\n getLeaseDataAuthToken: (\n address: string,\n leaseUuid: string,\n metaHashHex: string,\n ) => Promise<string>,\n input: DeployAppInput,\n fetchFn?: typeof globalThis.fetch,\n): Promise<DeployAppResult> {\n // Validate mutually exclusive inputs\n if (input.image && input.services) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'image and services are mutually exclusive',\n );\n }\n if (!input.image && !input.services) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'either image or services is required',\n );\n }\n if (input.image && !input.port) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'port is required when using image',\n );\n }\n\n // Build manifest from typed input. The customDomain/serviceName\n // coherence checks now live in deployManifest, which derives them from\n // the manifest it receives (single-service vs stack) — so we just emit\n // the manifest JSON here and delegate.\n let manifestJson: string;\n if (input.services) {\n for (const name of Object.keys(input.services)) {\n if (!validateServiceName(name)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid service name: \"${name}\". Must be 1-63 chars, lowercase alphanumeric + hyphens, no leading/trailing hyphens.`,\n );\n }\n }\n\n const services: Record<string, BuildManifestOptions> = {};\n for (const [name, svc] of Object.entries(input.services)) {\n services[name] = {\n image: svc.image,\n ports: svc.ports ?? {},\n env: svc.env,\n command: svc.command,\n args: svc.args,\n user: svc.user,\n tmpfs: svc.tmpfs,\n health_check: svc.health_check,\n stop_grace_period: svc.stop_grace_period,\n depends_on: svc.depends_on,\n expose: svc.expose,\n labels: svc.labels,\n };\n }\n manifestJson = JSON.stringify(buildStackManifest({ services }));\n } else {\n // image is guaranteed defined here: the guard above ensures !image && !services is false,\n // and the if-branch handles the services case. TypeScript can't narrow across if/else.\n const image = input.image as string;\n manifestJson = JSON.stringify(\n buildManifest({\n image,\n ports: { [`${input.port}/tcp`]: {} },\n env: input.env,\n command: input.command,\n args: input.args,\n user: input.user,\n tmpfs: input.tmpfs,\n health_check: input.health_check,\n stop_grace_period: input.stop_grace_period,\n init: input.init,\n expose: input.expose,\n labels: input.labels,\n depends_on: input.depends_on,\n }),\n );\n }\n\n return deployManifest(\n {\n manifest: manifestJson,\n sku: { kind: 'byName', size: input.size },\n storage: input.storage,\n customDomain: input.customDomain,\n serviceName: input.serviceName,\n gasMultiplier: input.gasMultiplier,\n onLeaseCreated: input.onLeaseCreated,\n abortSignal: input.abortSignal,\n pollOptions: input.pollOptions,\n },\n { clientManager, getAuthToken, getLeaseDataAuthToken, fetchFn },\n );\n}\n"],"mappings":";;;;AAyFA,eAAsB,UACpB,eACA,cACA,uBAKA,OACA,SAC0B;CAE1B,IAAI,MAAM,SAAS,MAAM,UACvB,MAAM,IAAI,iBACR,qBAAqB,gBACrB,2CACF;CAEF,IAAI,CAAC,MAAM,SAAS,CAAC,MAAM,UACzB,MAAM,IAAI,iBACR,qBAAqB,gBACrB,sCACF;CAEF,IAAI,MAAM,SAAS,CAAC,MAAM,MACxB,MAAM,IAAI,iBACR,qBAAqB,gBACrB,mCACF;CAOF,IAAI;CACJ,IAAI,MAAM,UAAU;EAClB,KAAK,MAAM,QAAQ,OAAO,KAAK,MAAM,QAAQ,GAC3C,IAAI,CAAC,oBAAoB,IAAI,GAC3B,MAAM,IAAI,iBACR,qBAAqB,gBACrB,0BAA0B,KAAK,sFACjC;EAIJ,MAAM,WAAiD,CAAC;EACxD,KAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,MAAM,QAAQ,GACrD,SAAS,QAAQ;GACf,OAAO,IAAI;GACX,OAAO,IAAI,SAAS,CAAC;GACrB,KAAK,IAAI;GACT,SAAS,IAAI;GACb,MAAM,IAAI;GACV,MAAM,IAAI;GACV,OAAO,IAAI;GACX,cAAc,IAAI;GAClB,mBAAmB,IAAI;GACvB,YAAY,IAAI;GAChB,QAAQ,IAAI;GACZ,QAAQ,IAAI;EACd;EAEF,eAAe,KAAK,UAAU,mBAAmB,EAAE,SAAS,CAAC,CAAC;CAChE,OAAO;EAGL,MAAM,QAAQ,MAAM;EACpB,eAAe,KAAK,UAClB,cAAc;GACZ;GACA,OAAO,GAAG,GAAG,MAAM,KAAK,QAAQ,CAAC,EAAE;GACnC,KAAK,MAAM;GACX,SAAS,MAAM;GACf,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,cAAc,MAAM;GACpB,mBAAmB,MAAM;GACzB,MAAM,MAAM;GACZ,QAAQ,MAAM;GACd,QAAQ,MAAM;GACd,YAAY,MAAM;EACpB,CAAC,CACH;CACF;CAEA,OAAO,eACL;EACE,UAAU;EACV,KAAK;GAAE,MAAM;GAAU,MAAM,MAAM;EAAK;EACxC,SAAS,MAAM;EACf,cAAc,MAAM;EACpB,aAAa,MAAM;EACnB,eAAe,MAAM;EACrB,gBAAgB,MAAM;EACtB,aAAa,MAAM;EACnB,aAAa,MAAM;CACrB,GACA;EAAE;EAAe;EAAc;EAAuB;CAAQ,CAChE;AACF"}
1
+ {"version":3,"file":"deployApp.js","names":[],"sources":["../../src/tools/deployApp.ts"],"sourcesContent":["import type { CosmosClientManager } from '@manifest-network/manifest-mcp-core';\nimport {\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\nimport type { PollOptions } from '../http/fred.js';\nimport {\n type BuildManifestOptions,\n buildManifest,\n buildStackManifest,\n validateServiceName,\n} from '../manifest.js';\nimport type { DeployAppResult, SkuSelector } from './deployManifest.js';\nimport { deployManifest } from './deployManifest.js';\n\nexport type { DeployAppResult } from './deployManifest.js';\n\nexport interface ServiceConfig {\n image: string;\n ports?: Record<string, Record<string, never>>;\n env?: Record<string, string>;\n command?: string[];\n args?: string[];\n user?: string;\n tmpfs?: string[];\n health_check?: {\n test: string[];\n interval?: string;\n timeout?: string;\n retries?: number;\n start_period?: string;\n };\n stop_grace_period?: string;\n depends_on?: Record<string, { condition: string }>;\n expose?: string[];\n labels?: Record<string, string>;\n}\n\nexport interface DeployAppInput {\n image?: string;\n port?: number;\n size: string;\n /** Disambiguate a duplicate SKU name to one provider (ENG-258). */\n providerUuid?: string;\n /** Pin a specific SKU by uuid, bypassing name resolution (ENG-258). Wins over size/providerUuid. */\n skuUuid?: string;\n env?: Record<string, string>;\n command?: string[];\n args?: string[];\n user?: string;\n tmpfs?: string[];\n health_check?: {\n test: string[];\n interval?: string;\n timeout?: string;\n retries?: number;\n start_period?: string;\n };\n stop_grace_period?: string;\n init?: boolean;\n expose?: string[];\n labels?: Record<string, string>;\n storage?: string;\n depends_on?: Record<string, { condition: string }>;\n services?: Record<string, ServiceConfig>;\n gasMultiplier?: number;\n /**\n * Optional FQDN to attach to the lease item once the create-lease tx\n * confirms. The set-domain tx is submitted after `onLeaseCreated` fires\n * and before the manifest upload, so providerd has the domain available\n * when it provisions the container. Failures here surface as the same\n * \"Deploy partially succeeded: lease X was created...\" error shape as\n * upload/poll failures — the caller can `close_lease` to clean up or\n * retry `set_item_custom_domain` standalone.\n */\n customDomain?: string;\n /**\n * Required when `customDomain` is set on a stack lease (i.e., `services`\n * is provided). Must match one of the keys in `services`. Omit for an\n * image+port single-item legacy lease.\n */\n serviceName?: string;\n /** Fires once after the create-lease TX confirms, before upload/poll. Awaited. Errors propagate. */\n onLeaseCreated?: (\n leaseUuid: string,\n providerUrl: string,\n ) => void | Promise<void>;\n /** Aborts upload and poll (not the already-submitted chain TX). */\n abortSignal?: AbortSignal;\n /** Forwarded to the internal pollLeaseUntilReady call. abortSignal is the top-level field above. */\n pollOptions?: Omit<PollOptions, 'abortSignal'>;\n}\n\nfunction skuSelectorFromInput(input: DeployAppInput): SkuSelector {\n const skuUuid = input.skuUuid?.trim();\n const providerUuid = input.providerUuid?.trim();\n // `resolved` requires BOTH ids — only then can fred skip the lookup.\n if (skuUuid && providerUuid) {\n return { kind: 'resolved', skuUuid, providerUuid };\n }\n // Otherwise resolve by name, carrying whichever disambiguator we have so\n // core.resolveSku can narrow (provider) or pin (skuUuid → learns provider).\n return {\n kind: 'byName',\n size: input.size,\n ...(providerUuid ? { providerUuid } : {}),\n ...(skuUuid ? { skuUuid } : {}),\n };\n}\n\nexport async function deployApp(\n clientManager: CosmosClientManager,\n getAuthToken: (address: string, leaseUuid: string) => Promise<string>,\n getLeaseDataAuthToken: (\n address: string,\n leaseUuid: string,\n metaHashHex: string,\n ) => Promise<string>,\n input: DeployAppInput,\n fetchFn?: typeof globalThis.fetch,\n): Promise<DeployAppResult> {\n // Validate mutually exclusive inputs\n if (input.image && input.services) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'image and services are mutually exclusive',\n );\n }\n if (!input.image && !input.services) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'either image or services is required',\n );\n }\n if (input.image && !input.port) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'port is required when using image',\n );\n }\n\n // Build manifest from typed input. The customDomain/serviceName\n // coherence checks now live in deployManifest, which derives them from\n // the manifest it receives (single-service vs stack) — so we just emit\n // the manifest JSON here and delegate.\n let manifestJson: string;\n if (input.services) {\n for (const name of Object.keys(input.services)) {\n if (!validateServiceName(name)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid service name: \"${name}\". Must be 1-63 chars, lowercase alphanumeric + hyphens, no leading/trailing hyphens.`,\n );\n }\n }\n\n const services: Record<string, BuildManifestOptions> = {};\n for (const [name, svc] of Object.entries(input.services)) {\n services[name] = {\n image: svc.image,\n ports: svc.ports ?? {},\n env: svc.env,\n command: svc.command,\n args: svc.args,\n user: svc.user,\n tmpfs: svc.tmpfs,\n health_check: svc.health_check,\n stop_grace_period: svc.stop_grace_period,\n depends_on: svc.depends_on,\n expose: svc.expose,\n labels: svc.labels,\n };\n }\n manifestJson = JSON.stringify(buildStackManifest({ services }));\n } else {\n // image is guaranteed defined here: the guard above ensures !image && !services is false,\n // and the if-branch handles the services case. TypeScript can't narrow across if/else.\n const image = input.image as string;\n manifestJson = JSON.stringify(\n buildManifest({\n image,\n ports: { [`${input.port}/tcp`]: {} },\n env: input.env,\n command: input.command,\n args: input.args,\n user: input.user,\n tmpfs: input.tmpfs,\n health_check: input.health_check,\n stop_grace_period: input.stop_grace_period,\n init: input.init,\n expose: input.expose,\n labels: input.labels,\n depends_on: input.depends_on,\n }),\n );\n }\n\n return deployManifest(\n {\n manifest: manifestJson,\n sku: skuSelectorFromInput(input),\n storage: input.storage,\n customDomain: input.customDomain,\n serviceName: input.serviceName,\n gasMultiplier: input.gasMultiplier,\n onLeaseCreated: input.onLeaseCreated,\n abortSignal: input.abortSignal,\n pollOptions: input.pollOptions,\n },\n { clientManager, getAuthToken, getLeaseDataAuthToken, fetchFn },\n );\n}\n"],"mappings":";;;;AA6FA,SAAS,qBAAqB,OAAoC;CAChE,MAAM,UAAU,MAAM,SAAS,KAAK;CACpC,MAAM,eAAe,MAAM,cAAc,KAAK;CAE9C,IAAI,WAAW,cACb,OAAO;EAAE,MAAM;EAAY;EAAS;CAAa;CAInD,OAAO;EACL,MAAM;EACN,MAAM,MAAM;EACZ,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;EACvC,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;CAC/B;AACF;AAEA,eAAsB,UACpB,eACA,cACA,uBAKA,OACA,SAC0B;CAE1B,IAAI,MAAM,SAAS,MAAM,UACvB,MAAM,IAAI,iBACR,qBAAqB,gBACrB,2CACF;CAEF,IAAI,CAAC,MAAM,SAAS,CAAC,MAAM,UACzB,MAAM,IAAI,iBACR,qBAAqB,gBACrB,sCACF;CAEF,IAAI,MAAM,SAAS,CAAC,MAAM,MACxB,MAAM,IAAI,iBACR,qBAAqB,gBACrB,mCACF;CAOF,IAAI;CACJ,IAAI,MAAM,UAAU;EAClB,KAAK,MAAM,QAAQ,OAAO,KAAK,MAAM,QAAQ,GAC3C,IAAI,CAAC,oBAAoB,IAAI,GAC3B,MAAM,IAAI,iBACR,qBAAqB,gBACrB,0BAA0B,KAAK,sFACjC;EAIJ,MAAM,WAAiD,CAAC;EACxD,KAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,MAAM,QAAQ,GACrD,SAAS,QAAQ;GACf,OAAO,IAAI;GACX,OAAO,IAAI,SAAS,CAAC;GACrB,KAAK,IAAI;GACT,SAAS,IAAI;GACb,MAAM,IAAI;GACV,MAAM,IAAI;GACV,OAAO,IAAI;GACX,cAAc,IAAI;GAClB,mBAAmB,IAAI;GACvB,YAAY,IAAI;GAChB,QAAQ,IAAI;GACZ,QAAQ,IAAI;EACd;EAEF,eAAe,KAAK,UAAU,mBAAmB,EAAE,SAAS,CAAC,CAAC;CAChE,OAAO;EAGL,MAAM,QAAQ,MAAM;EACpB,eAAe,KAAK,UAClB,cAAc;GACZ;GACA,OAAO,GAAG,GAAG,MAAM,KAAK,QAAQ,CAAC,EAAE;GACnC,KAAK,MAAM;GACX,SAAS,MAAM;GACf,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,cAAc,MAAM;GACpB,mBAAmB,MAAM;GACzB,MAAM,MAAM;GACZ,QAAQ,MAAM;GACd,QAAQ,MAAM;GACd,YAAY,MAAM;EACpB,CAAC,CACH;CACF;CAEA,OAAO,eACL;EACE,UAAU;EACV,KAAK,qBAAqB,KAAK;EAC/B,SAAS,MAAM;EACf,cAAc,MAAM;EACpB,aAAa,MAAM;EACnB,eAAe,MAAM;EACrB,gBAAgB,MAAM;EACtB,aAAa,MAAM;EACnB,aAAa,MAAM;CACrB,GACA;EAAE;EAAe;EAAc;EAAuB;CAAQ,CAChE;AACF"}
@@ -1,13 +1,9 @@
1
1
  import { ConnectionDetails } from "../http/provider.js";
2
2
  import { PollOptions } from "../http/fred.js";
3
- import { CosmosClientManager, CosmosTxResult, LeaseState, ManifestQueryClient } from "@manifest-network/manifest-mcp-core";
3
+ import { CosmosClientManager, CosmosTxResult, LeaseState } from "@manifest-network/manifest-mcp-core";
4
4
 
5
5
  //#region src/tools/deployManifest.d.ts
6
6
  declare function extractLeaseUuid(txResult: CosmosTxResult): string;
7
- declare function findSkuUuid(queryClient: ManifestQueryClient, size: string, providerUuid?: string): Promise<{
8
- skuUuid: string;
9
- providerUuid: string;
10
- }>;
11
7
  interface DeployAppResult {
12
8
  readonly lease_uuid: string;
13
9
  readonly provider_uuid: string;
@@ -24,6 +20,8 @@ interface DeployAppResult {
24
20
  type SkuSelector = {
25
21
  kind: 'byName';
26
22
  size: string;
23
+ providerUuid?: string;
24
+ skuUuid?: string;
27
25
  } | {
28
26
  kind: 'resolved';
29
27
  skuUuid: string;
@@ -48,5 +46,5 @@ interface DeployManifestOptions {
48
46
  }
49
47
  declare function deployManifest(input: DeployManifestInput, opts: DeployManifestOptions): Promise<DeployAppResult>;
50
48
  //#endregion
51
- export { DeployAppResult, DeployManifestInput, DeployManifestOptions, SkuSelector, deployManifest, extractLeaseUuid, findSkuUuid };
49
+ export { DeployAppResult, DeployManifestInput, DeployManifestOptions, SkuSelector, deployManifest, extractLeaseUuid };
52
50
  //# sourceMappingURL=deployManifest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deployManifest.d.ts","names":[],"sources":["../../src/tools/deployManifest.ts"],"mappings":";;;;;iBA2BgB,gBAAA,CAAiB,QAAwB,EAAd,cAAc;AAAA,iBAgCnC,WAAA,CACpB,WAAA,EAAa,mBAAA,EACb,IAAA,UACA,YAAA,YACC,OAAO;EAAG,OAAA;EAAiB,YAAA;AAAA;AAAA,UAiCb,eAAA;EAAA,SACN,UAAA;EAAA,SACA,aAAA;EAAA,SACA,YAAA;EAAA,SACA,KAAA,EAAO,UAAA;EAAA,SACP,GAAA;EAAA,SACA,UAAA,GAAa,iBAAiB;EAAA,SAC9B,eAAA;EA1CT;EAAA,SA4CS,aAAA;EA1CR;EAAA,SA4CQ,YAAA;AAAA;AAAA,KAGC,WAAA;EACN,IAAA;EAAgB,IAAA;AAAA;EAChB,IAAA;EAAkB,OAAA;EAAiB,YAAA;AAAA;AAAA,UAExB,mBAAA;EACf,QAAA;EACA,GAAA,EAAK,WAAA;EACL,OAAA;EACA,YAAA;EACA,WAAA;EACA,aAAA;EACA,cAAA,IACE,SAAA,UACA,WAAA,oBACU,OAAA;EACZ,WAAA,GAAc,WAAA;EACd,WAAA,GAAc,IAAA,CAAK,WAAA;AAAA;AAAA,UAGJ,qBAAA;EACf,aAAA,EAAe,mBAAA;EACf,YAAA,GAAe,OAAA,UAAiB,SAAA,aAAsB,OAAA;EACtD,qBAAA,GACE,OAAA,UACA,SAAA,UACA,QAAA,aACG,OAAA;EACL,OAAA,UAAiB,UAAA,CAAW,KAAA;AAAA;AAAA,iBAKR,cAAA,CACpB,KAAA,EAAO,mBAAA,EACP,IAAA,EAAM,qBAAA,GACL,OAAA,CAAQ,eAAA"}
1
+ {"version":3,"file":"deployManifest.d.ts","names":[],"sources":["../../src/tools/deployManifest.ts"],"mappings":";;;;;iBAyBgB,gBAAA,CAAiB,QAAwB,EAAd,cAAc;AAAA,UAgCxC,eAAA;EAAA,SACN,UAAA;EAAA,SACA,aAAA;EAAA,SACA,YAAA;EAAA,SACA,KAAA,EAAO,UAAA;EAAA,SACP,GAAA;EAAA,SACA,UAAA,GAAa,iBAAiB;EAAA,SAC9B,eAAA;;WAEA,aAAA;EARA;EAAA,SAUA,YAAA;AAAA;AAAA,KAGC,WAAA;EACN,IAAA;EAAgB,IAAA;EAAc,YAAA;EAAuB,OAAA;AAAA;EACrD,IAAA;EAAkB,OAAA;EAAiB,YAAA;AAAA;AAAA,UAExB,mBAAA;EACf,QAAA;EACA,GAAA,EAAK,WAAA;EACL,OAAA;EACA,YAAA;EACA,WAAA;EACA,aAAA;EACA,cAAA,IACE,SAAA,UACA,WAAA,oBACU,OAAA;EACZ,WAAA,GAAc,WAAA;EACd,WAAA,GAAc,IAAA,CAAK,WAAA;AAAA;AAAA,UAGJ,qBAAA;EACf,aAAA,EAAe,mBAAA;EACf,YAAA,GAAe,OAAA,UAAiB,SAAA,aAAsB,OAAA;EACtD,qBAAA,GACE,OAAA,UACA,SAAA,UACA,QAAA,aACG,OAAA;EACL,OAAA,UAAiB,UAAA,CAAW,KAAA;AAAA;AAAA,iBAKR,cAAA,CACpB,KAAA,EAAO,mBAAA,EACP,IAAA,EAAM,qBAAA,GACL,OAAA,CAAQ,eAAA"}
@@ -2,7 +2,7 @@ import { getLeaseConnectionInfo, uploadLeaseData } from "../http/provider.js";
2
2
  import { TerminalChainStateError, pollLeaseUntilReady } from "../http/fred.js";
3
3
  import { getServiceNames, metaHashHex, validateManifest } from "../manifest.js";
4
4
  import { resolveProviderUrl } from "./resolveLeaseProvider.js";
5
- import { MAX_PAGE_LIMIT, ManifestMCPError, ManifestMCPErrorCode, cosmosTx, createPagination, logger, requireUuid, sanitizeForLogging, setItemCustomDomain } from "@manifest-network/manifest-mcp-core";
5
+ import { ManifestMCPError, ManifestMCPErrorCode, cosmosTx, logger, requireUuid, resolveSku, sanitizeForLogging, setItemCustomDomain } from "@manifest-network/manifest-mcp-core";
6
6
  //#region src/tools/deployManifest.ts
7
7
  function extractLeaseUuid(txResult) {
8
8
  if (!txResult.events) throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, "No events in transaction result; cannot extract lease UUID");
@@ -16,28 +16,6 @@ function extractLeaseUuid(txResult) {
16
16
  }
17
17
  throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, "Could not find lease UUID in transaction events", { events: txResult.events });
18
18
  }
19
- async function findSkuUuid(queryClient, size, providerUuid) {
20
- const pagination = createPagination(MAX_PAGE_LIMIT);
21
- const result = await queryClient.liftedinit.sku.v1.sKUs({
22
- activeOnly: true,
23
- pagination
24
- });
25
- const named = result.skus.filter((s) => s.name === size);
26
- if (named.length > 0) {
27
- if (providerUuid === void 0) return {
28
- skuUuid: named[0].uuid,
29
- providerUuid: named[0].providerUuid
30
- };
31
- const onProvider = named.find((s) => s.providerUuid === providerUuid);
32
- if (onProvider) return {
33
- skuUuid: onProvider.uuid,
34
- providerUuid: onProvider.providerUuid
35
- };
36
- throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, `SKU tier "${size}" is not offered by provider ${providerUuid} (the provider selected for the compute tier). Provider(s) offering "${size}": ${named.map((s) => s.providerUuid).join(", ")}.`);
37
- }
38
- const available = result.skus.map((s) => s.name);
39
- throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, `SKU tier "${size}" not found on any provider. Available: ${available.join(", ")}`);
40
- }
41
19
  const MAX_MANIFEST_BYTES = 256 * 1024;
42
20
  async function deployManifest(input, opts) {
43
21
  const { clientManager, getAuthToken, getLeaseDataAuthToken, fetchFn } = opts;
@@ -79,7 +57,11 @@ async function deployManifest(input, opts) {
79
57
  providerUuid = input.sku.providerUuid;
80
58
  break;
81
59
  case "byName": {
82
- const r = await findSkuUuid(queryClient, input.sku.size);
60
+ const r = await resolveSku(queryClient, {
61
+ size: input.sku.size,
62
+ ...input.sku.providerUuid !== void 0 ? { providerUuid: input.sku.providerUuid } : {},
63
+ ...input.sku.skuUuid !== void 0 ? { skuUuid: input.sku.skuUuid } : {}
64
+ });
83
65
  skuUuid = r.skuUuid;
84
66
  providerUuid = r.providerUuid;
85
67
  break;
@@ -91,8 +73,11 @@ async function deployManifest(input, opts) {
91
73
  }
92
74
  const leaseItems = isStack ? serviceNames.map((n) => `${skuUuid}:1:${n}`) : [`${skuUuid}:1`];
93
75
  if (input.storage) {
94
- const { skuUuid: storageSkuUuid } = await findSkuUuid(queryClient, input.storage, providerUuid);
95
- leaseItems.push(`${storageSkuUuid}:1`);
76
+ const storage = await resolveSku(queryClient, {
77
+ size: input.storage,
78
+ providerUuid
79
+ });
80
+ leaseItems.push(`${storage.skuUuid}:1`);
96
81
  }
97
82
  const providerUrl = await resolveProviderUrl(queryClient, providerUuid);
98
83
  const overrides = input.gasMultiplier !== void 0 ? { gasMultiplier: input.gasMultiplier } : void 0;
@@ -167,6 +152,6 @@ async function deployManifest(input, opts) {
167
152
  };
168
153
  }
169
154
  //#endregion
170
- export { deployManifest, extractLeaseUuid, findSkuUuid };
155
+ export { deployManifest, extractLeaseUuid };
171
156
 
172
157
  //# sourceMappingURL=deployManifest.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"deployManifest.js","names":[],"sources":["../../src/tools/deployManifest.ts"],"sourcesContent":["import type {\n CosmosClientManager,\n CosmosTxResult,\n LeaseState,\n ManifestQueryClient,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n cosmosTx,\n createPagination,\n logger,\n MAX_PAGE_LIMIT,\n ManifestMCPError,\n ManifestMCPErrorCode,\n requireUuid,\n sanitizeForLogging,\n setItemCustomDomain,\n} from '@manifest-network/manifest-mcp-core';\nimport type { FredLeaseStatus, PollOptions } from '../http/fred.js';\nimport { pollLeaseUntilReady, TerminalChainStateError } from '../http/fred.js';\nimport {\n type ConnectionDetails,\n getLeaseConnectionInfo,\n uploadLeaseData,\n} from '../http/provider.js';\nimport { getServiceNames, metaHashHex, validateManifest } from '../manifest.js';\nimport { resolveProviderUrl } from './resolveLeaseProvider.js';\n\nexport function extractLeaseUuid(txResult: CosmosTxResult): string {\n if (!txResult.events) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.TX_FAILED,\n 'No events in transaction result; cannot extract lease UUID',\n );\n }\n\n for (const event of txResult.events) {\n if (!event.type.includes('lease') && !event.type.includes('Lease'))\n continue;\n for (const attr of event.attributes) {\n if (attr.key === 'lease_uuid' || attr.key === 'uuid') {\n const raw = attr.value.replace(/^\"|\"$/g, '');\n // Validate the extracted value is a proper UUID\n requireUuid(\n { lease_uuid: raw },\n 'lease_uuid',\n ManifestMCPErrorCode.TX_FAILED,\n );\n return raw;\n }\n }\n }\n\n throw new ManifestMCPError(\n ManifestMCPErrorCode.TX_FAILED,\n 'Could not find lease UUID in transaction events',\n { events: txResult.events as unknown as Record<string, unknown>[] },\n );\n}\n\nexport async function findSkuUuid(\n queryClient: ManifestQueryClient,\n size: string,\n providerUuid?: string,\n): Promise<{ skuUuid: string; providerUuid: string }> {\n const pagination = createPagination(MAX_PAGE_LIMIT);\n const result = await queryClient.liftedinit.sku.v1.sKUs({\n activeOnly: true,\n pagination,\n });\n\n const named = result.skus.filter((s) => s.name === size);\n if (named.length > 0) {\n if (providerUuid === undefined) {\n return { skuUuid: named[0].uuid, providerUuid: named[0].providerUuid };\n }\n const onProvider = named.find((s) => s.providerUuid === providerUuid);\n if (onProvider) {\n return {\n skuUuid: onProvider.uuid,\n providerUuid: onProvider.providerUuid,\n };\n }\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `SKU tier \"${size}\" is not offered by provider ${providerUuid} (the provider selected for the compute tier). ` +\n `Provider(s) offering \"${size}\": ${named.map((s) => s.providerUuid).join(', ')}.`,\n );\n }\n\n const available = result.skus.map((s) => s.name);\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `SKU tier \"${size}\" not found on any provider. Available: ${available.join(', ')}`,\n );\n}\n\nexport interface DeployAppResult {\n readonly lease_uuid: string;\n readonly provider_uuid: string;\n readonly provider_url: string;\n readonly state: LeaseState;\n readonly url?: string;\n readonly connection?: ConnectionDetails;\n readonly connectionError?: string;\n /** Set when a `customDomain` was supplied AND the set-domain tx succeeded. */\n readonly custom_domain?: string;\n /** Set when a `serviceName` was supplied alongside a successful `customDomain` set. */\n readonly service_name?: string;\n}\n\nexport type SkuSelector =\n | { kind: 'byName'; size: string }\n | { kind: 'resolved'; skuUuid: string; providerUuid: string };\n\nexport interface DeployManifestInput {\n manifest: string;\n sku: SkuSelector;\n storage?: string;\n customDomain?: string;\n serviceName?: string;\n gasMultiplier?: number;\n onLeaseCreated?: (\n leaseUuid: string,\n providerUrl: string,\n ) => void | Promise<void>;\n abortSignal?: AbortSignal;\n pollOptions?: Omit<PollOptions, 'abortSignal'>;\n}\n\nexport interface DeployManifestOptions {\n clientManager: CosmosClientManager;\n getAuthToken: (address: string, leaseUuid: string) => Promise<string>;\n getLeaseDataAuthToken: (\n address: string,\n leaseUuid: string,\n metaHash: string,\n ) => Promise<string>;\n fetchFn?: typeof globalThis.fetch;\n}\n\nconst MAX_MANIFEST_BYTES = 256 * 1024;\n\nexport async function deployManifest(\n input: DeployManifestInput,\n opts: DeployManifestOptions,\n): Promise<DeployAppResult> {\n const { clientManager, getAuthToken, getLeaseDataAuthToken, fetchFn } = opts;\n\n const manifestBytes = new TextEncoder().encode(input.manifest);\n if (manifestBytes.length > MAX_MANIFEST_BYTES) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Manifest is ${manifestBytes.length} bytes; the maximum is ${MAX_MANIFEST_BYTES}.`,\n );\n }\n\n // Parse + validate at the boundary, before any tx (size cap above;\n // __proto__/constructor reject below; provider re-validates server-side).\n let parsed: unknown;\n try {\n parsed = JSON.parse(input.manifest);\n } catch (err) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Manifest is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (parsed !== null && typeof parsed === 'object') {\n const topKeys = Object.keys(parsed as Record<string, unknown>);\n if (topKeys.includes('__proto__') || topKeys.includes('constructor')) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Manifest must not contain a top-level \"__proto__\" or \"constructor\" key.',\n );\n }\n }\n const result = validateManifest(parsed);\n if (!result.valid) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid manifest: ${result.errors.join('; ')}`,\n { errors: result.errors },\n );\n }\n const isStack = result.format === 'stack';\n const serviceNames = isStack ? getServiceNames(parsed) : [];\n\n // customDomain / serviceName coherence (manifest-derived).\n let normalizedCustomDomain: string | undefined;\n if (input.customDomain !== undefined) {\n normalizedCustomDomain = input.customDomain.trim();\n if (normalizedCustomDomain === '') {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'customDomain cannot be empty or whitespace-only',\n );\n }\n if (isStack) {\n if (!input.serviceName) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName is required when setting customDomain on a stack lease; pick one of the service keys',\n );\n }\n if (!serviceNames.includes(input.serviceName)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `serviceName \"${input.serviceName}\" does not match any service. Available: ${serviceNames.join(', ')}`,\n );\n }\n } else if (input.serviceName) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName must not be set on a single-service deployment (image+port or a single-service manifest); omit it — the custom domain attaches to the sole item',\n );\n }\n } else if (input.serviceName !== undefined) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName is only meaningful when customDomain is set',\n );\n }\n\n const address = await clientManager.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n\n const manifestMetaHash = await metaHashHex(input.manifest);\n\n // SKU resolution (ENG-258 #1).\n let skuUuid: string;\n let providerUuid: string;\n switch (input.sku.kind) {\n case 'resolved':\n // Pre-resolved IDs are trusted verbatim, so validate them at the boundary:\n // an empty skuUuid would build a malformed `:1` lease item and reach\n // create-lease, and an empty providerUuid fails later with a misleading\n // QUERY_FAILED. Reject early with an actionable INVALID_CONFIG instead.\n if (\n input.sku.skuUuid.trim() === '' ||\n input.sku.providerUuid.trim() === ''\n ) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'sku.skuUuid and sku.providerUuid must both be non-empty for a pre-resolved (kind: \"resolved\") SKU selector',\n );\n }\n skuUuid = input.sku.skuUuid;\n providerUuid = input.sku.providerUuid;\n break;\n case 'byName': {\n const r = await findSkuUuid(queryClient, input.sku.size);\n skuUuid = r.skuUuid;\n providerUuid = r.providerUuid;\n break;\n }\n default: {\n const _exhaustive: never = input.sku;\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Unknown sku selector: ${JSON.stringify(_exhaustive)}`,\n );\n }\n }\n\n const leaseItems: string[] = isStack\n ? serviceNames.map((n) => `${skuUuid}:1:${n}`)\n : [`${skuUuid}:1`];\n\n // Storage on the SAME provider (ENG-258 #2).\n if (input.storage) {\n const { skuUuid: storageSkuUuid } = await findSkuUuid(\n queryClient,\n input.storage,\n providerUuid,\n );\n leaseItems.push(`${storageSkuUuid}:1`);\n }\n\n const providerUrl = await resolveProviderUrl(queryClient, providerUuid);\n\n const overrides =\n input.gasMultiplier !== undefined\n ? { gasMultiplier: input.gasMultiplier }\n : undefined;\n logger.info(\n `[deploy] creating lease (meta_hash=${manifestMetaHash}, items=${leaseItems.length})`,\n );\n const txResult = await cosmosTx(\n clientManager,\n 'billing',\n 'create-lease',\n ['--meta-hash', manifestMetaHash, ...leaseItems],\n true,\n overrides,\n );\n const leaseUuid = extractLeaseUuid(txResult);\n logger.info(\n `[deploy] lease ${leaseUuid} created on provider ${providerUuid}`,\n );\n\n await input.onLeaseCreated?.(leaseUuid, providerUrl);\n\n let step: 'set_domain' | 'upload' | 'poll' | undefined;\n let status: FredLeaseStatus;\n try {\n input.abortSignal?.throwIfAborted();\n if (normalizedCustomDomain !== undefined) {\n step = 'set_domain';\n await setItemCustomDomain(\n clientManager,\n leaseUuid,\n normalizedCustomDomain,\n { serviceName: input.serviceName },\n overrides,\n );\n }\n step = 'upload';\n const leaseDataToken = await getLeaseDataAuthToken(\n address,\n leaseUuid,\n manifestMetaHash,\n );\n await uploadLeaseData(\n providerUrl,\n leaseUuid,\n // Reuse the bytes computed for the size cap — same deterministic\n // UTF-8 encoding of the (immutable) input string, one fewer allocation.\n manifestBytes,\n leaseDataToken,\n fetchFn,\n input.abortSignal,\n );\n step = 'poll';\n status = await pollLeaseUntilReady(\n providerUrl,\n leaseUuid,\n () => getAuthToken(address, leaseUuid),\n { ...input.pollOptions, abortSignal: input.abortSignal },\n fetchFn,\n );\n } catch (err) {\n // A chain-terminal state (rejected / closed / expired) is self-explanatory\n // and the chain has already cleared the lease, so `close_lease` is NOT the\n // remedy — re-throw with lease context and an honest breadcrumb rather than\n // the partial-success \"close_lease\" advice below.\n if (err instanceof TerminalChainStateError) {\n logger.warn(\n `[deploy] lease ${leaseUuid} reached a terminal chain state during deploy`,\n );\n throw err.withContext({\n lease_uuid: leaseUuid,\n providerUuid,\n providerUrl,\n });\n }\n // Wrap a post-create-lease failure as a partial-success error so callers\n // know the lease exists and must be cleaned up.\n logger.warn(\n `[deploy] lease ${leaseUuid} created but a subsequent step${step ? ` ('${step}')` : ''} failed; close_lease to clean up`,\n );\n // A deliberate cancellation (throwIfAborted, or an aborted upload/poll) is\n // a user action, not an infra fault — code it OPERATION_CANCELLED (which is\n // non-retryable by code, so a partial-success cancellation is never\n // blind-retried into a second lease). `abortSignal.aborted` is true in both\n // the pre-step throwIfAborted and the mid-flight-abort cases.\n const code = input.abortSignal?.aborted\n ? ManifestMCPErrorCode.OPERATION_CANCELLED\n : err instanceof ManifestMCPError\n ? err.code\n : ManifestMCPErrorCode.QUERY_FAILED;\n const base = err instanceof ManifestMCPError ? err.details : undefined;\n throw new ManifestMCPError(\n code,\n `Deploy partially succeeded: lease ${leaseUuid} was created but subsequent steps failed. ` +\n `Close this lease with close_lease if needed. Error: ${err instanceof Error ? err.message : String(err)}`,\n {\n ...base,\n partial: true,\n ...(step !== undefined && { failedStep: step }),\n lease_uuid: leaseUuid,\n provider_uuid: providerUuid,\n provider_url: providerUrl,\n },\n );\n }\n\n // Fetch connection info (best-effort) and assemble the success result. A\n // failure here is non-fatal: the lease is already active, so we surface the\n // error in `connectionError` rather than throwing.\n let connection: ConnectionDetails | undefined;\n let url: string | undefined;\n let connectionError: string | undefined;\n try {\n const authToken = await getAuthToken(address, leaseUuid);\n const connResp = await getLeaseConnectionInfo(\n providerUrl,\n leaseUuid,\n authToken,\n fetchFn,\n );\n connection = connResp.connection;\n if (connection.host && connection.ports) {\n const firstPort = Object.values(connection.ports)[0];\n if (typeof firstPort === 'number' || typeof firstPort === 'string') {\n url = `${connection.host}:${firstPort}`;\n }\n }\n } catch (err) {\n const rawMsg = err instanceof Error ? err.message : String(err);\n // Log raw message to stderr for debugging; sanitize only the user-facing return value\n logger.error(\n `[deploy] Failed to fetch connection info for lease ${leaseUuid}: ${rawMsg}`,\n );\n connectionError = sanitizeForLogging(rawMsg) as string;\n }\n\n return {\n lease_uuid: leaseUuid,\n provider_uuid: providerUuid,\n provider_url: providerUrl,\n state: status.state,\n ...(url && { url }),\n ...(connection && { connection }),\n ...(connectionError && { connectionError }),\n // Reaching this return implies the set-domain tx (if requested)\n // succeeded — failures earlier in the try block throw and never\n // get here. Echo the trimmed canonical form, matching what the\n // chain stored.\n ...(normalizedCustomDomain && { custom_domain: normalizedCustomDomain }),\n ...(normalizedCustomDomain &&\n input.serviceName && { service_name: input.serviceName }),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"deployManifest.js","names":[],"sources":["../../src/tools/deployManifest.ts"],"sourcesContent":["import type {\n CosmosClientManager,\n CosmosTxResult,\n LeaseState,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n cosmosTx,\n logger,\n ManifestMCPError,\n ManifestMCPErrorCode,\n requireUuid,\n resolveSku,\n sanitizeForLogging,\n setItemCustomDomain,\n} from '@manifest-network/manifest-mcp-core';\nimport type { FredLeaseStatus, PollOptions } from '../http/fred.js';\nimport { pollLeaseUntilReady, TerminalChainStateError } from '../http/fred.js';\nimport {\n type ConnectionDetails,\n getLeaseConnectionInfo,\n uploadLeaseData,\n} from '../http/provider.js';\nimport { getServiceNames, metaHashHex, validateManifest } from '../manifest.js';\nimport { resolveProviderUrl } from './resolveLeaseProvider.js';\n\nexport function extractLeaseUuid(txResult: CosmosTxResult): string {\n if (!txResult.events) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.TX_FAILED,\n 'No events in transaction result; cannot extract lease UUID',\n );\n }\n\n for (const event of txResult.events) {\n if (!event.type.includes('lease') && !event.type.includes('Lease'))\n continue;\n for (const attr of event.attributes) {\n if (attr.key === 'lease_uuid' || attr.key === 'uuid') {\n const raw = attr.value.replace(/^\"|\"$/g, '');\n // Validate the extracted value is a proper UUID\n requireUuid(\n { lease_uuid: raw },\n 'lease_uuid',\n ManifestMCPErrorCode.TX_FAILED,\n );\n return raw;\n }\n }\n }\n\n throw new ManifestMCPError(\n ManifestMCPErrorCode.TX_FAILED,\n 'Could not find lease UUID in transaction events',\n { events: txResult.events as unknown as Record<string, unknown>[] },\n );\n}\n\nexport interface DeployAppResult {\n readonly lease_uuid: string;\n readonly provider_uuid: string;\n readonly provider_url: string;\n readonly state: LeaseState;\n readonly url?: string;\n readonly connection?: ConnectionDetails;\n readonly connectionError?: string;\n /** Set when a `customDomain` was supplied AND the set-domain tx succeeded. */\n readonly custom_domain?: string;\n /** Set when a `serviceName` was supplied alongside a successful `customDomain` set. */\n readonly service_name?: string;\n}\n\nexport type SkuSelector =\n | { kind: 'byName'; size: string; providerUuid?: string; skuUuid?: string }\n | { kind: 'resolved'; skuUuid: string; providerUuid: string };\n\nexport interface DeployManifestInput {\n manifest: string;\n sku: SkuSelector;\n storage?: string;\n customDomain?: string;\n serviceName?: string;\n gasMultiplier?: number;\n onLeaseCreated?: (\n leaseUuid: string,\n providerUrl: string,\n ) => void | Promise<void>;\n abortSignal?: AbortSignal;\n pollOptions?: Omit<PollOptions, 'abortSignal'>;\n}\n\nexport interface DeployManifestOptions {\n clientManager: CosmosClientManager;\n getAuthToken: (address: string, leaseUuid: string) => Promise<string>;\n getLeaseDataAuthToken: (\n address: string,\n leaseUuid: string,\n metaHash: string,\n ) => Promise<string>;\n fetchFn?: typeof globalThis.fetch;\n}\n\nconst MAX_MANIFEST_BYTES = 256 * 1024;\n\nexport async function deployManifest(\n input: DeployManifestInput,\n opts: DeployManifestOptions,\n): Promise<DeployAppResult> {\n const { clientManager, getAuthToken, getLeaseDataAuthToken, fetchFn } = opts;\n\n const manifestBytes = new TextEncoder().encode(input.manifest);\n if (manifestBytes.length > MAX_MANIFEST_BYTES) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Manifest is ${manifestBytes.length} bytes; the maximum is ${MAX_MANIFEST_BYTES}.`,\n );\n }\n\n // Parse + validate at the boundary, before any tx (size cap above;\n // __proto__/constructor reject below; provider re-validates server-side).\n let parsed: unknown;\n try {\n parsed = JSON.parse(input.manifest);\n } catch (err) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Manifest is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (parsed !== null && typeof parsed === 'object') {\n const topKeys = Object.keys(parsed as Record<string, unknown>);\n if (topKeys.includes('__proto__') || topKeys.includes('constructor')) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Manifest must not contain a top-level \"__proto__\" or \"constructor\" key.',\n );\n }\n }\n const result = validateManifest(parsed);\n if (!result.valid) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid manifest: ${result.errors.join('; ')}`,\n { errors: result.errors },\n );\n }\n const isStack = result.format === 'stack';\n const serviceNames = isStack ? getServiceNames(parsed) : [];\n\n // customDomain / serviceName coherence (manifest-derived).\n let normalizedCustomDomain: string | undefined;\n if (input.customDomain !== undefined) {\n normalizedCustomDomain = input.customDomain.trim();\n if (normalizedCustomDomain === '') {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'customDomain cannot be empty or whitespace-only',\n );\n }\n if (isStack) {\n if (!input.serviceName) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName is required when setting customDomain on a stack lease; pick one of the service keys',\n );\n }\n if (!serviceNames.includes(input.serviceName)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `serviceName \"${input.serviceName}\" does not match any service. Available: ${serviceNames.join(', ')}`,\n );\n }\n } else if (input.serviceName) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName must not be set on a single-service deployment (image+port or a single-service manifest); omit it — the custom domain attaches to the sole item',\n );\n }\n } else if (input.serviceName !== undefined) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName is only meaningful when customDomain is set',\n );\n }\n\n const address = await clientManager.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n\n const manifestMetaHash = await metaHashHex(input.manifest);\n\n // SKU resolution (ENG-258 #1).\n let skuUuid: string;\n let providerUuid: string;\n switch (input.sku.kind) {\n case 'resolved':\n // Pre-resolved IDs are trusted verbatim: the chain's create-lease is the\n // authoritative validation (existence/active/provider) — re-querying here\n // would reject momentarily-inactive-but-valid pins and still not close the\n // TOCTOU window (design §4.3 + §6). Only guard against empty strings, which\n // would build a malformed `:1` lease item / misleading downstream error.\n if (\n input.sku.skuUuid.trim() === '' ||\n input.sku.providerUuid.trim() === ''\n ) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'sku.skuUuid and sku.providerUuid must both be non-empty for a pre-resolved (kind: \"resolved\") SKU selector',\n );\n }\n skuUuid = input.sku.skuUuid;\n providerUuid = input.sku.providerUuid;\n break;\n case 'byName': {\n const r = await resolveSku(queryClient, {\n size: input.sku.size,\n ...(input.sku.providerUuid !== undefined\n ? { providerUuid: input.sku.providerUuid }\n : {}),\n ...(input.sku.skuUuid !== undefined\n ? { skuUuid: input.sku.skuUuid }\n : {}),\n });\n skuUuid = r.skuUuid;\n providerUuid = r.providerUuid;\n break;\n }\n default: {\n const _exhaustive: never = input.sku;\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Unknown sku selector: ${JSON.stringify(_exhaustive)}`,\n );\n }\n }\n\n const leaseItems: string[] = isStack\n ? serviceNames.map((n) => `${skuUuid}:1:${n}`)\n : [`${skuUuid}:1`];\n\n // Storage on the SAME provider (ENG-258 #2).\n if (input.storage) {\n const storage = await resolveSku(queryClient, {\n size: input.storage,\n providerUuid,\n });\n leaseItems.push(`${storage.skuUuid}:1`);\n }\n\n const providerUrl = await resolveProviderUrl(queryClient, providerUuid);\n\n const overrides =\n input.gasMultiplier !== undefined\n ? { gasMultiplier: input.gasMultiplier }\n : undefined;\n logger.info(\n `[deploy] creating lease (meta_hash=${manifestMetaHash}, items=${leaseItems.length})`,\n );\n const txResult = await cosmosTx(\n clientManager,\n 'billing',\n 'create-lease',\n ['--meta-hash', manifestMetaHash, ...leaseItems],\n true,\n overrides,\n );\n const leaseUuid = extractLeaseUuid(txResult);\n logger.info(\n `[deploy] lease ${leaseUuid} created on provider ${providerUuid}`,\n );\n\n await input.onLeaseCreated?.(leaseUuid, providerUrl);\n\n let step: 'set_domain' | 'upload' | 'poll' | undefined;\n let status: FredLeaseStatus;\n try {\n input.abortSignal?.throwIfAborted();\n if (normalizedCustomDomain !== undefined) {\n step = 'set_domain';\n await setItemCustomDomain(\n clientManager,\n leaseUuid,\n normalizedCustomDomain,\n { serviceName: input.serviceName },\n overrides,\n );\n }\n step = 'upload';\n const leaseDataToken = await getLeaseDataAuthToken(\n address,\n leaseUuid,\n manifestMetaHash,\n );\n await uploadLeaseData(\n providerUrl,\n leaseUuid,\n // Reuse the bytes computed for the size cap — same deterministic\n // UTF-8 encoding of the (immutable) input string, one fewer allocation.\n manifestBytes,\n leaseDataToken,\n fetchFn,\n input.abortSignal,\n );\n step = 'poll';\n status = await pollLeaseUntilReady(\n providerUrl,\n leaseUuid,\n () => getAuthToken(address, leaseUuid),\n { ...input.pollOptions, abortSignal: input.abortSignal },\n fetchFn,\n );\n } catch (err) {\n // A chain-terminal state (rejected / closed / expired) is self-explanatory\n // and the chain has already cleared the lease, so `close_lease` is NOT the\n // remedy — re-throw with lease context and an honest breadcrumb rather than\n // the partial-success \"close_lease\" advice below.\n if (err instanceof TerminalChainStateError) {\n logger.warn(\n `[deploy] lease ${leaseUuid} reached a terminal chain state during deploy`,\n );\n throw err.withContext({\n lease_uuid: leaseUuid,\n providerUuid,\n providerUrl,\n });\n }\n // Wrap a post-create-lease failure as a partial-success error so callers\n // know the lease exists and must be cleaned up.\n logger.warn(\n `[deploy] lease ${leaseUuid} created but a subsequent step${step ? ` ('${step}')` : ''} failed; close_lease to clean up`,\n );\n // A deliberate cancellation (throwIfAborted, or an aborted upload/poll) is\n // a user action, not an infra fault — code it OPERATION_CANCELLED (which is\n // non-retryable by code, so a partial-success cancellation is never\n // blind-retried into a second lease). `abortSignal.aborted` is true in both\n // the pre-step throwIfAborted and the mid-flight-abort cases.\n const code = input.abortSignal?.aborted\n ? ManifestMCPErrorCode.OPERATION_CANCELLED\n : err instanceof ManifestMCPError\n ? err.code\n : ManifestMCPErrorCode.QUERY_FAILED;\n const base = err instanceof ManifestMCPError ? err.details : undefined;\n throw new ManifestMCPError(\n code,\n `Deploy partially succeeded: lease ${leaseUuid} was created but subsequent steps failed. ` +\n `Close this lease with close_lease if needed. Error: ${err instanceof Error ? err.message : String(err)}`,\n {\n ...base,\n partial: true,\n ...(step !== undefined && { failedStep: step }),\n lease_uuid: leaseUuid,\n provider_uuid: providerUuid,\n provider_url: providerUrl,\n },\n );\n }\n\n // Fetch connection info (best-effort) and assemble the success result. A\n // failure here is non-fatal: the lease is already active, so we surface the\n // error in `connectionError` rather than throwing.\n let connection: ConnectionDetails | undefined;\n let url: string | undefined;\n let connectionError: string | undefined;\n try {\n const authToken = await getAuthToken(address, leaseUuid);\n const connResp = await getLeaseConnectionInfo(\n providerUrl,\n leaseUuid,\n authToken,\n fetchFn,\n );\n connection = connResp.connection;\n if (connection.host && connection.ports) {\n const firstPort = Object.values(connection.ports)[0];\n if (typeof firstPort === 'number' || typeof firstPort === 'string') {\n url = `${connection.host}:${firstPort}`;\n }\n }\n } catch (err) {\n const rawMsg = err instanceof Error ? err.message : String(err);\n // Log raw message to stderr for debugging; sanitize only the user-facing return value\n logger.error(\n `[deploy] Failed to fetch connection info for lease ${leaseUuid}: ${rawMsg}`,\n );\n connectionError = sanitizeForLogging(rawMsg) as string;\n }\n\n return {\n lease_uuid: leaseUuid,\n provider_uuid: providerUuid,\n provider_url: providerUrl,\n state: status.state,\n ...(url && { url }),\n ...(connection && { connection }),\n ...(connectionError && { connectionError }),\n // Reaching this return implies the set-domain tx (if requested)\n // succeeded — failures earlier in the try block throw and never\n // get here. Echo the trimmed canonical form, matching what the\n // chain stored.\n ...(normalizedCustomDomain && { custom_domain: normalizedCustomDomain }),\n ...(normalizedCustomDomain &&\n input.serviceName && { service_name: input.serviceName }),\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manifest-network/manifest-mcp-fred",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "MCP server for Manifest provider (Fred) operations (deploy, status, logs, restart, update)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -51,7 +51,7 @@
51
51
  ],
52
52
  "dependencies": {
53
53
  "@cosmjs/encoding": "0.32.4",
54
- "@manifest-network/manifest-mcp-core": "^0.13.1",
54
+ "@manifest-network/manifest-mcp-core": "^0.14.0",
55
55
  "@modelcontextprotocol/sdk": "1.29.0",
56
56
  "zod": "^4.3.6"
57
57
  },