@oobe-protocol-labs/sap-mcp-server 0.9.7 → 0.9.9

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 (48) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/USER_DOCS/03_PAYMENTS_X402_PAYSH.md +6 -0
  3. package/USER_DOCS/04_CLIENT_CONFIGS.md +3 -3
  4. package/USER_DOCS/05_SKILLS_AND_TOOLS.md +17 -4
  5. package/USER_DOCS/06_DESKTOP_GUI_WIZARD.md +5 -5
  6. package/dist/core/constants.d.ts +1 -1
  7. package/dist/core/constants.js +1 -1
  8. package/dist/payments/hosted-tool-eligibility.d.ts +21 -0
  9. package/dist/payments/hosted-tool-eligibility.d.ts.map +1 -0
  10. package/dist/payments/hosted-tool-eligibility.js +167 -0
  11. package/dist/payments/hosted-tool-eligibility.js.map +1 -0
  12. package/dist/payments/monetization-gate.d.ts.map +1 -1
  13. package/dist/payments/monetization-gate.js +6 -0
  14. package/dist/payments/monetization-gate.js.map +1 -1
  15. package/dist/payments/pricing.d.ts.map +1 -1
  16. package/dist/payments/pricing.js +2 -1
  17. package/dist/payments/pricing.js.map +1 -1
  18. package/dist/prompts/context/sap-agent-context.prompt.js +13 -8
  19. package/dist/prompts/context/sap-agent-context.prompt.js.map +1 -1
  20. package/dist/prompts/context/sap-agent-start.prompt.js +1 -0
  21. package/dist/prompts/context/sap-agent-start.prompt.js.map +1 -1
  22. package/dist/prompts/registry/register-sap-agent.prompt.js +2 -2
  23. package/dist/prompts/registry/register-sap-agent.prompt.js.map +1 -1
  24. package/dist/schemas/registry.schema.d.ts +2 -2
  25. package/dist/tools/agent-start-tool.js +3 -1
  26. package/dist/tools/agent-start-tool.js.map +1 -1
  27. package/dist/tools/client-sdk-tools.js +2 -2
  28. package/dist/tools/client-sdk-tools.js.map +1 -1
  29. package/dist/tools/sap-sdk-tools.d.ts.map +1 -1
  30. package/dist/tools/sap-sdk-tools.js +318 -89
  31. package/dist/tools/sap-sdk-tools.js.map +1 -1
  32. package/dist/tools/sap-sns-tools.d.ts +2 -2
  33. package/dist/tools/sap-sns-tools.js +6 -6
  34. package/dist/tools/sap-sns-tools.js.map +1 -1
  35. package/docs/06_PAYMENTS_X402_AND_PAYSH.md +7 -0
  36. package/docs/07_ENDPOINTS_AND_CLIENTS.md +1 -1
  37. package/docs/09_TOOLS_SKILLS_AND_AGENT_GUIDE.md +7 -0
  38. package/docs/14_DESKTOP_WIZARD_RELEASE.md +1 -1
  39. package/package.json +1 -1
  40. package/server.json +3 -3
  41. package/skills/README.md +4 -2
  42. package/skills/sap-agent-registry/SKILL.md +15 -2
  43. package/skills/sap-defi/SKILL.md +8 -4
  44. package/skills/sap-discovery-indexing/SKILL.md +15 -4
  45. package/skills/sap-mcp/SKILL.md +15 -3
  46. package/skills/sap-mcp/TOOL_REFERENCE.md +11 -5
  47. package/skills/sap-operations/SKILL.md +4 -3
  48. package/skills/sap-sns/SKILL.md +16 -11
@@ -147,6 +147,40 @@ function summarizeProtocolIndexes(protocolIndexes) {
147
147
  }))
148
148
  .sort((left, right) => left.protocolId.localeCompare(right.protocolId));
149
149
  }
150
+ function normalizeDirectoryToken(value) {
151
+ return value.trim().toLowerCase();
152
+ }
153
+ function matchesNormalizedValue(values, expected) {
154
+ const normalizedExpected = normalizeDirectoryToken(expected);
155
+ return values.some((value) => normalizeDirectoryToken(value) === normalizedExpected);
156
+ }
157
+ function matchesNormalizedSubstring(values, query) {
158
+ return values.some((value) => normalizeDirectoryToken(value).includes(query));
159
+ }
160
+ function encodeDirectoryCursor(offset) {
161
+ return encodeDirectoryCursorPayload(offset);
162
+ }
163
+ function decodeDirectoryCursor(cursor) {
164
+ if (!cursor) {
165
+ return undefined;
166
+ }
167
+ try {
168
+ const parsed = decodeDirectoryCursorPayload(cursor);
169
+ const offset = parsed.offset;
170
+ return typeof offset === 'number' && Number.isInteger(offset) && offset >= 0 ? offset : undefined;
171
+ }
172
+ catch {
173
+ throw new Error('cursor must be a nextCursor value returned by sap_list_all_agents or sap_discover_agents');
174
+ }
175
+ }
176
+ function encodeDirectoryCursorPayload(offset) {
177
+ const base64 = Buffer.from(JSON.stringify({ offset }), 'utf-8').toString('base64');
178
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/u, '');
179
+ }
180
+ function decodeDirectoryCursorPayload(cursor) {
181
+ const padded = cursor.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(cursor.length / 4) * 4, '=');
182
+ return JSON.parse(Buffer.from(padded, 'base64').toString('utf-8'));
183
+ }
150
184
  /**
151
185
  * @name buildAgentDirectoryEntry
152
186
  * @description Converts an AgentAccount into a compact directory row with optional stats and protocol-index membership.
@@ -185,14 +219,257 @@ function matchesAgentDirectoryFilters(entry, filters) {
185
219
  if (!filters.includeInactive && !entry.isActive) {
186
220
  return false;
187
221
  }
188
- if (filters.protocol && !entry.protocols.includes(filters.protocol) && !entry.indexedProtocols.includes(filters.protocol)) {
222
+ if (filters.wallet && normalizeDirectoryToken(entry.wallet) !== normalizeDirectoryToken(filters.wallet)) {
223
+ return false;
224
+ }
225
+ if (filters.agentPda && normalizeDirectoryToken(entry.agentPda) !== normalizeDirectoryToken(filters.agentPda)) {
226
+ return false;
227
+ }
228
+ if (filters.hasX402Endpoint !== undefined && Boolean(entry.x402Endpoint) !== filters.hasX402Endpoint) {
189
229
  return false;
190
230
  }
191
- if (filters.capability && !entry.capabilities.includes(filters.capability)) {
231
+ if (filters.protocol
232
+ && !matchesNormalizedValue(entry.protocols, filters.protocol)
233
+ && !matchesNormalizedValue(entry.indexedProtocols, filters.protocol)) {
192
234
  return false;
193
235
  }
236
+ const requiredCapabilities = [
237
+ ...(filters.capability ? [filters.capability] : []),
238
+ ...(filters.capabilities ?? []),
239
+ ];
240
+ if (requiredCapabilities.length > 0) {
241
+ const mode = filters.capabilityMode ?? 'any';
242
+ const predicate = (capability) => matchesNormalizedValue(entry.capabilities, capability);
243
+ if (mode === 'all' ? !requiredCapabilities.every(predicate) : !requiredCapabilities.some(predicate)) {
244
+ return false;
245
+ }
246
+ }
247
+ const query = filters.query ? normalizeDirectoryToken(filters.query) : undefined;
248
+ if (query) {
249
+ const searchableValues = [
250
+ entry.agentPda,
251
+ entry.wallet,
252
+ entry.name,
253
+ entry.description,
254
+ entry.agentId,
255
+ entry.agentUri,
256
+ entry.x402Endpoint,
257
+ ...entry.protocols,
258
+ ...entry.indexedProtocols,
259
+ ...entry.capabilities,
260
+ ...entry.activePlugins,
261
+ ].filter((value) => typeof value === 'string');
262
+ if (!matchesNormalizedSubstring(searchableValues, query)) {
263
+ return false;
264
+ }
265
+ }
194
266
  return true;
195
267
  }
268
+ function compactAgentDirectoryEntry(entry) {
269
+ return {
270
+ agentPda: entry.agentPda,
271
+ wallet: entry.wallet,
272
+ name: entry.name,
273
+ description: entry.description,
274
+ agentId: entry.agentId,
275
+ x402Endpoint: entry.x402Endpoint,
276
+ isActive: entry.isActive,
277
+ protocols: entry.protocols,
278
+ indexedProtocols: entry.indexedProtocols,
279
+ capabilities: entry.capabilities,
280
+ reputationScore: entry.reputationScore,
281
+ totalCallsServed: entry.totalCallsServed,
282
+ uptimePercent: entry.uptimePercent,
283
+ createdAt: entry.createdAt,
284
+ };
285
+ }
286
+ function directoryAgentGuidance(page) {
287
+ return {
288
+ paidHostedRead: true,
289
+ useThisWhen: [
290
+ 'The user asks to find SAP agents, list the ecosystem, inspect available on-chain agents, or discover x402-enabled agents.',
291
+ 'Use query/protocol/capability/capabilities filters first; avoid repeated broad scans.',
292
+ ],
293
+ pagination: page.hasMore
294
+ ? `Call the same tool with cursor="${page.nextCursor}" to fetch the next page.`
295
+ : 'No further page is available for the current filters.',
296
+ nextTools: [
297
+ 'sap_get_agent_profile with wallet for one hydrated owner profile.',
298
+ 'sap_fetch_protocol_index with protocolId when the user wants protocol membership.',
299
+ 'sap_fetch_capability_index with capabilityId when the user wants a capability-specific index.',
300
+ 'sap_x402_estimate_cost before calling a paid x402 endpoint exposed by a discovered agent.',
301
+ ],
302
+ matchingNotes: {
303
+ query: 'Matches name, description, agentId, wallet, PDA, endpoint, protocols, capabilities, and active plugin labels.',
304
+ capability: 'Capability matching is case-insensitive and exact after trimming. For multiple capabilities set capabilityMode to any or all.',
305
+ x402: 'Set hasX402Endpoint=true to find agents that advertise paid HTTP/x402 resources.',
306
+ },
307
+ };
308
+ }
309
+ async function buildAgentDirectoryPage(client, options) {
310
+ const accounts = getSapAnchorAccounts(client);
311
+ const [agentAccounts, statsAccounts, protocolIndexes, overview] = await Promise.all([
312
+ accounts.agentAccount.all(),
313
+ accounts.agentStats.all(),
314
+ accounts.protocolIndex.all(),
315
+ client.discovery.getNetworkOverview(),
316
+ ]);
317
+ const statsByAgent = new Map(statsAccounts.map(({ account }) => [account.agent.toBase58(), account]));
318
+ const indexedProtocolsByAgent = buildProtocolMembership(protocolIndexes);
319
+ const filteredAgents = agentAccounts
320
+ .map((account) => buildAgentDirectoryEntry(account, statsByAgent, indexedProtocolsByAgent))
321
+ .filter((entry) => matchesAgentDirectoryFilters(entry, options))
322
+ .sort((left, right) => Number(right.createdAt) - Number(left.createdAt));
323
+ const page = filteredAgents.slice(options.offset, options.offset + options.limit);
324
+ const hasMore = options.offset + page.length < filteredAgents.length;
325
+ const nextOffset = hasMore ? options.offset + page.length : null;
326
+ const nextCursor = nextOffset === null ? null : encodeDirectoryCursor(nextOffset);
327
+ const agents = options.view === 'compact' ? page.map(compactAgentDirectoryEntry) : page;
328
+ return {
329
+ source: 'program.account.agentAccount.all + program.account.protocolIndex.all',
330
+ overview,
331
+ filters: {
332
+ includeInactive: options.includeInactive,
333
+ protocol: options.protocol ?? null,
334
+ capability: options.capability ?? null,
335
+ capabilities: options.capabilities ?? [],
336
+ capabilityMode: options.capabilityMode ?? 'any',
337
+ query: options.query ?? null,
338
+ wallet: options.wallet ?? null,
339
+ agentPda: options.agentPda ?? null,
340
+ hasX402Endpoint: options.hasX402Endpoint ?? null,
341
+ view: options.view,
342
+ },
343
+ count: page.length,
344
+ totalEnumerated: filteredAgents.length,
345
+ returned: page.length,
346
+ offset: options.offset,
347
+ limit: options.limit,
348
+ truncated: hasMore,
349
+ pagination: {
350
+ total: filteredAgents.length,
351
+ returned: page.length,
352
+ offset: options.offset,
353
+ limit: options.limit,
354
+ hasMore,
355
+ nextOffset,
356
+ nextCursor,
357
+ },
358
+ totalAgentAccounts: agentAccounts.length,
359
+ activeAgentAccounts: agentAccounts.filter(({ account }) => account.isActive).length,
360
+ protocolIndexes: options.includeProtocolIndexes ? summarizeProtocolIndexes(protocolIndexes) : undefined,
361
+ agents,
362
+ agentGuidance: directoryAgentGuidance({ hasMore, nextCursor, filters: options }),
363
+ };
364
+ }
365
+ function parseCapabilityMode(input) {
366
+ const mode = optionalString(input, 'capabilityMode')?.trim().toLowerCase();
367
+ if (!mode) {
368
+ return 'any';
369
+ }
370
+ if (mode === 'any' || mode === 'all') {
371
+ return mode;
372
+ }
373
+ throw new Error('capabilityMode must be "any" or "all"');
374
+ }
375
+ function parseOptionalStringArray(input, field) {
376
+ const value = input[field];
377
+ if (value === undefined || value === null) {
378
+ return [];
379
+ }
380
+ if (!Array.isArray(value)) {
381
+ throw new Error(`${field} must be an array of strings`);
382
+ }
383
+ return value
384
+ .filter((item) => typeof item === 'string' && item.trim().length > 0)
385
+ .map((item) => item.trim());
386
+ }
387
+ function parseDirectoryOffset(input) {
388
+ return Math.max(0, decodeDirectoryCursor(optionalString(input, 'cursor')) ?? optionalNumber(input, 'offset') ?? 0);
389
+ }
390
+ function parseDirectoryView(input) {
391
+ const view = optionalString(input, 'view');
392
+ if (!view && input.hydrate === true) {
393
+ return 'full';
394
+ }
395
+ if (!view || view === 'compact') {
396
+ return 'compact';
397
+ }
398
+ if (view === 'full') {
399
+ return 'full';
400
+ }
401
+ throw new Error('view must be "compact" or "full"');
402
+ }
403
+ function parseHasX402Endpoint(input) {
404
+ return optionalBoolean(input, 'hasX402Endpoint');
405
+ }
406
+ function makeAgentDirectoryInputSchema(defaultLimit) {
407
+ return {
408
+ query: {
409
+ type: 'string',
410
+ description: 'Optional text search across name, description, agentId, wallet, PDA, x402 endpoint, protocols, capabilities, and active plugins. Example: "XONA" or "creative".',
411
+ },
412
+ wallet: {
413
+ type: 'string',
414
+ description: 'Optional exact owner wallet public key filter. Use this when a wallet is known; it is the most reliable lookup path.',
415
+ },
416
+ agentPda: {
417
+ type: 'string',
418
+ description: 'Optional exact SAP agent PDA filter.',
419
+ },
420
+ protocol: {
421
+ type: 'string',
422
+ description: 'Optional protocol filter matched case-insensitively against agent protocols and protocol-index membership. Example: "jupiter", "creative", "payments".',
423
+ },
424
+ capability: {
425
+ type: 'string',
426
+ description: 'Optional single capability ID filter matched case-insensitively. Example: "creative:imageGeneration" or "jupiter:swap".',
427
+ },
428
+ capabilities: {
429
+ type: 'array',
430
+ items: { type: 'string' },
431
+ description: 'Optional list of capability IDs. Use capabilityMode="all" when the agent must have every capability.',
432
+ },
433
+ capabilityMode: {
434
+ type: 'string',
435
+ enum: ['any', 'all'],
436
+ description: 'How to match the capability/capabilities filters. Defaults to "any".',
437
+ },
438
+ hasX402Endpoint: {
439
+ type: 'boolean',
440
+ description: 'Optional filter for agents that advertise a paid HTTP/x402 endpoint.',
441
+ },
442
+ includeInactive: {
443
+ type: 'boolean',
444
+ description: 'Include inactive agents. Defaults to false.',
445
+ },
446
+ limit: {
447
+ type: 'number',
448
+ description: `Maximum rows to return. Defaults to ${defaultLimit}; hard-capped at 500.`,
449
+ },
450
+ offset: {
451
+ type: 'number',
452
+ description: 'Zero-based pagination offset. Defaults to 0. Prefer cursor after the first page when nextCursor is returned.',
453
+ },
454
+ cursor: {
455
+ type: 'string',
456
+ description: 'Opaque pagination cursor returned as pagination.nextCursor by a previous sap_list_all_agents or sap_discover_agents call.',
457
+ },
458
+ view: {
459
+ type: 'string',
460
+ enum: ['compact', 'full'],
461
+ description: 'Result shape. Use compact for broad discovery and full for detailed rows. Defaults to compact.',
462
+ },
463
+ hydrate: {
464
+ type: 'boolean',
465
+ description: 'Deprecated compatibility alias. Use view="full" for full rows or view="compact" for directory rows.',
466
+ },
467
+ includeProtocolIndexes: {
468
+ type: 'boolean',
469
+ description: 'Include compact protocol index summaries. Defaults to false for sap_discover_agents and true for sap_list_all_agents.',
470
+ },
471
+ };
472
+ }
196
473
  /**
197
474
  * @name asRecord
198
475
  * @description Normalizes MCP tool input into an object.
@@ -723,7 +1000,7 @@ function buildSapSdkToolDescription(definition) {
723
1000
  }
724
1001
  function getSapSdkToolContext(name) {
725
1002
  if (name === 'sap_register_agent') {
726
- return 'SAP MCP context: This is the primary on-chain SAP agent registration tool for the connected profile signer. Use agentUri or metadataUri for off-chain metadata, including a Metaplex or DAS-backed identity document when the agent also has NFT/collection metadata. After registration, use sap_publish_tool_by_name for advertised MCP capabilities and AgentKit metaplex-nft_* tools for NFT collection, badge, or metadata workflows.';
1003
+ return 'SAP MCP context: This is the primary on-chain SAP agent registration tool for a local profile signer or external signer. Hosted accountless SAP MCP rejects direct registration before x402 payment because OOBE never custodies user wallet keys. Use agentUri or metadataUri for off-chain metadata, including a Metaplex or DAS-backed identity document when the agent also has NFT/collection metadata. After registration, use sap_publish_tool_by_name for advertised MCP capabilities and AgentKit metaplex-nft_* tools for NFT collection, badge, or metadata workflows.';
727
1004
  }
728
1005
  if (name === 'sap_update_agent') {
729
1006
  return 'SAP MCP context: Use this after sap_register_agent to refresh name, description, capabilities, pricing, supported protocols, x402 endpoint, or metadataUri. For NFT-backed identity changes, update the Metaplex asset first when needed, then point the SAP agent metadataUri at the current metadata document.';
@@ -752,7 +1029,7 @@ const agentTools = [
752
1029
  {
753
1030
  name: 'sap_register_agent',
754
1031
  title: 'Register SAP Agent',
755
- description: 'Register the connected wallet as a SAP agent using SDK AgentModule.register.',
1032
+ description: 'Local-signer-only: register the connected wallet as a SAP agent using SDK AgentModule.register. Hosted accountless SAP MCP rejects this direct write before x402 payment; run it through a local SAP MCP profile or external signer.',
756
1033
  inputSchema: {
757
1034
  name: { type: 'string', description: 'Human-readable name for the SAP agent' },
758
1035
  description: { type: 'string', description: 'Detailed description of the agent\'s purpose and capabilities' },
@@ -864,107 +1141,59 @@ const discoveryTools = [
864
1141
  {
865
1142
  name: 'sap_discover_agents',
866
1143
  title: 'Discover SAP Agents',
867
- description: 'Discover agents by protocol, capability, or capability list. For unfiltered global listing, use sap_list_all_agents.',
868
- inputSchema: {
869
- protocol: { type: 'string', description: 'Protocol identifier to filter agents by (e.g. "jupiter", "drift")' },
870
- capability: { type: 'string', description: 'Single capability ID to filter agents by (e.g. "jupiter:swap")' },
871
- capabilities: { type: 'array', items: { type: 'string' }, description: 'Array of capability IDs to filter agents by (requires at least one)' },
872
- hydrate: { type: 'boolean', description: 'Whether to include full hydrated agent profiles in results (default: true)' },
873
- limit: { type: 'number', description: 'Maximum number of agents to return (default: 50)' },
874
- },
1144
+ description: 'Paid hosted discovery for SAP agents. Search and filter the current on-chain AgentAccount directory by query, wallet, protocol, capability, x402 endpoint presence, and cursor pagination. Use this for targeted agent discovery before calling per-agent fetch tools.',
1145
+ inputSchema: makeAgentDirectoryInputSchema(50),
875
1146
  handler: async (input, client) => {
876
- const hydrate = input.hydrate !== false;
877
- const limit = optionalNumber(input, 'limit') ?? 50;
878
- const capabilities = Array.isArray(input.capabilities)
879
- ? input.capabilities.filter((item) => typeof item === 'string' && item.length > 0)
880
- : [];
881
- const protocol = optionalString(input, 'protocol');
1147
+ const limit = Math.max(1, Math.min(optionalNumber(input, 'limit') ?? 50, 500));
882
1148
  const capability = optionalString(input, 'capability');
883
- if (protocol) {
884
- const agents = await client.discovery.findAgentsByProtocol(protocol, { hydrate });
885
- return { count: Math.min(agents.length, limit), agents: agents.slice(0, limit) };
886
- }
887
- if (capability) {
888
- const agents = await client.discovery.findAgentsByCapability(capability, { hydrate });
889
- return { count: Math.min(agents.length, limit), agents: agents.slice(0, limit) };
890
- }
891
- if (capabilities.length > 0) {
892
- const agents = await client.discovery.findAgentsByCapabilities(capabilities, { hydrate });
893
- return { count: Math.min(agents.length, limit), agents: agents.slice(0, limit) };
894
- }
895
- return {
896
- count: 0,
897
- agents: [],
898
- overview: await client.discovery.getNetworkOverview(),
899
- note: 'SDK filtered discovery requires a protocol or capability. For global on-chain directory snapshots, call sap_list_all_agents.',
900
- };
1149
+ const capabilities = parseOptionalStringArray(input, 'capabilities');
1150
+ return buildAgentDirectoryPage(client, {
1151
+ includeInactive: input.includeInactive === true,
1152
+ protocol: optionalString(input, 'protocol'),
1153
+ capability,
1154
+ capabilities,
1155
+ capabilityMode: parseCapabilityMode(input),
1156
+ query: optionalString(input, 'query'),
1157
+ wallet: optionalString(input, 'wallet'),
1158
+ agentPda: optionalString(input, 'agentPda'),
1159
+ hasX402Endpoint: parseHasX402Endpoint(input),
1160
+ limit,
1161
+ offset: parseDirectoryOffset(input),
1162
+ view: parseDirectoryView(input),
1163
+ includeProtocolIndexes: input.includeProtocolIndexes === true,
1164
+ });
901
1165
  },
902
1166
  },
903
1167
  {
904
1168
  name: 'sap_list_agents',
905
1169
  title: 'List SAP Agents',
906
- description: 'Compatibility alias for filtered SAP agent discovery. Requires protocol or capability. Use sap_list_all_agents for a global on-chain directory snapshot.',
907
- inputSchema: {
908
- protocol: { type: 'string', description: 'Protocol identifier to filter agents by (e.g. "jupiter", "drift")' },
909
- capability: { type: 'string', description: 'Single capability ID to filter agents by (e.g. "jupiter:swap")' },
910
- capabilities: { type: 'array', items: { type: 'string' }, description: 'Array of capability IDs to filter agents by (requires at least one)' },
911
- hydrate: { type: 'boolean', description: 'Whether to include full hydrated agent profiles in results (default: true)' },
912
- limit: { type: 'number', description: 'Maximum number of agents to return (default: 50)' },
913
- },
1170
+ description: 'Compatibility alias for sap_discover_agents. Supports query, wallet, protocol, capability, x402 endpoint filtering, and cursor pagination.',
1171
+ inputSchema: makeAgentDirectoryInputSchema(50),
914
1172
  handler: async (input, client) => discoveryTools[3].handler(input, client),
915
1173
  },
916
1174
  {
917
1175
  name: 'sap_list_all_agents',
918
1176
  title: 'List All SAP Agents',
919
- description: 'Enumerate SAP AgentAccount PDAs directly from the on-chain program account set. Use this for current global directory requests such as "list all agents in the SAP ecosystem rn".',
920
- inputSchema: {
921
- limit: { type: 'number', description: 'Maximum rows to return. Defaults to 100; hard-capped at 500.' },
922
- offset: { type: 'number', description: 'Zero-based pagination offset. Defaults to 0.' },
923
- includeInactive: { type: 'boolean', description: 'Include inactive agents. Defaults to false.' },
924
- protocol: { type: 'string', description: 'Optional protocol filter matched against agent profile protocols and protocol-index membership.' },
925
- capability: { type: 'string', description: 'Optional capability ID filter such as jupiter:swap.' },
926
- includeProtocolIndexes: { type: 'boolean', description: 'Include compact protocol index summaries. Defaults to true.' },
927
- },
1177
+ description: 'Paid hosted global SAP agent directory read. Enumerates current on-chain AgentAccount PDAs and supports query, wallet, protocol, capability, x402 endpoint filtering, compact/full views, and cursor pagination.',
1178
+ inputSchema: makeAgentDirectoryInputSchema(100),
928
1179
  handler: async (input, client) => {
929
1180
  const limit = Math.max(1, Math.min(optionalNumber(input, 'limit') ?? 100, 500));
930
- const offset = Math.max(0, optionalNumber(input, 'offset') ?? 0);
931
- const includeInactive = input.includeInactive === true;
932
- const includeProtocolIndexes = input.includeProtocolIndexes !== false;
933
- const protocol = optionalString(input, 'protocol');
934
1181
  const capability = optionalString(input, 'capability');
935
- const accounts = getSapAnchorAccounts(client);
936
- const [agentAccounts, statsAccounts, protocolIndexes, overview] = await Promise.all([
937
- accounts.agentAccount.all(),
938
- accounts.agentStats.all(),
939
- accounts.protocolIndex.all(),
940
- client.discovery.getNetworkOverview(),
941
- ]);
942
- const statsByAgent = new Map(statsAccounts.map(({ account }) => [account.agent.toBase58(), account]));
943
- const indexedProtocolsByAgent = buildProtocolMembership(protocolIndexes);
944
- const filteredAgents = agentAccounts
945
- .map((account) => buildAgentDirectoryEntry(account, statsByAgent, indexedProtocolsByAgent))
946
- .filter((entry) => matchesAgentDirectoryFilters(entry, { includeInactive, protocol, capability }))
947
- .sort((left, right) => Number(right.createdAt) - Number(left.createdAt));
948
- const page = filteredAgents.slice(offset, offset + limit);
949
- return {
950
- source: 'program.account.agentAccount.all + program.account.protocolIndex.all',
951
- overview,
952
- filters: {
953
- includeInactive,
954
- protocol: protocol ?? null,
955
- capability: capability ?? null,
956
- },
957
- totalEnumerated: filteredAgents.length,
958
- totalAgentAccounts: agentAccounts.length,
959
- activeAgentAccounts: agentAccounts.filter(({ account }) => account.isActive).length,
960
- returned: page.length,
961
- offset,
1182
+ return buildAgentDirectoryPage(client, {
1183
+ includeInactive: input.includeInactive === true,
1184
+ protocol: optionalString(input, 'protocol'),
1185
+ capability,
1186
+ capabilities: parseOptionalStringArray(input, 'capabilities'),
1187
+ capabilityMode: parseCapabilityMode(input),
1188
+ query: optionalString(input, 'query'),
1189
+ wallet: optionalString(input, 'wallet'),
1190
+ agentPda: optionalString(input, 'agentPda'),
1191
+ hasX402Endpoint: parseHasX402Endpoint(input),
962
1192
  limit,
963
- truncated: offset + page.length < filteredAgents.length,
964
- protocolIndexes: includeProtocolIndexes ? summarizeProtocolIndexes(protocolIndexes) : undefined,
965
- agents: page,
966
- note: 'This is global account enumeration, not filtered DiscoveryRegistry lookup. Use sap_discover_agents for protocol/capability search and sap_fetch_protocol_index when you already know a protocol ID.',
967
- };
1193
+ offset: parseDirectoryOffset(input),
1194
+ view: parseDirectoryView(input),
1195
+ includeProtocolIndexes: input.includeProtocolIndexes !== false,
1196
+ });
968
1197
  },
969
1198
  },
970
1199
  {