@florent-uzio/custody-mcp 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -10,38 +10,46 @@ The server speaks only the 2026-07-28 MCP protocol (the one `@mastra/mcp` 2.x se
10
10
 
11
11
  ## Use it from your MCP client
12
12
 
13
- Add the server to your client's MCP config. It runs with `npx`, so there is nothing to clone or build:
13
+ The server runs with `npx`, so there is nothing to clone or build. In Claude Code, add it for all your projects with:
14
+
15
+ ```sh
16
+ claude mcp add custody --scope user -- npx -y @florent-uzio/custody-mcp@latest
17
+ ```
18
+
19
+ For other clients, add it to the client's MCP config:
14
20
 
15
21
  ```json
16
22
  {
17
23
  "mcpServers": {
18
24
  "custody": {
19
25
  "command": "npx",
20
- "args": ["-y", "@florent-uzio/custody-mcp"]
26
+ "args": ["-y", "@florent-uzio/custody-mcp@latest"]
21
27
  }
22
28
  }
23
29
  }
24
30
  ```
25
31
 
32
+ `@latest` makes `npx` check npm each time the client starts the server, so you get new releases on your next client restart instead of a copy cached by `npx`. To stay on one version, pin it instead, e.g. `@florent-uzio/custody-mcp@0.1.0`.
33
+
26
34
  The server needs at least one **Environment**: a Ripple Custody instance together with the credentials of your Custody user on it. Set one up with the setup CLI below.
27
35
 
28
36
  ## Setup CLI
29
37
 
30
38
  The same `custody-mcp` command, run with a subcommand, manages your Environments. With no subcommand it starts the MCP server.
31
39
 
32
- | Command | What it does |
33
- | -------------------------------------------------- | -------------------------------------------------------------------- |
34
- | `npx @florent-uzio/custody-mcp add-env <name>` | Sets up a new Environment, step by step. |
35
- | `npx @florent-uzio/custody-mcp list-envs` | Shows every Environment and where its key is stored (never the key). |
36
- | `npx @florent-uzio/custody-mcp remove-env <name>` | Removes an Environment from the config file. |
37
- | `npx @florent-uzio/custody-mcp set-default <name>` | Makes an Environment the one read tools use when a call names none. |
40
+ | Command | What it does |
41
+ | --------------------------------------------------------- | -------------------------------------------------------------------- |
42
+ | `npx @florent-uzio/custody-mcp@latest add-env <name>` | Sets up a new Environment, step by step. |
43
+ | `npx @florent-uzio/custody-mcp@latest list-envs` | Shows every Environment and where its key is stored (never the key). |
44
+ | `npx @florent-uzio/custody-mcp@latest remove-env <name>` | Removes an Environment from the config file. |
45
+ | `npx @florent-uzio/custody-mcp@latest set-default <name>` | Makes an Environment the one read tools use when a call names none. |
38
46
 
39
47
  Changes take effect straight away: the server re-reads the config file on every tool call, so you do not need to restart your AI client.
40
48
 
41
49
  ### `add-env <name>`
42
50
 
43
51
  ```sh
44
- npx @florent-uzio/custody-mcp add-env dev
52
+ npx @florent-uzio/custody-mcp@latest add-env dev
45
53
  ```
46
54
 
47
55
  It walks you through these steps:
@@ -2316,7 +2316,7 @@ Error message: ${Q0e(r)}`,cause:r}),this[cRe]=!0,this.value=t}static isInstance(
2316
2316
  ${i}`));let l=Date.now(),d=await this.store.get(a),u=d&&d.secret!==n.delivery.secret?{secret:d.secret,until:l+this.settings.secretGraceMs}:d?.previousSecret&&d.previousSecret.until>l?d.previousSecret:void 0,m={id:a,principal:o,url:i,name:t.name,arguments:r,secret:n.delivery.secret,...u?{previousSecret:u}:{},expiresAt:c===null?null:l+c,createdAt:d?.createdAt??l};if(this.closed)throw new We(mn.Unsupported,"Server is shutting down");let f=A8(m.expiresAt),h=this.states.get(a);if(h){let P=this.statusOf(h);return h.record=m,this.scheduleExpiry(h),this.resume(h),await this.persist(m),{id:a,refreshBefore:f,cursor:h.watermark,deliveryStatus:P}}let g=t.buffer,S=g.read(n.cursor,{maxEvents:Number.POSITIVE_INFINITY,maxAgeMs:n.maxAgeMs,match:P=>this.matches(t,r,P)}),_=this.createState(m,t,n.cursor==null?g.head:S.from,g.lastSeq);for(let{cursor:P,...y}of S.events)this.enqueue(_,y,P??S.cursor);return this.skip(_,S.cursor),await this.persist(m),{id:a,refreshBefore:f,cursor:_.watermark,...S.truncated?{truncated:!0}:{}}}assertCapacity(t){let{maxSubscriptions:r,maxSubscriptionsPerPrincipal:n}=this.settings,o=this.states.size,a=0;for(let i of this.states.values())i.record.principal===t&&a++;for(let[i,s]of this.reserved)this.states.has(i)||(o++,s===t&&a++);if(o>=r||a>=n)throw new We(mn.ResourceExhausted,"Too many webhook subscriptions")}rememberVerified(t){let r=Date.now();for(let[o,a]of this.verified)a<=r&&this.verified.delete(o);this.verified.delete(t),this.verified.set(t,r+this.settings.verificationTtlMs);let n=this.settings.maxSubscriptions*2;for(let o of this.verified.keys()){if(this.verified.size<=n)break;this.verified.delete(o)}}async persist(t){try{await this.store.set(t)}catch(r){throw this.dropState(t.id),r}}async unsubscribe(t){let r=X8.subscriptionId(t);await this.store.delete(r),this.dropState(r)}close(){this.closed=!0;for(let t of this.unsubscribers.splice(0))t();for(let t of[...this.states.keys()])this.dropState(t)}grantTtl(t){let{defaultTtlMs:r,minTtlMs:n,maxTtlMs:o,allowNoExpiry:a}=this.settings;return t===void 0?r:t===null?a?null:o:Math.min(Math.max(t,n),o)}async resolveUrl(t){try{return await QEe(t,this.config.options.url)}catch(r){throw pPt(r)}}isVerified(t,r,n){if(this.config.options.allowedUrls?.(n))return!0;let o=this.verified.get(`${t}
2317
2317
  ${r}`);return o!==void 0&&o>Date.now()}async verify(t,r,n){let o=bU(24).toString("base64url"),a=await this.post(n,{subscriptionId:t,webhookId:`msg_verification_${bU(12).toString("hex")}`,secrets:[r],body:JSON.stringify({type:"verification",challenge:o})});if("error"in a)throw K8(a.error);if(!SU(a.status)||!_Pt(a.body,o))throw K8("challenge_failed")}createState(t,r,n,o){let a={record:t,event:r,horizon:o,queue:[],watermark:n,active:!0,suspendedMs:0,samples:[],lastDeliveryAt:null,lastError:null,failedSince:null,timers:new Set,closed:!1};return this.states.set(t.id,a),this.scheduleExpiry(a),a}onEmit(t,r){let n=(this.fanOutChains.get(t.name)??Promise.resolve()).then(()=>this.fanOut(t,r)).catch(o=>this.config.getLogger().error(`Webhook fan-out for "${t.name}" failed`,{error:o}));this.fanOutChains.set(t.name,n)}async fanOut(t,r){let n=await this.store.list(t.name),o=Date.now();for(let a of n){if(this.closed)return;if(a.expiresAt!==null&&a.expiresAt<=o){await this.expire(a.id);continue}let i=this.states.get(a.id);if(!i){if(!await this.store.get(a.id))continue;if(this.closed)return;i=this.states.get(a.id)??this.createState(a,t,t.buffer.cursorFor(r.seq-1),r.seq-1)}r.seq<=i.horizon||(this.matches(t,a.arguments,r.occurrence.data)?this.enqueue(i,r.occurrence,r.cursor):this.skip(i,r.cursor))}}matches(t,r,n){let{match:o}=t.definition;if(!o)return!0;try{return o(r,n)}catch(a){return this.config.getLogger().error(`Event filter for "${t.name}" failed`,{error:a}),!1}}skip(t,r){let n=t.queue.at(-1);n?n.advanceTo=r:t.watermark=r}enqueue(t,r,n){t.queue.length>=this.settings.maxPending&&this.overflow(t);let o={occurrence:r,advanceTo:n,settled:!1,inFlight:!1,attempts:0,suspendedMsAtStart:t.suspendedMs};t.queue.push(o),this.attempt(t,o)}overflow(t){for(let n of t.queue)n.settled=!0,this.clearTimer(t,n);let r=t.queue.at(-1);r&&(t.watermark=r.advanceTo),t.queue=[],this.sendControl(t,{type:"gap",cursor:t.watermark},1)}async attempt(t,r){if(t.closed||!t.active||r.settled||r.inFlight)return;if(this.isExpired(t)){await this.expire(t.record.id);return}r.timer=void 0,r.inFlight=!0,r.attempts++,r.firstAttemptAt===void 0&&(r.firstAttemptAt=Date.now(),r.suspendedMsAtStart=t.suspendedMs);let n=await this.deliver(t,{webhookId:r.occurrence.eventId,body:JSON.stringify({...r.occurrence,cursor:t.watermark})});if(r.inFlight=!1,t.closed||r.settled)return;if(!("error"in n)&&(SU(n.status)||n.status===410||n.status===413)){SU(n.status)&&this.recordOutcome(t,null),this.settle(t,r);return}this.recordOutcome(t,"error"in n?n.error:sPt(n.status));let o=Date.now()-r.firstAttemptAt-(t.suspendedMs-r.suspendedMsAtStart);if(r.attempts>=this.settings.maxAttempts||o>=this.settings.maxWindowMs){this.settle(t,r);return}this.maybeSuspend(t)||this.scheduleRetry(t,r)}scheduleRetry(t,r){let n=Math.min(this.settings.maxDelayMs,this.settings.initialDelayMs*2**(r.attempts-1)),o=n/2+this.random()*(n/2);r.timer=this.setTimer(t,()=>{this.attempt(t,r)},o)}settle(t,r){for(r.settled=!0,this.clearTimer(t,r);t.queue[0]?.settled;)t.watermark=t.queue.shift().advanceTo}recordOutcome(t,r){let n=Date.now();t.samples.push({at:n,ok:r===null});let o=n-this.settings.suspendWindowMs;for(;t.samples.length>0&&t.samples[0].at<o;)t.samples.shift();r===null?(t.lastDeliveryAt=n,t.lastError=null,t.failedSince=null):(t.lastError=r,t.failedSince??=n)}maybeSuspend(t){let{suspendMinAttempts:r,suspendFailureRate:n,suspendWindowMs:o}=this.settings;if(t.samples.length<r||t.samples.filter(a=>!a.ok).length/t.samples.length<n)return!1;t.active=!1,t.suspendedAt=Date.now();for(let a of t.queue)this.clearTimer(t,a);return this.config.getLogger().warn(`Suspended webhook delivery for subscription ${t.record.id}`,{lastError:t.lastError}),t.record.expiresAt===null&&this.setTimer(t,()=>{this.terminate(t)},o),!0}resume(t){if(!t.active){t.active=!0,t.suspendedMs+=Date.now()-(t.suspendedAt??Date.now()),t.suspendedAt=void 0,t.samples=[];for(let r of t.timers)clearTimeout(r);t.timers.clear(),this.scheduleExpiry(t);for(let r of t.queue)r.timer=void 0,r.settled||this.attempt(t,r)}}async terminate(t){t.closed||t.active||(await this.sendControl(t,{type:"terminated",error:{code:mn.CallbackEndpointError,message:"Webhook delivery failed repeatedly",data:{reason:t.lastError??"connection_refused"}}},1),await this.expire(t.record.id))}async sendControl(t,r,n){if(t.closed)return;let o=await this.deliver(t,{webhookId:`msg_${String(r.type)}_${bU(12).toString("hex")}`,body:JSON.stringify(r)});if(t.closed||!("error"in o)&&SU(o.status)||n>=this.settings.maxAttempts||r.type==="terminated")return;let a=Math.min(this.settings.maxDelayMs,this.settings.initialDelayMs*2**(n-1));this.setTimer(t,()=>{this.sendControl(t,r,n+1)},a)}async deliver(t,r){let{record:n}=t,o;try{o=await QEe(n.url,this.config.options.url)}catch{return{error:"connection_refused"}}let a=Date.now(),i=[n.secret];return n.previousSecret&&n.previousSecret.until>a&&i.push(n.previousSecret.secret),this.post(o,{subscriptionId:n.id,secrets:i,...r})}async post(t,r){let n=Math.floor(Date.now()/1e3);try{return await this.transport({url:t.url,address:t.address,family:t.family,timeoutMs:this.settings.timeoutMs,body:r.body,headers:{"content-type":"application/json",[gU.id]:r.webhookId,[gU.timestamp]:String(n),[gU.signature]:ebt({secret:r.secrets,id:r.webhookId,timestamp:n,body:r.body}),[gU.subscriptionId]:r.subscriptionId}})}catch(o){return this.config.getLogger().warn("Webhook transport failed",{error:o}),{error:"connection_refused"}}}statusOf(t){return{active:t.active,lastDeliveryAt:A8(t.lastDeliveryAt),lastError:t.lastError,...t.failedSince!==null?{failedSince:A8(t.failedSince)}:{}}}isExpired(t){return t.record.expiresAt!==null&&t.record.expiresAt<=Date.now()}expiryTimers=new Map;scheduleExpiry(t){let{id:r,expiresAt:n}=t.record,o=this.expiryTimers.get(r);if(o&&clearTimeout(o),this.expiryTimers.delete(r),n===null)return;let a=setTimeout(()=>{this.expiryTimers.delete(r),this.isExpired(t)?this.expire(r):this.scheduleExpiry(t)},Math.min(Math.max(n-Date.now(),0),dPt));a.unref?.(),this.expiryTimers.set(r,a)}async expire(t){this.dropState(t),await this.store.delete(t)}dropState(t){let r=this.states.get(t),n=this.expiryTimers.get(t);if(n&&clearTimeout(n),this.expiryTimers.delete(t),!!r){r.closed=!0;for(let o of r.timers)clearTimeout(o);r.timers.clear(),this.states.delete(t)}}setTimer(t,r,n){let o=setTimeout(()=>{t.timers.delete(o),r()},n);return o.unref?.(),t.timers.add(o),o}clearTimer(t,r){r.timer&&(clearTimeout(r.timer),t.timers.delete(r.timer),r.timer=void 0)}};function _Pt(e,t){let r;try{r=JSON.parse(e)}catch{return!1}if(typeof r!="object"||r===null||!("challenge"in r))return!1;let n=r.challenge;if(typeof n!="string")return!1;let o=Buffer.from(n),a=Buffer.from(t);return o.length===a.length&&rgt(o,a)}function W8(e){return zU("sha256").update(e).digest("base64url")}var Vd=class extends Error{scope;limit;kind;constructor(e,t,r="open"){super(`Too many ${r} MCP tasks ${e==="principal"?"for this principal":"in this store"} (limit ${t})`),this.scope=e,this.limit=t,this.kind=r,this.name="MCPTaskLimitError"}};function tAe(e){if(e)try{let t=JSON.parse(Buffer.from(e,"base64url").toString("utf8"));if(typeof t.before=="string"&&!Number.isNaN(Date.parse(t.before))&&Array.isArray(t.seen))return{before:t.before,seen:t.seen.filter(r=>typeof r=="string")}}catch{}}function rAe(e,t){if(!t)return!0;let r=Date.parse(e.createdAt),n=Date.parse(t.before);return r<n||r===n&&!t.seen.includes(e.taskKey)}function nAe(e,t){let r=e.at(-1);if(!r)return;let n=Date.parse(r.createdAt),o=e.filter(i=>Date.parse(i.createdAt)===n).map(i=>i.taskKey);t&&Date.parse(t.before)===n&&o.push(...t.seen);let a={before:new Date(n).toISOString(),seen:o};return Buffer.from(JSON.stringify(a)).toString("base64url")}function oAe(e,t){return!(t.status&&e.status!==t.status||t.surface&&e.context?.toolName!==t.surface||t.principalKey&&e.principalKey!==t.principalKey||t.taskKey&&e.taskKey!==t.taskKey)}function gZ(e){let t=e;return typeof t.list=="function"&&typeof t.updateByKey=="function"&&typeof t.cancelByKey=="function"}function aAe(e,t,r,n,o){return{taskKey:e,principalKey:t,...o!==void 0&&{clientLabel:o},...r&&{context:r},status:n.status,...n.statusMessage!==void 0&&{statusMessage:n.statusMessage},createdAt:n.createdAt,lastUpdatedAt:n.lastUpdatedAt,ttlMs:n.ttlMs,...n.status==="input_required"&&{inputRequests:n.inputRequests},...n.status==="completed"&&{result:n.result},...n.status==="failed"&&{error:n.error}}}var iAe=1,fPt=new Date("9999-12-31T00:00:00.000Z"),_0e=2**31-1,Z8=6e4,hPt="Worker process terminated before task completed",sAe="task lost: the server restarted while it was running",NU="Task expired before it finished",cAe=new Set(["completed","failed","cancelled","timed_out"]),IU=["pending","running","suspended"],CPt=3,gPt={working:["pending","running","suspended"],input_required:["suspended"],completed:["completed"],cancelled:["cancelled"],failed:["failed","timed_out",...IU]},yPt=50,vPt=1e3,k8=100;function SPt(e){return e.status==="running"||e.status==="pending"&&!!e.ownerId}function rA(e){return cAe.has(e.status)}function zg(e){let t=e.metadata;return t?.v===iAe&&typeof t.principalKey=="string"?t:void 0}function UU(e){let t=e.inputResponses??{};return Object.fromEntries(Object.entries(e.inputRequests??{}).filter(([r])=>!(r in t)))}function w8(e,t){return e.status==="suspended"&&Object.keys(t.inputRequests??{}).length>0&&Object.keys(UU(t)).length===0}function TPt(e,t){return e.status==="cancelled"||e.status==="timed_out"||e.status==="failed"&&VU(e,t)}function VU(e,t){return e.expiresAt!==void 0&&e.expiresAt.getTime()<=t}function f0e(e,t){return rA(e)&&e.expiresAt!==void 0&&e.expiresAt.getTime()+Z8<=t}function bPt(...e){return new Date(Math.max(...e.filter(t=>t!==void 0).map(t=>t.getTime())))}function h0e(e,t,r,n){let o={taskId:r,status:"working",createdAt:e.createdAt.toISOString(),lastUpdatedAt:bPt(e.createdAt,e.startedAt,e.suspendedAt,e.completedAt).toISOString(),ttlMs:t.ttlMs,pollIntervalMs:t.pollIntervalMs??fZ(e.createdAt.getTime(),n)},a=i=>({...o,status:"failed",error:{code:tA.InternalError,message:i}});if(!rA(e)&&VU(e,n))return a(NU);switch(e.status){case"pending":case"running":return o;case"suspended":{let i=UU(t);return Object.keys(i).length===0?o:{...o,status:"input_required",inputRequests:i}}case"completed":return{...o,status:"completed",result:{...e.result}};case"cancelled":return{...o,status:"cancelled"};case"timed_out":return a(VU(e,n)?NU:e.error?.message??"Task timed out");case"failed":{let i=e.error?.message??"Task failed";return a(i===hPt?sAe:i)}}}var C0e=class{#e;#t;#r;#a;#o;#n=new Map;#i=new Map;#l=new Set;#d;#u;#s=!1;#m;#g=0;constructor(e){this.#e=e.manager,this.#t=`mcp-server:${e.serverId}`,this.#r=e.workFor,this.#a=e.recoverFor,this.#d=e.onCancel,this.#o=e.logger}get owner(){return this.#t}#_(e){return`${this.#t}:${e}`}registerTool(e){let t=this.#_(e);this.#e.registerStaticExecutor(t,{execute:(r,n)=>{let{context:o}=r;return this.#I(this.#r(o),r,n)}}),this.#l.add(t)}async create(e,{pinToWorker:t=!1,clientLabel:r}={}){let n=e.context;if(!n||typeof n.toolName!="string")throw new Error("A background-task MCP task needs the tool it runs in its context");this.#l.has(this.#_(n.toolName))||this.registerTool(n.toolName),this.#c();let o=WRe(),a=wn(o),i=this.#a(n.toolName),s=W8(e.principal);e.limits&&await this.#f(e.limits,s);let c={v:iAe,principalKey:s,...r!==void 0&&{clientLabel:r},...e.trace&&{trace:{traceId:e.trace.traceId,spanId:e.trace.spanId}},ttlMs:e.ttlMs,...e.pollIntervalMs!==void 0&&{pollIntervalMs:e.pollIntervalMs},recover:i,usedInputKeys:[]},l=Date.now(),d={};d.promise=new Promise(h=>d.resolve=h),this.#n.set(o,d);let u=!0,m={executor:{execute:async(h,g)=>{let{work:S,options:_}=await d.promise,P=u?_.initialAttempt:void 0;return u=!1,P&&d.rowId&&this.#i.delete(d.rowId),this.#I(S,h,g,P)}}},f={taskKey:a,context:n};try{let{task:h,fallbackToSync:g}=await this.#e.enqueue({runId:a,toolName:this.#_(n.toolName),toolCallId:a,args:f,agentId:this.#t,resourceId:`${this.#t}:${s}`,timeoutMs:e.ttlMs===null?_0e:Math.min(Math.max(e.ttlMs,1),_0e),maxRetries:i?1:0,expiresAt:e.ttlMs===null?fPt:new Date(l+e.ttlMs),metadata:c},m,{pinToWorker:t});if(g)throw new Error("Background tasks are at capacity and backpressure is 'fallback-sync'; MCP tasks need 'queue'");d.rowId=h.id}catch(h){throw this.#n.delete(o),h}return{taskId:o,status:"working",createdAt:new Date(l).toISOString(),lastUpdatedAt:new Date(l).toISOString(),ttlMs:e.ttlMs,pollIntervalMs:e.pollIntervalMs??fZ(l,l)}}async#f(e,t){let r={agentId:this.#t,status:IU,dateFilterBy:"expiresAt",fromDate:new Date(Date.now()+1),perPage:1};if((await this.#e.listTasks({...r,resourceId:`${this.#t}:${t}`})).total>=e.perPrincipal)throw new Vd("principal",e.perPrincipal);if((await this.#e.listTasks(r)).total>=e.total)throw new Vd("total",e.total)}run(e,t,r={}){let n=this.#n.get(e);if(!n)throw new Error(`MCP task ${wn(e)} not found`);this.#n.delete(e);let{initialAttempt:o}=r;if(o&&n.rowId){let a=n.rowId;this.#i.set(a,o.abort);let i=()=>{this.#i.get(a)===o.abort&&this.#i.delete(a)};o.outcome.then(i,i)}n.resolve({work:t,options:r})}async get(e,t){this.#c();let r=await this.#b(wn(e),t);if(!r)return;let{row:n,envelope:o}=r;return rA(n)&&this.#e.deregisterTaskContext(n.id),w8(n,o)&&await this.#P(n,o),h0e(n,o,e,Date.now())}async update(e,t,r){let n=wn(e);return await this.#h(n,t,`with key "${n}"`,r)!=="not_found"}async cancel(e,t){return await this.#C(wn(e),t)!=="not_found"}async list(e={}){this.#c();let t=e.limit??50,r=tAe(e.cursor),n=Date.now(),o=[];for(let i=0;o.length<=t;i++){let{tasks:s}=await this.#e.listTasks({agentId:this.#t,...e.surface&&{toolName:this.#_(e.surface)},...e.principalKey&&{resourceId:`${this.#t}:${e.principalKey}`},...e.taskKey&&{toolCallId:e.taskKey},...e.status&&{status:gPt[e.status]},...r&&{toDate:new Date(Date.parse(r.before)+1)},orderBy:"createdAt",orderDirection:"desc",page:i,perPage:t+1});for(let c of s){let l=zg(c);if(!l||f0e(c,n))continue;let{context:d}=c.args,u=aAe(c.toolCallId,l.principalKey,d,h0e(c,l,"",n),l.clientLabel);if(!(!rAe(u,r)||!oAe(u,e))&&(o.push(u),o.length>t))break}if(s.length<=t)break}let a=o.slice(0,t);return{listings:a,...o.length>t&&{nextCursor:nAe(a,r)}}}updateByKey(e,t){return this.#h(e,t,`with key "${e}"`)}cancelByKey(e){return this.#C(e)}async#h(e,t,r,n){this.#c();let o=await this.#b(e,n);if(!o)return"not_found";let{row:a,envelope:i}=o,s=!1,c=await this.#e.getStorage();for(let l=0;l<CPt&&a.status==="suspended";l++){let d=UU(i),u=Object.entries(t).filter(([S])=>S in d);if(u.length===0)break;let m={...i,inputResponses:{...i.inputResponses,...Object.fromEntries(u)}},f=await c.updateTask(a.id,{metadata:m},{expectedStatus:"suspended",expectedSuspendedAt:a.suspendedAt??null}),h=await c.getTask(a.id),g=h&&zg(h);if(!h||!g)return"not_found";if(a=h,i=g,f&&u.every(([S])=>S in(i.inputResponses??{}))){s=!0;break}}if(!s&&a.status==="suspended"){let l=UU(i);if(Object.keys(t).some(d=>d in l&&!(d in(i.inputResponses??{}))))throw new Error(`Could not store the input responses for MCP task ${r}`)}return w8(a,i)&&await this.#P(a,i),s?"done":"ignored"}async traceOf(e,t){return(await this.#b(wn(e),t))?.envelope.trace}async#C(e,t){this.#c();let r=await this.#b(e,t);if(!r)return"not_found";if(rA(r.row))return"ignored";try{await this.#e.cancel(r.row.id)}catch(n){this.#o?.().warn("Could not cancel MCP task",{error:n})}return this.#p(r.row.id,new Error("Task cancelled")),this.#y(r.row),"done"}#y(e){let t=this.#d;if(!t)return;let r=e.args.context;Promise.resolve().then(()=>t(r)).catch(n=>this.#o?.().warn("Could not stop what a cancelled MCP task stands for",{error:n}))}#v(e){(async()=>{for(let r=yPt;!this.#s;r=Math.min(r*2,vPt)){let n;try{n=(await this.#e.listTasks({agentId:this.#t,toolCallId:e,perPage:1})).tasks[0]}catch(o){this.#o?.().warn("Could not read an aborted MCP task attempt; reading it again",{error:o}),await new Promise(a=>setTimeout(a,r));continue}if(!n)return;if(TPt(n,Date.now()))return this.#y(n);if(n.status!=="running"||n.ownerId!==this.#e.ownerId)return;await new Promise(o=>setTimeout(o,r))}})().catch(r=>this.#o?.().warn("Could not check why an MCP task attempt was aborted",{error:r}))}#p(e,t){let r=this.#i.get(e);r&&(this.#i.delete(e),r(t))}async close(){this.#s=!0,clearInterval(this.#u),this.#u=void 0,await this.#m?.catch(()=>{});for(let e of this.#l)this.#e.unregisterStaticExecutor(e);this.#l.clear()}sweep(){return this.#s?Promise.resolve():(this.#m??=this.#S().finally(()=>this.#m=void 0),this.#m)}startSweeping(){this.#c()}#c(){this.#u||this.#s||(this.#u=setInterval(()=>{this.sweep().catch(e=>this.#o?.().debug("MCP task sweep failed",{error:e}))},this.#e.config.leaseDurationMs),this.#u.unref?.())}async#S(){let e=await this.#e.getStorage(),t=Date.now(),r=this.#e.config.leaseDurationMs;for(let s of this.#i.keys()){if(this.#s)return;let c=await e.getTask(s).catch(()=>{});c&&rA(c)&&!this.#s&&this.#p(s,new Error("Task cancelled"))}if(this.#s||(e.supportsTaskExpiry&&await e.deleteTasks({agentId:this.#t,status:[...cAe],dateFilterBy:"expiresAt",toDate:new Date(t-Z8)}).catch(s=>this.#o?.().debug("Could not delete expired MCP tasks",{error:s})),this.#s))return;let{tasks:n}=await this.#e.listTasks({agentId:this.#t,status:IU,dateFilterBy:"expiresAt",toDate:new Date(t+1),perPage:k8});for(let s of n){if(this.#s)return;zg(s)&&await this.#E(e,s).catch(c=>this.#o?.().debug("Could not expire MCP task",{error:c}))}if(this.#s)return;let{tasks:o}=await this.#e.listTasks({agentId:this.#t,status:IU,orderBy:"createdAt",orderDirection:"asc",page:this.#g,perPage:k8});this.#g=o.length<k8?0:this.#g+1;let a=[];for(let s of o){if(this.#s)return;let c=zg(s);if(!(!c||VU(s,t)))try{w8(s,c)?await this.#P(s,c):SPt(s)&&(!s.leaseExpiresAt||s.leaseExpiresAt.getTime()<=t)?await this.#R(e,s,c)&&!this.#s&&a.push(s.id):s.status==="pending"&&!s.ownerId&&t-s.createdAt.getTime()>=r&&a.push(s.id)}catch(l){this.#o?.().debug("Could not sweep MCP task",{error:l})}}if(a.length===0||this.#s)return;let i=await this.#T(e);for(let s of a.slice(0,i)){if(this.#s)return;await this.#A(s)}}async#T(e){let{globalConcurrency:t,perAgentConcurrency:r,perOwnerConcurrency:n}=this.#e.config,o=n&&Object.hasOwn(n,this.#t)?n[this.#t]:r,[a,i]=await Promise.all([e.getRunningCount(),e.getRunningCountByAgent(this.#t)]);return Math.max(0,Math.min(t-a,o-i))}async#E(e,t){await this.#e.cancel(t.id),this.#p(t.id,new Error(NU)),this.#y(t),await e.updateTask(t.id,{status:"failed",error:{message:NU},completedAt:new Date,expiresAt:new Date(Date.now()+Z8)},{expectedStatus:"cancelled"})}async#R(e,t,r){let n={expectedStatus:t.status,expectedOwnerId:t.ownerId??null,expectedLeaseExpiresAt:t.leaseExpiresAt??null};return r.recover&&t.retryCount<t.maxRetries?await e.updateTask(t.id,{status:"pending",retryCount:t.retryCount+1,ownerId:void 0,leaseExpiresAt:void 0},n):(await e.updateTask(t.id,{status:"failed",error:{message:sAe},completedAt:new Date,ownerId:void 0,leaseExpiresAt:void 0},n),!1)}async#A(e){try{await this.#e.restart(e)}catch(t){this.#o?.().debug("Could not restart MCP task",{error:t})}}async#b(e,t){let r=t&&W8(t.principal),{tasks:n}=await this.#e.listTasks({agentId:this.#t,...r!==void 0&&{resourceId:`${this.#t}:${r}`},toolCallId:e,perPage:1}),o=n[0],a=o&&zg(o);if(!(!o||!a||r!==void 0&&a.principalKey!==r)&&!f0e(o,Date.now()))return{row:o,envelope:a}}async#P(e,t){let r={inputResponses:t.inputResponses??{},suspendPayload:e.suspendPayload};try{await this.#e.resume(e.id,r,{onNoSlot:"queue"})}catch(n){this.#o?.().debug("Could not resume MCP task",{error:n})}}async#k(e,t){let{tasks:r}=await this.#e.listTasks({agentId:this.#t,toolCallId:e,perPage:1}),n=r[0],o=n&&zg(n);if(!n||!o?.trace)return;let a=(o.attempts??0)+1;return await(await this.#e.getStorage()).updateTask(n.id,{metadata:{...o,attempts:a}},{expectedStatus:"running",expectedOwnerId:n.ownerId??null}).catch(()=>{}),{...o.trace,taskKey:e,attempt:a,resumed:t,recovered:!t&&a>1}}async#I(e,t,r,n){let o=r?.abortSignal??new AbortController().signal,a=r?.resumeData,i=await this.#k(t.taskKey,a!==void 0),s;if(n){let S=()=>n.abort(o.reason);o.aborted?S():o.addEventListener("abort",S,{once:!0}),s=n.outcome.finally(()=>o.removeEventListener("abort",S))}else{let S={signal:o,...i&&{trace:i},...a&&{resume:{inputResponses:a.inputResponses,suspendPayload:a.suspendPayload}}};s=Promise.resolve().then(()=>e(S))}let c=await new Promise((S,_)=>{let P=()=>{_(o.reason??new Error("Task cancelled")),this.#v(t.taskKey)};o.aborted?P():o.addEventListener("abort",P,{once:!0}),s.then(S,_).finally(()=>o.removeEventListener("abort",P))});if(c.status==="completed")return c.result;let l=await this.#e.getStorage(),{tasks:d}=await this.#e.listTasks({agentId:this.#t,toolCallId:t.taskKey,perPage:1}),u=d[0],m=u&&zg(u);if(!u||!m)throw new Error("MCP task row is gone");let f=Object.keys(c.inputRequests);if(f.length===0)throw new Error("Task asked for input without any input request");let h=f.find(S=>m.usedInputKeys.includes(S));if(h!==void 0)throw new Error(`Input request key "${h}" was already used by this task`);let g={...m,inputRequests:c.inputRequests,inputResponses:{},usedInputKeys:[...m.usedInputKeys,...f]};if(!await l.updateTask(u.id,{metadata:g},{expectedStatus:"running",expectedOwnerId:this.#e.ownerId}))throw new Error("MCP task is no longer running here");if(!r?.suspend)throw new Error("Background task runtime cannot suspend this task");await r.suspend(c.suspendPayload)}},PPt=6e4,g0e=2147483647,FU=class{#e=new Map;#t=new Map;#r=new Map;#a=0;#o=new Map;#n;#i;#l;constructor(e={}){this.#n=e.now??Date.now,this.#i=e.expiredRetentionMs??PPt,this.#l=e.onCancel}async create(e,{clientLabel:t}={}){y0e("ttlMs",e.ttlMs),y0e("pollIntervalMs",e.pollIntervalMs);let r=this.#r.get(e.principal)??0,n=this.#o.get(e.principal)??0,{limits:o}=e;if(o){if(r>=o.perPrincipal)throw new Vd("principal",o.perPrincipal);if(this.#a>=o.total)throw new Vd("total",o.total);if(o.retainedPerPrincipal!==void 0&&n>=o.retainedPerPrincipal)throw new Vd("principal",o.retainedPerPrincipal,"retained");if(o.retainedTotal!==void 0&&this.#e.size>=o.retainedTotal)throw new Vd("total",o.retainedTotal,"retained")}let a=this.#n(),i=WRe(),s={taskId:i,taskKey:wn(i),principal:e.principal,clientLabel:t,context:e.context,...e.trace&&{trace:e.trace},attempts:0,status:"working",createdAt:a,lastUpdatedAt:a,ttlMs:e.ttlMs,pollIntervalMs:e.pollIntervalMs,usedInputKeys:new Set,abort:new AbortController};return s.ttlMs!==null&&this.#C(s,a+s.ttlMs,()=>this.#y(s)),this.#e.set(s.taskId,s),this.#t.set(s.taskKey,s),this.#r.set(s.principal,r+1),this.#a++,this.#o.set(s.principal,n+1),this.#S(s)}async get(e,t){let r=this.#f(wn(e),t);return r&&this.#T(r)}async update(e,t,r){return this.#d(wn(e),t,r)!=="not_found"}async cancel(e,t){return this.#u(wn(e),t)!=="not_found"}async list(e={}){let t=e.limit??50,r=tAe(e.cursor),n=[],o=this.#n();for(let i of[...this.#e.values()].reverse()){if(i.purgeAt!==void 0&&o>=i.purgeAt){this.#h(i);continue}if(e.taskKey&&i.taskKey!==e.taskKey)continue;let s=aAe(i.taskKey,W8(i.principal),i.context,this.#T(i),i.clientLabel);if(!(!rAe(s,r)||!oAe(s,e))&&(n.push(s),n.length>t))break}let a=n.slice(0,t);return{listings:a,...n.length>t&&{nextCursor:nAe(a,r)}}}async updateByKey(e,t){return this.#d(e,t)}async cancelByKey(e){return this.#u(e)}#d(e,t,r){let n=this.#f(e,r);if(!n)return"not_found";let o=n.pendingInput;if(!o)return"ignored";let a=!1;for(let[i,s]of Object.entries(t))Object.hasOwn(o.requests,i)&&!o.responses.has(i)&&(o.responses.set(i,s),a=!0);return Object.keys(o.requests).every(i=>o.responses.has(i))&&(n.pendingInput=void 0,this.#p(n,"working"),o.resolve(Object.fromEntries(o.responses))),a?"done":"ignored"}#u(e,t){let r=this.#f(e,t);return r?Sb(r.status)?"ignored":(this.#c(r,"cancelled",new Error("Task was cancelled")),this.#v(r),"done"):"not_found"}async traceOf(e,t){return this.#f(wn(e),t)?.trace}has(e){return this.#e.has(e)}context(e){return this.#e.get(e)?.context}run(e,t,r={}){let n=this.#e.get(e);if(!n)throw new Error(`MCP task ${wn(e)} not found`);let o=r.initialAttempt;if(!o){this.#s(n,t);return}n.attempts++;let a=n.abort.signal,i=()=>o.abort(a.reason);a.aborted?i():a.addEventListener("abort",i,{once:!0}),this.#m(n,t,o.outcome.finally(()=>a.removeEventListener("abort",i)))}#s(e,t,r){e.attempts++;let n={signal:e.abort.signal,...r&&{resume:r},...e.trace&&{trace:{...e.trace,taskKey:wn(e.taskId),attempt:e.attempts,resumed:r!==void 0,recovered:!1}}};this.#m(e,t,Promise.resolve().then(()=>t(n)))}#m(e,t,r){r.then(n=>this.#g(e,t,n),n=>this.#_(e,{code:tA.InternalError,message:n instanceof Error?n.message:String(n)}))}#g(e,t,r){if(Sb(e.status))return;if(r.status==="completed"){e.result=r.result,this.#c(e,"completed");return}let n=Object.keys(r.inputRequests),o=n.find(i=>e.usedInputKeys.has(i));if(n.length===0||o!==void 0){this.#_(e,{code:tA.InternalError,message:o===void 0?"Task asked for input without any input request":`Input request key "${o}" was already used by this task`});return}for(let i of n)e.usedInputKeys.add(i);let{suspendPayload:a}=r;e.pendingInput={requests:r.inputRequests,responses:new Map,resolve:i=>this.#s(e,t,{inputResponses:i,suspendPayload:a}),reject:()=>{}},this.#p(e,"input_required")}#_(e,t){Sb(e.status)||(e.error=t,this.#c(e,"failed",new Error(t.message)))}handle(e){let t=this.#e.get(e);if(!t)throw new Error(`MCP task ${wn(e)} not found`);let r=()=>!Sb(t.status);return{taskId:e,signal:t.abort.signal,status:n=>{r()&&(t.statusMessage=n,t.lastUpdatedAt=this.#n())},requireInput:n=>{if(!r())return Promise.reject(new Error(`MCP task ${wn(e)} is no longer running`));if(t.pendingInput)return Promise.reject(new Error(`MCP task ${wn(e)} is already waiting for input`));let o=Object.keys(n);if(o.length===0)return Promise.reject(new Error("requireInput needs at least one input request"));let a=o.find(i=>t.usedInputKeys.has(i));if(a!==void 0)return Promise.reject(new Error(`Input request key "${a}" was already used by MCP task ${wn(e)}`));for(let i of o)t.usedInputKeys.add(i);return new Promise((i,s)=>{t.pendingInput={requests:n,responses:new Map,resolve:i,reject:s},this.#p(t,"input_required")})},complete:n=>{r()&&(t.result=n,this.#c(t,"completed"))},fail:n=>{r()&&(t.error=n,this.#c(t,"failed"))}}}close(){for(let e of this.#e.values())clearTimeout(e.expiryTimer),Sb(e.status)||this.#c(e,"cancelled",new Error("MCP task store closed"));this.#e.clear(),this.#t.clear(),this.#r.clear(),this.#a=0,this.#o.clear()}#f(e,t){let r=this.#t.get(e);if(!(!r||t&&r.principal!==t.principal)){if(r.purgeAt!==void 0&&this.#n()>=r.purgeAt){this.#h(r);return}return r}}#h(e){if(this.#e.get(e.taskId)!==e)return;clearTimeout(e.expiryTimer),this.#e.delete(e.taskId),this.#t.delete(e.taskKey);let t=this.#o.get(e.principal)-1;t===0?this.#o.delete(e.principal):this.#o.set(e.principal,t)}#C(e,t,r){let n=t-this.#n();e.expiryTimer=n>g0e?setTimeout(()=>this.#C(e,t,r),g0e):setTimeout(r,n),e.expiryTimer.unref?.()}#y(e){Sb(e.status)||(e.error={code:tA.InternalError,message:"Task expired before it finished"},this.#c(e,"failed",new Error("Task expired")),this.#v(e)),e.purgeAt=this.#n()+this.#i,this.#C(e,e.purgeAt,()=>this.#h(e))}#v(e){let t=this.#l,r=e.context;!t||!r||Promise.resolve().then(()=>t(r)).catch(()=>{})}#p(e,t){e.status=t,e.lastUpdatedAt=this.#n()}#c(e,t,r){let n=this.#r.get(e.principal)-1;n===0?this.#r.delete(e.principal):this.#r.set(e.principal,n),this.#a--,this.#p(e,t);let o=e.pendingInput;e.pendingInput=void 0,o?.reject(r??new Error(`MCP task "${wn(e.taskId)}" ${t} while waiting for input`)),r&&e.abort.abort(r)}#S(e){return{taskId:e.taskId,status:e.status,...e.statusMessage!==void 0&&{statusMessage:e.statusMessage},createdAt:new Date(e.createdAt).toISOString(),lastUpdatedAt:new Date(e.lastUpdatedAt).toISOString(),ttlMs:e.ttlMs,pollIntervalMs:e.pollIntervalMs??fZ(e.createdAt,this.#n())}}#T(e){let t=this.#S(e);switch(e.status){case"input_required":{let{requests:r,responses:n}=e.pendingInput,o=Object.fromEntries(Object.entries(r).filter(([a])=>!n.has(a)));return{...t,status:"input_required",inputRequests:o}}case"completed":return{...t,status:"completed",result:{...e.result}};case"failed":return{...t,status:"failed",error:{...e.error}};case"working":case"cancelled":return{...t,status:e.status}}}};function y0e(e,t){if(t!=null&&(!Number.isSafeInteger(t)||t<0))throw new RangeError(`${e} must be a non-negative integer, got ${t}`)}var IPt=["off","auto","always"],v0e=2e3,EPt=100,RPt=1e3,APt=1e3,kPt=1e4;function Ud(e){return new Xr({id:"MCP_SERVER_TASKS_INVALID_CONFIG",domain:Zn.MCP,category:Jn.USER,text:e})}function S0e(e,t){if(!IPt.includes(t))throw Ud(`${e} has an unknown task mode "${String(t)}"; use 'off', 'auto' or 'always'.`)}function O8(e,t){if(t!=null&&(!Number.isSafeInteger(t)||t<0))throw Ud(`${e} must be a non-negative integer number of milliseconds, got ${t}.`)}function T0e(e,t){if(t!==void 0&&typeof t!="boolean")throw Ud(`${e} must be a boolean, got ${String(t)}.`)}function b0e(e,t){if(t!==void 0){if(!Number.isSafeInteger(t)||t<1)throw Ud(`${e} must be a positive integer, got ${t}.`);return t}}function P0e([e,t,r],[n,o,a]){let i=b0e(e,t),s=b0e(n,o);if(i!==void 0&&s!==void 0&&s<i)throw Ud(`${n} (${s}) must be at least ${e} (${i}).`);return[i??Math.min(r,s??r),s??Math.max(a,i??0)]}function wPt(e,t){let r=e??{},n=r.default??"off";S0e("tasks.default",n),O8("tasks.ttlMs",r.ttlMs);let o=r.ttlMs===void 0?fbt:r.ttlMs;T0e("tasks.recover",r.recover);let a=r.recover??!1,[i,s]=P0e(["tasks.maxOpenTasksPerClient",r.maxOpenTasksPerClient,EPt],["tasks.maxRetainedTasksPerClient",r.maxRetainedTasksPerClient,APt]),[c,l]=P0e(["tasks.maxOpenTasks",r.maxOpenTasks,RPt],["tasks.maxRetainedTasks",r.maxRetainedTasks,kPt]),d={perPrincipal:i,total:c,retainedPerPrincipal:s,retainedTotal:l},u=(y,T,D,R)=>{let E=new Set(T),I=new Set(t.tools),O=new Map,N=r[y]??{},V=y.slice(0,-1);for(let[F,G]of Object.entries(N)){if(!E.has(F))throw Ud(`tasks.${y} names "${F}", which is not one of this server's ${y}.`);let H=`tasks.${y}.${F}`;if(y!=="tools"&&I.has(D(F)))throw Ud(`${H} names a ${V} hidden by the tool "${D(F)}", so it is never served.`);let X=typeof G=="string"?{mode:G}:G;if(S0e(H,X.mode),O8(`${H}.syncWindowMs`,X.syncWindowMs),O8(`${H}.ttlMs`,X.ttlMs),y==="workflows"&&"recover"in X)throw Ud(`${H}.recover is not a ${V} option: a workflow task always re-attaches to its run.`);T0e(`${H}.recover`,X.recover),O.set(D(F),{mode:X.mode,syncWindowMs:X.syncWindowMs??v0e,ttlMs:X.ttlMs===void 0?o:X.ttlMs,recover:R(X)})}return O},m=u("tools",t.tools,y=>y,y=>y.recover??a),f=u("workflows",t.workflows??[],y=>`run_${y}`,()=>!0),h=u("agents",t.agents??[],y=>`ask_${y}`,y=>y.recover??a),g={mode:n,syncWindowMs:v0e,ttlMs:o,recover:a},S=(y,T)=>T==="workflow"?f.get(y)??{...g,recover:!0}:T==="agent"?h.get(y)??g:m.get(y)??g,_=new Map([...t.tools.map(y=>[y,void 0]),...(t.workflows??[]).map(y=>[`run_${y}`,"workflow"]),...(t.agents??[]).map(y=>[`ask_${y}`,"agent"])]),P=new Set(r.required??[]);for(let y of P){if(!_.has(y))throw Ud(`tasks.required names "${y}", which is not one of this server's tools.`);if(S(y,_.get(y)).mode==="off")throw Ud(`tasks.required names "${y}", which is not served as a task.`)}return{enabled:n!=="off"||[...m.values(),...f.values(),...h.values()].some(y=>y.mode!=="off"),store:r.store,limits:d,required:P,authorize:r.authorize,modeFor:S}}var YR=class{#e;#t;#r;#a=[];constructor(e){this.#e=e}resolve(){return this.#n()}reselect(){let e=this.#r;if(e){if(!(e instanceof FU))return;this.#a.push(e),this.#r=void 0,this.#t=void 0;return}let t=this.#t;if(!t)return;let r=t.then(()=>(this.reselect(),this.#n()));r.catch(()=>{}),this.#t=r}close(){for(let e of[...this.#a.splice(0),this.#r])e instanceof FU&&e.close()}#o(e){let t=this.#a.find(r=>r.has(e));return t?Promise.resolve(t):this.#n()}#n(){return this.#t??=this.#e().then(e=>this.#r=e,e=>{throw this.#t=void 0,e}),this.#t}async create(e){return(await this.#n()).create(e)}async get(e,t){return(await this.#o(e)).get(e,t)}async update(e,t,r){return(await this.#o(e)).update(e,t,r)}async cancel(e,t){return(await this.#o(e)).cancel(e,t)}async traceOf(e,t){return(await this.#o(e)).traceOf?.(e,t)}run(e,t,r){let n=this.#a.find(o=>o.has(e))??this.#r;if(!n)throw new Error(`MCP task ${wn(e)} not found`);n.run(e,t,r)}async admin(){let e=await this.#n();return gZ(e)?e:void 0}},yZ="mastra.ai/mcp-task-result";function D8(e){return{isError:!0,content:[{type:"text",text:"MCP task created"}],_meta:{[yZ]:e}}}function lAe(e){return e?._meta?.[yZ]}function OPt(e){let t=e.send.bind(e);e.send=(r,n)=>{let o="result"in r?r.result:void 0,a=lAe(o);if(a){let{[yZ]:i,...s}=o._meta;r={...r,result:{...a,resultType:KRe,...Object.keys(s).length>0?{_meta:s}:{}}}}return t(r,n)}}function TU(){return new We(tA.InvalidParams,"Task not found")}function DPt(e,t){let{store:r,authorize:n}=t,o=t.trace??((i,s,c,l,d)=>d(new Wn)),a=(i,s,c,l,d)=>{let u=wn(s.taskId);return o(e,i,{mcpTaskKey:u},c,async m=>{let f=await t.principal(c,m);if(n){let h=await r.get(s.taskId,{principal:f});if(!h||!await n({principal:f,task:h,requestContext:m}))throw TU()}return l({principal:f})},{parentOf:r.traceOf?async m=>r.traceOf(s.taskId,{principal:await t.principal(c,m)}):void 0,metadata:{mcpTaskKey:u,...d}})};e.setRequestHandler(vb.get,{params:Sbt,result:Pbt},(i,s)=>o(e,vb.get,{},s,async c=>{let l=await t.principal(s,c),d=await r.get(i.taskId,{principal:l});if(!d||n&&!await n({principal:l,task:d,requestContext:c}))throw TU();return d},{untraced:!0})),e.setRequestHandler(vb.update,{params:bbt,result:e0e},(i,s)=>a(vb.update,i,s,async c=>{let l=s.mcpReq.inputResponses??{};if(!await r.update(i.taskId,l,c))throw TU();return{}})),e.setRequestHandler(vb.cancel,{params:Tbt,result:e0e},(i,s)=>a(vb.cancel,i,s,async c=>{if(!await r.cancel(i.taskId,c))throw TU();return{}},{mcpTaskStatus:"cancelled"}))}function MPt(e,t){let r=[];for(let[n,o]of t){let{mode:a,recover:i,ttlMs:s}=e.modeFor(n,o);a!=="off"&&r.push({name:n,type:o==="workflow"?"workflow":o==="agent"?"agent":"tool",mode:a,recover:i,required:e.required.has(n),ttlMs:s})}return r}function xPt(e,t){return{enabled:e.enabled,listable:!e.store||gZ(e.store),surfaces:MPt(e,t)}}function LPt(e){let t=async()=>{let r=await e();if(!r)throw new Error("This MCP task store cannot be listed");return r};return{async list(r={}){let{listings:n,nextCursor:o}=await(await t()).list(r);return{tasks:n.map(I0e),...o&&{nextCursor:o}}},async find(r){let{listings:n}=await(await t()).list({taskKey:wn(r),limit:1});return n[0]&&I0e(n[0])},async cancel(r){return await(await t()).cancelByKey(r)==="done"},async answer(r,n){return await(await t()).updateByKey(r,n)==="done"}}}function I0e(e){let t=e.context?.toolName;return{taskKey:e.taskKey,surface:typeof t=="string"?t:"",status:e.status,...e.statusMessage!==void 0&&{statusMessage:e.statusMessage},createdAt:e.createdAt,lastUpdatedAt:e.lastUpdatedAt,...e.ttlMs!==null&&{expiresAt:new Date(new Date(e.createdAt).getTime()+e.ttlMs).toISOString()},principalKey:e.principalKey,...e.clientLabel!==void 0&&{clientLabel:e.clientLabel},...e.inputRequests&&{inputRequests:e.inputRequests},...e.result&&{result:e.result},...e.error&&{error:e.error}}}function M8(e){return e?.kind==="workflow"&&typeof e.runId=="string"&&typeof e.workflowKey=="string"}var $Pt=new Set(["success","failed","tripwire","canceled","bailed"]);function NPt(e){return $Pt.has(e)}function UPt(e){return{status:e.status,input:e.input,steps:e.steps,result:e.result}}function E0e(e){let t=e.error;return typeof t=="string"?t:t&&typeof t.message=="string"?t.message:"unknown error"}function VPt(e,t){for(let[r,n]of Object.entries(t.steps??{})){let o=n;if(o?.status!=="suspended")continue;let{__workflow_meta:a,...i}=o.suspendPayload??{},s=a?.path,c=[r,...Array.isArray(s)?s.filter(l=>typeof l=="string"):[]];return{path:c,step:FPt(e,c),payload:i,suspendedAt:o.suspendedAt}}}function R0e(e,t){let r=e.steps?.[t.step[0]];return e.status!=="suspended"||r?.status!=="suspended"?!1:t.suspendedAt===void 0||r.suspendedAt===t.suspendedAt}function FPt(e,t){let r=e.steps,n;for(let o of t){if(n=r?.[o],!n)return;r=n.steps}return n}var A0e={type:"object",properties:{}},dAe=class extends VTe{mcpVersion=2;resources;prompts;toolActions;events;resourceOptions;promptOptions;appResourceList=[];appResourceHtml=new Map;jsonSchemaValidator;cacheHints;mapAuthInfoToUser;fga;requestStateCodec;eventRegistry;eventsOptions;eventsInspector;tasksInspector;webhooks;legacy;tasks;taskStore;executingRuns=new Map;backgroundTaskStore;httpHandler;nodeHandler;stdioHandle;stdioInstance;constructor(e){super(e),this.jsonSchemaValidator=e.jsonSchemaValidator,this.cacheHints=e.cacheHints,this.mapAuthInfoToUser=e.mapAuthInfoToUser,this.fga=e.fga,this.promptOptions=e.prompts,this.resourceOptions=e.resources,this.loadAppResources(e.appResources),this.requestStateCodec=this.createRequestStateCodec(e.requestState),this.eventsOptions=e.eventsOptions??{},this.legacy=e.legacy??"reject",this.tasks=wPt(e.tasks,{tools:Object.keys(e.tools??{}),workflows:Object.keys(e.workflows??{}),agents:Object.keys(e.agents??{})}),this.hasStableId=!!e.id,this.taskStore=this.tasks.store??new YR(()=>this.selectTaskStore());let t=this.eventsOptions.webhook;this.eventRegistry=new Hbt(e.events,{webhook:t!==void 0}),t&&this.eventRegistry.size>0&&(this.webhooks=new mPt({registry:this.eventRegistry,options:t,getLogger:()=>this.logger})),e.resources?.resumeSchema&&this.assertFormRepresentable("resources",e.resources.resumeSchema),e.prompts?.resumeSchema&&this.assertFormRepresentable("prompts",e.prompts.resumeSchema);let r={getLogger:()=>this.logger,getNotifier:()=>this.notifier()};this.toolActions=new Ubt({...r,addTools:n=>this.addTools(n),removeTools:n=>this.removeTools(n)}),this.resources=new Vbt(r),this.prompts=new Fbt(r),this.events=new zbt(this.eventRegistry,()=>this.eventRegistry.list().map(n=>hZ(n,(o,a)=>this.jsonSchema(o,a))))}createRequestStateCodec(e){let t=e?.key;return t===void 0&&(t=globalThis.crypto.getRandomValues(new Uint8Array(32)),this.logger.warn("No requestState.key configured: continuation state is signed with a per-process key, so a suspended request can only be resumed on this process. Set requestState.key in multi-instance and serverless deployments.")),CEe({key:t,ttlSeconds:e?.ttlSeconds})}convertTools(e,t,r){let n={},o=(a,i)=>{let s=_9(i,{name:a,requestContext:new Wn,tracingContext:{},mastra:this.mastra,logger:this.logger,description:"description"in i?i.description:void 0});n[a]={...s,id:a}};for(let[a,i]of Object.entries(e)){if(!i||!("execute"in i)||typeof i.execute!="function"){this.logger.warn("Tool has no execute function, skipping",{tool:a});continue}"resumeSchema"in i&&i.resumeSchema&&this.assertFormRepresentable(`tool '${a}'`,i.resumeSchema),o(a,i)}for(let[a,i]of Object.entries(t??{})){let s=i.getDescription();if(!s)throw new Xr({id:"MCP_SERVER_AGENT_OR_WORKFLOW_TOOL_CONVERSION_FAILED",domain:Zn.MCP,category:Jn.USER,text:`Agent '${i.name}' (key: '${a}') must have a non-empty description to be used in an MCPServer.`});let c=`ask_${a}`;if(n[c]){this.logger.warn(`Tool '${c}' already exists; agent '${a}' is not exposed.`);continue}o(c,Fu({id:c,description:`Ask agent '${i.name}' a question. Agent description: ${s}`,inputSchema:{type:"object",properties:{message:{type:"string",description:"The question or input for the agent."}},required:["message"],additionalProperties:!1},mcp:{toolType:"agent"},execute:async(l,d)=>{let{message:u}=l;return i.generate(u,{requestContext:d?.requestContext,tracingContext:d?.tracingContext,abortSignal:d?.abortSignal})}}))}for(let[a,i]of Object.entries(r??{})){if(!i.description)throw new Xr({id:"MCP_SERVER_AGENT_OR_WORKFLOW_TOOL_CONVERSION_FAILED",domain:Zn.MCP,category:Jn.USER,text:`Workflow '${i.id}' (key: '${a}') must have a non-empty description to be used in an MCPServer.`});let s=`run_${a}`;if(n[s]){this.logger.warn(`Tool '${s}' already exists; workflow '${a}' is not exposed.`);continue}o(s,Fu({id:s,description:`Run workflow '${a}'. Workflow description: ${i.description}`,inputSchema:i.inputSchema,mcp:{toolType:"workflow"},execute:async(c,l)=>(await i.createRun({runId:l?.requestContext?.get("runId")})).start({inputData:c,requestContext:l?.requestContext,tracingContext:l?.tracingContext})}))}return this.logger.info(`${Object.keys(n).length} tools registered`),n}assertFormRepresentable(e,t){let r=w0e(e,this.formSchema(t));if(r instanceof Promise||r.issues)throw new Xr({id:"MCP_SERVER_RESUME_SCHEMA_NOT_REPRESENTABLE",domain:Zn.MCP,category:Jn.USER,text:`The resumeSchema of ${e} cannot be presented as an input request: it must be an object whose properties are strings, numbers, booleans or enums.`,details:{owner:e,issues:JSON.stringify(r instanceof Promise?"async":r.issues)}})}representableForm(e){let t=e?this.formSchema(e):{type:"object",properties:{}},r=w0e("form",t);return r instanceof Promise||r.issues?void 0:t}formSchema(e){let t=Yr(e)?e:Sr(e),{$schema:r,...n}=this.jsonSchema(t,{io:"input"})??{};return Object.keys(n).length?n:{type:"object",properties:{}}}jsonSchema(e,t){if(!e)return;if(!Yr(e))return e.jsonSchema??e;let r=yn(e,{...t,target:"draft-2020-12"});return!r.$schema||r.$schema==="https://json-schema.org/draft/2020-12/schema"?r:dbt(r)??r}addTools(e){let t=this.convertTools(e);for(let r of Object.keys(t))this.convertedTools[r]&&this.logger.warn(`Tool '${r}' already exists and will be replaced.`);if(this.convertedTools={...this.convertedTools,...t},this.originalTools={...this.originalTools,...e},this.mastra)for(let[r,n]of Object.entries(e))zPt(n)&&this.mastra.addTool(n,this.mastraToolKey(r,n))}removeTools(e){let t=[],r={...this.convertedTools},n={...this.originalTools};for(let o of e){if(!r[o]){this.logger.warn(`Cannot remove tool '${o}': tool not found.`);continue}let a=n[o];delete r[o],delete n[o],t.push(o),this.mastra&&a&&typeof a=="object"&&"id"in a&&this.mastra.removeTool(this.mastraToolKey(o,a))}return this.convertedTools=r,this.originalTools=n,t}mastraToolKey(e,t){return"id"in t&&typeof t.id=="string"?t.id:e}hasInputSchema(e){let t=this.originalTools[e];return!t||!("inputSchema"in t)||t.inputSchema!==void 0}resumeSchemaOf(e){let t=this.originalTools[e];if(!(!t||!("resumeSchema"in t)||!t.resumeSchema))return this.formSchema(t.resumeSchema)}advertisedSchema(e,t,r){let n=this.originalTools[e],o=n&&t in n?n[t]:void 0;return this.jsonSchema(Yr(o)?o:r,{io:t==="inputSchema"?"input":"output"})}toolInfo(e,t){return{id:e,name:e,description:t.description,inputSchema:this.hasInputSchema(e)?this.advertisedSchema(e,"inputSchema",t.parameters)??A0e:A0e,outputSchema:this.advertisedSchema(e,"outputSchema",t.outputSchema),toolType:t.mcp?.toolType,_meta:pbt(t.mcp?._meta,t.strict)}}toMCPTool(e,t){let r=this.toolInfo(e,t),n=WN.Tool["~standard"].validate({name:e,title:t.title,description:r.description,inputSchema:r.inputSchema,outputSchema:r.outputSchema,annotations:t.mcp?.annotations,_meta:KPt(r._meta)});if(n instanceof Promise||n.issues)throw new Error(`Tool '${e}' does not describe a valid MCP tool (input schemas must be objects)`);return n.value}hasUiMetadata(){return Object.values(this.convertedTools).some(e=>!!J8(e.mcp?._meta))}capabilities(){let e={tools:{listChanged:!0},logging:{}};return(this.resourceOptions||this.appResourceList.length>0)&&(e.resources={subscribe:!0,listChanged:!0}),this.promptOptions&&(e.prompts={listChanged:!0}),(this.appResourceList.length>0||this.hasUiMetadata())&&(e.extensions={"io.modelcontextprotocol/ui":{}}),this.eventRegistry.size>0&&(e.extensions={...e.extensions,[OTt]:this.eventsCapability()}),this.tasks.enabled&&(e.extensions={...e.extensions,...t0e()}),e}eventsCapability(){return{}}createServerInstance(){let e=new cU({name:this.name,version:this.version,title:this.title,description:this.description,websiteUrl:this.websiteUrl,icons:this.icons},{capabilities:this.capabilities(),instructions:this.instructions,jsonSchemaValidator:this.jsonSchemaValidator,cacheHints:this.cacheHints,requestState:{verify:this.requestStateCodec.verify}});if(e.removeRequestHandler("logging/setLevel"),this.registerToolHandlers(e),this.registerResourceHandlers(e),this.registerPromptHandlers(e),this.eventRegistry.size>0&&Zbt(e,{registry:this.eventRegistry,heartbeatIntervalMs:this.eventsOptions.heartbeatIntervalMs??3e4,listPageSize:this.eventsOptions.listPageSize,maxStreams:this.eventsOptions.maxStreams,webhooks:this.webhooks,getLogger:()=>this.logger,jsonSchema:(t,r)=>this.jsonSchema(t,r),trace:(t,r,n,o,a)=>this.traceCustomRequest(t,r,n,o,a)}),this.tasks.enabled){DPt(e,{store:this.taskStore,authorize:this.tasks.authorize,principal:Pb,trace:(r,n,o,a,i,s)=>this.traceCustomRequest(r,n,o,a,i,s)});let t=e.connect.bind(e);e.connect=async r=>(OPt(r),t(r))}return e}async traceCustomRequest(e,t,r,n,o,a={}){if(a.untraced)return o(await I8(n,this.mapAuthInfoToUser));let i=i0e(n),{metadata:s}=a,c;try{c=await I8(n,this.mapAuthInfoToUser)}catch(u){throw this.startRequestSpan(t,r,{server:e,ctx:n,caller:i,metadata:s})?.error({error:u}),u}let l=await a.parentOf?.(c).catch(()=>{}),d=this.startRequestSpan(t,r,{server:e,ctx:n,caller:i,requestContext:c,metadata:s,task:l});return this.traceRequest(d,()=>o(c))}setTracedHandler(e,t,r){e.setRequestHandler(t,async(n,o)=>{let a=n.params,i=i0e(o),s;try{s=await I8(o,this.mapAuthInfoToUser)}catch(u){throw this.startRequestSpan(t,a,{server:e,ctx:o,caller:i})?.error({error:u}),u}let c=this.startRequestSpan(t,a,{server:e,ctx:o,caller:i,requestContext:s}),l,d=await this.traceRequest(c,()=>r(n,o,{requestSpan:c,requestContext:s,reportError:u=>{l=u}}),()=>l);return t==="tools/call"?Mbt(d,c,i!==void 0):d})}startRequestSpan(e,t,r){let n=r?.ctx?.mcpReq.envelope,o=n?.[oo]??r?.server?.getNegotiatedProtocolVersion(),a=n?.[yi]??r?.server?.getClientVersion(),i=t?.name??t?.uri,s=typeof i=="string"?i:void 0,c=r?.caller;return eT({type:$L.MCP_SERVER_REQUEST,name:s?`${e} ${s}`:e,entityType:nl.MCP_SERVER,entityId:this.id,entityName:this.name,input:Rbt(t),attributes:{mcpMethod:e,targetName:s,mcpServer:this.name,serverVersion:this.version,mcpProtocolVersion:typeof o=="string"?o:void 0,clientName:a?.name,clientVersion:a?.version,callerTraceparent:c?.traceparent,callerTracestate:c?.tracestate},tracingContext:{},links:c&&[c.link],metadata:r?.metadata,...r?.task&&{tracingOptions:{traceId:r.task.traceId},resumedFromSpanId:r.task.spanId},requestContext:r?.requestContext,mastra:this.mastra})}async traceRequest(e,t,r){try{let n=await t(),o=lAe(n);if(o){let i=wn(o.taskId);return e?.end({output:{task:{status:o.status,mcpTaskKey:i}},metadata:{mcpTaskKey:i,mcpTaskStatus:o.status}}),e&&this.flushTaskRoot(e),n}let a=n;return a?.isError?e?.error({error:r?.()??new Xr({id:"MCP_SERVER_REQUEST_FAILED",domain:Zn.MCP,category:Jn.USER,text:a.content?.map(i=>i.text).filter(Boolean).join(`
2318
2318
  `)})}):e?.end({output:n}),n}catch(n){throw e?.error({error:n,endTree:!0}),n}}flushTaskRoot(e){e.observabilityInstance.flush().catch(t=>this.logger.debug("Could not flush a task trace root",{error:t}))}async inputRequired(e,t,r){let{method:n,name:o,argsHash:a,round:i,suspendPayload:s}=r;return XN({inputRequests:{[Rb]:this.inputRequestForm(o,s,r.resumeSchema)},requestState:await this.requestStateCodec.mint({method:n,name:o,argsHash:a,round:i,suspendPayload:s,principal:Pb(e,t),iat:Math.floor(Date.now()/1e3)})})}inputRequestForm(e,t,r){let n=t,o=typeof n?.message=="string"?n.message:typeof r?.description=="string"?r.description:`"${e}" needs more input`;return XN.elicit({message:o,requestedSchema:r??{type:"object",properties:{}}})}async serverRequest(e,t,r,n,o,a){let i=q8(o),s=c0e(e,t,{method:r,name:n,argsHash:i});if(s?.outcome==="accept"&&a){let l=await a["~standard"].validate(s.resumeData);if(l.issues)throw new We(ze.InvalidParams,`Input response for ${r} "${n}" does not match its resumeSchema: ${l.issues.map(d=>d.message).join("; ")}`);s.resumeData=l.value}let c;return{request:{extra:JR(e,this.name).extra,requestContext:t,suspend:async l=>{c={payload:l}},resumeData:s?.resumeData,suspendPayload:s?.suspendPayload},continuation:s,suspended:()=>c,argsHash:i}}registerToolHandlers(e){this.setTracedHandler(e,"tools/list",async(t,r,n)=>({tools:(await this.authorizedToolEntries(n.requestContext)).map(([o,a])=>this.toMCPTool(o,a))})),this.setTracedHandler(e,"tools/call",async(t,r,n)=>{let o=t.params.name,a=this.convertedTools[o];if(!a)return this.logger.warn("Unknown tool requested",{tool:o}),$o(`Unknown tool: ${o}`);let i=t.params.arguments??{},s=q8(i),c=n.requestContext,l=c0e(r,c,{method:"tools/call",name:o,argsHash:s}),d=JR(r,this.name),u=Date.now();try{if(await this.enforceToolExecutionFGA(o,c),l&&l.outcome!=="accept")return $o(`Tool '${o}' was ${l.outcome==="decline"?"declined":"cancelled"}`);let m=l&&{resumeData:l.resumeData,suspendPayload:l.suspendPayload},f=this.tasks.modeFor(o,a.mcp?.toolType),h=f.mode!=="off"&&GPt(r);if(f.mode!=="off"&&!h&&this.tasks.required.has(o))throw new Ug({requiredCapabilities:{extensions:t0e()}},`Tool '${o}' only runs as a task; declare the io.modelcontextprotocol/tasks extension`);if(h&&a.mcp?.toolType==="workflow")return await this.startWorkflowTask(r,c,o,i,f,{currentSpan:n.requestSpan});if(h&&f.mode==="always")try{return await this.startToolTask(r,c,o,i,f,{resume:m,rootSpan:n.requestSpan})}catch(P){if(P instanceof Vd)return $o(x8(P));throw P}let g={requestContext:c,mcp:d,tracingContext:{currentSpan:n.requestSpan},...m},S;if(h){let P=await this.runWithinSyncWindow(r,c,o,a,i,f,g);if("task"in P)return P.task;S=P.execution}else S=await this.runTool(o,a,i,g);if(S.status==="suspended")return this.logger.debug(`Tool '${o}' requires client input.`),this.inputRequired(r,c,{method:"tools/call",name:o,argsHash:s,round:(l?.round??0)+1,suspendPayload:S.suspendPayload,resumeSchema:S.resumeSchema});let _=e.projectCallToolResult(this.toCallToolResult(o,a,S.output),this.toolInfo(o,a).outputSchema);return this.logger.info(`Tool '${o}' finished in ${Date.now()-u}ms.`),_}catch(m){if(m instanceof We)throw m;let f=this.toolError(o,m);return n.reportError(f),$o(JSON.stringify(f.toJSON()))}})}toolError(e,t){return this.logger.error("Tool execution failed",{tool:e,error:t}),t instanceof Xr?t:new Xr({id:"TOOL_EXECUTION_FAILED",domain:Zn.TOOL,category:Jn.USER,details:{toolName:e}},t)}async runWithinSyncWindow(e,t,r,n,o,a,i){let s=new AbortController,c=e.mcpReq.signal,l=()=>s.abort(c.reason);c.aborted?l():c.addEventListener("abort",l,{once:!0});let d=this.runTool(r,n,o,{...i,abortSignal:s.signal,mcp:{...i.mcp,extra:{...i.mcp.extra,signal:s.signal},log:(...g)=>i.mcp.log(...g).catch(()=>{}),progress:g=>i.mcp.progress(g).catch(()=>{})}}).then(g=>({execution:g}),g=>({error:g})),u,m=new Promise(g=>{u=setTimeout(()=>g("timeout"),a.syncWindowMs)}),f=await Promise.race([d,m]);if(clearTimeout(u),c.removeEventListener("abort",l),f!=="timeout"){if("error"in f)throw f.error;return f}try{return{task:await this.startToolTask(e,t,r,o,a,{rootSpan:i.tracingContext?.currentSpan,resume:i.resumeData===void 0?void 0:{resumeData:i.resumeData,suspendPayload:i.suspendPayload},initialAttempt:()=>({outcome:d.then(g=>"error"in g?this.toolErrorOutcome(r,g.error):this.toTaskOutcome(r,n,g.execution)),abort:g=>s.abort(g)})})}}catch(g){if(!(g instanceof Vd))throw s.abort(g),g}this.logger.debug(`Tool '${r}' outlived its sync window at a task cap; answering synchronously.`),c.aborted?l():c.addEventListener("abort",l,{once:!0});let h=await d;if(c.removeEventListener("abort",l),"error"in h)throw h.error;return h}async startToolTask(e,t,r,n,o,{resume:a,initialAttempt:i,rootSpan:s}){let c={toolName:r,args:n,requestContext:this.persistTaskRequestContext(r,t),...a&&{resume:a}},l={ttlMs:o.ttlMs,principal:Pb(e,t),context:c,limits:this.tasks.limits,...s&&{trace:{traceId:s.traceId,spanId:s.id}}},{store:d,task:u}=await this.createTask(e,t,l,i!==void 0);return d.run(u.taskId,this.toolTaskWork(c),{initialAttempt:i&&this.tracedInitialAttempt(r,i(),l.trace,u.taskId,t)}),this.logger.debug(`Tool '${r}' continues as task.`),D8(u)}persistTaskRequestContext(e,t){let{persisted:r,dropped:n}=HPt(t);return n.length>0&&this.logger.warn(`Tool task '${e}' can't persist request context keys; runs from the task won't see them`,{tool:e,keys:n}),r}async createTask(e,t,r,n){let o=this.taskStore instanceof YR?await this.taskStore.resolve():this.taskStore;return{store:o,task:o instanceof C0e?await o.create(r,{pinToWorker:n,clientLabel:s0e(e,t)}):o instanceof FU?await o.create(r,{clientLabel:s0e(e,t)}):await o.create(r)}}toolTaskWork(e){let t=e.toolName;return async r=>{let n=this.convertedTools[t];if(!n)throw new Error(`Tool '${t}' is no longer served`);let o=new Wn(Object.entries(e.requestContext));try{await this.enforceToolExecutionFGA(t,o)}catch(c){return this.toolErrorOutcome(t,c)}let{resumeData:a,suspendPayload:i}=e.resume??{};if(r.resume){let[c]=Object.keys(r.resume.inputResponses),l=NR(r.resume.inputResponses,c);if(l.kind!=="elicit")return Ol($o(`Unsupported input response for tool '${t}'`));if(l.action!=="accept")return Ol($o(`Tool '${t}' was ${l.action==="decline"?"declined":"cancelled"}`));a=l.content??{},i=r.resume.suspendPayload}let s=r.trace&&this.openTaskSpan(`task attempt ${t} #${r.trace.attempt}`,r.trace,{requestContext:o,input:e.args,mcpMethod:"tools/call",targetName:t,metadata:{mcpTaskKey:r.trace.taskKey,attempt:r.trace.attempt,resumed:r.trace.resumed,recovered:r.trace.recovered}});return this.endAttemptSpan(s,r.signal,(async()=>{try{let c=await this.runTool(t,n,e.args,{requestContext:o,abortSignal:r.signal,resumeData:a,suspendPayload:i,tracingContext:s?{currentSpan:s}:void 0});return this.toTaskOutcome(t,n,c)}catch(c){return this.toolErrorOutcome(t,c)}})())}}openTaskSpan(e,t,r){return eT({type:$L.MCP_SERVER_REQUEST,name:e,entityType:nl.MCP_SERVER,entityId:this.id,entityName:this.name,input:r.input,attributes:{mcpMethod:r.mcpMethod,...r.targetName!==void 0&&{targetName:r.targetName},mcpServer:this.name,serverVersion:this.version},metadata:r.metadata,tracingOptions:{traceId:t.traceId},resumedFromSpanId:t.spanId,requestContext:r.requestContext,mastra:this.mastra})}async endAttemptSpan(e,t,r){if(!e)return r;try{let n=await r;return t?.aborted?e.end({metadata:{mcpTaskStatus:"cancelled"}}):n.status==="input_required"?e.end({metadata:{mcpTaskStatus:"input_required"}}):n.result.isError?e.error({error:new Xr({id:"MCP_SERVER_TASK_ATTEMPT_FAILED",domain:Zn.MCP,category:Jn.USER,text:n.result.content.map(o=>o.type==="text"?o.text:"").filter(Boolean).join(`
2319
- `)}),metadata:{mcpTaskStatus:"completed"}}):e.end({output:n.result,metadata:{mcpTaskStatus:"completed"}}),n}catch(n){throw t?.aborted?e.end({metadata:{mcpTaskStatus:"cancelled"}}):e.error({error:n,endTree:!0,metadata:{mcpTaskStatus:"failed"}}),n}}tracedInitialAttempt(e,t,r,n,o,a){if(!r)return t;let i=a??this.openTaskSpan(`task attempt ${e} #1`,r,{requestContext:o,...k0e(e,n)}),s=new AbortController;return{outcome:this.endAttemptSpan(i,s.signal,t.outcome),abort:c=>{s.abort(c),t.abort(c)}}}toTaskOutcome(e,t,r){return r.status==="suspended"?(this.logger.debug(`Tool task '${e}' requires client input.`),{status:"input_required",inputRequests:{[`${Rb}-${Ib()}`]:this.inputRequestForm(e,r.suspendPayload,r.resumeSchema)},suspendPayload:r.suspendPayload}):t.mcp?.toolType==="agent"&&r.output?.finishReason==="suspended"?Ol($o(`Agent '${e.slice(4)}' paused for tool approval or input, which MCP tasks don't support yet`)):Ol(this.toCallToolResult(e,t,r.output))}toolErrorOutcome(e,t){return Ol($o(JSON.stringify(this.toolError(e,t).toJSON())))}async startWorkflowTask(e,t,r,n,o,a){let i=r.slice(4),s=this.workflowFor(i);if(!s)throw new Error(`Workflow '${i}' is not served`);let c=Vu(s.inputSchema,n,r);if(c.error)return this.logger.warn(`Tool '${r}' rejected its input.`,{error:c.error.message}),$o(c.error.message);let l={toolName:r,kind:"workflow",workflowKey:i,runId:Ib(),requestContext:this.persistTaskRequestContext(r,t)},d=a.currentSpan,u={ttlMs:o.ttlMs,principal:Pb(e,t),context:l,limits:this.tasks.limits,...d&&{trace:{traceId:d.traceId,spanId:d.id}}},m=T=>{let D=s.createRun({runId:l.runId}),R=D.then(O=>O.start({inputData:c.data,requestContext:t,tracingContext:T})),E=()=>{D.then(O=>O.cancel()).catch(O=>this.logger.warn("Could not cancel workflow run",{tool:r,error:O}))};this.executingRuns.set(l.runId,E);let I=()=>this.executingRuns.delete(l.runId);return R.then(I,I),{result:R,cancel:E}};if(o.mode==="always"){let T;try{T=await this.createTask(e,t,u,!0)}catch(O){if(O instanceof Vd)return $o(x8(O));throw O}let{store:D,task:R}=T,E=u.trace&&this.openTaskSpan(`task attempt ${r} #1`,u.trace,k0e(r,R.taskId)),I=m(E?{currentSpan:E}:a);return D.run(R.taskId,this.workflowTaskWork(l),{initialAttempt:this.tracedInitialAttempt(r,this.liveWorkflowAttempt(s,l,I.result),u.trace,R.taskId,t,E)}),this.logger.debug(`Workflow tool '${r}' runs as task.`),D8(R)}let f=m(a),h=e.mcpReq.signal,g=()=>f.cancel();h.aborted?g():h.addEventListener("abort",g,{once:!0});let S=f.result.then(T=>({view:T}),T=>({error:T})),_,P=new Promise(T=>{_=setTimeout(()=>T("timeout"),o.syncWindowMs)}),y=await Promise.race([S,P]);if(clearTimeout(_),h.removeEventListener("abort",g),y!=="timeout"){if("error"in y)throw y.error;let T=this.workflowRunOutcome(s,l,y.view);if(T?.status==="completed")return T.result}try{let{store:T,task:D}=await this.createTask(e,t,u,!0);return T.run(D.taskId,this.workflowTaskWork(l),{initialAttempt:this.tracedInitialAttempt(r,this.liveWorkflowAttempt(s,l,f.result),u.trace,D.taskId,t)}),this.logger.debug(`Workflow tool '${r}' continues as task.`),D8(D)}catch(T){if(!(T instanceof Vd))throw f.cancel(),T;this.logger.debug(`Workflow tool '${r}' outlived its sync window at the open task cap.`),h.aborted?g():h.addEventListener("abort",g,{once:!0});let D=await S;if(h.removeEventListener("abort",g),"error"in D)throw D.error;let R=this.workflowRunOutcome(s,l,D.view);return R?.status==="completed"?R.result:(f.cancel(),$o(x8(T)))}}liveWorkflowAttempt(e,t,r){let n=new AbortController;return{outcome:r.then(o=>this.workflowRunOutcome(e,t,o)??this.watchWorkflowRun(e,t,n.signal),o=>this.toolErrorOutcome(t.toolName,o)),abort:o=>n.abort(o)}}workflowTaskWork(e){let t=e.toolName;return async r=>{let n=this.workflowFor(e.workflowKey);if(!n)throw new Error(`Workflow '${e.workflowKey}' is no longer served`);let o=new Wn(Object.entries(e.requestContext));try{await this.enforceToolExecutionFGA(t,o)}catch(i){return this.toolErrorOutcome(t,i)}let a=r.trace&&this.openTaskSpan(`task attempt ${t} #${r.trace.attempt}`,r.trace,{requestContext:o,mcpMethod:"tools/call",targetName:t,metadata:{mcpTaskKey:r.trace.taskKey,attempt:r.trace.attempt,resumed:r.trace.resumed,recovered:r.trace.recovered}});return this.endAttemptSpan(a,r.signal,r.resume?this.resumeWorkflowRun(n,e,r,o,a):this.watchWorkflowRun(n,e,r.signal))}}async resumeWorkflowRun(e,t,r,n,o){let{inputResponses:a,suspendPayload:i}=r.resume,[s]=Object.keys(a),c=NR(a,s),l=`Workflow '${t.workflowKey}'`;if(c.kind!=="elicit")return Ol($o(`Unsupported input response for ${l}`));if(c.action!=="accept")return await this.cancelWorkflowRun(t),Ol($o(`${l} was ${c.action==="decline"?"declined":"cancelled"}`));let d=await e.getWorkflowRunById(t.runId,{withNestedWorkflows:!1});if(!d)throw new Error(`${l} run '${t.runId}' was lost`);let u=i;if(!R0e({status:d.status,steps:d.steps},u))return this.watchWorkflowRun(e,t,r.signal);let m=await e.createRun({runId:t.runId}),f;this.executingRuns.set(t.runId,()=>{m.cancel().catch(h=>this.logger.warn("Could not cancel workflow run",{tool:t.toolName,error:h}))});try{f=await m.resume({step:u.step,resumeData:c.content??{},requestContext:n,...o&&{tracingContext:{currentSpan:o}}})}catch(h){if(BPt(h))return this.watchWorkflowRun(e,t,r.signal);let g=await e.getWorkflowRunById(t.runId,{withNestedWorkflows:!1});return g&&!R0e({status:g.status,steps:g.steps},u)?this.watchWorkflowRun(e,t,r.signal):this.toolErrorOutcome(t.toolName,h)}finally{this.executingRuns.delete(t.runId)}return this.workflowRunOutcome(e,t,f)??this.watchWorkflowRun(e,t,r.signal)}async watchWorkflowRun(e,t,r){let n=!0;for(;;){r.throwIfAborted();let o=await e.getWorkflowRunById(t.runId,{withNestedWorkflows:!1});if(!o||n&&o.status==="pending")throw new Error(`Workflow '${t.workflowKey}' run '${t.runId}' was lost`);n=!1;let a=this.workflowRunOutcome(e,t,{status:o.status,input:o.payload,steps:o.steps,result:o.result,error:o.error});if(a)return a;await qPt(jPt,r)}}workflowRunOutcome(e,t,r){let n=`Workflow '${t.workflowKey}'`;switch(r.status){case"success":case"bailed":return Ol({isError:!1,content:[{type:"text",text:JSON.stringify(UPt(r))}]});case"failed":return Ol($o(`${n} failed: ${E0e(r)}`));case"tripwire":return Ol($o(`${n} was blocked: ${E0e(r)}`));case"canceled":return Ol($o(`${n} run was cancelled`));case"suspended":{let o=VPt(e,r);if(!o)return;let a=this.representableForm(o.step?.resumeSchema);return a?(this.logger.debug(`Workflow task '${t.toolName}' requires client input.`),{status:"input_required",inputRequests:{[`${Rb}-${Ib()}`]:this.inputRequestForm(t.toolName,o.payload,a)},suspendPayload:{step:o.path,suspendedAt:o.suspendedAt}}):void 0}default:return}}async cancelWorkflowRun(e){let t=this.executingRuns.get(e.runId);if(t)return t();let r=this.workflowFor(e.workflowKey);if(r)try{let n=await r.getWorkflowRunById(e.runId,{withNestedWorkflows:!1});if(!n||NPt(n.status)||n.status==="running")return;await(await r.createRun({runId:e.runId})).cancel()}catch(n){this.logger.warn("Could not cancel workflow run",{tool:e.toolName,error:n})}}workflowFor(e){return this.workflows?.[e]}async runTool(e,t,r,n){if(!t.execute)throw new Error(`Tool '${e}' cannot be executed.`);let o,a=await t.execute(r,{toolCallId:"",messages:[],skipToolSpan:!0,requestContext:n.requestContext,tracingContext:n.tracingContext,abortSignal:n.abortSignal??n.mcp?.extra.signal,...n.mcp?{mcp:n.mcp}:{},suspend:async(i,s)=>{o={payload:i,resumeSchema:s?.resumeSchema}},resumeData:n.resumeData,suspendPayload:n.suspendPayload});if(o){let{payload:i,resumeSchema:s}=o,{$schema:c,...l}=s?JSON.parse(s):{};return{status:"suspended",suspendPayload:i,resumeSchema:this.resumeSchemaOf(e)??(s?l:void 0)}}return{status:"completed",output:a}}toCallToolResult(e,t,r){if(uT(r))return this.logger.warn(`Tool '${e}' rejected its input.`,{error:r.message}),$o(r.message);let n=J8(t.mcp?._meta),o=n?{_meta:{ui:{resourceUri:n},[MN]:n}}:{};if(!t.outputSchema){let i=typeof r=="string"?r:JSON.stringify(r);return{isError:!1,content:i===void 0?[]:[{type:"text",text:i}],...o}}let a=JSON.stringify(r);return a===void 0?(this.logger.warn(`Tool '${e}' declares an outputSchema but returned no structured output.`),{...$o(`Tool '${e}' declares an outputSchema but returned no output.`),...o}):{isError:!1,structuredContent:r,content:[{type:"text",text:a}],...o}}registerResourceHandlers(e){let t=this.resourceOptions,r=this.appResourceList.length>0;if(!t&&!r)return;let n=async(o,a)=>[...this.appResourceList,...await t?.listResources({extra:JR(o,this.name).extra,requestContext:a})??[]];this.setTracedHandler(e,"resources/list",async(o,a,i)=>({resources:await n(a,i.requestContext)})),this.setTracedHandler(e,"resources/read",async(o,a,i)=>{let s=o.params.uri,c=(await n(a,i.requestContext)).find(S=>S.uri===s);if(!c)throw new We(ze.InvalidParams,`Resource not found: ${s}`);let l=this.appResourceHtml.get(s);if(l!==void 0)return{contents:[this.resourceContents(c,{text:l})]};if(!t)throw new We(ze.InvalidParams,`Resource not found: ${s}`);let{request:d,continuation:u,suspended:m,argsHash:f}=await this.serverRequest(a,i.requestContext,"resources/read",s,{},t.resumeSchema);if(u&&u.outcome!=="accept")throw new We(ze.InvalidParams,`Reading '${s}' was ${u.outcome}ed`);let h=await t.getResourceContent({uri:s,...d}),g=m();if(g)return this.inputRequired(a,d.requestContext,{method:"resources/read",name:s,argsHash:f,round:(u?.round??0)+1,suspendPayload:g.payload,resumeSchema:t.resumeSchema?this.formSchema(t.resumeSchema):void 0});if(h===void 0)throw new Error(`Resource '${s}' returned no content`);return{contents:(Array.isArray(h)?h:[h]).map(S=>this.resourceContents(c,S))}}),t?.resourceTemplates&&this.setTracedHandler(e,"resources/templates/list",async(o,a,i)=>({resourceTemplates:await t.resourceTemplates({extra:JR(a,this.name).extra,requestContext:i.requestContext})}))}resourceContents(e,t){let r={uri:e.uri,mimeType:e.mimeType,...e._meta?{_meta:e._meta}:{}};if("text"in t&&t.text!==void 0)return{...r,text:t.text};if("blob"in t&&t.blob!==void 0)return{...r,blob:t.blob};throw new Error(`Resource '${e.uri}' returned content with neither text nor blob`)}registerPromptHandlers(e){let t=this.promptOptions;if(!t)return;let r=async(n,o)=>{let a=await t.listPrompts({extra:JR(n,this.name).extra,requestContext:o});for(let i of a)Hu.parse(i);return a};this.setTracedHandler(e,"prompts/list",async(n,o,a)=>({prompts:await r(o,a.requestContext)})),t.getPromptMessages&&this.setTracedHandler(e,"prompts/get",async(n,o,a)=>{let{name:i,arguments:s}=n.params,c=(await r(o,a.requestContext)).find(g=>g.name===i);if(!c)throw new We(ze.InvalidParams,`Prompt "${i}" not found`);for(let g of c.arguments??[])if(g.required&&(s?.[g.name]===void 0||s?.[g.name]===null))throw new We(ze.InvalidParams,`Missing required argument: ${g.name}`);let{request:l,continuation:d,suspended:u,argsHash:m}=await this.serverRequest(o,a.requestContext,"prompts/get",i,s??{},t.resumeSchema);if(d&&d.outcome!=="accept")throw new We(ze.InvalidParams,`Prompt "${i}" was ${d.outcome}ed`);let f=await t.getPromptMessages({name:i,args:s,...l}),h=u();if(h)return this.inputRequired(o,l.requestContext,{method:"prompts/get",name:i,argsHash:m,round:(d?.round??0)+1,suspendPayload:h.payload,resumeSchema:t.resumeSchema?this.formSchema(t.resumeSchema):void 0});if(f===void 0)throw new Error(`Prompt "${i}" returned no messages`);return{description:c.description,messages:f}})}loadAppResources(e){for(let[t,r]of Object.entries(e??{})){let n=r.html??(r.htmlPath?igt(r.htmlPath,"utf-8"):void 0);if(n===void 0){this.logger.warn(`App resource '${t}' has neither html nor htmlPath; skipping`);continue}this.appResourceHtml.set(t,n),this.appResourceList.push({uri:t,name:r.name,description:r.description,mimeType:cPe,...r.meta?{_meta:{ui:r.meta}}:{}})}}async enforceToolExecutionFGA(e,t){let r=this.mastra?.getServer?.()?.fga;if(!r)return;let{getMCPToolFGAResourceId:n,requireFGA:o,FGADeniedError:a,MastraFGAPermissions:i}=await Promise.resolve().then(()=>(FEe(),VEe)),s=n(this.id,e),c=t.get("user");if(!c)throw new a({id:"unknown"},{type:"tool",id:s},i.TOOLS_EXECUTE);let l=this.fga?.permissionMapping?.[i.TOOLS_EXECUTE]??i.TOOLS_EXECUTE,d=this.fga?.resourceMapping?.tool??this.fga?.resourceMapping?.tools;await o({fgaProvider:r,user:c,resource:d?{type:d.fgaResourceType,id:d.deriveId?.({user:c,resourceId:s,requestContext:t})??s}:{type:"tool",id:s},permission:l,requestContext:t,context:{resourceId:s},metadata:{mcpServerId:this.id,mcpServerName:this.name,toolId:e}})}async authorizedToolEntries(e){let t=Object.entries(this.convertedTools);return this.mastra?.getServer?.()?.fga?e.get("user")?(await Promise.all(t.map(async r=>{try{return await this.enforceToolExecutionFGA(r[0],e),r}catch(n){if(n instanceof Error&&n.name==="FGADeniedError")return;throw n}}))).filter(r=>r!==void 0):[]:t}notifier(){if(this.httpHandler)return this.httpHandler.notify;let e=this.stdioInstance;if(!e)return;let t=r=>this.logger.error("Failed to publish stdio notification",{error:r});return{toolsChanged:()=>{e.sendToolListChanged().catch(t)},promptsChanged:()=>{e.sendPromptListChanged().catch(t)},resourcesChanged:()=>{e.sendResourceListChanged().catch(t)},resourceUpdated:r=>{e.sendResourceUpdated({uri:r}).catch(t)}}}getNodeHandler(){return this.nodeHandler||(this.httpHandler=_Ee(()=>this.createServerInstance(),{legacy:this.legacy,onerror:e=>this.logger.error("MCP handler error",{error:e.toString()})}),this.nodeHandler=LEe(this.httpHandler,{onerror:e=>this.logger.error("MCP Node handler adapter error",{error:e.toString()})})),this.nodeHandler}async startStdio(){this.stdioHandle=UEe(()=>(this.stdioInstance=this.createServerInstance(),this.stdioInstance),{legacy:"reject",onerror:e=>this.logger.error("MCP stdio handler error",{error:e.toString()})}),this.logger.info("Started MCP Server (stdio, 2026-07-28)")}async startHTTP({url:e,httpPath:t,req:r,res:n,options:o}){if(e.pathname!==t){n.writeHead(404),n.end();return}if(o?.enableDnsRebindingProtection){if(o.allowedHosts?.length){let a=o.allowedHosts.map(i=>new URL(`http://${i}`).hostname);if(!DEe(a)(r,n))return}if(o.allowedOrigins?.length){let a=o.allowedOrigins.map(i=>new URL(i).hostname);if(!MEe(a)(r,n))return}}try{let a=r.body;await this.getNodeHandler()(r,n,a)}catch(a){let i=new Xr({id:"MCP_SERVER_HTTP_CONNECTION_FAILED",domain:Zn.MCP,category:Jn.USER,text:"Failed to handle MCP request"},a);this.logger.trackException(i),n.headersSent||(n.writeHead(500,{"Content-Type":"application/json"}),n.end(JSON.stringify({jsonrpc:"2.0",error:{code:-32603,message:"Internal server error"},id:null})))}}async close(){this.webhooks?.close(),this.eventRegistry.openStreams.size>0&&(this.eventRegistry.closeStreams(),await new Promise(e=>setImmediate(e))),this.stdioHandle&&(await this.stdioHandle.close(),this.stdioHandle=void 0,this.stdioInstance=void 0),this.httpHandler&&(await this.httpHandler.close(),this.httpHandler=void 0,this.nodeHandler=void 0),this.taskStore instanceof YR&&this.taskStore.close(),await this.backgroundTaskStore?.close()}hasStableId=!1;setId(e){super.setId(e),this.id===e&&(this.hasStableId=!0)}__registerMastra(e){super.__registerMastra(e);let t=this.backgroundTaskStore;!this.prepareBackgroundTaskStore()||!(this.taskStore instanceof YR)||(t||this.taskStore.reselect(),this.taskStore.resolve().catch(r=>this.logger.debug(`MCP server '${this.name}' could not choose its task store yet`,{error:r})))}prepareBackgroundTaskStore(){if(this.backgroundTaskStore)return this.backgroundTaskStore;if(!this.tasks.enabled||this.tasks.store)return;let e=this.mastra?.backgroundTaskManager;if(!e)return;let t=new C0e({manager:e,serverId:this.id,workFor:r=>M8(r)?this.workflowTaskWork(r):this.toolTaskWork(r),onCancel:r=>M8(r)?this.cancelWorkflowRun(r):void 0,recoverFor:r=>this.tasks.modeFor(r,this.convertedTools[r]?.mcp?.toolType).recover,logger:()=>this.logger});for(let[r,n]of Object.entries(this.convertedTools))this.tasks.modeFor(r,n.mcp?.toolType).mode!=="off"&&t.registerTool(r);return this.backgroundTaskStore=t,t}async selectTaskStore(){let e=this.mastra?.backgroundTaskManager;if(e){let t=await e.getStorage();if(t.supportsTaskExpiry===!0){let r=this.backgroundTaskStore,n=this.prepareBackgroundTaskStore();return this.hasStableId||this.logger.warn(`MCP server '${this.name}' keeps tasks in Mastra storage under the generated id '${this.id}', which changes on every start: other instances and restarts won't find its tasks. Pass \`id\` to the MCPServer, or register it under a key in Mastra's \`mcpServers\`.`),n.startSweeping(),r||this.logger.warn(`MCP server '${this.name}' serves tasks from Mastra background tasks that started after it was registered; tasks created before a restart may not have been recovered`),n}this.logger.warn(`MCP server '${this.name}' keeps tasks in memory: the background tasks storage (${t.constructor.name}) does not support task expiry. Upgrade the storage adapter to serve tasks across instances.`),await this.backgroundTaskStore?.close(),this.backgroundTaskStore=void 0}return new FU({onCancel:t=>M8(t)?this.cancelWorkflowRun(t):void 0})}getServerInfo(){return{id:this.id,name:this.name,description:this.description,repository:this.repository,version_detail:{version:this.version,release_date:this.releaseDate,is_latest:this.isLatest}}}getServerDetail(){return{...this.getServerInfo(),package_canonical:this.packageCanonical,packages:this.packages,remotes:this.remotes}}getToolListInfo(e){let t=r=>({tools:r.map(([n,o])=>this.toolInfo(n,o))});return this.mastra?.getServer?.()?.fga?e?this.authorizedToolEntries(e).then(t):{tools:[]}:t(Object.entries(this.convertedTools))}getToolInfo(e){let t=this.convertedTools[e];return t?this.toolInfo(e,t):void 0}async executeTool(e,t,r={}){let n=r.requestContext??new Wn,o=this.startRequestSpan("tools/call",{name:e,arguments:t},{requestContext:n});return this.traceRequest(o,async()=>{let a=this.convertedTools[e];if(!a)throw this.logger.warn("Unknown tool requested",{tool:e,server:this.name}),new Xr({id:"MCP_SERVER_TOOL_EXECUTE_PREPARATION_FAILED",domain:Zn.MCP,category:Jn.USER,text:`Unknown tool: ${e}`,details:{toolId:e}});await this.enforceToolExecutionFGA(e,n);try{let i=await this.runTool(e,a,t,{requestContext:n,tracingContext:{currentSpan:o},resumeData:r.resumeData,suspendPayload:r.suspendPayload});if(i.status==="completed"&&uT(i.output))throw new Xr({id:"MCP_SERVER_TOOL_INVALID_INPUT",domain:Zn.MCP,category:Jn.USER,text:i.output.message,details:{toolId:e}});return this.logger.info("Tool executed successfully",{tool:e}),i}catch(i){if(i instanceof Xr&&i.id==="MCP_SERVER_TOOL_INVALID_INPUT")throw i;let s=new Xr({id:"MCP_SERVER_TOOL_EXECUTE_FAILED",domain:Zn.MCP,category:Jn.USER,details:{toolId:e,args:JSON.stringify(t)}},i);throw this.logger.trackException(s),s}})}async readResource(e){let t=this.appResourceHtml.get(e),r=this.appResourceList.find(n=>n.uri===e);if(t===void 0||!r)throw new Xr({id:"MCP_SERVER_RESOURCE_NOT_FOUND",domain:Zn.MCP,category:Jn.USER,text:`Resource '${e}' not found; application resources are only readable through an MCP request`,details:{uri:e}});return{contents:[this.resourceContents(r,{text:t})]}}async listResources(){return{resources:[...this.appResourceList]}}getEventsInfo(){if(this.eventRegistry.size!==0)return{events:rPt(this.eventsDeps())}}getEventsInspector(){if(this.eventRegistry.size!==0)return this.eventsInspector??=nPt(this.eventsDeps()),this.eventsInspector}getTasksInfo(){if(this.tasks.enabled)return xPt(this.tasks,this.taskToolTypes())}getTasksInspector(){if(this.getTasksInfo()?.listable)return this.tasksInspector??=LPt(async()=>this.taskStore instanceof YR?this.taskStore.admin():gZ(this.taskStore)?this.taskStore:void 0),this.tasksInspector}taskToolTypes(){return Object.entries(this.convertedTools).map(([e,t])=>[e,t.mcp?.toolType])}eventsDeps(){return{registry:this.eventRegistry,heartbeatIntervalMs:this.eventsOptions.heartbeatIntervalMs??3e4,maxStreams:this.eventsOptions.maxStreams,getLogger:()=>this.logger,jsonSchema:(e,t)=>this.jsonSchema(e,t)}}};function zPt(e){return!!e&&typeof e=="object"&&"id"in e&&typeof e.id=="string"}function x8({scope:e,limit:t,kind:r}){return r==="retained"?e==="principal"?`Too many recent tasks for this client (limit ${t}). Try again once older tasks expire.`:`Too many recent tasks on this server (limit ${t}). Try again later.`:e==="principal"?`Too many open tasks for this client (limit ${t}). Wait for one to finish or cancel one.`:`Too many open tasks on this server (limit ${t}). Try again later.`}function $o(e){return{content:[{type:"text",text:e}],isError:!0}}function k0e(e,t){return{mcpMethod:"tools/call",targetName:e,metadata:{mcpTaskKey:wn(t),attempt:1,resumed:!1,recovered:!1}}}function Ol(e){return{status:"completed",result:e}}var jPt=1e3;function w0e(e,t){return WN.ElicitRequestFormParams["~standard"].validate({message:e,requestedSchema:t})}function BPt(e){return e instanceof Xr&&(e.id==="WORKFLOW_RESUME_ALREADY_CLAIMED"||e.id==="WORKFLOW_RUN_NOT_SUSPENDED")}function qPt(e,t){return new Promise((r,n)=>{let o=()=>{clearTimeout(a),n(t.reason)},a=setTimeout(()=>{t.removeEventListener("abort",o),r()},e);t.addEventListener("abort",o,{once:!0})})}function GPt(e){let t=e.mcpReq.envelope;return Ibt(t?.[Mo])}function HPt(e){let{authInfo:t,[fL]:r,...n}=e.toJSON(),o=n;if(t&&typeof t=="object"){let{token:i,...s}=t;o={...n,authInfo:s}}else t!==void 0&&(o={...n,authInfo:t});let a=JSON.parse(JSON.stringify(o));return{persisted:a,dropped:[...e.keys()].filter(i=>typeof i=="string"&&i!==fL&&e.get(i)!==void 0&&!(i in a))}}function J8(e){return e?.ui?.resourceUri||e?.[MN]||void 0}function KPt(e){let t=J8(e);if(!e||!t)return e;let r=e.ui;return{...e,ui:{...r,resourceUri:t},[MN]:t}}function XPt(e){try{let t=JSON.parse(e);if(t&&typeof t=="object"&&!Array.isArray(t))return t}catch{}}var K7t=v.object({server:v.string().min(1).describe("Name of the MCP server, as configured on the client"),event:v.string().min(1).describe("Event type name, from mcp_events_list"),arguments:v.string().refine(e=>XPt(e)!==void 0,"arguments must be a JSON object").optional().describe(`Subscription arguments as a JSON-encoded object matching the event's inputSchema, for example {"project":"A"}. Omit if the event takes none.`)});Js();var JU={name:"@florent-uzio/custody-mcp",version:"0.1.0",description:"MCP server for Ripple Custody, built on custody.js",license:"ISC",author:"Florent Uzio",repository:{type:"git",url:"git+https://github.com/florent-uzio/custody-mcp.git"},type:"module",bin:{"custody-mcp":"dist/custody-mcp.js"},files:["dist/custody-mcp.js","dist/THIRD_PARTY_LICENSES.txt"],publishConfig:{access:"public"},engines:{node:">=22.13.0"},keywords:["ripple-custody","mcp","model-context-protocol"],scripts:{test:"vitest run","test:watch":"vitest",typecheck:"tsc",mcp:"tsx src/stdio.ts",build:"node scripts/build.mjs",format:"prettier --write .","format:check":"prettier --check .",prepack:"npm run build",prepublishOnly:"npm run format:check && npm run typecheck && npm test",release:"changeset publish"},devDependencies:{"@changesets/cli":"^3.0.3","@florent-uzio/custody":"^2.15.0","@mastra/core":"^1.81.0","@mastra/mcp":"^2.7.0","@types/node":"^22.20.5",esbuild:"^0.28.2",prettier:"^3.9.10","prettier-plugin-organize-imports":"^4.3.0",tsx:"^4.23.15",typescript:"^7.0.2",vitest:"^5.1.0",zod:"^4.6.7"}};Js();Js();import{readFile as ZPt}from"node:fs/promises";var pAe=async(e,t)=>{let[r,n]=uAe(e,":");if(r==="env"){let o=t[n];if(!o)throw new Error(`env var ${n} is not set`);return o}if(r==="keychain"){let[o,a]=uAe(n,"/");if(!o||!a)throw new Error(`"${e}" is not of the form keychain:<service>/<account>`);return HX(o,a)}if(r==="file")return ZPt(n,"utf8");throw new Error(`unsupported Key reference "${e}"`)},uAe=(e,t)=>{let r=e.indexOf(t);return r===-1?[e,""]:[e.slice(0,r),e.slice(r+1)]};var JPt={environment:v.string().optional().describe("Name of the Environment to use (see listEnvironments). Defaults to the default Environment.")},YPt={environment:v.string().describe("Name of the Environment to act in (see listEnvironments). Required: writes never use the default.")},jn={domainId:v.string().optional().describe("Id of the Custody domain to use (see listDomains). Defaults to the Environment's Default domain.")},On=async({input:e,environment:t,custody:r})=>{let n=e.domainId??t.defaultDomainId;if(n!==void 0)return n;let{domains:o}=await r.users.me();if(o.length===1)return o[0].id;let a=o.map(i=>`${i.alias} (${i.id})`).join(", ");throw new Error(`Environment "${t.name}" has no Default domain and its user belongs to several domains: ${a}. Pass a domainId.`)},ja={limit:v.number().int().min(1).max(100).default(20).describe("How many items to return (default 20, at most 100)."),cursor:v.string().optional().describe("The nextCursor of the previous page, to get the page after it.")},Ba=({limit:e,cursor:t})=>({limit:e,startingAfter:t}),qa=e=>e.nextStartingAfter===void 0?{}:{nextCursor:e.nextStartingAfter},mAe=(e,t)=>{let r=new Map,n=async a=>{let{environments:i,defaultName:s}=await HS(e),c=a??s,l=i.find(d=>d.name===c);if(!l){let d=i.map(u=>u.name).join(", ");throw new Error(`Unknown Environment "${c}". Known Environments: ${d}.`)}return l},o=async a=>{let i=JSON.stringify([a.authUrl,a.apiUrl,a.publicKey,a.keyReference]),s=r.get(a.name);if(s?.settings!==i){let c=await pAe(a.keyReference,e).catch(l=>{throw new Error(`Cannot read the private key of Environment "${a.name}" from ${a.keyReference}: ${l.message}`)});s={settings:i,custody:t({...a,privateKey:c})},r.set(a.name,s)}return s.custody};return a=>{let i=a.access??"read";return Fu({id:a.id,description:a.description,inputSchema:v.object({...i==="read"?JPt:YPt,...a.input}),resumeSchema:a.resumeSchema,execute:async(s,c)=>{let{environment:l,...d}=s,u=await n(l);if(Wm.indexOf(u.access)<Wm.indexOf(i))throw new Error(`${a.id} needs the "${i}" Access level, but Environment "${u.name}" has "${u.access}".`);let m=await o(u);try{let f=await a.run({input:d,custody:m,environment:u,context:c});return{environment:u.name,...f}}catch(f){throw f instanceof Error?f:new Error(JSON.stringify(f)??String(f))}}})}};var vZ={accountId:v.string().describe("Id of the Custody account (see listAccounts).")},_Ae=e=>({listDomains:e({id:"listDomains",description:"List the Custody domains this Environment can see, one page at a time.",input:ja,run:async({custody:t,input:r})=>{let n=await t.domains.list(Ba(r));return{domains:n.items.map(({data:o})=>({id:o.id,alias:o.alias,parentId:o.parentId,lock:o.lock})),...qa(n)}}}),listAccounts:e({id:"listAccounts",description:"List the accounts of a Custody domain, one page at a time.",input:{...jn,...ja},run:async t=>{let r=await On(t),n=await t.custody.accounts.list({domainId:r},Ba(t.input));return{domainId:r,accounts:n.items.map(({data:o})=>({id:o.id,alias:o.alias,ledgerIds:o.ledgerIds??(o.ledgerId===void 0?[]:[o.ledgerId]),lock:o.lock})),...qa(n)}}}),getAccount:e({id:"getAccount",description:"Get one account of a Custody domain, with its ledgers and processing status.",input:{...jn,...vZ},run:async t=>{let r=await On(t),{data:n,additionalDetails:o}=await t.custody.accounts.get({domainId:r,accountId:t.input.accountId});return{domainId:r,account:n,additionalDetails:o}}}),listAccountAddresses:e({id:"listAccountAddresses",description:"List the ledger addresses of a Custody account, one page at a time.",input:{...jn,...vZ,...ja},run:async t=>{let r=await On(t),{accountId:n}=t.input,o=await t.custody.accounts.addresses({domainId:r,accountId:n},Ba(t.input));return{domainId:r,accountId:n,addresses:o.items.map(a=>({id:a.id,ledgerId:a.ledgerId,address:a.address,scope:a.scope})),...qa(o)}}}),getAccountBalances:e({id:"getAccountBalances",description:"List the balances of a Custody account, one ticker per item, one page at a time.",input:{...jn,...vZ,...ja},run:async t=>{let r=await On(t),{accountId:n}=t.input,o=await t.custody.accounts.getAccountBalances({domainId:r,accountId:n},Ba(t.input));return{domainId:r,accountId:n,balances:o.items.map(a=>({tickerId:a.tickerId,totalAmount:a.totalAmount,availableAmount:a.availableAmount,reservedAmount:a.reservedAmount,quarantinedAmount:a.quarantinedAmount,lastUpdatedAt:a.lastUpdatedAt})),...qa(o)}}}),findAccountByAddress:e({id:"findAccountByAddress",description:"Find the Custody account that owns a ledger address, in any domain. Returns account null when no account has it.",input:{address:v.string().describe("The ledger address, as shown on the ledger."),ledgerId:v.string().optional().describe("Id of the ledger, when the address is on several."),domainId:v.string().optional().describe("Id of the domain to look in, when the address is in several.")},run:async({custody:t,input:r})=>{let n=await t.accounts.findByAddress(r.address,{ledgerId:r.ledgerId,domainId:r.domainId});return{account:n===void 0?null:{domainId:n.domainId,accountId:n.accountId,ledgerId:n.ledgerId,address:n.address}}}})});Js();var QPt="io.modelcontextprotocol/clientCapabilities",eIt=e=>{let r=e.mcp?.extra.mcpReq.envelope?.[QPt]?.elicitation;return r!==void 0&&(r.form!==void 0||r.url===void 0)},tIt="No in-client confirmation was shown: this client cannot display one, so the decision rested on the yes you got in the conversation.",rIt=(e,t)=>{let{details:r}=t.data;return[e,`Type: ${r.payload.type}`,`Description: ${r.metadata.description??"(none)"}`,`Proposed by ${"id"in r.author?`user ${r.author.id} (domain ${r.author.domainId})`:r.author.subject}, expires ${r.expiryAt}`,`Payload: ${JSON.stringify(r.payload)}`].join(`
2319
+ `)}),metadata:{mcpTaskStatus:"completed"}}):e.end({output:n.result,metadata:{mcpTaskStatus:"completed"}}),n}catch(n){throw t?.aborted?e.end({metadata:{mcpTaskStatus:"cancelled"}}):e.error({error:n,endTree:!0,metadata:{mcpTaskStatus:"failed"}}),n}}tracedInitialAttempt(e,t,r,n,o,a){if(!r)return t;let i=a??this.openTaskSpan(`task attempt ${e} #1`,r,{requestContext:o,...k0e(e,n)}),s=new AbortController;return{outcome:this.endAttemptSpan(i,s.signal,t.outcome),abort:c=>{s.abort(c),t.abort(c)}}}toTaskOutcome(e,t,r){return r.status==="suspended"?(this.logger.debug(`Tool task '${e}' requires client input.`),{status:"input_required",inputRequests:{[`${Rb}-${Ib()}`]:this.inputRequestForm(e,r.suspendPayload,r.resumeSchema)},suspendPayload:r.suspendPayload}):t.mcp?.toolType==="agent"&&r.output?.finishReason==="suspended"?Ol($o(`Agent '${e.slice(4)}' paused for tool approval or input, which MCP tasks don't support yet`)):Ol(this.toCallToolResult(e,t,r.output))}toolErrorOutcome(e,t){return Ol($o(JSON.stringify(this.toolError(e,t).toJSON())))}async startWorkflowTask(e,t,r,n,o,a){let i=r.slice(4),s=this.workflowFor(i);if(!s)throw new Error(`Workflow '${i}' is not served`);let c=Vu(s.inputSchema,n,r);if(c.error)return this.logger.warn(`Tool '${r}' rejected its input.`,{error:c.error.message}),$o(c.error.message);let l={toolName:r,kind:"workflow",workflowKey:i,runId:Ib(),requestContext:this.persistTaskRequestContext(r,t)},d=a.currentSpan,u={ttlMs:o.ttlMs,principal:Pb(e,t),context:l,limits:this.tasks.limits,...d&&{trace:{traceId:d.traceId,spanId:d.id}}},m=T=>{let D=s.createRun({runId:l.runId}),R=D.then(O=>O.start({inputData:c.data,requestContext:t,tracingContext:T})),E=()=>{D.then(O=>O.cancel()).catch(O=>this.logger.warn("Could not cancel workflow run",{tool:r,error:O}))};this.executingRuns.set(l.runId,E);let I=()=>this.executingRuns.delete(l.runId);return R.then(I,I),{result:R,cancel:E}};if(o.mode==="always"){let T;try{T=await this.createTask(e,t,u,!0)}catch(O){if(O instanceof Vd)return $o(x8(O));throw O}let{store:D,task:R}=T,E=u.trace&&this.openTaskSpan(`task attempt ${r} #1`,u.trace,k0e(r,R.taskId)),I=m(E?{currentSpan:E}:a);return D.run(R.taskId,this.workflowTaskWork(l),{initialAttempt:this.tracedInitialAttempt(r,this.liveWorkflowAttempt(s,l,I.result),u.trace,R.taskId,t,E)}),this.logger.debug(`Workflow tool '${r}' runs as task.`),D8(R)}let f=m(a),h=e.mcpReq.signal,g=()=>f.cancel();h.aborted?g():h.addEventListener("abort",g,{once:!0});let S=f.result.then(T=>({view:T}),T=>({error:T})),_,P=new Promise(T=>{_=setTimeout(()=>T("timeout"),o.syncWindowMs)}),y=await Promise.race([S,P]);if(clearTimeout(_),h.removeEventListener("abort",g),y!=="timeout"){if("error"in y)throw y.error;let T=this.workflowRunOutcome(s,l,y.view);if(T?.status==="completed")return T.result}try{let{store:T,task:D}=await this.createTask(e,t,u,!0);return T.run(D.taskId,this.workflowTaskWork(l),{initialAttempt:this.tracedInitialAttempt(r,this.liveWorkflowAttempt(s,l,f.result),u.trace,D.taskId,t)}),this.logger.debug(`Workflow tool '${r}' continues as task.`),D8(D)}catch(T){if(!(T instanceof Vd))throw f.cancel(),T;this.logger.debug(`Workflow tool '${r}' outlived its sync window at the open task cap.`),h.aborted?g():h.addEventListener("abort",g,{once:!0});let D=await S;if(h.removeEventListener("abort",g),"error"in D)throw D.error;let R=this.workflowRunOutcome(s,l,D.view);return R?.status==="completed"?R.result:(f.cancel(),$o(x8(T)))}}liveWorkflowAttempt(e,t,r){let n=new AbortController;return{outcome:r.then(o=>this.workflowRunOutcome(e,t,o)??this.watchWorkflowRun(e,t,n.signal),o=>this.toolErrorOutcome(t.toolName,o)),abort:o=>n.abort(o)}}workflowTaskWork(e){let t=e.toolName;return async r=>{let n=this.workflowFor(e.workflowKey);if(!n)throw new Error(`Workflow '${e.workflowKey}' is no longer served`);let o=new Wn(Object.entries(e.requestContext));try{await this.enforceToolExecutionFGA(t,o)}catch(i){return this.toolErrorOutcome(t,i)}let a=r.trace&&this.openTaskSpan(`task attempt ${t} #${r.trace.attempt}`,r.trace,{requestContext:o,mcpMethod:"tools/call",targetName:t,metadata:{mcpTaskKey:r.trace.taskKey,attempt:r.trace.attempt,resumed:r.trace.resumed,recovered:r.trace.recovered}});return this.endAttemptSpan(a,r.signal,r.resume?this.resumeWorkflowRun(n,e,r,o,a):this.watchWorkflowRun(n,e,r.signal))}}async resumeWorkflowRun(e,t,r,n,o){let{inputResponses:a,suspendPayload:i}=r.resume,[s]=Object.keys(a),c=NR(a,s),l=`Workflow '${t.workflowKey}'`;if(c.kind!=="elicit")return Ol($o(`Unsupported input response for ${l}`));if(c.action!=="accept")return await this.cancelWorkflowRun(t),Ol($o(`${l} was ${c.action==="decline"?"declined":"cancelled"}`));let d=await e.getWorkflowRunById(t.runId,{withNestedWorkflows:!1});if(!d)throw new Error(`${l} run '${t.runId}' was lost`);let u=i;if(!R0e({status:d.status,steps:d.steps},u))return this.watchWorkflowRun(e,t,r.signal);let m=await e.createRun({runId:t.runId}),f;this.executingRuns.set(t.runId,()=>{m.cancel().catch(h=>this.logger.warn("Could not cancel workflow run",{tool:t.toolName,error:h}))});try{f=await m.resume({step:u.step,resumeData:c.content??{},requestContext:n,...o&&{tracingContext:{currentSpan:o}}})}catch(h){if(BPt(h))return this.watchWorkflowRun(e,t,r.signal);let g=await e.getWorkflowRunById(t.runId,{withNestedWorkflows:!1});return g&&!R0e({status:g.status,steps:g.steps},u)?this.watchWorkflowRun(e,t,r.signal):this.toolErrorOutcome(t.toolName,h)}finally{this.executingRuns.delete(t.runId)}return this.workflowRunOutcome(e,t,f)??this.watchWorkflowRun(e,t,r.signal)}async watchWorkflowRun(e,t,r){let n=!0;for(;;){r.throwIfAborted();let o=await e.getWorkflowRunById(t.runId,{withNestedWorkflows:!1});if(!o||n&&o.status==="pending")throw new Error(`Workflow '${t.workflowKey}' run '${t.runId}' was lost`);n=!1;let a=this.workflowRunOutcome(e,t,{status:o.status,input:o.payload,steps:o.steps,result:o.result,error:o.error});if(a)return a;await qPt(jPt,r)}}workflowRunOutcome(e,t,r){let n=`Workflow '${t.workflowKey}'`;switch(r.status){case"success":case"bailed":return Ol({isError:!1,content:[{type:"text",text:JSON.stringify(UPt(r))}]});case"failed":return Ol($o(`${n} failed: ${E0e(r)}`));case"tripwire":return Ol($o(`${n} was blocked: ${E0e(r)}`));case"canceled":return Ol($o(`${n} run was cancelled`));case"suspended":{let o=VPt(e,r);if(!o)return;let a=this.representableForm(o.step?.resumeSchema);return a?(this.logger.debug(`Workflow task '${t.toolName}' requires client input.`),{status:"input_required",inputRequests:{[`${Rb}-${Ib()}`]:this.inputRequestForm(t.toolName,o.payload,a)},suspendPayload:{step:o.path,suspendedAt:o.suspendedAt}}):void 0}default:return}}async cancelWorkflowRun(e){let t=this.executingRuns.get(e.runId);if(t)return t();let r=this.workflowFor(e.workflowKey);if(r)try{let n=await r.getWorkflowRunById(e.runId,{withNestedWorkflows:!1});if(!n||NPt(n.status)||n.status==="running")return;await(await r.createRun({runId:e.runId})).cancel()}catch(n){this.logger.warn("Could not cancel workflow run",{tool:e.toolName,error:n})}}workflowFor(e){return this.workflows?.[e]}async runTool(e,t,r,n){if(!t.execute)throw new Error(`Tool '${e}' cannot be executed.`);let o,a=await t.execute(r,{toolCallId:"",messages:[],skipToolSpan:!0,requestContext:n.requestContext,tracingContext:n.tracingContext,abortSignal:n.abortSignal??n.mcp?.extra.signal,...n.mcp?{mcp:n.mcp}:{},suspend:async(i,s)=>{o={payload:i,resumeSchema:s?.resumeSchema}},resumeData:n.resumeData,suspendPayload:n.suspendPayload});if(o){let{payload:i,resumeSchema:s}=o,{$schema:c,...l}=s?JSON.parse(s):{};return{status:"suspended",suspendPayload:i,resumeSchema:this.resumeSchemaOf(e)??(s?l:void 0)}}return{status:"completed",output:a}}toCallToolResult(e,t,r){if(uT(r))return this.logger.warn(`Tool '${e}' rejected its input.`,{error:r.message}),$o(r.message);let n=J8(t.mcp?._meta),o=n?{_meta:{ui:{resourceUri:n},[MN]:n}}:{};if(!t.outputSchema){let i=typeof r=="string"?r:JSON.stringify(r);return{isError:!1,content:i===void 0?[]:[{type:"text",text:i}],...o}}let a=JSON.stringify(r);return a===void 0?(this.logger.warn(`Tool '${e}' declares an outputSchema but returned no structured output.`),{...$o(`Tool '${e}' declares an outputSchema but returned no output.`),...o}):{isError:!1,structuredContent:r,content:[{type:"text",text:a}],...o}}registerResourceHandlers(e){let t=this.resourceOptions,r=this.appResourceList.length>0;if(!t&&!r)return;let n=async(o,a)=>[...this.appResourceList,...await t?.listResources({extra:JR(o,this.name).extra,requestContext:a})??[]];this.setTracedHandler(e,"resources/list",async(o,a,i)=>({resources:await n(a,i.requestContext)})),this.setTracedHandler(e,"resources/read",async(o,a,i)=>{let s=o.params.uri,c=(await n(a,i.requestContext)).find(S=>S.uri===s);if(!c)throw new We(ze.InvalidParams,`Resource not found: ${s}`);let l=this.appResourceHtml.get(s);if(l!==void 0)return{contents:[this.resourceContents(c,{text:l})]};if(!t)throw new We(ze.InvalidParams,`Resource not found: ${s}`);let{request:d,continuation:u,suspended:m,argsHash:f}=await this.serverRequest(a,i.requestContext,"resources/read",s,{},t.resumeSchema);if(u&&u.outcome!=="accept")throw new We(ze.InvalidParams,`Reading '${s}' was ${u.outcome}ed`);let h=await t.getResourceContent({uri:s,...d}),g=m();if(g)return this.inputRequired(a,d.requestContext,{method:"resources/read",name:s,argsHash:f,round:(u?.round??0)+1,suspendPayload:g.payload,resumeSchema:t.resumeSchema?this.formSchema(t.resumeSchema):void 0});if(h===void 0)throw new Error(`Resource '${s}' returned no content`);return{contents:(Array.isArray(h)?h:[h]).map(S=>this.resourceContents(c,S))}}),t?.resourceTemplates&&this.setTracedHandler(e,"resources/templates/list",async(o,a,i)=>({resourceTemplates:await t.resourceTemplates({extra:JR(a,this.name).extra,requestContext:i.requestContext})}))}resourceContents(e,t){let r={uri:e.uri,mimeType:e.mimeType,...e._meta?{_meta:e._meta}:{}};if("text"in t&&t.text!==void 0)return{...r,text:t.text};if("blob"in t&&t.blob!==void 0)return{...r,blob:t.blob};throw new Error(`Resource '${e.uri}' returned content with neither text nor blob`)}registerPromptHandlers(e){let t=this.promptOptions;if(!t)return;let r=async(n,o)=>{let a=await t.listPrompts({extra:JR(n,this.name).extra,requestContext:o});for(let i of a)Hu.parse(i);return a};this.setTracedHandler(e,"prompts/list",async(n,o,a)=>({prompts:await r(o,a.requestContext)})),t.getPromptMessages&&this.setTracedHandler(e,"prompts/get",async(n,o,a)=>{let{name:i,arguments:s}=n.params,c=(await r(o,a.requestContext)).find(g=>g.name===i);if(!c)throw new We(ze.InvalidParams,`Prompt "${i}" not found`);for(let g of c.arguments??[])if(g.required&&(s?.[g.name]===void 0||s?.[g.name]===null))throw new We(ze.InvalidParams,`Missing required argument: ${g.name}`);let{request:l,continuation:d,suspended:u,argsHash:m}=await this.serverRequest(o,a.requestContext,"prompts/get",i,s??{},t.resumeSchema);if(d&&d.outcome!=="accept")throw new We(ze.InvalidParams,`Prompt "${i}" was ${d.outcome}ed`);let f=await t.getPromptMessages({name:i,args:s,...l}),h=u();if(h)return this.inputRequired(o,l.requestContext,{method:"prompts/get",name:i,argsHash:m,round:(d?.round??0)+1,suspendPayload:h.payload,resumeSchema:t.resumeSchema?this.formSchema(t.resumeSchema):void 0});if(f===void 0)throw new Error(`Prompt "${i}" returned no messages`);return{description:c.description,messages:f}})}loadAppResources(e){for(let[t,r]of Object.entries(e??{})){let n=r.html??(r.htmlPath?igt(r.htmlPath,"utf-8"):void 0);if(n===void 0){this.logger.warn(`App resource '${t}' has neither html nor htmlPath; skipping`);continue}this.appResourceHtml.set(t,n),this.appResourceList.push({uri:t,name:r.name,description:r.description,mimeType:cPe,...r.meta?{_meta:{ui:r.meta}}:{}})}}async enforceToolExecutionFGA(e,t){let r=this.mastra?.getServer?.()?.fga;if(!r)return;let{getMCPToolFGAResourceId:n,requireFGA:o,FGADeniedError:a,MastraFGAPermissions:i}=await Promise.resolve().then(()=>(FEe(),VEe)),s=n(this.id,e),c=t.get("user");if(!c)throw new a({id:"unknown"},{type:"tool",id:s},i.TOOLS_EXECUTE);let l=this.fga?.permissionMapping?.[i.TOOLS_EXECUTE]??i.TOOLS_EXECUTE,d=this.fga?.resourceMapping?.tool??this.fga?.resourceMapping?.tools;await o({fgaProvider:r,user:c,resource:d?{type:d.fgaResourceType,id:d.deriveId?.({user:c,resourceId:s,requestContext:t})??s}:{type:"tool",id:s},permission:l,requestContext:t,context:{resourceId:s},metadata:{mcpServerId:this.id,mcpServerName:this.name,toolId:e}})}async authorizedToolEntries(e){let t=Object.entries(this.convertedTools);return this.mastra?.getServer?.()?.fga?e.get("user")?(await Promise.all(t.map(async r=>{try{return await this.enforceToolExecutionFGA(r[0],e),r}catch(n){if(n instanceof Error&&n.name==="FGADeniedError")return;throw n}}))).filter(r=>r!==void 0):[]:t}notifier(){if(this.httpHandler)return this.httpHandler.notify;let e=this.stdioInstance;if(!e)return;let t=r=>this.logger.error("Failed to publish stdio notification",{error:r});return{toolsChanged:()=>{e.sendToolListChanged().catch(t)},promptsChanged:()=>{e.sendPromptListChanged().catch(t)},resourcesChanged:()=>{e.sendResourceListChanged().catch(t)},resourceUpdated:r=>{e.sendResourceUpdated({uri:r}).catch(t)}}}getNodeHandler(){return this.nodeHandler||(this.httpHandler=_Ee(()=>this.createServerInstance(),{legacy:this.legacy,onerror:e=>this.logger.error("MCP handler error",{error:e.toString()})}),this.nodeHandler=LEe(this.httpHandler,{onerror:e=>this.logger.error("MCP Node handler adapter error",{error:e.toString()})})),this.nodeHandler}async startStdio(){this.stdioHandle=UEe(()=>(this.stdioInstance=this.createServerInstance(),this.stdioInstance),{legacy:"reject",onerror:e=>this.logger.error("MCP stdio handler error",{error:e.toString()})}),this.logger.info("Started MCP Server (stdio, 2026-07-28)")}async startHTTP({url:e,httpPath:t,req:r,res:n,options:o}){if(e.pathname!==t){n.writeHead(404),n.end();return}if(o?.enableDnsRebindingProtection){if(o.allowedHosts?.length){let a=o.allowedHosts.map(i=>new URL(`http://${i}`).hostname);if(!DEe(a)(r,n))return}if(o.allowedOrigins?.length){let a=o.allowedOrigins.map(i=>new URL(i).hostname);if(!MEe(a)(r,n))return}}try{let a=r.body;await this.getNodeHandler()(r,n,a)}catch(a){let i=new Xr({id:"MCP_SERVER_HTTP_CONNECTION_FAILED",domain:Zn.MCP,category:Jn.USER,text:"Failed to handle MCP request"},a);this.logger.trackException(i),n.headersSent||(n.writeHead(500,{"Content-Type":"application/json"}),n.end(JSON.stringify({jsonrpc:"2.0",error:{code:-32603,message:"Internal server error"},id:null})))}}async close(){this.webhooks?.close(),this.eventRegistry.openStreams.size>0&&(this.eventRegistry.closeStreams(),await new Promise(e=>setImmediate(e))),this.stdioHandle&&(await this.stdioHandle.close(),this.stdioHandle=void 0,this.stdioInstance=void 0),this.httpHandler&&(await this.httpHandler.close(),this.httpHandler=void 0,this.nodeHandler=void 0),this.taskStore instanceof YR&&this.taskStore.close(),await this.backgroundTaskStore?.close()}hasStableId=!1;setId(e){super.setId(e),this.id===e&&(this.hasStableId=!0)}__registerMastra(e){super.__registerMastra(e);let t=this.backgroundTaskStore;!this.prepareBackgroundTaskStore()||!(this.taskStore instanceof YR)||(t||this.taskStore.reselect(),this.taskStore.resolve().catch(r=>this.logger.debug(`MCP server '${this.name}' could not choose its task store yet`,{error:r})))}prepareBackgroundTaskStore(){if(this.backgroundTaskStore)return this.backgroundTaskStore;if(!this.tasks.enabled||this.tasks.store)return;let e=this.mastra?.backgroundTaskManager;if(!e)return;let t=new C0e({manager:e,serverId:this.id,workFor:r=>M8(r)?this.workflowTaskWork(r):this.toolTaskWork(r),onCancel:r=>M8(r)?this.cancelWorkflowRun(r):void 0,recoverFor:r=>this.tasks.modeFor(r,this.convertedTools[r]?.mcp?.toolType).recover,logger:()=>this.logger});for(let[r,n]of Object.entries(this.convertedTools))this.tasks.modeFor(r,n.mcp?.toolType).mode!=="off"&&t.registerTool(r);return this.backgroundTaskStore=t,t}async selectTaskStore(){let e=this.mastra?.backgroundTaskManager;if(e){let t=await e.getStorage();if(t.supportsTaskExpiry===!0){let r=this.backgroundTaskStore,n=this.prepareBackgroundTaskStore();return this.hasStableId||this.logger.warn(`MCP server '${this.name}' keeps tasks in Mastra storage under the generated id '${this.id}', which changes on every start: other instances and restarts won't find its tasks. Pass \`id\` to the MCPServer, or register it under a key in Mastra's \`mcpServers\`.`),n.startSweeping(),r||this.logger.warn(`MCP server '${this.name}' serves tasks from Mastra background tasks that started after it was registered; tasks created before a restart may not have been recovered`),n}this.logger.warn(`MCP server '${this.name}' keeps tasks in memory: the background tasks storage (${t.constructor.name}) does not support task expiry. Upgrade the storage adapter to serve tasks across instances.`),await this.backgroundTaskStore?.close(),this.backgroundTaskStore=void 0}return new FU({onCancel:t=>M8(t)?this.cancelWorkflowRun(t):void 0})}getServerInfo(){return{id:this.id,name:this.name,description:this.description,repository:this.repository,version_detail:{version:this.version,release_date:this.releaseDate,is_latest:this.isLatest}}}getServerDetail(){return{...this.getServerInfo(),package_canonical:this.packageCanonical,packages:this.packages,remotes:this.remotes}}getToolListInfo(e){let t=r=>({tools:r.map(([n,o])=>this.toolInfo(n,o))});return this.mastra?.getServer?.()?.fga?e?this.authorizedToolEntries(e).then(t):{tools:[]}:t(Object.entries(this.convertedTools))}getToolInfo(e){let t=this.convertedTools[e];return t?this.toolInfo(e,t):void 0}async executeTool(e,t,r={}){let n=r.requestContext??new Wn,o=this.startRequestSpan("tools/call",{name:e,arguments:t},{requestContext:n});return this.traceRequest(o,async()=>{let a=this.convertedTools[e];if(!a)throw this.logger.warn("Unknown tool requested",{tool:e,server:this.name}),new Xr({id:"MCP_SERVER_TOOL_EXECUTE_PREPARATION_FAILED",domain:Zn.MCP,category:Jn.USER,text:`Unknown tool: ${e}`,details:{toolId:e}});await this.enforceToolExecutionFGA(e,n);try{let i=await this.runTool(e,a,t,{requestContext:n,tracingContext:{currentSpan:o},resumeData:r.resumeData,suspendPayload:r.suspendPayload});if(i.status==="completed"&&uT(i.output))throw new Xr({id:"MCP_SERVER_TOOL_INVALID_INPUT",domain:Zn.MCP,category:Jn.USER,text:i.output.message,details:{toolId:e}});return this.logger.info("Tool executed successfully",{tool:e}),i}catch(i){if(i instanceof Xr&&i.id==="MCP_SERVER_TOOL_INVALID_INPUT")throw i;let s=new Xr({id:"MCP_SERVER_TOOL_EXECUTE_FAILED",domain:Zn.MCP,category:Jn.USER,details:{toolId:e,args:JSON.stringify(t)}},i);throw this.logger.trackException(s),s}})}async readResource(e){let t=this.appResourceHtml.get(e),r=this.appResourceList.find(n=>n.uri===e);if(t===void 0||!r)throw new Xr({id:"MCP_SERVER_RESOURCE_NOT_FOUND",domain:Zn.MCP,category:Jn.USER,text:`Resource '${e}' not found; application resources are only readable through an MCP request`,details:{uri:e}});return{contents:[this.resourceContents(r,{text:t})]}}async listResources(){return{resources:[...this.appResourceList]}}getEventsInfo(){if(this.eventRegistry.size!==0)return{events:rPt(this.eventsDeps())}}getEventsInspector(){if(this.eventRegistry.size!==0)return this.eventsInspector??=nPt(this.eventsDeps()),this.eventsInspector}getTasksInfo(){if(this.tasks.enabled)return xPt(this.tasks,this.taskToolTypes())}getTasksInspector(){if(this.getTasksInfo()?.listable)return this.tasksInspector??=LPt(async()=>this.taskStore instanceof YR?this.taskStore.admin():gZ(this.taskStore)?this.taskStore:void 0),this.tasksInspector}taskToolTypes(){return Object.entries(this.convertedTools).map(([e,t])=>[e,t.mcp?.toolType])}eventsDeps(){return{registry:this.eventRegistry,heartbeatIntervalMs:this.eventsOptions.heartbeatIntervalMs??3e4,maxStreams:this.eventsOptions.maxStreams,getLogger:()=>this.logger,jsonSchema:(e,t)=>this.jsonSchema(e,t)}}};function zPt(e){return!!e&&typeof e=="object"&&"id"in e&&typeof e.id=="string"}function x8({scope:e,limit:t,kind:r}){return r==="retained"?e==="principal"?`Too many recent tasks for this client (limit ${t}). Try again once older tasks expire.`:`Too many recent tasks on this server (limit ${t}). Try again later.`:e==="principal"?`Too many open tasks for this client (limit ${t}). Wait for one to finish or cancel one.`:`Too many open tasks on this server (limit ${t}). Try again later.`}function $o(e){return{content:[{type:"text",text:e}],isError:!0}}function k0e(e,t){return{mcpMethod:"tools/call",targetName:e,metadata:{mcpTaskKey:wn(t),attempt:1,resumed:!1,recovered:!1}}}function Ol(e){return{status:"completed",result:e}}var jPt=1e3;function w0e(e,t){return WN.ElicitRequestFormParams["~standard"].validate({message:e,requestedSchema:t})}function BPt(e){return e instanceof Xr&&(e.id==="WORKFLOW_RESUME_ALREADY_CLAIMED"||e.id==="WORKFLOW_RUN_NOT_SUSPENDED")}function qPt(e,t){return new Promise((r,n)=>{let o=()=>{clearTimeout(a),n(t.reason)},a=setTimeout(()=>{t.removeEventListener("abort",o),r()},e);t.addEventListener("abort",o,{once:!0})})}function GPt(e){let t=e.mcpReq.envelope;return Ibt(t?.[Mo])}function HPt(e){let{authInfo:t,[fL]:r,...n}=e.toJSON(),o=n;if(t&&typeof t=="object"){let{token:i,...s}=t;o={...n,authInfo:s}}else t!==void 0&&(o={...n,authInfo:t});let a=JSON.parse(JSON.stringify(o));return{persisted:a,dropped:[...e.keys()].filter(i=>typeof i=="string"&&i!==fL&&e.get(i)!==void 0&&!(i in a))}}function J8(e){return e?.ui?.resourceUri||e?.[MN]||void 0}function KPt(e){let t=J8(e);if(!e||!t)return e;let r=e.ui;return{...e,ui:{...r,resourceUri:t},[MN]:t}}function XPt(e){try{let t=JSON.parse(e);if(t&&typeof t=="object"&&!Array.isArray(t))return t}catch{}}var K7t=v.object({server:v.string().min(1).describe("Name of the MCP server, as configured on the client"),event:v.string().min(1).describe("Event type name, from mcp_events_list"),arguments:v.string().refine(e=>XPt(e)!==void 0,"arguments must be a JSON object").optional().describe(`Subscription arguments as a JSON-encoded object matching the event's inputSchema, for example {"project":"A"}. Omit if the event takes none.`)});Js();var JU={name:"@florent-uzio/custody-mcp",version:"0.1.1",description:"MCP server for Ripple Custody, built on custody.js",license:"ISC",author:"Florent Uzio",repository:{type:"git",url:"git+https://github.com/florent-uzio/custody-mcp.git"},type:"module",bin:{"custody-mcp":"dist/custody-mcp.js"},files:["dist/custody-mcp.js","dist/THIRD_PARTY_LICENSES.txt"],publishConfig:{access:"public"},engines:{node:">=22.13.0"},keywords:["ripple-custody","mcp","model-context-protocol"],scripts:{test:"vitest run","test:watch":"vitest",typecheck:"tsc",mcp:"tsx src/stdio.ts",build:"node scripts/build.mjs",format:"prettier --write .","format:check":"prettier --check .",prepack:"npm run build",prepublishOnly:"npm run format:check && npm run typecheck && npm test",release:"changeset publish"},devDependencies:{"@changesets/cli":"^3.0.3","@florent-uzio/custody":"^2.15.0","@mastra/core":"^1.81.0","@mastra/mcp":"^2.7.0","@types/node":"^22.20.5",esbuild:"^0.28.2",prettier:"^3.9.10","prettier-plugin-organize-imports":"^4.3.0",tsx:"^4.23.15",typescript:"^7.0.2",vitest:"^5.1.0",zod:"^4.6.7"}};Js();Js();import{readFile as ZPt}from"node:fs/promises";var pAe=async(e,t)=>{let[r,n]=uAe(e,":");if(r==="env"){let o=t[n];if(!o)throw new Error(`env var ${n} is not set`);return o}if(r==="keychain"){let[o,a]=uAe(n,"/");if(!o||!a)throw new Error(`"${e}" is not of the form keychain:<service>/<account>`);return HX(o,a)}if(r==="file")return ZPt(n,"utf8");throw new Error(`unsupported Key reference "${e}"`)},uAe=(e,t)=>{let r=e.indexOf(t);return r===-1?[e,""]:[e.slice(0,r),e.slice(r+1)]};var JPt={environment:v.string().optional().describe("Name of the Environment to use (see listEnvironments). Defaults to the default Environment.")},YPt={environment:v.string().describe("Name of the Environment to act in (see listEnvironments). Required: writes never use the default.")},jn={domainId:v.string().optional().describe("Id of the Custody domain to use (see listDomains). Defaults to the Environment's Default domain.")},On=async({input:e,environment:t,custody:r})=>{let n=e.domainId??t.defaultDomainId;if(n!==void 0)return n;let{domains:o}=await r.users.me();if(o.length===1)return o[0].id;let a=o.map(i=>`${i.alias} (${i.id})`).join(", ");throw new Error(`Environment "${t.name}" has no Default domain and its user belongs to several domains: ${a}. Pass a domainId.`)},ja={limit:v.number().int().min(1).max(100).default(20).describe("How many items to return (default 20, at most 100)."),cursor:v.string().optional().describe("The nextCursor of the previous page, to get the page after it.")},Ba=({limit:e,cursor:t})=>({limit:e,startingAfter:t}),qa=e=>e.nextStartingAfter===void 0?{}:{nextCursor:e.nextStartingAfter},mAe=(e,t)=>{let r=new Map,n=async a=>{let{environments:i,defaultName:s}=await HS(e),c=a??s,l=i.find(d=>d.name===c);if(!l){let d=i.map(u=>u.name).join(", ");throw new Error(`Unknown Environment "${c}". Known Environments: ${d}.`)}return l},o=async a=>{let i=JSON.stringify([a.authUrl,a.apiUrl,a.publicKey,a.keyReference]),s=r.get(a.name);if(s?.settings!==i){let c=await pAe(a.keyReference,e).catch(l=>{throw new Error(`Cannot read the private key of Environment "${a.name}" from ${a.keyReference}: ${l.message}`)});s={settings:i,custody:t({...a,privateKey:c})},r.set(a.name,s)}return s.custody};return a=>{let i=a.access??"read";return Fu({id:a.id,description:a.description,inputSchema:v.object({...i==="read"?JPt:YPt,...a.input}),resumeSchema:a.resumeSchema,execute:async(s,c)=>{let{environment:l,...d}=s,u=await n(l);if(Wm.indexOf(u.access)<Wm.indexOf(i))throw new Error(`${a.id} needs the "${i}" Access level, but Environment "${u.name}" has "${u.access}".`);let m=await o(u);try{let f=await a.run({input:d,custody:m,environment:u,context:c});return{environment:u.name,...f}}catch(f){throw f instanceof Error?f:new Error(JSON.stringify(f)??String(f))}}})}};var vZ={accountId:v.string().describe("Id of the Custody account (see listAccounts).")},_Ae=e=>({listDomains:e({id:"listDomains",description:"List the Custody domains this Environment can see, one page at a time.",input:ja,run:async({custody:t,input:r})=>{let n=await t.domains.list(Ba(r));return{domains:n.items.map(({data:o})=>({id:o.id,alias:o.alias,parentId:o.parentId,lock:o.lock})),...qa(n)}}}),listAccounts:e({id:"listAccounts",description:"List the accounts of a Custody domain, one page at a time.",input:{...jn,...ja},run:async t=>{let r=await On(t),n=await t.custody.accounts.list({domainId:r},Ba(t.input));return{domainId:r,accounts:n.items.map(({data:o})=>({id:o.id,alias:o.alias,ledgerIds:o.ledgerIds??(o.ledgerId===void 0?[]:[o.ledgerId]),lock:o.lock})),...qa(n)}}}),getAccount:e({id:"getAccount",description:"Get one account of a Custody domain, with its ledgers and processing status.",input:{...jn,...vZ},run:async t=>{let r=await On(t),{data:n,additionalDetails:o}=await t.custody.accounts.get({domainId:r,accountId:t.input.accountId});return{domainId:r,account:n,additionalDetails:o}}}),listAccountAddresses:e({id:"listAccountAddresses",description:"List the ledger addresses of a Custody account, one page at a time.",input:{...jn,...vZ,...ja},run:async t=>{let r=await On(t),{accountId:n}=t.input,o=await t.custody.accounts.addresses({domainId:r,accountId:n},Ba(t.input));return{domainId:r,accountId:n,addresses:o.items.map(a=>({id:a.id,ledgerId:a.ledgerId,address:a.address,scope:a.scope})),...qa(o)}}}),getAccountBalances:e({id:"getAccountBalances",description:"List the balances of a Custody account, one ticker per item, one page at a time.",input:{...jn,...vZ,...ja},run:async t=>{let r=await On(t),{accountId:n}=t.input,o=await t.custody.accounts.getAccountBalances({domainId:r,accountId:n},Ba(t.input));return{domainId:r,accountId:n,balances:o.items.map(a=>({tickerId:a.tickerId,totalAmount:a.totalAmount,availableAmount:a.availableAmount,reservedAmount:a.reservedAmount,quarantinedAmount:a.quarantinedAmount,lastUpdatedAt:a.lastUpdatedAt})),...qa(o)}}}),findAccountByAddress:e({id:"findAccountByAddress",description:"Find the Custody account that owns a ledger address, in any domain. Returns account null when no account has it.",input:{address:v.string().describe("The ledger address, as shown on the ledger."),ledgerId:v.string().optional().describe("Id of the ledger, when the address is on several."),domainId:v.string().optional().describe("Id of the domain to look in, when the address is in several.")},run:async({custody:t,input:r})=>{let n=await t.accounts.findByAddress(r.address,{ledgerId:r.ledgerId,domainId:r.domainId});return{account:n===void 0?null:{domainId:n.domainId,accountId:n.accountId,ledgerId:n.ledgerId,address:n.address}}}})});Js();var QPt="io.modelcontextprotocol/clientCapabilities",eIt=e=>{let r=e.mcp?.extra.mcpReq.envelope?.[QPt]?.elicitation;return r!==void 0&&(r.form!==void 0||r.url===void 0)},tIt="No in-client confirmation was shown: this client cannot display one, so the decision rested on the yes you got in the conversation.",rIt=(e,t)=>{let{details:r}=t.data;return[e,`Type: ${r.payload.type}`,`Description: ${r.metadata.description??"(none)"}`,`Proposed by ${"id"in r.author?`user ${r.author.id} (domain ${r.author.domainId})`:r.author.subject}, expires ${r.expiryAt}`,`Payload: ${JSON.stringify(r.payload)}`].join(`
2320
2320
  `)},nIt=async(e,t,r)=>{let{domains:n}=await e.users.me(),o=n.find(a=>a.id===r)?.userReference.id;if(o===void 0)throw new Error(`The user of Environment "${t.name}" does not belong to domain ${r}.`);return{domainId:r,id:o}},fAe=v.string().describe("Id of the Intent (see listPendingApprovals)."),hAe=async({input:e,custody:t,environment:r,context:n},o,a)=>{let i=await On({input:e,custody:t,environment:r}),s=await t.intents.get({domainId:i,intentId:e.intentId}),c=n.resumeData!==void 0;if(!c&&eIt(n)){let l=`${o} Intent ${e.intentId} in Environment "${r.name}" (domain ${i})?`;return await n.suspend?.({message:rIt(l,s)}),{}}return await a({author:await nIt(t,r,i),targetDomainId:i,intentId:e.intentId,proposalSignature:s.data.details.proposalSignature}),{domainId:i,intentId:e.intentId,...c?{}:{note:tIt}}},CAe=e=>({approveIntent:e({id:"approveIntent",description:"Approve a pending Intent as the Environment user, after the human confirms.",access:"approve",input:{...jn,intentId:fAe},resumeSchema:v.object({}),run:t=>hAe(t,"Approve",r=>t.custody.intents.approve({request:{...r,type:"Approve"}}))}),rejectIntent:e({id:"rejectIntent",description:"Reject a pending Intent as the Environment user, with a reason, after the human confirms.",access:"approve",input:{...jn,intentId:fAe,reason:v.string().min(1).describe("Why the Intent is rejected, recorded in Custody.")},resumeSchema:v.object({}),run:t=>hAe(t,`Reject (reason: ${t.input.reason})`,r=>t.custody.intents.reject({request:{...r,rejectReason:t.input.reason,type:"Reject"}}))})});Js();var gAe=(e,...t)=>e.intents.list(...t),oIt=async(e,t,{data:r})=>{if(r.state.status!=="Open")return[];try{return await e.intents.remainingUsers({domainId:t,intentId:r.id})}catch(n){if(n instanceof Xe&&n.statusCode===400)return[];throw n}},SZ=async(e,t,r)=>{let{id:n,details:o,state:a}=r.data;return{id:n,type:o.payload.type,status:a.status,author:o.author,description:o.metadata.description,createdAt:o.metadata.createdAt,expiryAt:o.expiryAt,missingApprovals:await oIt(e,t,r)}},yAe=e=>({listIntents:e({id:"listIntents",description:"List the Intents of a Custody domain, one page at a time: type, status, author and the approvals still missing.",input:{...jn,...ja},run:async t=>{let r=await On(t),{custody:n}=t,o=await gAe(n,{domainId:r},Ba(t.input));return{domainId:r,intents:await Promise.all(o.items.map(a=>SZ(n,r,a))),...qa(o)}}}),listPendingApprovals:e({id:"listPendingApprovals",description:"List the open Intents of a Custody domain still awaiting the Environment user's decision, one page at a time. A page can hold fewer items than the limit, or none, and still have a nextCursor: follow it to see the rest.",input:{...jn,...ja},run:async t=>{let r=await On(t),{custody:n}=t,[o,a]=await Promise.all([n.users.me(),gAe(n,{domainId:r},{...Ba(t.input),"state.status":["Open"]})]),i=(c,l)=>o.domains.some(d=>d.id===c&&d.userReference.id===l),s=await Promise.all(a.items.map(c=>SZ(n,r,c)));return{domainId:r,intents:s.filter(c=>c.missingApprovals.some(({domainId:l,users:d})=>d.some(u=>i(l,u)))),...qa(a)}}}),getIntent:e({id:"getIntent",description:"Get one Intent of a Custody domain: its summary, what it proposes (payload), why it failed if it did, and the approvals still missing.",input:{...jn,intentId:v.string().describe("Id of the Intent (see listIntents).")},run:async t=>{let r=await On(t),n=await t.custody.intents.get({domainId:r,intentId:t.input.intentId}),{payload:o}=n.data.details,{error:a}=n.data.state;return{domainId:r,intent:{...await SZ(t.custody,r,n),payload:o,error:a}}}})});var vAe=`
2321
2321
  You are a Ripple Custody assistant. Use your tools to read from a Ripple Custody instance as the user's own Custody user.
2322
2322
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@florent-uzio/custody-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "MCP server for Ripple Custody, built on custody.js",
5
5
  "license": "ISC",
6
6
  "author": "Florent Uzio",