@manifest-network/manifest-mcp-fred 0.9.0 → 0.11.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.
- package/dist/http/auth-token-service.js.map +1 -1
- package/dist/http/fred.js.map +1 -1
- package/dist/http/provider.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/server/fetch-gate.d.ts +34 -0
- package/dist/server/fetch-gate.d.ts.map +1 -0
- package/dist/server/fetch-gate.js +40 -0
- package/dist/server/fetch-gate.js.map +1 -0
- package/dist/server/register-resources.js.map +1 -1
- package/dist/server/register-tools.d.ts +8 -0
- package/dist/server/register-tools.d.ts.map +1 -1
- package/dist/server/register-tools.js +10 -10
- package/dist/server/register-tools.js.map +1 -1
- package/dist/tools/browseCatalog.js.map +1 -1
- package/dist/tools/deployApp.js.map +1 -1
- package/dist/tools/fetchActiveLease.d.ts +2 -2
- package/dist/tools/fetchActiveLease.d.ts.map +1 -1
- package/dist/tools/getLogs.js.map +1 -1
- package/dist/tools/restartApp.js.map +1 -1
- package/dist/tools/updateApp.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-token-service.js","names":[],"sources":["../../src/http/auth-token-service.ts"],"sourcesContent":["import {\n ManifestMCPError,\n ManifestMCPErrorCode,\n type WalletProvider,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n AuthTimestampTracker,\n createAuthToken,\n createLeaseDataSignMessage,\n createSignMessage,\n} from './auth.js';\n\n/**\n * Wallet-bound builder for ADR-036 provider auth tokens.\n *\n * Owns the stateful pieces (timestamp serialization, signArbitrary binding) so\n * that `FredMCPServer` can depend on a single collaborator rather than hand-\n * rolling the flow at each tool call site. The underlying stateless builders\n * (`createSignMessage`, `createAuthToken`, etc.) remain in `./auth.ts` for\n * library callers who need them without our specific wallet wiring.\n *\n * The `signArbitrary` requirement is enforced lazily — a wallet without\n * ADR-036 support still lets the server boot and serve non-auth-gated paths;\n * only provider-auth tool calls throw `INVALID_CONFIG`.\n */\nexport class AuthTokenService {\n private readonly timestamps = new AuthTimestampTracker();\n\n constructor(private readonly walletProvider: WalletProvider) {}\n\n async providerToken(address: string, leaseUuid: string): Promise<string> {\n const signArbitrary = this.requireSignArbitrary();\n const timestamp = await this.timestamps.next();\n const message = createSignMessage(address, leaseUuid, timestamp);\n const { pub_key, signature } = await signArbitrary(address, message);\n return createAuthToken(\n address,\n leaseUuid,\n timestamp,\n pub_key.value,\n signature,\n );\n }\n\n async leaseDataToken(\n address: string,\n leaseUuid: string,\n metaHashHex: string,\n ): Promise<string> {\n const signArbitrary = this.requireSignArbitrary();\n const timestamp = await this.timestamps.next();\n const message = createLeaseDataSignMessage(\n leaseUuid,\n metaHashHex,\n timestamp,\n );\n const { pub_key, signature } = await signArbitrary(address, message);\n return createAuthToken(\n address,\n leaseUuid,\n timestamp,\n pub_key.value,\n signature,\n metaHashHex,\n );\n }\n\n private requireSignArbitrary(): NonNullable<WalletProvider['signArbitrary']> {\n if (!this.walletProvider.signArbitrary) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Wallet does not support signArbitrary (ADR-036). Required for provider authentication. Use a wallet provider that implements signArbitrary.',\n );\n }\n return this.walletProvider.signArbitrary.bind(this.walletProvider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,IAAa,mBAAb,MAA8B;CAG5B,YAAY,gBAAiD;AAAhC,OAAA,iBAAA;AAF7B,OAAiB,aAAa,IAAI,sBAAsB;;CAIxD,MAAM,cAAc,SAAiB,WAAoC;EACvE,MAAM,gBAAgB,KAAK,sBAAsB;EACjD,MAAM,YAAY,MAAM,KAAK,WAAW,MAAM;EAE9C,MAAM,EAAE,SAAS,cAAc,MAAM,cAAc,SADnC,kBAAkB,SAAS,WAAW,
|
|
1
|
+
{"version":3,"file":"auth-token-service.js","names":[],"sources":["../../src/http/auth-token-service.ts"],"sourcesContent":["import {\n ManifestMCPError,\n ManifestMCPErrorCode,\n type WalletProvider,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n AuthTimestampTracker,\n createAuthToken,\n createLeaseDataSignMessage,\n createSignMessage,\n} from './auth.js';\n\n/**\n * Wallet-bound builder for ADR-036 provider auth tokens.\n *\n * Owns the stateful pieces (timestamp serialization, signArbitrary binding) so\n * that `FredMCPServer` can depend on a single collaborator rather than hand-\n * rolling the flow at each tool call site. The underlying stateless builders\n * (`createSignMessage`, `createAuthToken`, etc.) remain in `./auth.ts` for\n * library callers who need them without our specific wallet wiring.\n *\n * The `signArbitrary` requirement is enforced lazily — a wallet without\n * ADR-036 support still lets the server boot and serve non-auth-gated paths;\n * only provider-auth tool calls throw `INVALID_CONFIG`.\n */\nexport class AuthTokenService {\n private readonly timestamps = new AuthTimestampTracker();\n\n constructor(private readonly walletProvider: WalletProvider) {}\n\n async providerToken(address: string, leaseUuid: string): Promise<string> {\n const signArbitrary = this.requireSignArbitrary();\n const timestamp = await this.timestamps.next();\n const message = createSignMessage(address, leaseUuid, timestamp);\n const { pub_key, signature } = await signArbitrary(address, message);\n return createAuthToken(\n address,\n leaseUuid,\n timestamp,\n pub_key.value,\n signature,\n );\n }\n\n async leaseDataToken(\n address: string,\n leaseUuid: string,\n metaHashHex: string,\n ): Promise<string> {\n const signArbitrary = this.requireSignArbitrary();\n const timestamp = await this.timestamps.next();\n const message = createLeaseDataSignMessage(\n leaseUuid,\n metaHashHex,\n timestamp,\n );\n const { pub_key, signature } = await signArbitrary(address, message);\n return createAuthToken(\n address,\n leaseUuid,\n timestamp,\n pub_key.value,\n signature,\n metaHashHex,\n );\n }\n\n private requireSignArbitrary(): NonNullable<WalletProvider['signArbitrary']> {\n if (!this.walletProvider.signArbitrary) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Wallet does not support signArbitrary (ADR-036). Required for provider authentication. Use a wallet provider that implements signArbitrary.',\n );\n }\n return this.walletProvider.signArbitrary.bind(this.walletProvider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,IAAa,mBAAb,MAA8B;CAG5B,YAAY,gBAAiD;AAAhC,OAAA,iBAAA;AAF7B,OAAiB,aAAa,IAAI,sBAAsB;;CAIxD,MAAM,cAAc,SAAiB,WAAoC;EACvE,MAAM,gBAAgB,KAAK,sBAAsB;EACjD,MAAM,YAAY,MAAM,KAAK,WAAW,MAAM;EAE9C,MAAM,EAAE,SAAS,cAAc,MAAM,cAAc,SADnC,kBAAkB,SAAS,WAAW,UACa,CAAC;AACpE,SAAO,gBACL,SACA,WACA,WACA,QAAQ,OACR,UACD;;CAGH,MAAM,eACJ,SACA,WACA,aACiB;EACjB,MAAM,gBAAgB,KAAK,sBAAsB;EACjD,MAAM,YAAY,MAAM,KAAK,WAAW,MAAM;EAM9C,MAAM,EAAE,SAAS,cAAc,MAAM,cAAc,SALnC,2BACd,WACA,aACA,UAEiE,CAAC;AACpE,SAAO,gBACL,SACA,WACA,WACA,QAAQ,OACR,WACA,YACD;;CAGH,uBAA6E;AAC3E,MAAI,CAAC,KAAK,eAAe,cACvB,OAAM,IAAI,iBACR,qBAAqB,gBACrB,8IACD;AAEH,SAAO,KAAK,eAAe,cAAc,KAAK,KAAK,eAAe"}
|
package/dist/http/fred.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fred.js","names":[],"sources":["../../src/http/fred.ts"],"sourcesContent":["import { toBase64 } from '@cosmjs/encoding';\nimport {\n LeaseState,\n leaseStateFromJSON,\n logger,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n checkedFetch,\n ProviderApiError,\n parseJsonResponse,\n validateProviderUrl,\n} from './provider.js';\n\nexport const MAX_TAIL = 1000;\n\nexport interface FredInstanceInfo {\n readonly name: string;\n readonly status: string;\n readonly ports?: Record<string, number>;\n readonly fqdn?: string;\n}\n\nexport interface FredServiceStatus {\n readonly instances: readonly FredInstanceInfo[];\n}\n\nexport interface FredLeaseStatus {\n readonly state: LeaseState;\n readonly provision_status?: string;\n readonly phase?: string;\n readonly steps?: Record<string, string>;\n readonly instances?: readonly FredInstanceInfo[];\n readonly endpoints?: Record<string, string>;\n readonly last_error?: string;\n readonly fail_count?: number;\n readonly created_at?: string;\n readonly services?: Record<string, FredServiceStatus>;\n}\n\n/** Raw wire shape before LeaseState conversion */\ninterface RawLeaseStatus extends Omit<FredLeaseStatus, 'state'> {\n readonly state: string;\n}\n\nexport async function getLeaseStatus(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseStatus> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/status`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n const raw = await parseJsonResponse<RawLeaseStatus>(res, url);\n const state = leaseStateFromJSON(raw.state);\n if (state === LeaseState.UNRECOGNIZED) {\n logger.warn(\n `[getLeaseStatus] Unrecognized lease state \"${raw.state}\" for lease ${leaseUuid}. ` +\n 'The provider may be running a newer version than the client supports.',\n );\n }\n return { ...raw, state };\n}\n\nexport interface FredLeaseLogs {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly logs: Record<string, string>;\n}\n\nexport async function getLeaseLogs(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n tail?: number,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseLogs> {\n const validated = validateProviderUrl(providerUrl);\n const cappedTail = tail !== undefined ? Math.min(tail, MAX_TAIL) : undefined;\n const qs = cappedTail !== undefined ? `?tail=${cappedTail}` : '';\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/logs${qs}`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseLogs>(res, url);\n}\n\nexport interface FredLeaseProvision {\n readonly status: string;\n readonly fail_count: number;\n /**\n * Set only when the most recent provisioning attempt failed. The Fred\n * provider omits the field on success, so the optional marker matches\n * the wire shape (and matches the same field on FredLeaseStatus above).\n */\n readonly last_error?: string;\n}\n\nexport async function getLeaseProvision(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseProvision> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/provision`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseProvision>(res, url);\n}\n\nexport interface FredActionResponse {\n readonly status: string;\n}\n\nexport async function restartLease(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredActionResponse> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/restart`;\n const res = await checkedFetch(\n url,\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredActionResponse>(res, url);\n}\n\nexport async function updateLease(\n providerUrl: string,\n leaseUuid: string,\n payload: Uint8Array,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredActionResponse> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/update`;\n // The provider expects JSON with a base64-encoded payload (Go []byte field).\n const b64 = toBase64(payload);\n const res = await checkedFetch(\n url,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ payload: b64 }),\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredActionResponse>(res, url);\n}\n\nexport interface FredLeaseRelease {\n readonly version: number;\n readonly image: string;\n readonly status: string;\n readonly created_at: string;\n readonly error?: string;\n readonly manifest?: string;\n}\n\nexport interface FredLeaseReleases {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly releases: readonly FredLeaseRelease[];\n}\n\nexport async function getLeaseReleases(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseReleases> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/releases`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseReleases>(res, url);\n}\n\nexport interface FredLeaseInfo {\n readonly host: string;\n readonly ports?: Record<string, unknown>;\n}\n\nexport async function getLeaseInfo(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseInfo> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/info`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseInfo>(res, url);\n}\n\nexport type TerminalChainLeaseState = 'closed' | 'rejected' | 'expired';\n\nexport interface TerminalChainState {\n readonly state: TerminalChainLeaseState;\n}\n\nexport interface PollOptions {\n readonly intervalMs?: number;\n readonly timeoutMs?: number;\n readonly abortSignal?: AbortSignal;\n readonly onProgress?: (status: FredLeaseStatus) => void;\n /** Runs once per iteration before the provider is queried. Non-null return throws; errors propagate. */\n readonly checkChainState?: () => Promise<TerminalChainState | null>;\n}\n\nconst CHAIN_STATE_TO_LEASE_STATE: Record<TerminalChainLeaseState, LeaseState> =\n {\n closed: LeaseState.LEASE_STATE_CLOSED,\n rejected: LeaseState.LEASE_STATE_REJECTED,\n expired: LeaseState.LEASE_STATE_EXPIRED,\n };\n\nfunction leaseStateName(state: LeaseState): string {\n return LeaseState[state] ?? String(state);\n}\n\n/**\n * Thrown by pollLeaseUntilReady when the caller's checkChainState callback\n * reports a terminal lease state on-chain. Extends ProviderApiError so\n * existing catchers keep working; use `instanceof TerminalChainStateError`\n * or read `chainState` to distinguish from provider-reported terminal states.\n */\nexport interface TerminalChainStateContext {\n readonly providerUuid?: string;\n readonly providerUrl?: string;\n}\n\nexport class TerminalChainStateError extends ProviderApiError {\n public readonly chainState: TerminalChainLeaseState;\n public readonly leaseUuid: string;\n public readonly providerUuid?: string;\n public readonly providerUrl?: string;\n\n constructor(\n leaseUuid: string,\n chainState: TerminalChainLeaseState,\n context?: TerminalChainStateContext,\n ) {\n const mapped = CHAIN_STATE_TO_LEASE_STATE[chainState];\n super(\n 0,\n `Lease ${leaseUuid} entered terminal state ${leaseStateName(mapped)} on chain`,\n );\n this.name = 'TerminalChainStateError';\n this.chainState = chainState;\n this.leaseUuid = leaseUuid;\n this.providerUuid = context?.providerUuid;\n this.providerUrl = context?.providerUrl;\n Object.setPrototypeOf(this, TerminalChainStateError.prototype);\n }\n\n /**\n * Returns a new instance with the same lease/state and the supplied context,\n * preserving the original stack trace so debugging points to where the\n * terminal state was first detected.\n */\n withContext(context: TerminalChainStateContext): TerminalChainStateError {\n const enriched = new TerminalChainStateError(\n this.leaseUuid,\n this.chainState,\n context,\n );\n if (this.stack) enriched.stack = this.stack;\n return enriched;\n }\n}\n\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));\n signal.throwIfAborted();\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(\n signal.reason ??\n new DOMException('The operation was aborted', 'AbortError'),\n );\n };\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nexport async function pollLeaseUntilReady(\n providerUrl: string,\n leaseUuid: string,\n authToken: string | (() => Promise<string>),\n opts: PollOptions = {},\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseStatus> {\n const {\n intervalMs = 3_000,\n timeoutMs = 120_000,\n abortSignal,\n onProgress,\n checkChainState,\n } = opts;\n const deadline = Date.now() + timeoutMs;\n let lastState: LeaseState | undefined;\n\n while (Date.now() < deadline) {\n abortSignal?.throwIfAborted();\n if (checkChainState) {\n const chainState = await checkChainState();\n if (chainState) {\n throw new TerminalChainStateError(leaseUuid, chainState.state);\n }\n abortSignal?.throwIfAborted();\n }\n const token =\n typeof authToken === 'function' ? await authToken() : authToken;\n abortSignal?.throwIfAborted();\n const status = await getLeaseStatus(providerUrl, leaseUuid, token, fetchFn);\n lastState = status.state;\n onProgress?.(status);\n switch (status.state) {\n case LeaseState.LEASE_STATE_ACTIVE:\n return status;\n case LeaseState.LEASE_STATE_PENDING:\n break;\n case LeaseState.LEASE_STATE_CLOSED:\n case LeaseState.LEASE_STATE_REJECTED:\n case LeaseState.LEASE_STATE_EXPIRED:\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} entered terminal state ${leaseStateName(status.state)}`,\n );\n default:\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} returned unexpected state ${leaseStateName(status.state)}`,\n );\n }\n await abortableSleep(intervalMs, abortSignal);\n }\n\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} poll timed out after ${timeoutMs}ms (last state: ${lastState !== undefined ? leaseStateName(lastState) : 'unknown'})`,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fred.js","names":[],"sources":["../../src/http/fred.ts"],"sourcesContent":["import { toBase64 } from '@cosmjs/encoding';\nimport {\n LeaseState,\n leaseStateFromJSON,\n logger,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n checkedFetch,\n ProviderApiError,\n parseJsonResponse,\n validateProviderUrl,\n} from './provider.js';\n\nexport const MAX_TAIL = 1000;\n\nexport interface FredInstanceInfo {\n readonly name: string;\n readonly status: string;\n readonly ports?: Record<string, number>;\n readonly fqdn?: string;\n}\n\nexport interface FredServiceStatus {\n readonly instances: readonly FredInstanceInfo[];\n}\n\nexport interface FredLeaseStatus {\n readonly state: LeaseState;\n readonly provision_status?: string;\n readonly phase?: string;\n readonly steps?: Record<string, string>;\n readonly instances?: readonly FredInstanceInfo[];\n readonly endpoints?: Record<string, string>;\n readonly last_error?: string;\n readonly fail_count?: number;\n readonly created_at?: string;\n readonly services?: Record<string, FredServiceStatus>;\n}\n\n/** Raw wire shape before LeaseState conversion */\ninterface RawLeaseStatus extends Omit<FredLeaseStatus, 'state'> {\n readonly state: string;\n}\n\nexport async function getLeaseStatus(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseStatus> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/status`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n const raw = await parseJsonResponse<RawLeaseStatus>(res, url);\n const state = leaseStateFromJSON(raw.state);\n if (state === LeaseState.UNRECOGNIZED) {\n logger.warn(\n `[getLeaseStatus] Unrecognized lease state \"${raw.state}\" for lease ${leaseUuid}. ` +\n 'The provider may be running a newer version than the client supports.',\n );\n }\n return { ...raw, state };\n}\n\nexport interface FredLeaseLogs {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly logs: Record<string, string>;\n}\n\nexport async function getLeaseLogs(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n tail?: number,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseLogs> {\n const validated = validateProviderUrl(providerUrl);\n const cappedTail = tail !== undefined ? Math.min(tail, MAX_TAIL) : undefined;\n const qs = cappedTail !== undefined ? `?tail=${cappedTail}` : '';\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/logs${qs}`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseLogs>(res, url);\n}\n\nexport interface FredLeaseProvision {\n readonly status: string;\n readonly fail_count: number;\n /**\n * Set only when the most recent provisioning attempt failed. The Fred\n * provider omits the field on success, so the optional marker matches\n * the wire shape (and matches the same field on FredLeaseStatus above).\n */\n readonly last_error?: string;\n}\n\nexport async function getLeaseProvision(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseProvision> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/provision`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseProvision>(res, url);\n}\n\nexport interface FredActionResponse {\n readonly status: string;\n}\n\nexport async function restartLease(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredActionResponse> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/restart`;\n const res = await checkedFetch(\n url,\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredActionResponse>(res, url);\n}\n\nexport async function updateLease(\n providerUrl: string,\n leaseUuid: string,\n payload: Uint8Array,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredActionResponse> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/update`;\n // The provider expects JSON with a base64-encoded payload (Go []byte field).\n const b64 = toBase64(payload);\n const res = await checkedFetch(\n url,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ payload: b64 }),\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredActionResponse>(res, url);\n}\n\nexport interface FredLeaseRelease {\n readonly version: number;\n readonly image: string;\n readonly status: string;\n readonly created_at: string;\n readonly error?: string;\n readonly manifest?: string;\n}\n\nexport interface FredLeaseReleases {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly releases: readonly FredLeaseRelease[];\n}\n\nexport async function getLeaseReleases(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseReleases> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/releases`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseReleases>(res, url);\n}\n\nexport interface FredLeaseInfo {\n readonly host: string;\n readonly ports?: Record<string, unknown>;\n}\n\nexport async function getLeaseInfo(\n providerUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseInfo> {\n const validated = validateProviderUrl(providerUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/info`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<FredLeaseInfo>(res, url);\n}\n\nexport type TerminalChainLeaseState = 'closed' | 'rejected' | 'expired';\n\nexport interface TerminalChainState {\n readonly state: TerminalChainLeaseState;\n}\n\nexport interface PollOptions {\n readonly intervalMs?: number;\n readonly timeoutMs?: number;\n readonly abortSignal?: AbortSignal;\n readonly onProgress?: (status: FredLeaseStatus) => void;\n /** Runs once per iteration before the provider is queried. Non-null return throws; errors propagate. */\n readonly checkChainState?: () => Promise<TerminalChainState | null>;\n}\n\nconst CHAIN_STATE_TO_LEASE_STATE: Record<TerminalChainLeaseState, LeaseState> =\n {\n closed: LeaseState.LEASE_STATE_CLOSED,\n rejected: LeaseState.LEASE_STATE_REJECTED,\n expired: LeaseState.LEASE_STATE_EXPIRED,\n };\n\nfunction leaseStateName(state: LeaseState): string {\n return LeaseState[state] ?? String(state);\n}\n\n/**\n * Thrown by pollLeaseUntilReady when the caller's checkChainState callback\n * reports a terminal lease state on-chain. Extends ProviderApiError so\n * existing catchers keep working; use `instanceof TerminalChainStateError`\n * or read `chainState` to distinguish from provider-reported terminal states.\n */\nexport interface TerminalChainStateContext {\n readonly providerUuid?: string;\n readonly providerUrl?: string;\n}\n\nexport class TerminalChainStateError extends ProviderApiError {\n public readonly chainState: TerminalChainLeaseState;\n public readonly leaseUuid: string;\n public readonly providerUuid?: string;\n public readonly providerUrl?: string;\n\n constructor(\n leaseUuid: string,\n chainState: TerminalChainLeaseState,\n context?: TerminalChainStateContext,\n ) {\n const mapped = CHAIN_STATE_TO_LEASE_STATE[chainState];\n super(\n 0,\n `Lease ${leaseUuid} entered terminal state ${leaseStateName(mapped)} on chain`,\n );\n this.name = 'TerminalChainStateError';\n this.chainState = chainState;\n this.leaseUuid = leaseUuid;\n this.providerUuid = context?.providerUuid;\n this.providerUrl = context?.providerUrl;\n Object.setPrototypeOf(this, TerminalChainStateError.prototype);\n }\n\n /**\n * Returns a new instance with the same lease/state and the supplied context,\n * preserving the original stack trace so debugging points to where the\n * terminal state was first detected.\n */\n withContext(context: TerminalChainStateContext): TerminalChainStateError {\n const enriched = new TerminalChainStateError(\n this.leaseUuid,\n this.chainState,\n context,\n );\n if (this.stack) enriched.stack = this.stack;\n return enriched;\n }\n}\n\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));\n signal.throwIfAborted();\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(\n signal.reason ??\n new DOMException('The operation was aborted', 'AbortError'),\n );\n };\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nexport async function pollLeaseUntilReady(\n providerUrl: string,\n leaseUuid: string,\n authToken: string | (() => Promise<string>),\n opts: PollOptions = {},\n fetchFn?: typeof globalThis.fetch,\n): Promise<FredLeaseStatus> {\n const {\n intervalMs = 3_000,\n timeoutMs = 120_000,\n abortSignal,\n onProgress,\n checkChainState,\n } = opts;\n const deadline = Date.now() + timeoutMs;\n let lastState: LeaseState | undefined;\n\n while (Date.now() < deadline) {\n abortSignal?.throwIfAborted();\n if (checkChainState) {\n const chainState = await checkChainState();\n if (chainState) {\n throw new TerminalChainStateError(leaseUuid, chainState.state);\n }\n abortSignal?.throwIfAborted();\n }\n const token =\n typeof authToken === 'function' ? await authToken() : authToken;\n abortSignal?.throwIfAborted();\n const status = await getLeaseStatus(providerUrl, leaseUuid, token, fetchFn);\n lastState = status.state;\n onProgress?.(status);\n switch (status.state) {\n case LeaseState.LEASE_STATE_ACTIVE:\n return status;\n case LeaseState.LEASE_STATE_PENDING:\n break;\n case LeaseState.LEASE_STATE_CLOSED:\n case LeaseState.LEASE_STATE_REJECTED:\n case LeaseState.LEASE_STATE_EXPIRED:\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} entered terminal state ${leaseStateName(status.state)}`,\n );\n default:\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} returned unexpected state ${leaseStateName(status.state)}`,\n );\n }\n await abortableSleep(intervalMs, abortSignal);\n }\n\n throw new ProviderApiError(\n 0,\n `Lease ${leaseUuid} poll timed out after ${timeoutMs}ms (last state: ${lastState !== undefined ? leaseStateName(lastState) : 'unknown'})`,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","names":[],"sources":["../../src/http/provider.ts"],"sourcesContent":["export class ProviderApiError extends Error {\n public readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = 'ProviderApiError';\n this.status = status;\n Object.setPrototypeOf(this, ProviderApiError.prototype);\n }\n}\n\nconst LOCALHOST_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function validateProviderUrl(url: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new ProviderApiError(0, `Invalid provider URL: ${url}`);\n }\n\n if (parsed.protocol === 'https:') {\n return url.replace(/\\/+$/, '');\n }\n\n if (parsed.protocol === 'http:' && LOCALHOST_HOSTS.has(parsed.hostname)) {\n return url.replace(/\\/+$/, '');\n }\n\n throw new ProviderApiError(\n 0,\n `Provider URL must use HTTPS (or HTTP for localhost): ${url}`,\n );\n}\n\nconst DEFAULT_FETCH_TIMEOUT_MS = 30_000;\n\nexport async function checkedFetch(\n url: string,\n init?: RequestInit,\n timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS,\n fetchFn: typeof globalThis.fetch = globalThis.fetch,\n): Promise<Response> {\n const callerSignal = init?.signal ?? undefined;\n\n // Compose the caller's signal with an internal timeout so callers cannot\n // accidentally disable the safety net by supplying their own signal.\n const composed = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let timedOut = false;\n let callerAbortHandler: (() => void) | undefined;\n\n if (callerSignal?.aborted) {\n composed.abort(callerSignal.reason);\n } else if (callerSignal) {\n callerAbortHandler = () => {\n // Clear the timer so a delayed fetch rejection can't be misclassified\n // as a timeout after the caller already cancelled.\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n composed.abort(callerSignal.reason);\n };\n callerSignal.addEventListener('abort', callerAbortHandler, { once: true });\n }\n if (timeoutMs > 0 && !composed.signal.aborted) {\n timer = setTimeout(() => {\n // Defensive: if the caller already aborted, don't flip timedOut.\n if (composed.signal.aborted) return;\n timedOut = true;\n composed.abort();\n }, timeoutMs);\n }\n\n let res: Response;\n try {\n // Don't even dispatch fetch if the caller's signal is already aborted.\n composed.signal.throwIfAborted();\n res = await fetchFn(url, { ...init, signal: composed.signal });\n } catch (err) {\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (timedOut) {\n throw new ProviderApiError(\n 0,\n `Request to ${url} timed out after ${timeoutMs}ms`,\n );\n }\n // Surface the caller's original abort reason (e.g. `new Error('cancelled')`)\n // rather than the fetch-internal DOMException AbortError.\n throw composed.signal.reason;\n }\n if (composed.signal.aborted && !timedOut) throw composed.signal.reason;\n throw new ProviderApiError(\n 0,\n `Network request to ${url} failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n if (callerAbortHandler && callerSignal) {\n callerSignal.removeEventListener('abort', callerAbortHandler);\n }\n }\n if (!res.ok) {\n const body = await res\n .text()\n .catch(\n (readErr: unknown) =>\n `[body read failed: ${readErr instanceof Error ? readErr.message : String(readErr)}]`,\n );\n throw new ProviderApiError(res.status, body || `HTTP ${res.status}`);\n }\n return res;\n}\n\nexport async function parseJsonResponse<T>(\n res: Response,\n url: string,\n): Promise<T> {\n const text = await res.text();\n try {\n return JSON.parse(text) as T;\n } catch (parseErr) {\n const reason =\n parseErr instanceof Error ? parseErr.message : 'parse failed';\n throw new ProviderApiError(\n res.status,\n `Invalid JSON from ${url} (${reason}): ${text.slice(0, 200)}`,\n );\n }\n}\n\nexport interface ProviderHealthResponse {\n readonly status: string;\n readonly provider_uuid: string;\n readonly checks?: {\n readonly chain?: { readonly status: string };\n };\n}\n\nexport async function getProviderHealth(\n providerApiUrl: string,\n timeoutMs = 5_000,\n fetchFn?: typeof globalThis.fetch,\n): Promise<ProviderHealthResponse> {\n const validated = validateProviderUrl(providerApiUrl);\n const url = `${validated}/health`;\n const res = await checkedFetch(url, undefined, timeoutMs, fetchFn);\n return await parseJsonResponse<ProviderHealthResponse>(res, url);\n}\n\nexport interface InstanceInfo {\n readonly instance_index: number;\n readonly container_id: string;\n readonly image: string;\n readonly status: string;\n readonly ports?: Record<string, unknown>;\n readonly fqdn?: string;\n}\n\nexport interface ServiceConnectionDetails {\n readonly host?: string;\n readonly fqdn?: string;\n readonly ports?: Record<string, unknown>;\n readonly instances?: readonly InstanceInfo[];\n}\n\nexport interface ConnectionDetails {\n readonly host: string;\n readonly fqdn?: string;\n readonly ports?: Record<string, unknown>;\n readonly instances?: readonly InstanceInfo[];\n readonly protocol?: string;\n readonly metadata?: Record<string, string>;\n readonly services?: Record<string, ServiceConnectionDetails>;\n}\n\nexport interface LeaseConnectionResponse {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly connection: ConnectionDetails;\n}\n\nexport async function getLeaseConnectionInfo(\n providerApiUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<LeaseConnectionResponse> {\n const validated = validateProviderUrl(providerApiUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/connection`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<LeaseConnectionResponse>(res, url);\n}\n\nexport async function uploadLeaseData(\n providerApiUrl: string,\n leaseUuid: string,\n payload: Uint8Array,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n abortSignal?: AbortSignal,\n): Promise<void> {\n const validated = validateProviderUrl(providerApiUrl);\n const init: RequestInit = {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: payload,\n };\n if (abortSignal) {\n init.signal = abortSignal;\n }\n await checkedFetch(\n `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/data`,\n init,\n undefined,\n fetchFn,\n );\n}\n"],"mappings":";AAAA,IAAa,mBAAb,MAAa,yBAAyB,MAAM;CAG1C,YAAY,QAAgB,SAAiB;AAC3C,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS;AACd,SAAO,eAAe,MAAM,iBAAiB,UAAU;;;AAI3D,MAAM,kBAAkB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAQ,CAAC;AAEpE,SAAgB,oBAAoB,KAAqB;CACvD,IAAI;AACJ,KAAI;AACF,WAAS,IAAI,IAAI,IAAI;SACf;AACN,QAAM,IAAI,iBAAiB,GAAG,yBAAyB,MAAM;;AAG/D,KAAI,OAAO,aAAa,SACtB,QAAO,IAAI,QAAQ,QAAQ,GAAG;AAGhC,KAAI,OAAO,aAAa,WAAW,gBAAgB,IAAI,OAAO,SAAS,CACrE,QAAO,IAAI,QAAQ,QAAQ,GAAG;AAGhC,OAAM,IAAI,iBACR,GACA,wDAAwD,MACzD;;AAGH,MAAM,2BAA2B;AAEjC,eAAsB,aACpB,KACA,MACA,YAAoB,0BACpB,UAAmC,WAAW,OAC3B;CACnB,MAAM,eAAe,MAAM,UAAU,KAAA;CAIrC,MAAM,WAAW,IAAI,iBAAiB;CACtC,IAAI;CACJ,IAAI,WAAW;CACf,IAAI;AAEJ,KAAI,cAAc,QAChB,UAAS,MAAM,aAAa,OAAO;UAC1B,cAAc;AACvB,6BAA2B;AAGzB,OAAI,UAAU,KAAA,GAAW;AACvB,iBAAa,MAAM;AACnB,YAAQ,KAAA;;AAEV,YAAS,MAAM,aAAa,OAAO;;AAErC,eAAa,iBAAiB,SAAS,oBAAoB,EAAE,MAAM,MAAM,CAAC;;AAE5E,KAAI,YAAY,KAAK,CAAC,SAAS,OAAO,QACpC,SAAQ,iBAAiB;AAEvB,MAAI,SAAS,OAAO,QAAS;AAC7B,aAAW;AACX,WAAS,OAAO;IACf,UAAU;CAGf,IAAI;AACJ,KAAI;AAEF,WAAS,OAAO,gBAAgB;AAChC,QAAM,MAAM,QAAQ,KAAK;GAAE,GAAG;GAAM,QAAQ,SAAS;GAAQ,CAAC;UACvD,KAAK;AACZ,MAAI,eAAe,gBAAgB,IAAI,SAAS,cAAc;AAC5D,OAAI,SACF,OAAM,IAAI,iBACR,GACA,cAAc,IAAI,mBAAmB,UAAU,IAChD;AAIH,SAAM,SAAS,OAAO;;AAExB,MAAI,SAAS,OAAO,WAAW,CAAC,SAAU,OAAM,SAAS,OAAO;AAChE,QAAM,IAAI,iBACR,GACA,sBAAsB,IAAI,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GACtF;WACO;AACR,MAAI,UAAU,KAAA,EAAW,cAAa,MAAM;AAC5C,MAAI,sBAAsB,aACxB,cAAa,oBAAoB,SAAS,mBAAmB;;AAGjE,KAAI,CAAC,IAAI,IAAI;EACX,MAAM,OAAO,MAAM,IAChB,MAAM,CACN,OACE,YACC,sBAAsB,mBAAmB,QAAQ,QAAQ,UAAU,OAAO,QAAQ,CAAC,GACtF;AACH,QAAM,IAAI,iBAAiB,IAAI,QAAQ,QAAQ,QAAQ,IAAI,SAAS;;AAEtE,QAAO;;AAGT,eAAsB,kBACpB,KACA,KACY;CACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,KAAI;AACF,SAAO,KAAK,MAAM,KAAK;UAChB,UAAU;EACjB,MAAM,SACJ,oBAAoB,QAAQ,SAAS,UAAU;AACjD,QAAM,IAAI,iBACR,IAAI,QACJ,qBAAqB,IAAI,IAAI,OAAO,KAAK,KAAK,MAAM,GAAG,IAAI,GAC5D;;;AAYL,eAAsB,kBACpB,gBACA,YAAY,KACZ,SACiC;CAEjC,MAAM,MAAM,GADM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"provider.js","names":[],"sources":["../../src/http/provider.ts"],"sourcesContent":["export class ProviderApiError extends Error {\n public readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = 'ProviderApiError';\n this.status = status;\n Object.setPrototypeOf(this, ProviderApiError.prototype);\n }\n}\n\nconst LOCALHOST_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function validateProviderUrl(url: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new ProviderApiError(0, `Invalid provider URL: ${url}`);\n }\n\n if (parsed.protocol === 'https:') {\n return url.replace(/\\/+$/, '');\n }\n\n if (parsed.protocol === 'http:' && LOCALHOST_HOSTS.has(parsed.hostname)) {\n return url.replace(/\\/+$/, '');\n }\n\n throw new ProviderApiError(\n 0,\n `Provider URL must use HTTPS (or HTTP for localhost): ${url}`,\n );\n}\n\nconst DEFAULT_FETCH_TIMEOUT_MS = 30_000;\n\nexport async function checkedFetch(\n url: string,\n init?: RequestInit,\n timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS,\n fetchFn: typeof globalThis.fetch = globalThis.fetch,\n): Promise<Response> {\n const callerSignal = init?.signal ?? undefined;\n\n // Compose the caller's signal with an internal timeout so callers cannot\n // accidentally disable the safety net by supplying their own signal.\n const composed = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let timedOut = false;\n let callerAbortHandler: (() => void) | undefined;\n\n if (callerSignal?.aborted) {\n composed.abort(callerSignal.reason);\n } else if (callerSignal) {\n callerAbortHandler = () => {\n // Clear the timer so a delayed fetch rejection can't be misclassified\n // as a timeout after the caller already cancelled.\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n composed.abort(callerSignal.reason);\n };\n callerSignal.addEventListener('abort', callerAbortHandler, { once: true });\n }\n if (timeoutMs > 0 && !composed.signal.aborted) {\n timer = setTimeout(() => {\n // Defensive: if the caller already aborted, don't flip timedOut.\n if (composed.signal.aborted) return;\n timedOut = true;\n composed.abort();\n }, timeoutMs);\n }\n\n let res: Response;\n try {\n // Don't even dispatch fetch if the caller's signal is already aborted.\n composed.signal.throwIfAborted();\n res = await fetchFn(url, { ...init, signal: composed.signal });\n } catch (err) {\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (timedOut) {\n throw new ProviderApiError(\n 0,\n `Request to ${url} timed out after ${timeoutMs}ms`,\n );\n }\n // Surface the caller's original abort reason (e.g. `new Error('cancelled')`)\n // rather than the fetch-internal DOMException AbortError.\n throw composed.signal.reason;\n }\n if (composed.signal.aborted && !timedOut) throw composed.signal.reason;\n throw new ProviderApiError(\n 0,\n `Network request to ${url} failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n if (callerAbortHandler && callerSignal) {\n callerSignal.removeEventListener('abort', callerAbortHandler);\n }\n }\n if (!res.ok) {\n const body = await res\n .text()\n .catch(\n (readErr: unknown) =>\n `[body read failed: ${readErr instanceof Error ? readErr.message : String(readErr)}]`,\n );\n throw new ProviderApiError(res.status, body || `HTTP ${res.status}`);\n }\n return res;\n}\n\nexport async function parseJsonResponse<T>(\n res: Response,\n url: string,\n): Promise<T> {\n const text = await res.text();\n try {\n return JSON.parse(text) as T;\n } catch (parseErr) {\n const reason =\n parseErr instanceof Error ? parseErr.message : 'parse failed';\n throw new ProviderApiError(\n res.status,\n `Invalid JSON from ${url} (${reason}): ${text.slice(0, 200)}`,\n );\n }\n}\n\nexport interface ProviderHealthResponse {\n readonly status: string;\n readonly provider_uuid: string;\n readonly checks?: {\n readonly chain?: { readonly status: string };\n };\n}\n\nexport async function getProviderHealth(\n providerApiUrl: string,\n timeoutMs = 5_000,\n fetchFn?: typeof globalThis.fetch,\n): Promise<ProviderHealthResponse> {\n const validated = validateProviderUrl(providerApiUrl);\n const url = `${validated}/health`;\n const res = await checkedFetch(url, undefined, timeoutMs, fetchFn);\n return await parseJsonResponse<ProviderHealthResponse>(res, url);\n}\n\nexport interface InstanceInfo {\n readonly instance_index: number;\n readonly container_id: string;\n readonly image: string;\n readonly status: string;\n readonly ports?: Record<string, unknown>;\n readonly fqdn?: string;\n}\n\nexport interface ServiceConnectionDetails {\n readonly host?: string;\n readonly fqdn?: string;\n readonly ports?: Record<string, unknown>;\n readonly instances?: readonly InstanceInfo[];\n}\n\nexport interface ConnectionDetails {\n readonly host: string;\n readonly fqdn?: string;\n readonly ports?: Record<string, unknown>;\n readonly instances?: readonly InstanceInfo[];\n readonly protocol?: string;\n readonly metadata?: Record<string, string>;\n readonly services?: Record<string, ServiceConnectionDetails>;\n}\n\nexport interface LeaseConnectionResponse {\n readonly lease_uuid: string;\n readonly tenant: string;\n readonly provider_uuid: string;\n readonly connection: ConnectionDetails;\n}\n\nexport async function getLeaseConnectionInfo(\n providerApiUrl: string,\n leaseUuid: string,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n): Promise<LeaseConnectionResponse> {\n const validated = validateProviderUrl(providerApiUrl);\n const url = `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/connection`;\n const res = await checkedFetch(\n url,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n undefined,\n fetchFn,\n );\n return await parseJsonResponse<LeaseConnectionResponse>(res, url);\n}\n\nexport async function uploadLeaseData(\n providerApiUrl: string,\n leaseUuid: string,\n payload: Uint8Array,\n authToken: string,\n fetchFn?: typeof globalThis.fetch,\n abortSignal?: AbortSignal,\n): Promise<void> {\n const validated = validateProviderUrl(providerApiUrl);\n const init: RequestInit = {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: payload,\n };\n if (abortSignal) {\n init.signal = abortSignal;\n }\n await checkedFetch(\n `${validated}/v1/leases/${encodeURIComponent(leaseUuid)}/data`,\n init,\n undefined,\n fetchFn,\n );\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;cAmHa,aAAA;EAAA,QACH,SAAA;EAAA,QACA,aAAA;EAAA,QACA,cAAA;EAAA,QACA,UAAA;cAEI,OAAA,EAAS,0BAAA;EAiDrB,SAAA,CAAA,GAAa,MAAA;EAIb,gBAAA,CAAA,GAAoB,mBAAA;EAIpB,UAAA,CAAA;AAAA;AAAA,iBAKc,wBAAA,CACd,MAAA,EAAQ,oBAAA,GACP,OAAA,CAAQ,aAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AuthTimestampTracker, createAuthToken, createLeaseDataSignMessage, createSignMessage } from "./http/auth.js";
|
|
2
2
|
import { AuthTokenService } from "./http/auth-token-service.js";
|
|
3
|
+
import { resolveGuardedFetch } from "./server/fetch-gate.js";
|
|
3
4
|
import { registerPrompts } from "./server/register-prompts.js";
|
|
4
5
|
import { registerResources } from "./server/register-resources.js";
|
|
5
6
|
import { ProviderApiError, checkedFetch, getLeaseConnectionInfo, getProviderHealth, uploadLeaseData, validateProviderUrl } from "./http/provider.js";
|
|
@@ -34,11 +35,13 @@ var FredMCPServer = class {
|
|
|
34
35
|
resources: {},
|
|
35
36
|
prompts: {}
|
|
36
37
|
} });
|
|
38
|
+
const fetchFn = resolveGuardedFetch(typeof process !== "undefined" ? process.env.MANIFEST_FRED_FETCH_GUARDED : void 0, typeof process !== "undefined" && !!process.versions?.node);
|
|
37
39
|
registerTools({
|
|
38
40
|
mcpServer: this.mcpServer,
|
|
39
41
|
clientManager: this.clientManager,
|
|
40
42
|
walletProvider: this.walletProvider,
|
|
41
|
-
authTokens: this.authTokens
|
|
43
|
+
authTokens: this.authTokens,
|
|
44
|
+
fetchFn
|
|
42
45
|
});
|
|
43
46
|
registerResources({
|
|
44
47
|
mcpServer: this.mcpServer,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { WalletProvider } from '@manifest-network/manifest-mcp-core';\nimport {\n CosmosClientManager,\n createMnemonicServer,\n createValidatedConfig,\n type ManifestMCPServerOptions,\n type MnemonicServerConfig,\n VERSION,\n} from '@manifest-network/manifest-mcp-core';\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { AuthTokenService } from './http/auth-token-service.js';\nimport { registerPrompts } from './server/register-prompts.js';\nimport { registerResources } from './server/register-resources.js';\nimport { registerTools } from './server/register-tools.js';\n\nexport type { ManifestMCPServerOptions } from '@manifest-network/manifest-mcp-core';\nexport {\n INFRASTRUCTURE_ERROR_CODES,\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\nexport {\n AuthTimestampTracker,\n type AuthTokenPayload,\n createAuthToken,\n createLeaseDataSignMessage,\n createSignMessage,\n} from './http/auth.js';\nexport {\n type FredActionResponse,\n type FredInstanceInfo,\n type FredLeaseInfo,\n type FredLeaseLogs,\n type FredLeaseProvision,\n type FredLeaseRelease,\n type FredLeaseReleases,\n type FredLeaseStatus,\n type FredServiceStatus,\n getLeaseInfo,\n getLeaseLogs,\n getLeaseProvision,\n getLeaseReleases,\n getLeaseStatus,\n MAX_TAIL,\n type PollOptions,\n pollLeaseUntilReady,\n restartLease,\n type TerminalChainLeaseState,\n type TerminalChainState,\n type TerminalChainStateContext,\n TerminalChainStateError,\n updateLease,\n} from './http/fred.js';\nexport {\n type ConnectionDetails,\n checkedFetch,\n getLeaseConnectionInfo,\n getProviderHealth,\n type InstanceInfo,\n type LeaseConnectionResponse,\n ProviderApiError,\n type ProviderHealthResponse,\n type ServiceConnectionDetails,\n uploadLeaseData,\n validateProviderUrl,\n} from './http/provider.js';\nexport {\n type BuildManifestOptions,\n buildManifest,\n buildStackManifest,\n deriveAppNameFromImage,\n getServiceNames,\n isStackManifest,\n type ManifestFormat,\n type ManifestValidationResult,\n mergeManifest,\n metaHashHex,\n normalizePorts,\n parseStackManifest,\n validateManifest,\n validateServiceName,\n} from './manifest.js';\nexport { appStatus } from './tools/appStatus.js';\nexport { browseCatalog, mapWithConcurrency } from './tools/browseCatalog.js';\nexport {\n type BuildManifestPreviewInput,\n type BuildManifestPreviewResult,\n buildManifestPreview,\n type ManifestPreviewServiceInput,\n} from './tools/buildManifestPreview.js';\nexport {\n type CheckDeploymentReadinessInput,\n type CheckDeploymentReadinessResult,\n checkDeploymentReadiness,\n type SkuSummary,\n} from './tools/checkDeploymentReadiness.js';\nexport {\n type DeployAppInput,\n type DeployAppResult,\n deployApp,\n type ServiceConfig,\n} from './tools/deployApp.js';\nexport { fetchActiveLease } from './tools/fetchActiveLease.js';\nexport { getAppLogs } from './tools/getLogs.js';\nexport { resolveProviderUrl } from './tools/resolveLeaseProvider.js';\nexport { restartApp } from './tools/restartApp.js';\nexport { updateApp } from './tools/updateApp.js';\nexport {\n type WaitForAppReadyOptions,\n type WaitForAppReadyResult,\n waitForAppReady,\n} from './tools/waitForAppReady.js';\n\nexport class FredMCPServer {\n private mcpServer: McpServer;\n private clientManager: CosmosClientManager;\n private walletProvider: WalletProvider;\n private authTokens: AuthTokenService;\n\n constructor(options: ManifestMCPServerOptions) {\n const config = createValidatedConfig(options.config);\n this.walletProvider = options.walletProvider;\n this.clientManager = CosmosClientManager.getInstance(\n config,\n this.walletProvider,\n );\n this.authTokens = new AuthTokenService(this.walletProvider);\n\n this.mcpServer = new McpServer(\n {\n name: '@manifest-network/manifest-mcp-fred',\n version: VERSION,\n },\n {\n capabilities: {\n tools: {},\n resources: {},\n prompts: {},\n },\n },\n );\n\n registerTools({\n mcpServer: this.mcpServer,\n clientManager: this.clientManager,\n walletProvider: this.walletProvider,\n authTokens: this.authTokens,\n });\n registerResources({\n mcpServer: this.mcpServer,\n clientManager: this.clientManager,\n walletProvider: this.walletProvider,\n });\n registerPrompts(this.mcpServer);\n }\n\n getServer(): Server {\n return this.mcpServer.server;\n }\n\n getClientManager(): CosmosClientManager {\n return this.clientManager;\n }\n\n disconnect(): void {\n this.clientManager.disconnect();\n }\n}\n\nexport function createMnemonicFredServer(\n config: MnemonicServerConfig,\n): Promise<FredMCPServer> {\n return createMnemonicServer(config, FredMCPServer);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { WalletProvider } from '@manifest-network/manifest-mcp-core';\nimport {\n CosmosClientManager,\n createMnemonicServer,\n createValidatedConfig,\n type ManifestMCPServerOptions,\n type MnemonicServerConfig,\n VERSION,\n} from '@manifest-network/manifest-mcp-core';\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { AuthTokenService } from './http/auth-token-service.js';\nimport { resolveGuardedFetch } from './server/fetch-gate.js';\nimport { registerPrompts } from './server/register-prompts.js';\nimport { registerResources } from './server/register-resources.js';\nimport { registerTools } from './server/register-tools.js';\n\nexport type { ManifestMCPServerOptions } from '@manifest-network/manifest-mcp-core';\nexport {\n INFRASTRUCTURE_ERROR_CODES,\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\nexport {\n AuthTimestampTracker,\n type AuthTokenPayload,\n createAuthToken,\n createLeaseDataSignMessage,\n createSignMessage,\n} from './http/auth.js';\nexport {\n type FredActionResponse,\n type FredInstanceInfo,\n type FredLeaseInfo,\n type FredLeaseLogs,\n type FredLeaseProvision,\n type FredLeaseRelease,\n type FredLeaseReleases,\n type FredLeaseStatus,\n type FredServiceStatus,\n getLeaseInfo,\n getLeaseLogs,\n getLeaseProvision,\n getLeaseReleases,\n getLeaseStatus,\n MAX_TAIL,\n type PollOptions,\n pollLeaseUntilReady,\n restartLease,\n type TerminalChainLeaseState,\n type TerminalChainState,\n type TerminalChainStateContext,\n TerminalChainStateError,\n updateLease,\n} from './http/fred.js';\nexport {\n type ConnectionDetails,\n checkedFetch,\n getLeaseConnectionInfo,\n getProviderHealth,\n type InstanceInfo,\n type LeaseConnectionResponse,\n ProviderApiError,\n type ProviderHealthResponse,\n type ServiceConnectionDetails,\n uploadLeaseData,\n validateProviderUrl,\n} from './http/provider.js';\nexport {\n type BuildManifestOptions,\n buildManifest,\n buildStackManifest,\n deriveAppNameFromImage,\n getServiceNames,\n isStackManifest,\n type ManifestFormat,\n type ManifestValidationResult,\n mergeManifest,\n metaHashHex,\n normalizePorts,\n parseStackManifest,\n validateManifest,\n validateServiceName,\n} from './manifest.js';\nexport { appStatus } from './tools/appStatus.js';\nexport { browseCatalog, mapWithConcurrency } from './tools/browseCatalog.js';\nexport {\n type BuildManifestPreviewInput,\n type BuildManifestPreviewResult,\n buildManifestPreview,\n type ManifestPreviewServiceInput,\n} from './tools/buildManifestPreview.js';\nexport {\n type CheckDeploymentReadinessInput,\n type CheckDeploymentReadinessResult,\n checkDeploymentReadiness,\n type SkuSummary,\n} from './tools/checkDeploymentReadiness.js';\nexport {\n type DeployAppInput,\n type DeployAppResult,\n deployApp,\n type ServiceConfig,\n} from './tools/deployApp.js';\nexport { fetchActiveLease } from './tools/fetchActiveLease.js';\nexport { getAppLogs } from './tools/getLogs.js';\nexport { resolveProviderUrl } from './tools/resolveLeaseProvider.js';\nexport { restartApp } from './tools/restartApp.js';\nexport { updateApp } from './tools/updateApp.js';\nexport {\n type WaitForAppReadyOptions,\n type WaitForAppReadyResult,\n waitForAppReady,\n} from './tools/waitForAppReady.js';\n\nexport class FredMCPServer {\n private mcpServer: McpServer;\n private clientManager: CosmosClientManager;\n private walletProvider: WalletProvider;\n private authTokens: AuthTokenService;\n\n constructor(options: ManifestMCPServerOptions) {\n const config = createValidatedConfig(options.config);\n this.walletProvider = options.walletProvider;\n this.clientManager = CosmosClientManager.getInstance(\n config,\n this.walletProvider,\n );\n this.authTokens = new AuthTokenService(this.walletProvider);\n\n this.mcpServer = new McpServer(\n {\n name: '@manifest-network/manifest-mcp-fred',\n version: VERSION,\n },\n {\n capabilities: {\n tools: {},\n resources: {},\n prompts: {},\n },\n },\n );\n\n // SSRF guard (ENG-268). Fred fetches provider/Fred APIs at URLs sourced\n // from on-chain SKU records, so a malicious provider could point them at\n // an internal host. Route all outbound HTTP through an SSRF-guarded fetch\n // by default; operators opt out with MANIFEST_FRED_FETCH_GUARDED=0.\n const fetchFn = resolveGuardedFetch(\n typeof process !== 'undefined'\n ? process.env.MANIFEST_FRED_FETCH_GUARDED\n : undefined,\n typeof process !== 'undefined' && !!process.versions?.node,\n );\n\n registerTools({\n mcpServer: this.mcpServer,\n clientManager: this.clientManager,\n walletProvider: this.walletProvider,\n authTokens: this.authTokens,\n fetchFn,\n });\n registerResources({\n mcpServer: this.mcpServer,\n clientManager: this.clientManager,\n walletProvider: this.walletProvider,\n });\n registerPrompts(this.mcpServer);\n }\n\n getServer(): Server {\n return this.mcpServer.server;\n }\n\n getClientManager(): CosmosClientManager {\n return this.clientManager;\n }\n\n disconnect(): void {\n this.clientManager.disconnect();\n }\n}\n\nexport function createMnemonicFredServer(\n config: MnemonicServerConfig,\n): Promise<FredMCPServer> {\n return createMnemonicServer(config, FredMCPServer);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAmHA,IAAa,gBAAb,MAA2B;CAMzB,YAAY,SAAmC;EAC7C,MAAM,SAAS,sBAAsB,QAAQ,OAAO;AACpD,OAAK,iBAAiB,QAAQ;AAC9B,OAAK,gBAAgB,oBAAoB,YACvC,QACA,KAAK,eACN;AACD,OAAK,aAAa,IAAI,iBAAiB,KAAK,eAAe;AAE3D,OAAK,YAAY,IAAI,UACnB;GACE,MAAM;GACN,SAAS;GACV,EACD,EACE,cAAc;GACZ,OAAO,EAAE;GACT,WAAW,EAAE;GACb,SAAS,EAAE;GACZ,EACF,CACF;EAMD,MAAM,UAAU,oBACd,OAAO,YAAY,cACf,QAAQ,IAAI,8BACZ,KAAA,GACJ,OAAO,YAAY,eAAe,CAAC,CAAC,QAAQ,UAAU,KACvD;AAED,gBAAc;GACZ,WAAW,KAAK;GAChB,eAAe,KAAK;GACpB,gBAAgB,KAAK;GACrB,YAAY,KAAK;GACjB;GACD,CAAC;AACF,oBAAkB;GAChB,WAAW,KAAK;GAChB,eAAe,KAAK;GACpB,gBAAgB,KAAK;GACtB,CAAC;AACF,kBAAgB,KAAK,UAAU;;CAGjC,YAAoB;AAClB,SAAO,KAAK,UAAU;;CAGxB,mBAAwC;AACtC,SAAO,KAAK;;CAGd,aAAmB;AACjB,OAAK,cAAc,YAAY;;;AAInC,SAAgB,yBACd,QACwB;AACxB,QAAO,qBAAqB,QAAQ,cAAc"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/server/fetch-gate.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Env var gating fred's SSRF-guarded fetch. Default ON; operators opt out
|
|
4
|
+
* with `MANIFEST_FRED_FETCH_GUARDED=0`. Independent of the agent server's
|
|
5
|
+
* `MANIFEST_AGENT_FETCH_GUARDED` so each standalone server can be toggled
|
|
6
|
+
* on its own (ENG-268).
|
|
7
|
+
*/
|
|
8
|
+
declare const FRED_FETCH_GUARDED_ENV = "MANIFEST_FRED_FETCH_GUARDED";
|
|
9
|
+
/**
|
|
10
|
+
* Decide which `fetch` the fred MCP server injects into its tool layer.
|
|
11
|
+
*
|
|
12
|
+
* - Guard ON (default) + Node runtime → an SSRF-guarded fetch (blocks
|
|
13
|
+
* provider URLs that resolve to private/reserved ranges; closes the
|
|
14
|
+
* DNS-rebinding window at connect time).
|
|
15
|
+
* - Guard ON + non-Node runtime → `undefined` (so the HTTP layer falls back
|
|
16
|
+
* to `globalThis.fetch`), with a warning — `createGuardedFetch` is
|
|
17
|
+
* Node-only and would otherwise throw.
|
|
18
|
+
* - Guard OFF (`MANIFEST_FRED_FETCH_GUARDED=0`) → `undefined` (unguarded
|
|
19
|
+
* `globalThis.fetch` fallback).
|
|
20
|
+
* - Unrecognized env value → throws `INVALID_CONFIG` (never silently
|
|
21
|
+
* disables the guard).
|
|
22
|
+
*
|
|
23
|
+
* Returning `undefined` rather than `globalThis.fetch` keeps the injection
|
|
24
|
+
* a no-op for the HTTP layer's existing `fetchFn = globalThis.fetch`
|
|
25
|
+
* default, so library consumers that pass their own `fetchFn` are
|
|
26
|
+
* unaffected.
|
|
27
|
+
*
|
|
28
|
+
* @param envValue - Raw `MANIFEST_FRED_FETCH_GUARDED` value.
|
|
29
|
+
* @param isNode - Whether the current runtime is Node.js.
|
|
30
|
+
*/
|
|
31
|
+
declare function resolveGuardedFetch(envValue: string | undefined, isNode: boolean): typeof globalThis.fetch | undefined;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { FRED_FETCH_GUARDED_ENV, resolveGuardedFetch };
|
|
34
|
+
//# sourceMappingURL=fetch-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-gate.d.ts","names":[],"sources":["../../src/server/fetch-gate.ts"],"mappings":";;AAYA;;;;;cAAa,sBAAA;;;;;;;;;;;;;;;;;;;;;;;iBAwBG,mBAAA,CACd,QAAA,sBACA,MAAA,mBACQ,UAAA,CAAW,KAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createGuardedFetch, logger, parseBooleanEnv } from "@manifest-network/manifest-mcp-core";
|
|
2
|
+
//#region src/server/fetch-gate.ts
|
|
3
|
+
/**
|
|
4
|
+
* Env var gating fred's SSRF-guarded fetch. Default ON; operators opt out
|
|
5
|
+
* with `MANIFEST_FRED_FETCH_GUARDED=0`. Independent of the agent server's
|
|
6
|
+
* `MANIFEST_AGENT_FETCH_GUARDED` so each standalone server can be toggled
|
|
7
|
+
* on its own (ENG-268).
|
|
8
|
+
*/
|
|
9
|
+
const FRED_FETCH_GUARDED_ENV = "MANIFEST_FRED_FETCH_GUARDED";
|
|
10
|
+
/**
|
|
11
|
+
* Decide which `fetch` the fred MCP server injects into its tool layer.
|
|
12
|
+
*
|
|
13
|
+
* - Guard ON (default) + Node runtime → an SSRF-guarded fetch (blocks
|
|
14
|
+
* provider URLs that resolve to private/reserved ranges; closes the
|
|
15
|
+
* DNS-rebinding window at connect time).
|
|
16
|
+
* - Guard ON + non-Node runtime → `undefined` (so the HTTP layer falls back
|
|
17
|
+
* to `globalThis.fetch`), with a warning — `createGuardedFetch` is
|
|
18
|
+
* Node-only and would otherwise throw.
|
|
19
|
+
* - Guard OFF (`MANIFEST_FRED_FETCH_GUARDED=0`) → `undefined` (unguarded
|
|
20
|
+
* `globalThis.fetch` fallback).
|
|
21
|
+
* - Unrecognized env value → throws `INVALID_CONFIG` (never silently
|
|
22
|
+
* disables the guard).
|
|
23
|
+
*
|
|
24
|
+
* Returning `undefined` rather than `globalThis.fetch` keeps the injection
|
|
25
|
+
* a no-op for the HTTP layer's existing `fetchFn = globalThis.fetch`
|
|
26
|
+
* default, so library consumers that pass their own `fetchFn` are
|
|
27
|
+
* unaffected.
|
|
28
|
+
*
|
|
29
|
+
* @param envValue - Raw `MANIFEST_FRED_FETCH_GUARDED` value.
|
|
30
|
+
* @param isNode - Whether the current runtime is Node.js.
|
|
31
|
+
*/
|
|
32
|
+
function resolveGuardedFetch(envValue, isNode) {
|
|
33
|
+
if (!parseBooleanEnv(envValue, true, "MANIFEST_FRED_FETCH_GUARDED")) return void 0;
|
|
34
|
+
if (isNode) return createGuardedFetch();
|
|
35
|
+
logger.warn(`${FRED_FETCH_GUARDED_ENV} is enabled but the runtime is not Node.js; the SSRF guard is unavailable — falling back to globalThis.fetch.`);
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { FRED_FETCH_GUARDED_ENV, resolveGuardedFetch };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=fetch-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-gate.js","names":[],"sources":["../../src/server/fetch-gate.ts"],"sourcesContent":["import {\n createGuardedFetch,\n logger,\n parseBooleanEnv,\n} from '@manifest-network/manifest-mcp-core';\n\n/**\n * Env var gating fred's SSRF-guarded fetch. Default ON; operators opt out\n * with `MANIFEST_FRED_FETCH_GUARDED=0`. Independent of the agent server's\n * `MANIFEST_AGENT_FETCH_GUARDED` so each standalone server can be toggled\n * on its own (ENG-268).\n */\nexport const FRED_FETCH_GUARDED_ENV = 'MANIFEST_FRED_FETCH_GUARDED';\n\n/**\n * Decide which `fetch` the fred MCP server injects into its tool layer.\n *\n * - Guard ON (default) + Node runtime → an SSRF-guarded fetch (blocks\n * provider URLs that resolve to private/reserved ranges; closes the\n * DNS-rebinding window at connect time).\n * - Guard ON + non-Node runtime → `undefined` (so the HTTP layer falls back\n * to `globalThis.fetch`), with a warning — `createGuardedFetch` is\n * Node-only and would otherwise throw.\n * - Guard OFF (`MANIFEST_FRED_FETCH_GUARDED=0`) → `undefined` (unguarded\n * `globalThis.fetch` fallback).\n * - Unrecognized env value → throws `INVALID_CONFIG` (never silently\n * disables the guard).\n *\n * Returning `undefined` rather than `globalThis.fetch` keeps the injection\n * a no-op for the HTTP layer's existing `fetchFn = globalThis.fetch`\n * default, so library consumers that pass their own `fetchFn` are\n * unaffected.\n *\n * @param envValue - Raw `MANIFEST_FRED_FETCH_GUARDED` value.\n * @param isNode - Whether the current runtime is Node.js.\n */\nexport function resolveGuardedFetch(\n envValue: string | undefined,\n isNode: boolean,\n): typeof globalThis.fetch | undefined {\n const guarded = parseBooleanEnv(envValue, true, FRED_FETCH_GUARDED_ENV);\n if (!guarded) return undefined;\n if (isNode) return createGuardedFetch();\n logger.warn(\n `${FRED_FETCH_GUARDED_ENV} is enabled but the runtime is not Node.js; ` +\n 'the SSRF guard is unavailable — falling back to globalThis.fetch.',\n );\n return undefined;\n}\n"],"mappings":";;;;;;;;AAYA,MAAa,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;AAwBtC,SAAgB,oBACd,UACA,QACqC;AAErC,KAAI,CADY,gBAAgB,UAAU,MAAA,8BAC9B,CAAE,QAAO,KAAA;AACrB,KAAI,OAAQ,QAAO,oBAAoB;AACvC,QAAO,KACL,GAAG,uBAAuB,+GAE3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-resources.js","names":[],"sources":["../../src/server/register-resources.ts"],"sourcesContent":["import {\n bigIntReplacer,\n type CosmosClientManager,\n createPagination,\n LeaseState,\n leaseStateToJSON,\n MAX_PAGE_LIMIT,\n type WalletProvider,\n} from '@manifest-network/manifest-mcp-core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\ninterface RegisterResourcesDeps {\n mcpServer: McpServer;\n clientManager: CosmosClientManager;\n walletProvider: WalletProvider;\n}\n\nexport function registerResources(deps: RegisterResourcesDeps): void {\n const { mcpServer, clientManager, walletProvider } = deps;\n const fixedPagination = createPagination(MAX_PAGE_LIMIT);\n\n const resourceJson = (\n uri: URL,\n data: unknown,\n ): {\n contents: Array<{ uri: string; mimeType: string; text: string }>;\n } => ({\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify(data, bigIntReplacer, 2),\n },\n ],\n });\n\n // -- manifest://leases/active --\n mcpServer.registerResource(\n 'leases-active',\n 'manifest://leases/active',\n {\n title: \"Caller's active and pending leases\",\n description:\n \"Snapshot of the caller wallet's leases currently in ACTIVE or PENDING state. Useful as immutable context for an agent deciding which app to operate on.\",\n mimeType: 'application/json',\n },\n async (uri) => {\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const tenant = await walletProvider.getAddress();\n const billing = queryClient.liftedinit.billing.v1;\n\n const [active, pending] = await Promise.all([\n billing.leasesByTenant({\n tenant,\n stateFilter: LeaseState.LEASE_STATE_ACTIVE,\n pagination: fixedPagination,\n }),\n billing.leasesByTenant({\n tenant,\n stateFilter: LeaseState.LEASE_STATE_PENDING,\n pagination: fixedPagination,\n }),\n ]);\n\n const summarize = (l: {\n uuid: string;\n state: number;\n providerUuid: string;\n createdAt?: Date;\n metaHash?: Uint8Array;\n }) => ({\n uuid: l.uuid,\n state: leaseStateToJSON(l.state),\n provider_uuid: l.providerUuid,\n created_at: l.createdAt?.toISOString(),\n });\n\n return resourceJson(uri, {\n tenant,\n active: active.leases.map(summarize),\n pending: pending.leases.map(summarize),\n counts: {\n active: active.leases.length,\n pending: pending.leases.length,\n },\n });\n },\n );\n\n // -- manifest://leases/recent --\n mcpServer.registerResource(\n 'leases-recent',\n 'manifest://leases/recent',\n {\n title: \"Caller's most recent leases (any state)\",\n description:\n \"The caller's leases ordered by most recent first, up to 50, regardless of state. Useful for surfacing recently-closed or rejected leases the agent may want to act on.\",\n mimeType: 'application/json',\n },\n async (uri) => {\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const tenant = await walletProvider.getAddress();\n const result = await queryClient.liftedinit.billing.v1.leasesByTenant({\n tenant,\n stateFilter: LeaseState.LEASE_STATE_UNSPECIFIED,\n pagination: {\n key: new Uint8Array(),\n offset: 0n,\n limit: 50n,\n countTotal: true,\n reverse: true,\n },\n });\n\n return resourceJson(uri, {\n tenant,\n leases: result.leases.map((l) => ({\n uuid: l.uuid,\n state: leaseStateToJSON(l.state),\n provider_uuid: l.providerUuid,\n created_at: l.createdAt?.toISOString(),\n closed_at: l.closedAt?.toISOString(),\n })),\n total: result.pagination?.total?.toString(),\n });\n },\n );\n\n // -- manifest://providers --\n mcpServer.registerResource(\n 'providers',\n 'manifest://providers',\n {\n title: 'Provider catalog snapshot',\n description:\n 'All active providers and their available SKUs (chain-side data only — no live HTTP health check). Use browse_catalog when health is needed.',\n mimeType: 'application/json',\n },\n async (uri) => {\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const sku = queryClient.liftedinit.sku.v1;\n const [providersResult, skusResult] = await Promise.all([\n sku.providers({ activeOnly: true, pagination: fixedPagination }),\n sku.sKUs({ activeOnly: true, pagination: fixedPagination }),\n ]);\n\n const providers = providersResult.providers.map((p) => ({\n uuid: p.uuid,\n address: p.address,\n api_url: p.apiUrl,\n active: p.active,\n }));\n\n const skus = skusResult.skus.map((s) => ({\n uuid: s.uuid,\n name: s.name,\n provider_uuid: s.providerUuid,\n active: s.active,\n base_price: s.basePrice\n ? { amount: s.basePrice.amount, denom: s.basePrice.denom }\n : null,\n }));\n\n return resourceJson(uri, {\n providers,\n skus,\n counts: {\n providers: providers.length,\n skus: skus.length,\n },\n });\n },\n );\n}\n"],"mappings":";;AAiBA,SAAgB,kBAAkB,MAAmC;CACnE,MAAM,EAAE,WAAW,eAAe,mBAAmB;CACrD,MAAM,kBAAkB,iBAAiB,eAAe;CAExD,MAAM,gBACJ,KACA,UAGI,EACJ,UAAU,CACR;EACE,KAAK,IAAI;EACT,UAAU;EACV,MAAM,KAAK,UAAU,MAAM,gBAAgB,EAAE;EAC9C,CACF,EACF;AAGD,WAAU,iBACR,iBACA,4BACA;EACE,OAAO;EACP,aACE;EACF,UAAU;EACX,EACD,OAAO,QAAQ;AACb,QAAM,cAAc,kBAAkB;EACtC,MAAM,cAAc,MAAM,cAAc,gBAAgB;EACxD,MAAM,SAAS,MAAM,eAAe,YAAY;EAChD,MAAM,UAAU,YAAY,WAAW,QAAQ;EAE/C,MAAM,CAAC,QAAQ,WAAW,MAAM,QAAQ,IAAI,CAC1C,QAAQ,eAAe;GACrB;GACA,aAAa,WAAW;GACxB,YAAY;GACb,CAAC,EACF,QAAQ,eAAe;GACrB;GACA,aAAa,WAAW;GACxB,YAAY;GACb,CAAC,CACH,CAAC;EAEF,MAAM,aAAa,OAMZ;GACL,MAAM,EAAE;GACR,OAAO,iBAAiB,EAAE,MAAM;GAChC,eAAe,EAAE;GACjB,YAAY,EAAE,WAAW,aAAa;GACvC;AAED,SAAO,aAAa,KAAK;GACvB;GACA,QAAQ,OAAO,OAAO,IAAI,UAAU;GACpC,SAAS,QAAQ,OAAO,IAAI,UAAU;GACtC,QAAQ;IACN,QAAQ,OAAO,OAAO;IACtB,SAAS,QAAQ,OAAO;IACzB;GACF,CAAC;GAEL;AAGD,WAAU,iBACR,iBACA,4BACA;EACE,OAAO;EACP,aACE;EACF,UAAU;EACX,EACD,OAAO,QAAQ;AACb,QAAM,cAAc,kBAAkB;EACtC,MAAM,cAAc,MAAM,cAAc,gBAAgB;EACxD,MAAM,SAAS,MAAM,eAAe,YAAY;EAChD,MAAM,SAAS,MAAM,YAAY,WAAW,QAAQ,GAAG,eAAe;GACpE;GACA,aAAa,WAAW;GACxB,YAAY;IACV,KAAK,IAAI,YAAY;IACrB,QAAQ;IACR,OAAO;IACP,YAAY;IACZ,SAAS;IACV;GACF,CAAC;AAEF,SAAO,aAAa,KAAK;GACvB;GACA,QAAQ,OAAO,OAAO,KAAK,OAAO;IAChC,MAAM,EAAE;IACR,OAAO,iBAAiB,EAAE,MAAM;IAChC,eAAe,EAAE;IACjB,YAAY,EAAE,WAAW,aAAa;IACtC,WAAW,EAAE,UAAU,aAAa;IACrC,EAAE;GACH,OAAO,OAAO,YAAY,OAAO,UAAU;GAC5C,CAAC;GAEL;AAGD,WAAU,iBACR,aACA,wBACA;EACE,OAAO;EACP,aACE;EACF,UAAU;EACX,EACD,OAAO,QAAQ;AACb,QAAM,cAAc,kBAAkB;EAEtC,MAAM,
|
|
1
|
+
{"version":3,"file":"register-resources.js","names":[],"sources":["../../src/server/register-resources.ts"],"sourcesContent":["import {\n bigIntReplacer,\n type CosmosClientManager,\n createPagination,\n LeaseState,\n leaseStateToJSON,\n MAX_PAGE_LIMIT,\n type WalletProvider,\n} from '@manifest-network/manifest-mcp-core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\ninterface RegisterResourcesDeps {\n mcpServer: McpServer;\n clientManager: CosmosClientManager;\n walletProvider: WalletProvider;\n}\n\nexport function registerResources(deps: RegisterResourcesDeps): void {\n const { mcpServer, clientManager, walletProvider } = deps;\n const fixedPagination = createPagination(MAX_PAGE_LIMIT);\n\n const resourceJson = (\n uri: URL,\n data: unknown,\n ): {\n contents: Array<{ uri: string; mimeType: string; text: string }>;\n } => ({\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify(data, bigIntReplacer, 2),\n },\n ],\n });\n\n // -- manifest://leases/active --\n mcpServer.registerResource(\n 'leases-active',\n 'manifest://leases/active',\n {\n title: \"Caller's active and pending leases\",\n description:\n \"Snapshot of the caller wallet's leases currently in ACTIVE or PENDING state. Useful as immutable context for an agent deciding which app to operate on.\",\n mimeType: 'application/json',\n },\n async (uri) => {\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const tenant = await walletProvider.getAddress();\n const billing = queryClient.liftedinit.billing.v1;\n\n const [active, pending] = await Promise.all([\n billing.leasesByTenant({\n tenant,\n stateFilter: LeaseState.LEASE_STATE_ACTIVE,\n pagination: fixedPagination,\n }),\n billing.leasesByTenant({\n tenant,\n stateFilter: LeaseState.LEASE_STATE_PENDING,\n pagination: fixedPagination,\n }),\n ]);\n\n const summarize = (l: {\n uuid: string;\n state: number;\n providerUuid: string;\n createdAt?: Date;\n metaHash?: Uint8Array;\n }) => ({\n uuid: l.uuid,\n state: leaseStateToJSON(l.state),\n provider_uuid: l.providerUuid,\n created_at: l.createdAt?.toISOString(),\n });\n\n return resourceJson(uri, {\n tenant,\n active: active.leases.map(summarize),\n pending: pending.leases.map(summarize),\n counts: {\n active: active.leases.length,\n pending: pending.leases.length,\n },\n });\n },\n );\n\n // -- manifest://leases/recent --\n mcpServer.registerResource(\n 'leases-recent',\n 'manifest://leases/recent',\n {\n title: \"Caller's most recent leases (any state)\",\n description:\n \"The caller's leases ordered by most recent first, up to 50, regardless of state. Useful for surfacing recently-closed or rejected leases the agent may want to act on.\",\n mimeType: 'application/json',\n },\n async (uri) => {\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const tenant = await walletProvider.getAddress();\n const result = await queryClient.liftedinit.billing.v1.leasesByTenant({\n tenant,\n stateFilter: LeaseState.LEASE_STATE_UNSPECIFIED,\n pagination: {\n key: new Uint8Array(),\n offset: 0n,\n limit: 50n,\n countTotal: true,\n reverse: true,\n },\n });\n\n return resourceJson(uri, {\n tenant,\n leases: result.leases.map((l) => ({\n uuid: l.uuid,\n state: leaseStateToJSON(l.state),\n provider_uuid: l.providerUuid,\n created_at: l.createdAt?.toISOString(),\n closed_at: l.closedAt?.toISOString(),\n })),\n total: result.pagination?.total?.toString(),\n });\n },\n );\n\n // -- manifest://providers --\n mcpServer.registerResource(\n 'providers',\n 'manifest://providers',\n {\n title: 'Provider catalog snapshot',\n description:\n 'All active providers and their available SKUs (chain-side data only — no live HTTP health check). Use browse_catalog when health is needed.',\n mimeType: 'application/json',\n },\n async (uri) => {\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n const sku = queryClient.liftedinit.sku.v1;\n const [providersResult, skusResult] = await Promise.all([\n sku.providers({ activeOnly: true, pagination: fixedPagination }),\n sku.sKUs({ activeOnly: true, pagination: fixedPagination }),\n ]);\n\n const providers = providersResult.providers.map((p) => ({\n uuid: p.uuid,\n address: p.address,\n api_url: p.apiUrl,\n active: p.active,\n }));\n\n const skus = skusResult.skus.map((s) => ({\n uuid: s.uuid,\n name: s.name,\n provider_uuid: s.providerUuid,\n active: s.active,\n base_price: s.basePrice\n ? { amount: s.basePrice.amount, denom: s.basePrice.denom }\n : null,\n }));\n\n return resourceJson(uri, {\n providers,\n skus,\n counts: {\n providers: providers.length,\n skus: skus.length,\n },\n });\n },\n );\n}\n"],"mappings":"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"}
|
|
@@ -8,6 +8,14 @@ interface RegisterToolsDeps {
|
|
|
8
8
|
clientManager: CosmosClientManager;
|
|
9
9
|
walletProvider: WalletProvider;
|
|
10
10
|
authTokens: AuthTokenService;
|
|
11
|
+
/**
|
|
12
|
+
* Fetch implementation for all outbound provider/Fred HTTP calls. When
|
|
13
|
+
* omitted (e.g. external library consumers), the HTTP layer falls back to
|
|
14
|
+
* `globalThis.fetch`. `FredMCPServer` injects an SSRF-guarded fetch here by
|
|
15
|
+
* default so on-chain-sourced provider URLs cannot reach internal hosts
|
|
16
|
+
* (ENG-268).
|
|
17
|
+
*/
|
|
18
|
+
fetchFn?: typeof globalThis.fetch;
|
|
11
19
|
}
|
|
12
20
|
declare function registerTools(deps: RegisterToolsDeps): void;
|
|
13
21
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-tools.d.ts","names":[],"sources":["../../src/server/register-tools.ts"],"mappings":";;;;;UAqCU,iBAAA;EACR,SAAA,EAAW,SAAA;EACX,aAAA,EAAe,mBAAA;EACf,cAAA,EAAgB,cAAA;EAChB,UAAA,EAAY,gBAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"register-tools.d.ts","names":[],"sources":["../../src/server/register-tools.ts"],"mappings":";;;;;UAqCU,iBAAA;EACR,SAAA,EAAW,SAAA;EACX,aAAA,EAAe,mBAAA;EACf,cAAA,EAAgB,cAAA;EAChB,UAAA,EAAY,gBAAA;EAHD;;;;;;;EAWX,OAAA,UAAiB,UAAA,CAAW,KAAA;AAAA;AAAA,iBAGd,aAAA,CAAc,IAAA,EAAM,iBAAA"}
|
|
@@ -15,7 +15,7 @@ import { DNS_LABEL_RE, ManifestMCPError, ManifestMCPErrorCode, bigIntReplacer, j
|
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
//#region src/server/register-tools.ts
|
|
17
17
|
function registerTools(deps) {
|
|
18
|
-
const { mcpServer, clientManager, walletProvider, authTokens } = deps;
|
|
18
|
+
const { mcpServer, clientManager, walletProvider, authTokens, fetchFn } = deps;
|
|
19
19
|
mcpServer.registerTool("browse_catalog", {
|
|
20
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.",
|
|
21
21
|
outputSchema: {
|
|
@@ -29,7 +29,7 @@ function registerTools(deps) {
|
|
|
29
29
|
})
|
|
30
30
|
}, withErrorHandling("browse_catalog", async () => {
|
|
31
31
|
await clientManager.acquireRateLimit();
|
|
32
|
-
return structuredResponse(await browseCatalog(await clientManager.getQueryClient()), bigIntReplacer);
|
|
32
|
+
return structuredResponse(await browseCatalog(await clientManager.getQueryClient(), fetchFn), bigIntReplacer);
|
|
33
33
|
}));
|
|
34
34
|
mcpServer.registerTool("app_status", {
|
|
35
35
|
description: "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.",
|
|
@@ -54,7 +54,7 @@ function registerTools(deps) {
|
|
|
54
54
|
const leaseUuid = args.lease_uuid;
|
|
55
55
|
const address = await walletProvider.getAddress();
|
|
56
56
|
await clientManager.acquireRateLimit();
|
|
57
|
-
return structuredResponse(await appStatus(await clientManager.getQueryClient(), address, leaseUuid, (addr, uuid) => authTokens.providerToken(addr, uuid)), bigIntReplacer);
|
|
57
|
+
return structuredResponse(await appStatus(await clientManager.getQueryClient(), address, leaseUuid, (addr, uuid) => authTokens.providerToken(addr, uuid), fetchFn), bigIntReplacer);
|
|
58
58
|
}));
|
|
59
59
|
mcpServer.registerTool("wait_for_app_ready", {
|
|
60
60
|
description: "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.",
|
|
@@ -87,7 +87,7 @@ function registerTools(deps) {
|
|
|
87
87
|
onProgress: emit ? (status) => {
|
|
88
88
|
emit(`Polling lease: state=${leaseStateToJSON(status.state)}${status.provision_status ? `, provision=${status.provision_status}` : ""}`);
|
|
89
89
|
} : void 0
|
|
90
|
-
}), bigIntReplacer);
|
|
90
|
+
}, fetchFn), bigIntReplacer);
|
|
91
91
|
}));
|
|
92
92
|
mcpServer.registerTool("get_logs", {
|
|
93
93
|
description: "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.",
|
|
@@ -105,7 +105,7 @@ function registerTools(deps) {
|
|
|
105
105
|
const tail = args.tail;
|
|
106
106
|
const address = await walletProvider.getAddress();
|
|
107
107
|
await clientManager.acquireRateLimit();
|
|
108
|
-
return jsonResponse(await getAppLogs(await clientManager.getQueryClient(), address, leaseUuid, (addr, uuid) => authTokens.providerToken(addr, uuid), tail), bigIntReplacer);
|
|
108
|
+
return jsonResponse(await getAppLogs(await clientManager.getQueryClient(), address, leaseUuid, (addr, uuid) => authTokens.providerToken(addr, uuid), tail, fetchFn), bigIntReplacer);
|
|
109
109
|
}));
|
|
110
110
|
mcpServer.registerTool("check_deployment_readiness", {
|
|
111
111
|
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.",
|
|
@@ -325,7 +325,7 @@ function registerTools(deps) {
|
|
|
325
325
|
pollOptions: emit ? { onProgress: (status) => {
|
|
326
326
|
emit(`Polling lease: state=${leaseStateToJSON(status.state)}${status.provision_status ? `, provision=${status.provision_status}` : ""}`);
|
|
327
327
|
} } : void 0
|
|
328
|
-
}), bigIntReplacer);
|
|
328
|
+
}, fetchFn), bigIntReplacer);
|
|
329
329
|
}));
|
|
330
330
|
mcpServer.registerTool("restart_app", {
|
|
331
331
|
description: "Restart a running app via the provider without closing its lease. Use this to apply configuration changes or recover from a crash.",
|
|
@@ -339,7 +339,7 @@ function registerTools(deps) {
|
|
|
339
339
|
const leaseUuid = args.lease_uuid;
|
|
340
340
|
const address = await walletProvider.getAddress();
|
|
341
341
|
await clientManager.acquireRateLimit();
|
|
342
|
-
return jsonResponse(await restartApp(await clientManager.getQueryClient(), address, leaseUuid, (addr, uuid) => authTokens.providerToken(addr, uuid)), bigIntReplacer);
|
|
342
|
+
return jsonResponse(await restartApp(await clientManager.getQueryClient(), address, leaseUuid, (addr, uuid) => authTokens.providerToken(addr, uuid), fetchFn), bigIntReplacer);
|
|
343
343
|
}));
|
|
344
344
|
mcpServer.registerTool("update_app", {
|
|
345
345
|
description: "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.",
|
|
@@ -368,7 +368,7 @@ function registerTools(deps) {
|
|
|
368
368
|
const leaseUuid = args.lease_uuid;
|
|
369
369
|
const address = await walletProvider.getAddress();
|
|
370
370
|
await clientManager.acquireRateLimit();
|
|
371
|
-
return structuredResponse(await updateApp(await clientManager.getQueryClient(), address, leaseUuid, (addr, uuid) => authTokens.providerToken(addr, uuid), manifest, args.existing_manifest), bigIntReplacer);
|
|
371
|
+
return structuredResponse(await updateApp(await clientManager.getQueryClient(), address, leaseUuid, (addr, uuid) => authTokens.providerToken(addr, uuid), manifest, args.existing_manifest, fetchFn), bigIntReplacer);
|
|
372
372
|
}));
|
|
373
373
|
mcpServer.registerTool("app_diagnostics", {
|
|
374
374
|
description: "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.",
|
|
@@ -389,7 +389,7 @@ function registerTools(deps) {
|
|
|
389
389
|
const address = await walletProvider.getAddress();
|
|
390
390
|
await clientManager.acquireRateLimit();
|
|
391
391
|
const queryClient = await clientManager.getQueryClient();
|
|
392
|
-
const provision = await getLeaseProvision(await resolveProviderUrl(queryClient, (await fetchActiveLease(queryClient, leaseUuid, "cannot be diagnosed")).providerUuid), leaseUuid, await authTokens.providerToken(address, leaseUuid));
|
|
392
|
+
const provision = await getLeaseProvision(await resolveProviderUrl(queryClient, (await fetchActiveLease(queryClient, leaseUuid, "cannot be diagnosed")).providerUuid), leaseUuid, await authTokens.providerToken(address, leaseUuid), fetchFn);
|
|
393
393
|
return structuredResponse({
|
|
394
394
|
lease_uuid: leaseUuid,
|
|
395
395
|
provision_status: provision.status,
|
|
@@ -421,7 +421,7 @@ function registerTools(deps) {
|
|
|
421
421
|
const queryClient = await clientManager.getQueryClient();
|
|
422
422
|
return structuredResponse({
|
|
423
423
|
lease_uuid: leaseUuid,
|
|
424
|
-
releases: (await getLeaseReleases(await resolveProviderUrl(queryClient, (await fetchActiveLease(queryClient, leaseUuid, "releases are not available")).providerUuid), leaseUuid, await authTokens.providerToken(address, leaseUuid))).releases
|
|
424
|
+
releases: (await getLeaseReleases(await resolveProviderUrl(queryClient, (await fetchActiveLease(queryClient, leaseUuid, "releases are not available")).providerUuid), leaseUuid, await authTokens.providerToken(address, leaseUuid), fetchFn)).releases
|
|
425
425
|
}, bigIntReplacer);
|
|
426
426
|
}));
|
|
427
427
|
}
|
|
@@ -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\nexport function registerTools(deps: RegisterToolsDeps): void {\n const { mcpServer, clientManager, walletProvider, authTokens } = 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);\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 );\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 );\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 );\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 );\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 );\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 );\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 );\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(providerUrl, leaseUuid, authToken);\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 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 +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,OAAO;CAC5C,IAAI,YAAY;CAEhB,eAAe,SAAwB;AACrC,SAAO,YAAY,MAAM,QAAQ;GAC/B,MAAM,MAAM;AACZ,WAAQ,OAAO,MAAM,GAAG,MAAM,KAAK;;;CAIvC,MAAM,cAAc,KAAK,IAAI,KAAK,IAAI,OAAO,EAAE,EAAE,MAAM,OAAO;CAC9D,MAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,aAAa,QAAQ,QAAQ,CAAC;AACnE,OAAM,QAAQ,IAAI,QAAQ;AAC1B,QAAO;;AAGT,eAAsB,cACpB,aACA,SACA;CACA,MAAM,MAAM,YAAY,WAAW,IAAI;CAEvC,MAAM,aAAa,iBAAiB,eAAe;CAEnD,MAAM,CAAC,iBAAiB,cAAc,MAAM,QAAQ,IAAI,CACtD,IAAI,UAAU;EAAE,YAAY;EAAM;EAAY,CAAC,EAC/C,IAAI,KAAK;EAAE,YAAY;EAAM;EAAY,CAAC,CAC3C,CAAC;CAEF,MAAM,YAAY,MAAM,mBACtB,gBAAgB,WAChB,8BACA,OAAO,MAAM;EACX,IAAI,UAAU;EACd,IAAI;EACJ,IAAI;AACJ,MAAI;GACF,MAAM,SAAS,MAAM,kBAAkB,EAAE,QAAQ,KAAA,GAAW,QAAQ;AACpE,aAAU,OAAO,WAAW,QAAQ,OAAO,WAAW;AACtD,kBAAe,OAAO;WACf,KAAK;AACZ,OACE,eAAe,oBACf,2BAA2B,IAAI,IAAI,KAAK,CAExC,OAAM;AACR,OAAI,eAAe,iBACjB,eAAc,QAAQ,IAAI,OAAO,IAAI,IAAI;OAEzC,eAAc,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;AAG1F,SAAO;GACL,MAAM,EAAE;GACR,SAAS,EAAE;GACX,QAAQ,EAAE;GACV,QAAQ,EAAE;GACV;GACA;GACA,GAAI,eAAe,EAAE,aAAa;GACnC;GAEJ;CAED,MAAM,iBAAiB,IAAI,IACzB,gBAAgB,UAAU,KAAK,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAClD;CAED,MAAM,QAGF,EAAE;AACN,MAAK,MAAM,KAAK,WAAW,MAAM;EAE/B,MAAM,QAAQ;GACZ,UAFe,eAAe,IAAI,EAAE,
|
|
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,OAAO;CAC5C,IAAI,YAAY;CAEhB,eAAe,SAAwB;AACrC,SAAO,YAAY,MAAM,QAAQ;GAC/B,MAAM,MAAM;AACZ,WAAQ,OAAO,MAAM,GAAG,MAAM,KAAK;;;CAIvC,MAAM,cAAc,KAAK,IAAI,KAAK,IAAI,OAAO,EAAE,EAAE,MAAM,OAAO;CAC9D,MAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,aAAa,QAAQ,QAAQ,CAAC;AACnE,OAAM,QAAQ,IAAI,QAAQ;AAC1B,QAAO;;AAGT,eAAsB,cACpB,aACA,SACA;CACA,MAAM,MAAM,YAAY,WAAW,IAAI;CAEvC,MAAM,aAAa,iBAAiB,eAAe;CAEnD,MAAM,CAAC,iBAAiB,cAAc,MAAM,QAAQ,IAAI,CACtD,IAAI,UAAU;EAAE,YAAY;EAAM;EAAY,CAAC,EAC/C,IAAI,KAAK;EAAE,YAAY;EAAM;EAAY,CAAC,CAC3C,CAAC;CAEF,MAAM,YAAY,MAAM,mBACtB,gBAAgB,WAChB,8BACA,OAAO,MAAM;EACX,IAAI,UAAU;EACd,IAAI;EACJ,IAAI;AACJ,MAAI;GACF,MAAM,SAAS,MAAM,kBAAkB,EAAE,QAAQ,KAAA,GAAW,QAAQ;AACpE,aAAU,OAAO,WAAW,QAAQ,OAAO,WAAW;AACtD,kBAAe,OAAO;WACf,KAAK;AACZ,OACE,eAAe,oBACf,2BAA2B,IAAI,IAAI,KAAK,CAExC,OAAM;AACR,OAAI,eAAe,iBACjB,eAAc,QAAQ,IAAI,OAAO,IAAI,IAAI;OAEzC,eAAc,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;AAG1F,SAAO;GACL,MAAM,EAAE;GACR,SAAS,EAAE;GACX,QAAQ,EAAE;GACV,QAAQ,EAAE;GACV;GACA;GACA,GAAI,eAAe,EAAE,aAAa;GACnC;GAEJ;CAED,MAAM,iBAAiB,IAAI,IACzB,gBAAgB,UAAU,KAAK,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAClD;CAED,MAAM,QAGF,EAAE;AACN,MAAK,MAAM,KAAK,WAAW,MAAM;EAE/B,MAAM,QAAQ;GACZ,UAFe,eAAe,IAAI,EAAE,aAElB,EAAE,UAAU,EAAE;GAChC,OAAO,EAAE,WAAW,UAAU;GAC9B,MAAM,EAAE,WAAW,SAAS;GAC7B;AACD,MAAI,CAAC,MAAM,EAAE,MACX,OAAM,EAAE,QAAQ,EAAE;AAEpB,QAAM,EAAE,MAAM,KAAK,MAAM;;AAG3B,QAAO;EAAE;EAAW;EAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deployApp.js","names":[],"sources":["../../src/tools/deployApp.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 {\n type BuildManifestOptions,\n buildManifest,\n buildStackManifest,\n metaHashHex,\n validateServiceName,\n} from '../manifest.js';\nimport { resolveProviderUrl } from './resolveLeaseProvider.js';\n\nfunction 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\nasync function findSkuUuid(\n queryClient: ManifestQueryClient,\n size: 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 for (const sku of result.skus) {\n if (sku.name === size) {\n return { skuUuid: sku.uuid, providerUuid: sku.providerUuid };\n }\n }\n\n const available = result.skus.map((s) => s.name);\n throw new ManifestMCPError(\n ManifestMCPErrorCode.QUERY_FAILED,\n `SKU tier \"${size}\" not found. Available: ${available.join(', ')}`,\n );\n}\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 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 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 // Validate custom-domain inputs eagerly, before any chain tx, so a\n // misconfigured deploy doesn't leave a paid-for lease behind.\n // The chain still validates FQDN format and reserved-suffix rules\n // (`IsValidFQDN`, `MatchesReservedSuffix`) when the set-domain tx\n // arrives — we just catch the obvious shape mistakes up-front.\n //\n // The trimmed value is the canonical form used downstream (set-domain\n // tx + result echo). Forwarding the untrimmed string would let\n // surrounding whitespace survive to the chain, which `IsValidFQDN`\n // rejects — orphaning a paid-for lease behind a \"Deploy partially\n // succeeded\" wrap, exactly the failure mode this block is meant to\n // prevent.\n // `serviceName` is documented as only meaningful when `customDomain` is set.\n // Reject it eagerly when the caller forgot the domain, otherwise the input\n // is silently ignored — confusing for the caller and a foot-gun (a typo\n // turning `customDomain` into `customdomain` would skip the domain claim\n // and quietly drop the service routing intent on the floor).\n if (input.customDomain === undefined && input.serviceName !== undefined) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName is only meaningful when customDomain is set; pass customDomain or omit serviceName',\n );\n }\n\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 (input.services) {\n if (!input.serviceName) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName is required when setting customDomain on a stack lease (services); pick one of the service keys',\n );\n }\n // Use Object.keys+includes rather than `in`: `'constructor' in {}`\n // is true (prototype chain), so an attacker passing\n // `service_name: 'constructor'` (or any Object.prototype key) on\n // a stack lease that doesn't define a same-named service would\n // pass this check, sail through create-lease, and only fail at\n // the set-domain tx — leaving the paid-for lease behind. Avoids\n // ES2022's `Object.hasOwn` (base tsconfig targets ES2020) and\n // biome's `noPrototypeBuiltins` rule on `hasOwnProperty.call`.\n if (!Object.keys(input.services).includes(input.serviceName)) {\n const available = Object.keys(input.services).join(', ');\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `serviceName \"${input.serviceName}\" does not match any service in the deployment. Available: ${available}`,\n );\n }\n } else if (input.serviceName) {\n // image+port mode — the underlying lease has a single legacy item\n // with service_name=\"\". Passing a service_name here would address\n // a non-existent item on the chain.\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName must not be set on an image+port (legacy 1-item) lease — omit it or switch to services mode',\n );\n }\n }\n\n const address = await clientManager.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n\n // 1. Build manifest\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 // 2. SHA-256 hash of manifest (must match `meta_hash` recorded on-chain).\n const manifestMetaHash = await metaHashHex(manifestJson);\n\n // 3. Find matching SKU(s)\n const { skuUuid, providerUuid } = await findSkuUuid(queryClient, input.size);\n\n let leaseItems: string[];\n if (input.services) {\n const serviceNames = Object.keys(input.services);\n leaseItems = serviceNames.map((name) => `${skuUuid}:1:${name}`);\n } else {\n leaseItems = [`${skuUuid}:1`];\n }\n\n if (input.storage) {\n const { skuUuid: storageSkuUuid } = await findSkuUuid(\n queryClient,\n input.storage,\n );\n leaseItems.push(`${storageSkuUuid}:1`);\n }\n\n // 4. Get provider URL\n const providerUrl = await resolveProviderUrl(queryClient, providerUuid);\n\n // 5. Create lease\n const overrides =\n input.gasMultiplier !== undefined\n ? { gasMultiplier: input.gasMultiplier }\n : undefined;\n const txResult = await cosmosTx(\n clientManager,\n 'billing',\n 'create-lease',\n ['--meta-hash', manifestMetaHash, ...leaseItems],\n true,\n overrides,\n );\n\n // 6. Extract lease UUID\n const leaseUuid = extractLeaseUuid(txResult);\n\n // Outside the partial-success try: callback errors surface raw, not wrapped.\n // The lease exists on-chain regardless of abortSignal state — always notify.\n await input.onLeaseCreated?.(leaseUuid, providerUrl);\n\n let status: FredLeaseStatus;\n try {\n input.abortSignal?.throwIfAborted();\n\n // 7. Optionally attach custom_domain to the freshly-created lease.\n // Submitted before the manifest upload so providerd has the domain\n // available when it provisions. Failures here flow through the\n // same partial-success error wrap below as upload/poll failures.\n // The chain accepts MsgSetItemCustomDomain in PENDING or ACTIVE\n // state; if providerd races and rejects the lease before this\n // tx lands, the chain auto-clears the index entry so no stuck\n // state is left behind.\n if (normalizedCustomDomain !== undefined) {\n await setItemCustomDomain(\n clientManager,\n leaseUuid,\n normalizedCustomDomain,\n { serviceName: input.serviceName },\n overrides,\n );\n }\n\n // 8. Upload manifest with lease-data auth token\n const leaseDataToken = await getLeaseDataAuthToken(\n address,\n leaseUuid,\n manifestMetaHash,\n );\n await uploadLeaseData(\n providerUrl,\n leaseUuid,\n new TextEncoder().encode(manifestJson),\n leaseDataToken,\n fetchFn,\n input.abortSignal,\n );\n\n // 9. Poll until ready\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 // Chain-terminal states are self-explanatory and need no \"close this lease\"\n // advice (the lease is already terminal on-chain). Let Barney & friends\n // observe the typed error directly via `instanceof` / `err.chainState`.\n // withContext preserves the original stack (debugging points at the poll,\n // not at this catch) while attaching deployApp's provider context so\n // callers don't need to re-query the chain to recover it.\n if (err instanceof TerminalChainStateError) {\n throw err.withContext({ providerUuid, providerUrl });\n }\n const code =\n err instanceof ManifestMCPError\n ? err.code\n : ManifestMCPErrorCode.QUERY_FAILED;\n const details =\n err instanceof ManifestMCPError\n ? {\n ...err.details,\n lease_uuid: leaseUuid,\n provider_uuid: providerUuid,\n provider_url: providerUrl,\n }\n : {\n lease_uuid: leaseUuid,\n provider_uuid: providerUuid,\n provider_url: providerUrl,\n };\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 details,\n );\n }\n\n // 10. Get connection info (best-effort)\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_app] 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":"deployApp.js","names":[],"sources":["../../src/tools/deployApp.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 {\n type BuildManifestOptions,\n buildManifest,\n buildStackManifest,\n metaHashHex,\n validateServiceName,\n} from '../manifest.js';\nimport { resolveProviderUrl } from './resolveLeaseProvider.js';\n\nfunction 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\nasync function findSkuUuid(\n queryClient: ManifestQueryClient,\n size: 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 for (const sku of result.skus) {\n if (sku.name === size) {\n return { skuUuid: sku.uuid, providerUuid: sku.providerUuid };\n }\n }\n\n const available = result.skus.map((s) => s.name);\n throw new ManifestMCPError(\n ManifestMCPErrorCode.QUERY_FAILED,\n `SKU tier \"${size}\" not found. Available: ${available.join(', ')}`,\n );\n}\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 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 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 // Validate custom-domain inputs eagerly, before any chain tx, so a\n // misconfigured deploy doesn't leave a paid-for lease behind.\n // The chain still validates FQDN format and reserved-suffix rules\n // (`IsValidFQDN`, `MatchesReservedSuffix`) when the set-domain tx\n // arrives — we just catch the obvious shape mistakes up-front.\n //\n // The trimmed value is the canonical form used downstream (set-domain\n // tx + result echo). Forwarding the untrimmed string would let\n // surrounding whitespace survive to the chain, which `IsValidFQDN`\n // rejects — orphaning a paid-for lease behind a \"Deploy partially\n // succeeded\" wrap, exactly the failure mode this block is meant to\n // prevent.\n // `serviceName` is documented as only meaningful when `customDomain` is set.\n // Reject it eagerly when the caller forgot the domain, otherwise the input\n // is silently ignored — confusing for the caller and a foot-gun (a typo\n // turning `customDomain` into `customdomain` would skip the domain claim\n // and quietly drop the service routing intent on the floor).\n if (input.customDomain === undefined && input.serviceName !== undefined) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName is only meaningful when customDomain is set; pass customDomain or omit serviceName',\n );\n }\n\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 (input.services) {\n if (!input.serviceName) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName is required when setting customDomain on a stack lease (services); pick one of the service keys',\n );\n }\n // Use Object.keys+includes rather than `in`: `'constructor' in {}`\n // is true (prototype chain), so an attacker passing\n // `service_name: 'constructor'` (or any Object.prototype key) on\n // a stack lease that doesn't define a same-named service would\n // pass this check, sail through create-lease, and only fail at\n // the set-domain tx — leaving the paid-for lease behind. Avoids\n // ES2022's `Object.hasOwn` (base tsconfig targets ES2020) and\n // biome's `noPrototypeBuiltins` rule on `hasOwnProperty.call`.\n if (!Object.keys(input.services).includes(input.serviceName)) {\n const available = Object.keys(input.services).join(', ');\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `serviceName \"${input.serviceName}\" does not match any service in the deployment. Available: ${available}`,\n );\n }\n } else if (input.serviceName) {\n // image+port mode — the underlying lease has a single legacy item\n // with service_name=\"\". Passing a service_name here would address\n // a non-existent item on the chain.\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'serviceName must not be set on an image+port (legacy 1-item) lease — omit it or switch to services mode',\n );\n }\n }\n\n const address = await clientManager.getAddress();\n await clientManager.acquireRateLimit();\n const queryClient = await clientManager.getQueryClient();\n\n // 1. Build manifest\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 // 2. SHA-256 hash of manifest (must match `meta_hash` recorded on-chain).\n const manifestMetaHash = await metaHashHex(manifestJson);\n\n // 3. Find matching SKU(s)\n const { skuUuid, providerUuid } = await findSkuUuid(queryClient, input.size);\n\n let leaseItems: string[];\n if (input.services) {\n const serviceNames = Object.keys(input.services);\n leaseItems = serviceNames.map((name) => `${skuUuid}:1:${name}`);\n } else {\n leaseItems = [`${skuUuid}:1`];\n }\n\n if (input.storage) {\n const { skuUuid: storageSkuUuid } = await findSkuUuid(\n queryClient,\n input.storage,\n );\n leaseItems.push(`${storageSkuUuid}:1`);\n }\n\n // 4. Get provider URL\n const providerUrl = await resolveProviderUrl(queryClient, providerUuid);\n\n // 5. Create lease\n const overrides =\n input.gasMultiplier !== undefined\n ? { gasMultiplier: input.gasMultiplier }\n : undefined;\n const txResult = await cosmosTx(\n clientManager,\n 'billing',\n 'create-lease',\n ['--meta-hash', manifestMetaHash, ...leaseItems],\n true,\n overrides,\n );\n\n // 6. Extract lease UUID\n const leaseUuid = extractLeaseUuid(txResult);\n\n // Outside the partial-success try: callback errors surface raw, not wrapped.\n // The lease exists on-chain regardless of abortSignal state — always notify.\n await input.onLeaseCreated?.(leaseUuid, providerUrl);\n\n let status: FredLeaseStatus;\n try {\n input.abortSignal?.throwIfAborted();\n\n // 7. Optionally attach custom_domain to the freshly-created lease.\n // Submitted before the manifest upload so providerd has the domain\n // available when it provisions. Failures here flow through the\n // same partial-success error wrap below as upload/poll failures.\n // The chain accepts MsgSetItemCustomDomain in PENDING or ACTIVE\n // state; if providerd races and rejects the lease before this\n // tx lands, the chain auto-clears the index entry so no stuck\n // state is left behind.\n if (normalizedCustomDomain !== undefined) {\n await setItemCustomDomain(\n clientManager,\n leaseUuid,\n normalizedCustomDomain,\n { serviceName: input.serviceName },\n overrides,\n );\n }\n\n // 8. Upload manifest with lease-data auth token\n const leaseDataToken = await getLeaseDataAuthToken(\n address,\n leaseUuid,\n manifestMetaHash,\n );\n await uploadLeaseData(\n providerUrl,\n leaseUuid,\n new TextEncoder().encode(manifestJson),\n leaseDataToken,\n fetchFn,\n input.abortSignal,\n );\n\n // 9. Poll until ready\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 // Chain-terminal states are self-explanatory and need no \"close this lease\"\n // advice (the lease is already terminal on-chain). Let Barney & friends\n // observe the typed error directly via `instanceof` / `err.chainState`.\n // withContext preserves the original stack (debugging points at the poll,\n // not at this catch) while attaching deployApp's provider context so\n // callers don't need to re-query the chain to recover it.\n if (err instanceof TerminalChainStateError) {\n throw err.withContext({ providerUuid, providerUrl });\n }\n const code =\n err instanceof ManifestMCPError\n ? err.code\n : ManifestMCPErrorCode.QUERY_FAILED;\n const details =\n err instanceof ManifestMCPError\n ? {\n ...err.details,\n lease_uuid: leaseUuid,\n provider_uuid: providerUuid,\n provider_url: providerUrl,\n }\n : {\n lease_uuid: leaseUuid,\n provider_uuid: providerUuid,\n provider_url: providerUrl,\n };\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 details,\n );\n }\n\n // 10. Get connection info (best-effort)\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_app] 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,4 +1,4 @@
|
|
|
1
|
-
import * as _manifest_network_manifest_mcp_core0 from "@manifest-network/manifest-mcp-core";
|
|
1
|
+
import * as _$_manifest_network_manifest_mcp_core0 from "@manifest-network/manifest-mcp-core";
|
|
2
2
|
import { ManifestQueryClient } from "@manifest-network/manifest-mcp-core";
|
|
3
3
|
|
|
4
4
|
//#region src/tools/fetchActiveLease.d.ts
|
|
@@ -6,7 +6,7 @@ import { ManifestQueryClient } from "@manifest-network/manifest-mcp-core";
|
|
|
6
6
|
* Fetches a lease by UUID, validates it exists and is in active/pending state.
|
|
7
7
|
* Throws ManifestMCPError if the lease is not found or not in an active state.
|
|
8
8
|
*/
|
|
9
|
-
declare function fetchActiveLease(queryClient: ManifestQueryClient, leaseUuid: string, action: string): Promise<_manifest_network_manifest_mcp_core0.Lease>;
|
|
9
|
+
declare function fetchActiveLease(queryClient: ManifestQueryClient, leaseUuid: string, action: string): Promise<_$_manifest_network_manifest_mcp_core0.Lease>;
|
|
10
10
|
//#endregion
|
|
11
11
|
export { fetchActiveLease };
|
|
12
12
|
//# sourceMappingURL=fetchActiveLease.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetchActiveLease.d.ts","names":[],"sources":["../../src/tools/fetchActiveLease.ts"],"mappings":";;;;;;;AAYA;iBAAsB,gBAAA,CACpB,WAAA,EAAa,mBAAA,EACb,SAAA,UACA,MAAA,WAAc,OAAA,CAFkB,
|
|
1
|
+
{"version":3,"file":"fetchActiveLease.d.ts","names":[],"sources":["../../src/tools/fetchActiveLease.ts"],"mappings":";;;;;;;AAYA;iBAAsB,gBAAA,CACpB,WAAA,EAAa,mBAAA,EACb,SAAA,UACA,MAAA,WAAc,OAAA,CAFkB,sCAAA,CAElB,KAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getLogs.js","names":[],"sources":["../../src/tools/getLogs.ts"],"sourcesContent":["import type { ManifestQueryClient } from '@manifest-network/manifest-mcp-core';\nimport { getLeaseLogs } from '../http/fred.js';\nimport { fetchActiveLease } from './fetchActiveLease.js';\nimport { resolveProviderUrl } from './resolveLeaseProvider.js';\n\nconst MAX_LOG_CHARS = 4000;\n\nexport async function getAppLogs(\n queryClient: ManifestQueryClient,\n address: string,\n leaseUuid: string,\n getAuthToken: (address: string, leaseUuid: string) => Promise<string>,\n tail?: number,\n fetchFn?: typeof globalThis.fetch,\n) {\n const lease = await fetchActiveLease(\n queryClient,\n leaseUuid,\n 'logs are not available',\n );\n\n const providerUrl = await resolveProviderUrl(queryClient, lease.providerUuid);\n const authToken = await getAuthToken(address, leaseUuid);\n const result = await getLeaseLogs(\n providerUrl,\n leaseUuid,\n authToken,\n tail,\n fetchFn,\n );\n\n let truncated = false;\n const logs: Record<string, string> = {};\n let totalChars = 0;\n\n for (const [service, log] of Object.entries(result.logs)) {\n if (totalChars >= MAX_LOG_CHARS) {\n truncated = true;\n break;\n }\n const remaining = MAX_LOG_CHARS - totalChars;\n if (log.length > remaining) {\n logs[service] = log.slice(-remaining);\n truncated = true;\n } else {\n logs[service] = log;\n }\n totalChars += logs[service].length;\n }\n\n return {\n lease_uuid: leaseUuid,\n logs,\n truncated,\n };\n}\n"],"mappings":";;;;AAKA,MAAM,gBAAgB;AAEtB,eAAsB,WACpB,aACA,SACA,WACA,cACA,MACA,SACA;CASA,MAAM,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"getLogs.js","names":[],"sources":["../../src/tools/getLogs.ts"],"sourcesContent":["import type { ManifestQueryClient } from '@manifest-network/manifest-mcp-core';\nimport { getLeaseLogs } from '../http/fred.js';\nimport { fetchActiveLease } from './fetchActiveLease.js';\nimport { resolveProviderUrl } from './resolveLeaseProvider.js';\n\nconst MAX_LOG_CHARS = 4000;\n\nexport async function getAppLogs(\n queryClient: ManifestQueryClient,\n address: string,\n leaseUuid: string,\n getAuthToken: (address: string, leaseUuid: string) => Promise<string>,\n tail?: number,\n fetchFn?: typeof globalThis.fetch,\n) {\n const lease = await fetchActiveLease(\n queryClient,\n leaseUuid,\n 'logs are not available',\n );\n\n const providerUrl = await resolveProviderUrl(queryClient, lease.providerUuid);\n const authToken = await getAuthToken(address, leaseUuid);\n const result = await getLeaseLogs(\n providerUrl,\n leaseUuid,\n authToken,\n tail,\n fetchFn,\n );\n\n let truncated = false;\n const logs: Record<string, string> = {};\n let totalChars = 0;\n\n for (const [service, log] of Object.entries(result.logs)) {\n if (totalChars >= MAX_LOG_CHARS) {\n truncated = true;\n break;\n }\n const remaining = MAX_LOG_CHARS - totalChars;\n if (log.length > remaining) {\n logs[service] = log.slice(-remaining);\n truncated = true;\n } else {\n logs[service] = log;\n }\n totalChars += logs[service].length;\n }\n\n return {\n lease_uuid: leaseUuid,\n logs,\n truncated,\n };\n}\n"],"mappings":";;;;AAKA,MAAM,gBAAgB;AAEtB,eAAsB,WACpB,aACA,SACA,WACA,cACA,MACA,SACA;CASA,MAAM,SAAS,MAAM,aACnB,MAHwB,mBAAmB,cAAa,MANtC,iBAClB,aACA,WACA,yBACD,EAE+D,aAAa,EAI3E,WACA,MAJsB,aAAa,SAAS,UAAU,EAKtD,MACA,QACD;CAED,IAAI,YAAY;CAChB,MAAM,OAA+B,EAAE;CACvC,IAAI,aAAa;AAEjB,MAAK,MAAM,CAAC,SAAS,QAAQ,OAAO,QAAQ,OAAO,KAAK,EAAE;AACxD,MAAI,cAAc,eAAe;AAC/B,eAAY;AACZ;;EAEF,MAAM,YAAY,gBAAgB;AAClC,MAAI,IAAI,SAAS,WAAW;AAC1B,QAAK,WAAW,IAAI,MAAM,CAAC,UAAU;AACrC,eAAY;QAEZ,MAAK,WAAW;AAElB,gBAAc,KAAK,SAAS;;AAG9B,QAAO;EACL,YAAY;EACZ;EACA;EACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restartApp.js","names":[],"sources":["../../src/tools/restartApp.ts"],"sourcesContent":["import type { ManifestQueryClient } from '@manifest-network/manifest-mcp-core';\nimport { restartLease } from '../http/fred.js';\nimport { fetchActiveLease } from './fetchActiveLease.js';\nimport { resolveProviderUrl } from './resolveLeaseProvider.js';\n\nexport async function restartApp(\n queryClient: ManifestQueryClient,\n address: string,\n leaseUuid: string,\n getAuthToken: (address: string, leaseUuid: string) => Promise<string>,\n fetchFn?: typeof globalThis.fetch,\n) {\n const lease = await fetchActiveLease(\n queryClient,\n leaseUuid,\n 'cannot be restarted',\n );\n\n const providerUrl = await resolveProviderUrl(queryClient, lease.providerUuid);\n const authToken = await getAuthToken(address, leaseUuid);\n const result = await restartLease(providerUrl, leaseUuid, authToken, fetchFn);\n\n return {\n lease_uuid: leaseUuid,\n status: result.status,\n };\n}\n"],"mappings":";;;;AAKA,eAAsB,WACpB,aACA,SACA,WACA,cACA,SACA;AAWA,QAAO;EACL,YAAY;EACZ,
|
|
1
|
+
{"version":3,"file":"restartApp.js","names":[],"sources":["../../src/tools/restartApp.ts"],"sourcesContent":["import type { ManifestQueryClient } from '@manifest-network/manifest-mcp-core';\nimport { restartLease } from '../http/fred.js';\nimport { fetchActiveLease } from './fetchActiveLease.js';\nimport { resolveProviderUrl } from './resolveLeaseProvider.js';\n\nexport async function restartApp(\n queryClient: ManifestQueryClient,\n address: string,\n leaseUuid: string,\n getAuthToken: (address: string, leaseUuid: string) => Promise<string>,\n fetchFn?: typeof globalThis.fetch,\n) {\n const lease = await fetchActiveLease(\n queryClient,\n leaseUuid,\n 'cannot be restarted',\n );\n\n const providerUrl = await resolveProviderUrl(queryClient, lease.providerUuid);\n const authToken = await getAuthToken(address, leaseUuid);\n const result = await restartLease(providerUrl, leaseUuid, authToken, fetchFn);\n\n return {\n lease_uuid: leaseUuid,\n status: result.status,\n };\n}\n"],"mappings":";;;;AAKA,eAAsB,WACpB,aACA,SACA,WACA,cACA,SACA;AAWA,QAAO;EACL,YAAY;EACZ,SAAQ,MAJW,aAAa,MAFR,mBAAmB,cAAa,MANtC,iBAClB,aACA,WACA,sBACD,EAE+D,aAAa,EAE9B,WAAW,MADlC,aAAa,SAAS,UAAU,EACa,QAAQ,EAI5D;EAChB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updateApp.js","names":[],"sources":["../../src/tools/updateApp.ts"],"sourcesContent":["import type { ManifestQueryClient } from '@manifest-network/manifest-mcp-core';\nimport {\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\nimport { updateLease } from '../http/fred.js';\nimport {\n isStackManifest,\n mergeManifest,\n validateServiceName,\n} from '../manifest.js';\nimport { fetchActiveLease } from './fetchActiveLease.js';\nimport { resolveProviderUrl } from './resolveLeaseProvider.js';\n\nexport async function updateApp(\n queryClient: ManifestQueryClient,\n address: string,\n leaseUuid: string,\n getAuthToken: (address: string, leaseUuid: string) => Promise<string>,\n manifest: string,\n existingManifest?: string,\n fetchFn?: typeof globalThis.fetch,\n) {\n const lease = await fetchActiveLease(\n queryClient,\n leaseUuid,\n 'cannot be updated',\n );\n\n let finalManifest = manifest;\n if (existingManifest) {\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(manifest) as Record<string, unknown>;\n } catch (err) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid manifest JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (isStackManifest(parsed)) {\n for (const name of Object.keys(parsed.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 // Per-service merge: merge each service independently\n let oldParsed: unknown;\n try {\n oldParsed = JSON.parse(existingManifest);\n } catch (err) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid existing_manifest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (!isStackManifest(oldParsed)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Cannot merge: new manifest is a stack but existing_manifest is not. Provide a stack-format existing_manifest or omit existing_manifest for full replacement.',\n );\n }\n const mergedStack: Record<string, unknown> = {};\n for (const [svc, svcManifest] of Object.entries(parsed.services)) {\n const oldSvcJson = oldParsed.services[svc]\n ? JSON.stringify(oldParsed.services[svc])\n : '{}';\n mergedStack[svc] = mergeManifest(\n svcManifest as Record<string, unknown>,\n oldSvcJson,\n );\n }\n finalManifest = JSON.stringify({ services: mergedStack });\n } else {\n try {\n const merged = mergeManifest(parsed, existingManifest);\n finalManifest = JSON.stringify(merged);\n } catch (err) {\n if (err instanceof ManifestMCPError) throw err;\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid existing_manifest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n\n const providerUrl = await resolveProviderUrl(queryClient, lease.providerUuid);\n const authToken = await getAuthToken(address, leaseUuid);\n const result = await updateLease(\n providerUrl,\n leaseUuid,\n new TextEncoder().encode(finalManifest),\n authToken,\n fetchFn,\n );\n\n return {\n lease_uuid: leaseUuid,\n status: result.status,\n };\n}\n"],"mappings":";;;;;;AAcA,eAAsB,UACpB,aACA,SACA,WACA,cACA,UACA,kBACA,SACA;CACA,MAAM,QAAQ,MAAM,iBAClB,aACA,WACA,oBACD;CAED,IAAI,gBAAgB;AACpB,KAAI,kBAAkB;EACpB,IAAI;AACJ,MAAI;AACF,YAAS,KAAK,MAAM,SAAS;WACtB,KAAK;AACZ,SAAM,IAAI,iBACR,qBAAqB,gBACrB,0BAA0B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC3E;;AAEH,MAAI,gBAAgB,OAAO,EAAE;AAC3B,QAAK,MAAM,QAAQ,OAAO,KAAK,OAAO,SAAS,CAC7C,KAAI,CAAC,oBAAoB,KAAK,CAC5B,OAAM,IAAI,iBACR,qBAAqB,gBACrB,0BAA0B,KAAK,uFAChC;GAIL,IAAI;AACJ,OAAI;AACF,gBAAY,KAAK,MAAM,iBAAiB;YACjC,KAAK;AACZ,UAAM,IAAI,iBACR,qBAAqB,gBACrB,8BAA8B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC/E;;AAEH,OAAI,CAAC,gBAAgB,UAAU,CAC7B,OAAM,IAAI,iBACR,qBAAqB,gBACrB,+JACD;GAEH,MAAM,cAAuC,EAAE;AAC/C,QAAK,MAAM,CAAC,KAAK,gBAAgB,OAAO,QAAQ,OAAO,SAAS,CAI9D,aAAY,OAAO,cACjB,aAJiB,UAAU,SAAS,OAClC,KAAK,UAAU,UAAU,SAAS,KAAK,GACvC,KAIH;AAEH,mBAAgB,KAAK,UAAU,EAAE,UAAU,aAAa,CAAC;QAEzD,KAAI;GACF,MAAM,SAAS,cAAc,QAAQ,iBAAiB;AACtD,mBAAgB,KAAK,UAAU,OAAO;WAC/B,KAAK;AACZ,OAAI,eAAe,iBAAkB,OAAM;AAC3C,SAAM,IAAI,iBACR,qBAAqB,gBACrB,8BAA8B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC/E;;;CAKP,MAAM,cAAc,MAAM,mBAAmB,aAAa,MAAM,aAAa;CAC7E,MAAM,YAAY,MAAM,aAAa,SAAS,UAAU;AASxD,QAAO;EACL,YAAY;EACZ,
|
|
1
|
+
{"version":3,"file":"updateApp.js","names":[],"sources":["../../src/tools/updateApp.ts"],"sourcesContent":["import type { ManifestQueryClient } from '@manifest-network/manifest-mcp-core';\nimport {\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\nimport { updateLease } from '../http/fred.js';\nimport {\n isStackManifest,\n mergeManifest,\n validateServiceName,\n} from '../manifest.js';\nimport { fetchActiveLease } from './fetchActiveLease.js';\nimport { resolveProviderUrl } from './resolveLeaseProvider.js';\n\nexport async function updateApp(\n queryClient: ManifestQueryClient,\n address: string,\n leaseUuid: string,\n getAuthToken: (address: string, leaseUuid: string) => Promise<string>,\n manifest: string,\n existingManifest?: string,\n fetchFn?: typeof globalThis.fetch,\n) {\n const lease = await fetchActiveLease(\n queryClient,\n leaseUuid,\n 'cannot be updated',\n );\n\n let finalManifest = manifest;\n if (existingManifest) {\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(manifest) as Record<string, unknown>;\n } catch (err) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid manifest JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (isStackManifest(parsed)) {\n for (const name of Object.keys(parsed.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 // Per-service merge: merge each service independently\n let oldParsed: unknown;\n try {\n oldParsed = JSON.parse(existingManifest);\n } catch (err) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid existing_manifest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (!isStackManifest(oldParsed)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'Cannot merge: new manifest is a stack but existing_manifest is not. Provide a stack-format existing_manifest or omit existing_manifest for full replacement.',\n );\n }\n const mergedStack: Record<string, unknown> = {};\n for (const [svc, svcManifest] of Object.entries(parsed.services)) {\n const oldSvcJson = oldParsed.services[svc]\n ? JSON.stringify(oldParsed.services[svc])\n : '{}';\n mergedStack[svc] = mergeManifest(\n svcManifest as Record<string, unknown>,\n oldSvcJson,\n );\n }\n finalManifest = JSON.stringify({ services: mergedStack });\n } else {\n try {\n const merged = mergeManifest(parsed, existingManifest);\n finalManifest = JSON.stringify(merged);\n } catch (err) {\n if (err instanceof ManifestMCPError) throw err;\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Invalid existing_manifest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n\n const providerUrl = await resolveProviderUrl(queryClient, lease.providerUuid);\n const authToken = await getAuthToken(address, leaseUuid);\n const result = await updateLease(\n providerUrl,\n leaseUuid,\n new TextEncoder().encode(finalManifest),\n authToken,\n fetchFn,\n );\n\n return {\n lease_uuid: leaseUuid,\n status: result.status,\n };\n}\n"],"mappings":";;;;;;AAcA,eAAsB,UACpB,aACA,SACA,WACA,cACA,UACA,kBACA,SACA;CACA,MAAM,QAAQ,MAAM,iBAClB,aACA,WACA,oBACD;CAED,IAAI,gBAAgB;AACpB,KAAI,kBAAkB;EACpB,IAAI;AACJ,MAAI;AACF,YAAS,KAAK,MAAM,SAAS;WACtB,KAAK;AACZ,SAAM,IAAI,iBACR,qBAAqB,gBACrB,0BAA0B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC3E;;AAEH,MAAI,gBAAgB,OAAO,EAAE;AAC3B,QAAK,MAAM,QAAQ,OAAO,KAAK,OAAO,SAAS,CAC7C,KAAI,CAAC,oBAAoB,KAAK,CAC5B,OAAM,IAAI,iBACR,qBAAqB,gBACrB,0BAA0B,KAAK,uFAChC;GAIL,IAAI;AACJ,OAAI;AACF,gBAAY,KAAK,MAAM,iBAAiB;YACjC,KAAK;AACZ,UAAM,IAAI,iBACR,qBAAqB,gBACrB,8BAA8B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC/E;;AAEH,OAAI,CAAC,gBAAgB,UAAU,CAC7B,OAAM,IAAI,iBACR,qBAAqB,gBACrB,+JACD;GAEH,MAAM,cAAuC,EAAE;AAC/C,QAAK,MAAM,CAAC,KAAK,gBAAgB,OAAO,QAAQ,OAAO,SAAS,CAI9D,aAAY,OAAO,cACjB,aAJiB,UAAU,SAAS,OAClC,KAAK,UAAU,UAAU,SAAS,KAAK,GACvC,KAIH;AAEH,mBAAgB,KAAK,UAAU,EAAE,UAAU,aAAa,CAAC;QAEzD,KAAI;GACF,MAAM,SAAS,cAAc,QAAQ,iBAAiB;AACtD,mBAAgB,KAAK,UAAU,OAAO;WAC/B,KAAK;AACZ,OAAI,eAAe,iBAAkB,OAAM;AAC3C,SAAM,IAAI,iBACR,qBAAqB,gBACrB,8BAA8B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC/E;;;CAKP,MAAM,cAAc,MAAM,mBAAmB,aAAa,MAAM,aAAa;CAC7E,MAAM,YAAY,MAAM,aAAa,SAAS,UAAU;AASxD,QAAO;EACL,YAAY;EACZ,SAAQ,MAVW,YACnB,aACA,WACA,IAAI,aAAa,CAAC,OAAO,cAAc,EACvC,WACA,QACD,EAIgB;EAChB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manifest-network/manifest-mcp-fred",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"homepage": "https://github.com/manifest-network/manifest-mcp-mono#readme",
|
|
37
37
|
"bugs": "https://github.com/manifest-network/manifest-mcp-mono/issues",
|
|
38
38
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
39
|
+
"node": ">=22.19.0"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@cosmjs/encoding": "0.32.4",
|
|
49
|
-
"@manifest-network/manifest-mcp-core": "^0.
|
|
49
|
+
"@manifest-network/manifest-mcp-core": "^0.11.0",
|
|
50
50
|
"@modelcontextprotocol/sdk": "1.27.1",
|
|
51
51
|
"zod": "^4.3.6"
|
|
52
52
|
},
|