@oobe-protocol-labs/sap-mcp-server 0.9.11 → 0.9.15

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +2 -2
  3. package/USER_DOCS/00_START_HERE.md +24 -2
  4. package/USER_DOCS/03_PAYMENTS_X402_PAYSH.md +6 -1
  5. package/USER_DOCS/04_CLIENT_CONFIGS.md +4 -3
  6. package/USER_DOCS/05_SKILLS_AND_TOOLS.md +10 -5
  7. package/USER_DOCS/06_DESKTOP_GUI_WIZARD.md +6 -6
  8. package/dist/config/mcp-client-injection.d.ts.map +1 -1
  9. package/dist/config/mcp-client-injection.js +4 -3
  10. package/dist/config/mcp-client-injection.js.map +1 -1
  11. package/dist/core/constants.d.ts +9 -1
  12. package/dist/core/constants.d.ts.map +1 -1
  13. package/dist/core/constants.js +13 -3
  14. package/dist/core/constants.js.map +1 -1
  15. package/dist/payments/hosted-tool-eligibility.d.ts.map +1 -1
  16. package/dist/payments/hosted-tool-eligibility.js +3 -0
  17. package/dist/payments/hosted-tool-eligibility.js.map +1 -1
  18. package/dist/payments/index.d.ts +2 -2
  19. package/dist/payments/index.d.ts.map +1 -1
  20. package/dist/payments/index.js +1 -1
  21. package/dist/payments/index.js.map +1 -1
  22. package/dist/payments/pricing.d.ts +29 -0
  23. package/dist/payments/pricing.d.ts.map +1 -1
  24. package/dist/payments/pricing.js +81 -0
  25. package/dist/payments/pricing.js.map +1 -1
  26. package/dist/prompts/context/sap-agent-context.prompt.js +2 -0
  27. package/dist/prompts/context/sap-agent-context.prompt.js.map +1 -1
  28. package/dist/prompts/context/sap-agent-start.prompt.js +2 -0
  29. package/dist/prompts/context/sap-agent-start.prompt.js.map +1 -1
  30. package/dist/prompts/registry/register-sap-agent.prompt.js +18 -18
  31. package/dist/prompts/registry/register-sap-agent.prompt.js.map +1 -1
  32. package/dist/remote/public-home/sections.d.ts.map +1 -1
  33. package/dist/remote/public-home/sections.js +8 -2
  34. package/dist/remote/public-home/sections.js.map +1 -1
  35. package/dist/remote/public-home/types.d.ts +2 -0
  36. package/dist/remote/public-home/types.d.ts.map +1 -1
  37. package/dist/remote/server.d.ts +2 -0
  38. package/dist/remote/server.d.ts.map +1 -1
  39. package/dist/remote/server.js +163 -2
  40. package/dist/remote/server.js.map +1 -1
  41. package/dist/security/tool-permissions.d.ts.map +1 -1
  42. package/dist/security/tool-permissions.js +9 -0
  43. package/dist/security/tool-permissions.js.map +1 -1
  44. package/dist/tools/agent-start-tool.d.ts.map +1 -1
  45. package/dist/tools/agent-start-tool.js +227 -3
  46. package/dist/tools/agent-start-tool.js.map +1 -1
  47. package/dist/tools/client-sdk-tools.js +3 -3
  48. package/dist/tools/client-sdk-tools.js.map +1 -1
  49. package/dist/tools/magicblock-tools.d.ts.map +1 -1
  50. package/dist/tools/magicblock-tools.js +43 -26
  51. package/dist/tools/magicblock-tools.js.map +1 -1
  52. package/dist/tools/sap-sdk-tools.d.ts +412 -1
  53. package/dist/tools/sap-sdk-tools.d.ts.map +1 -1
  54. package/dist/tools/sap-sdk-tools.js +1044 -30
  55. package/dist/tools/sap-sdk-tools.js.map +1 -1
  56. package/dist/tools/sap-sns-tools.d.ts.map +1 -1
  57. package/dist/tools/sap-sns-tools.js +6 -2
  58. package/dist/tools/sap-sns-tools.js.map +1 -1
  59. package/dist/tools/skills-tools.d.ts.map +1 -1
  60. package/dist/tools/skills-tools.js +2 -0
  61. package/dist/tools/skills-tools.js.map +1 -1
  62. package/dist/tools/tool-aliases.d.ts.map +1 -1
  63. package/dist/tools/tool-aliases.js +7 -0
  64. package/dist/tools/tool-aliases.js.map +1 -1
  65. package/dist/tools/transaction-tools.d.ts +56 -0
  66. package/dist/tools/transaction-tools.d.ts.map +1 -1
  67. package/dist/tools/transaction-tools.js +215 -18
  68. package/dist/tools/transaction-tools.js.map +1 -1
  69. package/dist/tools/x402-paid-call-tool.d.ts.map +1 -1
  70. package/dist/tools/x402-paid-call-tool.js +422 -18
  71. package/dist/tools/x402-paid-call-tool.js.map +1 -1
  72. package/docs/03_CONFIGURATION_AND_WIZARD.md +1 -0
  73. package/docs/06_PAYMENTS_X402_AND_PAYSH.md +45 -3
  74. package/docs/07_ENDPOINTS_AND_CLIENTS.md +2 -1
  75. package/docs/14_DESKTOP_WIZARD_RELEASE.md +5 -4
  76. package/docs/16_SAP_AGENT_IDENTITY_PIPELINE.md +298 -0
  77. package/docs/README.md +2 -0
  78. package/docs/magicblock-tools.md +26 -0
  79. package/package.json +1 -1
  80. package/server.json +3 -3
  81. package/skills/sap-agent-registry/SKILL.md +63 -5
  82. package/skills/sap-escrow-settlement/SKILL.md +22 -4
  83. package/skills/sap-mcp/SKILL.md +73 -22
  84. package/skills/sap-mcp/TOOL_REFERENCE.md +22 -1
  85. package/skills/sap-nft-metaplex/SKILL.md +22 -0
  86. package/skills/sap-operations/SKILL.md +17 -0
  87. package/skills/sap-payments-x402/SKILL.md +33 -2
  88. package/skills/sap-sns/SKILL.md +17 -0
  89. package/smithery.config.schema.json +7 -0
@@ -7,7 +7,8 @@ import { createTextResponse } from '../adapters/mcp/tool-response.js';
7
7
  import { getActiveProfile, getProfileConfigPath } from '../config/profiles.js';
8
8
  import { executeX402PaidCall, executeExternalX402Call, getX402PaymentReadiness, inspectX402Receipt, probeX402PaymentChallenge, signX402PaymentChallenge, } from '../payments/x402-paid-call.js';
9
9
  import { finalizeTransactionWithLocalSigner } from './transaction-tools.js';
10
- import { parseRegisterAgentArgs } from './sap-sdk-tools.js';
10
+ import { SAP_AGENT_REGISTER_INPUT_SCHEMA, SAP_AGENT_UPDATE_INPUT_SCHEMA, parseRegisterAgentArgs, parseUpdateAgentArgs, } from './sap-sdk-tools.js';
11
+ import { SAP_PROTOCOL_TREASURY, SAP_REGISTRATION_FEE_LAMPORTS } from '../core/constants.js';
11
12
  const paidCallInputSchema = {
12
13
  endpoint: {
13
14
  type: 'string',
@@ -97,6 +98,7 @@ export function registerX402PaidCallTool(server, _context) {
97
98
  registerPaymentsCallPaidTool(server, 'sap_payments_call_paid_tool');
98
99
  registerPaymentsCallExternalX402Tool(server);
99
100
  registerPaymentsRegisterAgentTool(server, _context);
101
+ registerPaymentsUpdateAgentTool(server, _context);
100
102
  registerPaymentsFinalizeTransactionTool(server, _context);
101
103
  registerPaymentsPrepareChallengeTool(server);
102
104
  registerPaymentsSignChallengeTool(server);
@@ -107,30 +109,30 @@ export function registerX402PaidCallTool(server, _context) {
107
109
  function registerPaymentsRegisterAgentTool(server, context) {
108
110
  registerTool(server, 'sap_payments_register_agent', {
109
111
  title: 'Register SAP Agent With Local Signer',
110
- description: 'Local non-custodial SAP registry write for hosted users. Use this when hosted sap_register_agent returns hosted_local_signer_required. It registers the active local SAP MCP profile wallet as an on-chain SAP agent with the local signer; OOBE never receives keypair bytes. This tool does not pay a hosted x402 fee because it runs locally through sap_payments. Requires confirm: true.',
112
+ description: 'Canonical local non-custodial SAP registry write for hosted and local users. Use this after hosted sap_register_agent returns hosted_local_signer_required, or directly instead of raw sap_register_agent. It registers the active local SAP MCP profile wallet as an on-chain SAP agent with the local signer; OOBE never receives keypair bytes. This tool does not pay a hosted x402 fee because it runs locally through sap_payments. After confirmation it enforces the source-level expected 0.1 SOL SAP protocol registration fee against the protocol treasury. success is true only when the agent account exists and the protocol fee invariant is verified. Requires confirm: true.',
111
113
  inputSchema: {
112
- name: { type: 'string', description: 'Human-readable SAP agent name.' },
113
- description: { type: 'string', description: 'Detailed description of the agent purpose and capabilities.' },
114
- capabilities: { type: 'array', description: 'Capability objects or strings identifying what the agent can do, for example "jupiter:swap" or "sns:identity".' },
115
- pricing: { type: 'array', description: 'Pricing tier objects defining per-call costs, rate limits, and settlement terms. Use micro-USDC/base token units where applicable.' },
116
- protocols: { type: 'array', items: { type: 'string' }, description: 'Protocol identifiers supported by the agent, for example sap, mcp, jupiter, sns, metaplex, or x402.' },
117
- agentId: { type: 'string', description: 'Optional stable agent identifier, for example solking.' },
118
- agentUri: { type: 'string', description: 'Optional URI to the agent off-chain metadata or service endpoint.' },
119
- metadataUri: { type: 'string', description: 'Alias for agentUri; URI to off-chain agent metadata JSON.' },
120
- x402Endpoint: { type: 'string', description: 'Optional x402 payment/discovery endpoint URL advertised by this agent.' },
114
+ ...SAP_AGENT_REGISTER_INPUT_SCHEMA,
115
+ confirmationTimeoutMs: {
116
+ type: 'number',
117
+ description: 'Optional local confirmation wait in milliseconds. Defaults to 90000 so the tool can distinguish confirmed registration from an expired/not-landed transaction.',
118
+ },
121
119
  confirm: { type: 'boolean', description: 'Must be true. Confirms the user wants the local signer to submit this SAP registry transaction.' },
122
120
  },
123
121
  outputSchema: {
124
122
  type: 'object',
125
123
  properties: {
126
- success: { type: 'boolean', description: 'Whether the local SAP agent registration transaction was submitted.' },
124
+ success: { type: 'boolean', description: 'Whether the full SAP registration lifecycle completed: agent account confirmed and protocol fee invariant verified.' },
127
125
  signature: { type: 'string', description: 'Solana transaction signature returned by the SAP SDK.' },
126
+ confirmationStatus: { type: 'string', description: 'Final local confirmation result: confirmed, finalized, processed, expired, missing, or failed.' },
128
127
  signerPublicKey: { type: 'string', description: 'Public key of the local SAP MCP signer that registered the agent.' },
129
128
  profile: { type: 'string', description: 'Active local SAP MCP profile used for registration when available.' },
129
+ agentRegistered: { type: 'boolean', description: 'Whether the SAP agent account was found on-chain, even if protocolComplete is false.' },
130
130
  agent: { type: 'object', description: 'Agent registration fields submitted on-chain.' },
131
+ protocolFee: { type: 'object', description: 'Verification of the SAP protocol registration fee credited to the protocol treasury in the landed transaction.' },
132
+ protocolComplete: { type: 'boolean', description: 'True only when the agent account is confirmed and the expected protocol fee invariant is verified from transaction metadata.' },
131
133
  audit: { type: 'object', description: 'Agent-readable proof that the write was local, non-custodial, and did not use hosted x402.' },
132
134
  },
133
- required: ['success', 'signature', 'signerPublicKey', 'agent', 'audit'],
135
+ required: ['success', 'signature', 'confirmationStatus', 'signerPublicKey', 'agentRegistered', 'agent', 'protocolFee', 'protocolComplete', 'audit'],
134
136
  },
135
137
  annotations: {
136
138
  readOnlyHint: false,
@@ -148,11 +150,120 @@ function registerPaymentsRegisterAgentTool(server, context) {
148
150
  }
149
151
  const args = parseRegisterAgentArgs(parsed);
150
152
  const signature = await context.sapClient.agent.register(args);
153
+ const confirmation = await waitForLocalAgentRegistration(context, signature, parsed.confirmationTimeoutMs);
154
+ if (!confirmation.confirmed) {
155
+ return createTextResponse(JSON.stringify({
156
+ success: false,
157
+ signature,
158
+ confirmationStatus: confirmation.status,
159
+ signerPublicKey,
160
+ profile: activeProfile,
161
+ agentPda: confirmation.agentPda,
162
+ agentRegistered: false,
163
+ agent: {
164
+ name: args.name,
165
+ description: args.description,
166
+ agentId: args.agentId,
167
+ agentUri: args.agentUri,
168
+ x402Endpoint: args.x402Endpoint,
169
+ protocols: args.protocols,
170
+ capabilityCount: args.capabilities.length,
171
+ pricingTierCount: args.pricing.length,
172
+ },
173
+ protocolFee: buildUnconfirmedProtocolFeeStatus(signature),
174
+ protocolComplete: false,
175
+ audit: {
176
+ action: 'sap_payments_register_agent',
177
+ registeredLocally: true,
178
+ hostedX402Charged: false,
179
+ signerBoundary: 'local-sap-payments-bridge',
180
+ secretMaterial: 'keypair-bytes-never-returned',
181
+ transactionLanded: false,
182
+ retrySafe: confirmation.retrySafe,
183
+ rule: 'The local signer submitted the SAP registry transaction, but the transaction did not confirm or the agent account was not found inside the local confirmation window.',
184
+ nextAction: confirmation.retrySafe
185
+ ? 'Ask the user for confirmation, then retry sap_payments_register_agent once with the same fields and confirm: true. Do not call hosted sap_register_agent.'
186
+ : 'Do not retry automatically. Inspect the signature and local RPC health first.',
187
+ },
188
+ }, null, 2), { isError: true });
189
+ }
190
+ const protocolFee = await verifyProtocolRegistrationFee(context, signature);
191
+ if (protocolFee.status === 'missing_or_underpaid') {
192
+ return createTextResponse(JSON.stringify({
193
+ success: false,
194
+ signature,
195
+ confirmationStatus: confirmation.status,
196
+ signerPublicKey,
197
+ profile: activeProfile,
198
+ agentPda: confirmation.agentPda,
199
+ agentRegistered: true,
200
+ protocolComplete: false,
201
+ agent: {
202
+ name: args.name,
203
+ description: args.description,
204
+ agentId: args.agentId,
205
+ agentUri: args.agentUri,
206
+ x402Endpoint: args.x402Endpoint,
207
+ protocols: args.protocols,
208
+ capabilityCount: args.capabilities.length,
209
+ pricingTierCount: args.pricing.length,
210
+ },
211
+ protocolFee,
212
+ audit: {
213
+ action: 'sap_payments_register_agent',
214
+ registeredLocally: true,
215
+ hostedX402Charged: false,
216
+ signerBoundary: 'local-sap-payments-bridge',
217
+ secretMaterial: 'keypair-bytes-never-returned',
218
+ transactionLanded: true,
219
+ protocolIntegrity: 'failed',
220
+ rule: 'The SAP agent account was found after registration, but the source-level expected protocol treasury registration fee was not visible in the landed transaction. The account exists, but the SAP registration lifecycle is not complete and must not be marketed as protocol-complete.',
221
+ nextAction: 'Do not retry registration automatically. Inspect the signature, deployed SAP program version, and treasury account balance delta before changing fee policy or redeploying the program.',
222
+ },
223
+ }, null, 2), { isError: true });
224
+ }
225
+ if (protocolFee.status !== 'verified') {
226
+ return createTextResponse(JSON.stringify({
227
+ success: false,
228
+ signature,
229
+ confirmationStatus: confirmation.status,
230
+ signerPublicKey,
231
+ profile: activeProfile,
232
+ agentPda: confirmation.agentPda,
233
+ agentRegistered: true,
234
+ protocolComplete: false,
235
+ agent: {
236
+ name: args.name,
237
+ description: args.description,
238
+ agentId: args.agentId,
239
+ agentUri: args.agentUri,
240
+ x402Endpoint: args.x402Endpoint,
241
+ protocols: args.protocols,
242
+ capabilityCount: args.capabilities.length,
243
+ pricingTierCount: args.pricing.length,
244
+ },
245
+ protocolFee,
246
+ audit: {
247
+ action: 'sap_payments_register_agent',
248
+ registeredLocally: true,
249
+ hostedX402Charged: false,
250
+ signerBoundary: 'local-sap-payments-bridge',
251
+ secretMaterial: 'keypair-bytes-never-returned',
252
+ transactionLanded: true,
253
+ protocolIntegrity: 'fee-verification-unavailable',
254
+ rule: 'The SAP agent account was found, but transaction metadata did not prove the protocol treasury fee. The lifecycle is intentionally failed closed until another RPC or explorer verifies the fee delta.',
255
+ nextAction: 'Fetch transaction metadata from a reliable RPC or explorer. Do not announce registration complete until protocolFee.status is verified.',
256
+ },
257
+ }, null, 2), { isError: true });
258
+ }
151
259
  return createTextResponse(JSON.stringify({
152
260
  success: true,
153
261
  signature,
262
+ confirmationStatus: confirmation.status,
154
263
  signerPublicKey,
155
264
  profile: activeProfile,
265
+ agentPda: confirmation.agentPda,
266
+ agentRegistered: true,
156
267
  agent: {
157
268
  name: args.name,
158
269
  description: args.description,
@@ -163,13 +274,18 @@ function registerPaymentsRegisterAgentTool(server, context) {
163
274
  capabilityCount: args.capabilities.length,
164
275
  pricingTierCount: args.pricing.length,
165
276
  },
277
+ protocolFee,
278
+ protocolComplete: protocolFee.status === 'verified',
166
279
  audit: {
167
280
  action: 'sap_payments_register_agent',
168
281
  registeredLocally: true,
169
282
  hostedX402Charged: false,
170
283
  signerBoundary: 'local-sap-payments-bridge',
171
284
  secretMaterial: 'keypair-bytes-never-returned',
285
+ transactionLanded: true,
286
+ protocolIntegrity: 'verified',
172
287
  rule: 'Hosted sap_register_agent is accountless and cannot sign user-owned registry writes. This local bridge submitted the SAP registry transaction with the user-controlled profile signer.',
288
+ nextAction: 'Registration is complete. Fetch the agent by owner wallet and continue with optional Metaplex or SNS identity links.',
173
289
  },
174
290
  }, null, 2));
175
291
  }
@@ -178,6 +294,234 @@ function registerPaymentsRegisterAgentTool(server, context) {
178
294
  }
179
295
  });
180
296
  }
297
+ function buildUnconfirmedProtocolFeeStatus(signature) {
298
+ return {
299
+ status: 'not_checked',
300
+ signature,
301
+ expectedTreasury: SAP_PROTOCOL_TREASURY,
302
+ expectedLamports: SAP_REGISTRATION_FEE_LAMPORTS.toString(10),
303
+ reason: 'Registration transaction was not confirmed, so protocol fee verification was skipped.',
304
+ };
305
+ }
306
+ async function verifyProtocolRegistrationFee(context, signature) {
307
+ try {
308
+ const tx = await context.connection.getParsedTransaction(signature, {
309
+ commitment: 'confirmed',
310
+ maxSupportedTransactionVersion: 0,
311
+ });
312
+ if (!tx?.meta) {
313
+ return {
314
+ status: 'unavailable',
315
+ signature,
316
+ expectedTreasury: SAP_PROTOCOL_TREASURY,
317
+ expectedLamports: SAP_REGISTRATION_FEE_LAMPORTS.toString(10),
318
+ reason: 'Transaction metadata was unavailable from the configured RPC.',
319
+ };
320
+ }
321
+ const accountKeys = tx.transaction.message.accountKeys.map((account) => account.pubkey.toBase58());
322
+ const treasuryIndex = accountKeys.indexOf(SAP_PROTOCOL_TREASURY);
323
+ if (treasuryIndex < 0) {
324
+ return {
325
+ status: 'missing_or_underpaid',
326
+ signature,
327
+ expectedTreasury: SAP_PROTOCOL_TREASURY,
328
+ expectedLamports: SAP_REGISTRATION_FEE_LAMPORTS.toString(10),
329
+ observedLamportsDelta: '0',
330
+ reason: 'Protocol treasury account was not present in the transaction account list.',
331
+ };
332
+ }
333
+ const preBalance = BigInt(tx.meta.preBalances[treasuryIndex] ?? 0);
334
+ const postBalance = BigInt(tx.meta.postBalances[treasuryIndex] ?? 0);
335
+ const delta = postBalance - preBalance;
336
+ const verified = delta >= SAP_REGISTRATION_FEE_LAMPORTS;
337
+ return {
338
+ status: verified ? 'verified' : 'missing_or_underpaid',
339
+ signature,
340
+ expectedTreasury: SAP_PROTOCOL_TREASURY,
341
+ expectedLamports: SAP_REGISTRATION_FEE_LAMPORTS.toString(10),
342
+ observedLamportsDelta: delta.toString(10),
343
+ reason: verified
344
+ ? 'Protocol treasury balance delta satisfies the expected SAP registration fee.'
345
+ : 'Protocol treasury balance delta is below the expected SAP registration fee.',
346
+ };
347
+ }
348
+ catch (error) {
349
+ return {
350
+ status: 'unavailable',
351
+ signature,
352
+ expectedTreasury: SAP_PROTOCOL_TREASURY,
353
+ expectedLamports: SAP_REGISTRATION_FEE_LAMPORTS.toString(10),
354
+ reason: error instanceof Error ? error.message : 'Unable to fetch transaction metadata from RPC.',
355
+ };
356
+ }
357
+ }
358
+ function registerPaymentsUpdateAgentTool(server, context) {
359
+ registerTool(server, 'sap_payments_update_agent', {
360
+ title: 'Update SAP Agent With Local Signer',
361
+ description: 'Local non-custodial SAP registry update for hosted users. Use this when hosted sap_update_agent returns hosted_local_signer_required. It updates the active local SAP MCP profile wallet agent with the local signer, including name, description, capabilities, protocols, pricing, agentUri/metadataUri, or x402Endpoint. Use it for agent picture/profile metadata updates after uploading image or metadata to a public URL such as IPFS, Arweave, Kommodo, or a HTTPS metadata endpoint. OOBE never receives keypair bytes and no hosted x402 fee is charged. Requires confirm: true.',
362
+ inputSchema: {
363
+ ...SAP_AGENT_UPDATE_INPUT_SCHEMA,
364
+ confirmationTimeoutMs: {
365
+ type: 'number',
366
+ description: 'Optional local confirmation wait in milliseconds. Defaults to 90000 so the tool can distinguish confirmed update from an expired/not-landed transaction.',
367
+ },
368
+ confirm: { type: 'boolean', description: 'Must be true. Confirms the user wants the local signer to submit this SAP registry update transaction.' },
369
+ },
370
+ outputSchema: {
371
+ type: 'object',
372
+ properties: {
373
+ success: { type: 'boolean', description: 'Whether the local SAP agent update transaction was confirmed on-chain.' },
374
+ signature: { type: 'string', description: 'Solana transaction signature returned by the SAP SDK.' },
375
+ confirmationStatus: { type: 'string', description: 'Final local confirmation result: confirmed, finalized, processed, expired, missing, or failed.' },
376
+ signerPublicKey: { type: 'string', description: 'Public key of the local SAP MCP signer that updated the agent.' },
377
+ profile: { type: 'string', description: 'Active local SAP MCP profile used for update when available.' },
378
+ agentPda: { type: 'string', description: 'SAP agent PDA derived from the active local signer wallet.' },
379
+ update: { type: 'object', description: 'Update fields submitted on-chain. Omitted fields were intentionally left unchanged.' },
380
+ audit: { type: 'object', description: 'Agent-readable proof that the write was local, non-custodial, and did not use hosted x402.' },
381
+ },
382
+ required: ['success', 'signature', 'confirmationStatus', 'signerPublicKey', 'agentPda', 'update', 'audit'],
383
+ },
384
+ annotations: {
385
+ readOnlyHint: false,
386
+ destructiveHint: false,
387
+ idempotentHint: false,
388
+ openWorldHint: false,
389
+ },
390
+ }, async (input) => {
391
+ try {
392
+ const parsed = parseUpdateAgentInput(input);
393
+ const activeProfile = getActiveProfile();
394
+ const signerPublicKey = context.signer?.publicKey.toBase58();
395
+ if (!signerPublicKey) {
396
+ throw new Error('No local signer configured. Run the SAP MCP wizard full setup or repair the local sap_payments bridge, then restart the agent runtime.');
397
+ }
398
+ const args = parseUpdateAgentArgs(parsed);
399
+ const signature = await context.sapClient.agent.update(args);
400
+ const confirmation = await waitForLocalAgentUpdate(context, signature, parsed.confirmationTimeoutMs);
401
+ const update = summarizeAgentUpdate(args);
402
+ if (!confirmation.confirmed) {
403
+ return createTextResponse(JSON.stringify({
404
+ success: false,
405
+ signature,
406
+ confirmationStatus: confirmation.status,
407
+ signerPublicKey,
408
+ profile: activeProfile,
409
+ agentPda: confirmation.agentPda,
410
+ update,
411
+ audit: {
412
+ action: 'sap_payments_update_agent',
413
+ updatedLocally: true,
414
+ hostedX402Charged: false,
415
+ signerBoundary: 'local-sap-payments-bridge',
416
+ secretMaterial: 'keypair-bytes-never-returned',
417
+ transactionLanded: false,
418
+ retrySafe: confirmation.retrySafe,
419
+ rule: 'The local signer submitted the SAP registry update transaction, but the transaction did not confirm or the agent account was not found inside the local confirmation window.',
420
+ nextAction: confirmation.retrySafe
421
+ ? 'Ask the user for confirmation, then retry sap_payments_update_agent once with the same fields and confirm: true. Do not call hosted sap_update_agent.'
422
+ : 'Do not retry automatically. Inspect the signature and local RPC health first.',
423
+ },
424
+ }, null, 2), { isError: true });
425
+ }
426
+ return createTextResponse(JSON.stringify({
427
+ success: true,
428
+ signature,
429
+ confirmationStatus: confirmation.status,
430
+ signerPublicKey,
431
+ profile: activeProfile,
432
+ agentPda: confirmation.agentPda,
433
+ update,
434
+ audit: {
435
+ action: 'sap_payments_update_agent',
436
+ updatedLocally: true,
437
+ hostedX402Charged: false,
438
+ signerBoundary: 'local-sap-payments-bridge',
439
+ secretMaterial: 'keypair-bytes-never-returned',
440
+ transactionLanded: true,
441
+ rule: 'Hosted sap_update_agent is accountless and cannot sign user-owned registry writes. This local bridge submitted the SAP registry update with the user-controlled profile signer.',
442
+ },
443
+ }, null, 2));
444
+ }
445
+ catch (error) {
446
+ return createTextResponse(formatLocalRegistryError(error), { isError: true });
447
+ }
448
+ });
449
+ }
450
+ async function waitForLocalAgentRegistration(context, signature, timeoutMs = 90_000) {
451
+ return waitForLocalAgentWrite(context, signature, timeoutMs);
452
+ }
453
+ async function waitForLocalAgentWrite(context, signature, timeoutMs = 90_000) {
454
+ const boundedTimeoutMs = Math.max(15_000, Math.min(timeoutMs, 180_000));
455
+ const startedAt = Date.now();
456
+ const [agentPda] = context.sapClient.agent.deriveAgent();
457
+ let lastStatus = 'missing';
458
+ while (Date.now() - startedAt < boundedTimeoutMs) {
459
+ const statuses = await context.connection.getSignatureStatuses([signature], { searchTransactionHistory: true });
460
+ const status = statuses.value[0];
461
+ if (status?.err) {
462
+ return {
463
+ confirmed: false,
464
+ retrySafe: true,
465
+ status: 'failed',
466
+ agentPda: agentPda.toBase58(),
467
+ };
468
+ }
469
+ lastStatus = status?.confirmationStatus ?? lastStatus;
470
+ const agent = await context.sapClient.agent.fetchNullable();
471
+ if (agent) {
472
+ return {
473
+ confirmed: true,
474
+ retrySafe: false,
475
+ status: status?.confirmationStatus ?? 'confirmed',
476
+ agentPda: agentPda.toBase58(),
477
+ };
478
+ }
479
+ await sleep(2_000);
480
+ }
481
+ return {
482
+ confirmed: false,
483
+ retrySafe: lastStatus === 'missing',
484
+ status: lastStatus === 'missing' ? 'expired_or_not_landed' : lastStatus,
485
+ agentPda: agentPda.toBase58(),
486
+ };
487
+ }
488
+ async function waitForLocalAgentUpdate(context, signature, timeoutMs = 90_000) {
489
+ const boundedTimeoutMs = Math.max(15_000, Math.min(timeoutMs, 180_000));
490
+ const startedAt = Date.now();
491
+ const [agentPda] = context.sapClient.agent.deriveAgent();
492
+ let lastStatus = 'missing';
493
+ while (Date.now() - startedAt < boundedTimeoutMs) {
494
+ const statuses = await context.connection.getSignatureStatuses([signature], { searchTransactionHistory: true });
495
+ const status = statuses.value[0];
496
+ if (status?.err) {
497
+ return {
498
+ confirmed: false,
499
+ retrySafe: true,
500
+ status: 'failed',
501
+ agentPda: agentPda.toBase58(),
502
+ };
503
+ }
504
+ lastStatus = status?.confirmationStatus ?? lastStatus;
505
+ if (lastStatus === 'processed' || lastStatus === 'confirmed' || lastStatus === 'finalized') {
506
+ return {
507
+ confirmed: true,
508
+ retrySafe: false,
509
+ status: lastStatus,
510
+ agentPda: agentPda.toBase58(),
511
+ };
512
+ }
513
+ await sleep(2_000);
514
+ }
515
+ return {
516
+ confirmed: false,
517
+ retrySafe: lastStatus === 'missing',
518
+ status: lastStatus === 'missing' ? 'expired_or_not_landed' : lastStatus,
519
+ agentPda: agentPda.toBase58(),
520
+ };
521
+ }
522
+ async function sleep(ms) {
523
+ await new Promise(resolve => setTimeout(resolve, ms));
524
+ }
181
525
  function registerPaymentsCallExternalX402Tool(server) {
182
526
  registerTool(server, 'sap_payments_call_external_x402', {
183
527
  title: 'Pay And Call External x402 Endpoint',
@@ -241,7 +585,7 @@ function registerPaymentsCallExternalX402Tool(server) {
241
585
  function registerPaymentsFinalizeTransactionTool(server, context) {
242
586
  registerTool(server, 'sap_payments_finalize_transaction', {
243
587
  title: 'Finalize Transaction With Local Signer',
244
- description: 'Local non-custodial transaction finalizer for hosted SAP MCP builders. Use this when a hosted tool returns transactionBase64, transaction, or an unsigned Solana transaction. It previews, signs with the active local SAP MCP profile signer, and optionally submits through the configured RPC. Never create temporary signing scripts, read keypair JSON, or call hosted sap_sign_transaction for user-owned signatures. Requires confirm: true.',
588
+ description: 'Local non-custodial transaction finalizer for hosted SAP MCP builders. Use this when a hosted tool returns transactionBase64, transaction, or an unsigned Solana transaction. It previews, signs with the active local SAP MCP profile signer, and optionally submits through the OOBE hosted submit relay for reliable confirmation. The relay only broadcasts already-signed bytes and never receives keypair material. Never create temporary signing scripts, read keypair JSON, or call hosted sap_sign_transaction for user-owned signatures. Requires confirm: true.',
245
589
  inputSchema: {
246
590
  transaction: {
247
591
  type: 'string',
@@ -268,6 +612,23 @@ function registerPaymentsFinalizeTransactionTool(server, context) {
268
612
  type: 'number',
269
613
  description: 'Optional Solana RPC sendRawTransaction maxRetries value, used only when submit is true.',
270
614
  },
615
+ confirmationTimeoutMs: {
616
+ type: 'number',
617
+ description: 'Optional bounded confirmation wait in milliseconds. Defaults to 90000; maximum 180000.',
618
+ },
619
+ commitment: {
620
+ type: 'string',
621
+ enum: ['processed', 'confirmed', 'finalized'],
622
+ description: 'Desired confirmation status before returning success. Defaults to confirmed.',
623
+ },
624
+ submitViaRelay: {
625
+ type: 'boolean',
626
+ description: 'When submit is true, defaults to true. Set false only when the user explicitly wants local RPC submission instead of the OOBE hosted submit relay.',
627
+ },
628
+ submitRelayUrl: {
629
+ type: 'string',
630
+ description: 'Optional submit relay URL. Must be HTTPS, localhost, or 127.0.0.1. Defaults to https://mcp.sap.oobeprotocol.ai/tx/submit.',
631
+ },
271
632
  confirm: {
272
633
  type: 'boolean',
273
634
  description: 'Must be true. Confirms the user allows the local SAP MCP signer to sign this transaction.',
@@ -284,6 +645,9 @@ function registerPaymentsFinalizeTransactionTool(server, context) {
284
645
  action: { type: 'string', description: 'preview-sign or preview-sign-submit.' },
285
646
  submitted: { type: 'boolean', description: 'Whether the signed transaction was submitted to RPC.' },
286
647
  signature: { type: 'string', description: 'Solana transaction signature when submit is true.' },
648
+ confirmationStatus: { type: 'string', description: 'Confirmed/finalized/failed/expired_or_not_landed status when submit is true.' },
649
+ retrySafe: { type: 'boolean', description: 'Whether the agent may ask the user to retry without risking a known landed duplicate.' },
650
+ explorerUrl: { type: 'string', description: 'Solana explorer URL for the submitted signature.' },
287
651
  signerPublicKey: { type: 'string', description: 'Public key of the local signer that signed the transaction.' },
288
652
  nativeTransferSol: { type: 'number', description: 'Estimated native SOL transferred by the transaction.' },
289
653
  preview: { type: 'object', description: 'Decoded transaction preview and policy result.' },
@@ -608,6 +972,42 @@ function parseRegisterAgentInput(input) {
608
972
  }
609
973
  return record;
610
974
  }
975
+ function parseUpdateAgentInput(input) {
976
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
977
+ throw new Error('sap_payments_update_agent requires an input object.');
978
+ }
979
+ const record = input;
980
+ if (record.confirm !== true) {
981
+ throw new Error('confirm: true is required to submit a local SAP registry update transaction.');
982
+ }
983
+ const hasUpdate = [
984
+ 'name',
985
+ 'description',
986
+ 'capabilities',
987
+ 'pricing',
988
+ 'protocols',
989
+ 'agentId',
990
+ 'agentUri',
991
+ 'metadataUri',
992
+ 'x402Endpoint',
993
+ ].some((key) => Object.prototype.hasOwnProperty.call(record, key));
994
+ if (!hasUpdate) {
995
+ throw new Error('At least one update field is required: name, description, capabilities, pricing, protocols, agentId, agentUri/metadataUri, or x402Endpoint.');
996
+ }
997
+ return record;
998
+ }
999
+ function summarizeAgentUpdate(args) {
1000
+ return {
1001
+ name: args.name,
1002
+ description: args.description,
1003
+ agentId: args.agentId,
1004
+ agentUri: args.agentUri,
1005
+ x402Endpoint: args.x402Endpoint,
1006
+ protocols: args.protocols,
1007
+ capabilityCount: args.capabilities?.length ?? null,
1008
+ pricingTierCount: args.pricing?.length ?? null,
1009
+ };
1010
+ }
611
1011
  function parseProbeInput(input) {
612
1012
  if (!input || typeof input !== 'object' || Array.isArray(input)) {
613
1013
  throw new Error('sap_payments_prepare_challenge requires an input object.');
@@ -667,6 +1067,10 @@ function parseFinalizeTransactionInput(input) {
667
1067
  submit: record.submit === true,
668
1068
  skipPreflight: record.skipPreflight,
669
1069
  maxRetries: record.maxRetries,
1070
+ confirmationTimeoutMs: record.confirmationTimeoutMs,
1071
+ commitment: record.commitment,
1072
+ submitViaRelay: record.submitViaRelay !== false,
1073
+ submitRelayUrl: record.submitRelayUrl,
670
1074
  confirm: record.confirm === true,
671
1075
  intentId: record.intentId,
672
1076
  };
@@ -702,10 +1106,10 @@ function formatLocalRegistryError(error) {
702
1106
  retryable,
703
1107
  category,
704
1108
  hostedX402Charged: false,
705
- agentInstruction: 'For SAP agent registration from hosted-user setups, call sap_payments_register_agent from the local sap_payments bridge. Do not retry hosted sap_register_agent after hosted_local_signer_required, and do not create temporary signing scripts or read keypair JSON.',
1109
+ agentInstruction: 'For SAP agent registration or profile updates from hosted-user setups, call sap_payments_register_agent or sap_payments_update_agent from the local sap_payments bridge. Do not retry hosted sap_register_agent/sap_update_agent after hosted_local_signer_required, and do not create temporary signing scripts or read keypair JSON.',
706
1110
  nextAction: signerMissing
707
1111
  ? 'Call sap_payments_readiness. If signerConfigured is false, run the SAP MCP wizard full setup or repair flow and restart the agent runtime.'
708
- : 'Check the registration fields, local profile, SOL fee balance, and SAP program/RPC status. Retry sap_payments_register_agent only after explicit user confirmation.',
1112
+ : 'Check the registry fields, local profile, SOL fee balance, and SAP program/RPC status. Retry the matching sap_payments registry tool only after explicit user confirmation.',
709
1113
  }, null, 2);
710
1114
  }
711
1115
  function formatFinalizeTransactionError(error) {
@@ -717,8 +1121,8 @@ function formatFinalizeTransactionError(error) {
717
1121
  category: message.toLowerCase().includes('no local signer') || message.toLowerCase().includes('no signer')
718
1122
  ? 'local_signer_missing'
719
1123
  : 'local_transaction_finalization_failed',
720
- agentInstruction: 'Do not create temporary signing scripts, do not read keypair JSON, and do not call hosted sap_sign_transaction for user-owned signatures. Use sap_payments_finalize_transaction from the local sap_payments bridge. If this tool is missing or no signer is configured, ask the user to run the SAP MCP wizard full setup or repair flow and restart the agent runtime.',
721
- nextAction: 'Call sap_payments_readiness. If the local signer is ready, retry sap_payments_finalize_transaction with confirm: true. If readiness says missing bridge or signer, run the wizard repair flow.',
1124
+ agentInstruction: 'Do not create temporary signing scripts, do not read keypair JSON, and do not call hosted sap_sign_transaction for user-owned signatures. Use sap_payments_finalize_transaction from the local sap_payments bridge. For submit:true, prefer the default hosted submit relay unless the user explicitly requests local RPC submission.',
1125
+ nextAction: 'Call sap_payments_readiness. If the local signer is ready, retry sap_payments_finalize_transaction with confirm: true, submit: true, and the same transaction only when retrySafe is true or the prior attempt never submitted. If readiness says missing bridge or signer, run the wizard repair flow.',
722
1126
  }, null, 2);
723
1127
  }
724
1128
  function isRetryablePaymentError(message) {