@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.
- package/CHANGELOG.md +69 -0
- package/README.md +2 -2
- package/USER_DOCS/00_START_HERE.md +24 -2
- package/USER_DOCS/03_PAYMENTS_X402_PAYSH.md +6 -1
- package/USER_DOCS/04_CLIENT_CONFIGS.md +4 -3
- package/USER_DOCS/05_SKILLS_AND_TOOLS.md +10 -5
- package/USER_DOCS/06_DESKTOP_GUI_WIZARD.md +6 -6
- package/dist/config/mcp-client-injection.d.ts.map +1 -1
- package/dist/config/mcp-client-injection.js +4 -3
- package/dist/config/mcp-client-injection.js.map +1 -1
- package/dist/core/constants.d.ts +9 -1
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +13 -3
- package/dist/core/constants.js.map +1 -1
- package/dist/payments/hosted-tool-eligibility.d.ts.map +1 -1
- package/dist/payments/hosted-tool-eligibility.js +3 -0
- package/dist/payments/hosted-tool-eligibility.js.map +1 -1
- package/dist/payments/index.d.ts +2 -2
- package/dist/payments/index.d.ts.map +1 -1
- package/dist/payments/index.js +1 -1
- package/dist/payments/index.js.map +1 -1
- package/dist/payments/pricing.d.ts +29 -0
- package/dist/payments/pricing.d.ts.map +1 -1
- package/dist/payments/pricing.js +81 -0
- package/dist/payments/pricing.js.map +1 -1
- package/dist/prompts/context/sap-agent-context.prompt.js +2 -0
- package/dist/prompts/context/sap-agent-context.prompt.js.map +1 -1
- package/dist/prompts/context/sap-agent-start.prompt.js +2 -0
- package/dist/prompts/context/sap-agent-start.prompt.js.map +1 -1
- package/dist/prompts/registry/register-sap-agent.prompt.js +18 -18
- package/dist/prompts/registry/register-sap-agent.prompt.js.map +1 -1
- package/dist/remote/public-home/sections.d.ts.map +1 -1
- package/dist/remote/public-home/sections.js +8 -2
- package/dist/remote/public-home/sections.js.map +1 -1
- package/dist/remote/public-home/types.d.ts +2 -0
- package/dist/remote/public-home/types.d.ts.map +1 -1
- package/dist/remote/server.d.ts +2 -0
- package/dist/remote/server.d.ts.map +1 -1
- package/dist/remote/server.js +163 -2
- package/dist/remote/server.js.map +1 -1
- package/dist/security/tool-permissions.d.ts.map +1 -1
- package/dist/security/tool-permissions.js +9 -0
- package/dist/security/tool-permissions.js.map +1 -1
- package/dist/tools/agent-start-tool.d.ts.map +1 -1
- package/dist/tools/agent-start-tool.js +227 -3
- package/dist/tools/agent-start-tool.js.map +1 -1
- package/dist/tools/client-sdk-tools.js +3 -3
- package/dist/tools/client-sdk-tools.js.map +1 -1
- package/dist/tools/magicblock-tools.d.ts.map +1 -1
- package/dist/tools/magicblock-tools.js +43 -26
- package/dist/tools/magicblock-tools.js.map +1 -1
- package/dist/tools/sap-sdk-tools.d.ts +412 -1
- package/dist/tools/sap-sdk-tools.d.ts.map +1 -1
- package/dist/tools/sap-sdk-tools.js +1044 -30
- package/dist/tools/sap-sdk-tools.js.map +1 -1
- package/dist/tools/sap-sns-tools.d.ts.map +1 -1
- package/dist/tools/sap-sns-tools.js +6 -2
- package/dist/tools/sap-sns-tools.js.map +1 -1
- package/dist/tools/skills-tools.d.ts.map +1 -1
- package/dist/tools/skills-tools.js +2 -0
- package/dist/tools/skills-tools.js.map +1 -1
- package/dist/tools/tool-aliases.d.ts.map +1 -1
- package/dist/tools/tool-aliases.js +7 -0
- package/dist/tools/tool-aliases.js.map +1 -1
- package/dist/tools/transaction-tools.d.ts +56 -0
- package/dist/tools/transaction-tools.d.ts.map +1 -1
- package/dist/tools/transaction-tools.js +215 -18
- package/dist/tools/transaction-tools.js.map +1 -1
- package/dist/tools/x402-paid-call-tool.d.ts.map +1 -1
- package/dist/tools/x402-paid-call-tool.js +422 -18
- package/dist/tools/x402-paid-call-tool.js.map +1 -1
- package/docs/03_CONFIGURATION_AND_WIZARD.md +1 -0
- package/docs/06_PAYMENTS_X402_AND_PAYSH.md +45 -3
- package/docs/07_ENDPOINTS_AND_CLIENTS.md +2 -1
- package/docs/14_DESKTOP_WIZARD_RELEASE.md +5 -4
- package/docs/16_SAP_AGENT_IDENTITY_PIPELINE.md +298 -0
- package/docs/README.md +2 -0
- package/docs/magicblock-tools.md +26 -0
- package/package.json +1 -1
- package/server.json +3 -3
- package/skills/sap-agent-registry/SKILL.md +63 -5
- package/skills/sap-escrow-settlement/SKILL.md +22 -4
- package/skills/sap-mcp/SKILL.md +73 -22
- package/skills/sap-mcp/TOOL_REFERENCE.md +22 -1
- package/skills/sap-nft-metaplex/SKILL.md +22 -0
- package/skills/sap-operations/SKILL.md +17 -0
- package/skills/sap-payments-x402/SKILL.md +33 -2
- package/skills/sap-sns/SKILL.md +17 -0
- 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: '
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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) {
|