From fa610d7d1f5ecbe07c8d85f635d50169dac37fc7 Mon Sep 17 00:00:00 2001 From: rupesh-kumar-sah Date: Sat, 17 Oct 2026 15:19:44 +0545 Subject: [PATCH] docs(mcp): describe tool costs, spending limits, argument formats, and negative guidance Tool descriptions previously omitted costs, spending limits, argument formats, and guidance on when not to call each tool. Rewrite tool descriptions for bazaar_search and paid_call to explicitly state: - Purpose, preconditions, and cost (free vs real cryptographic funds) - Per-call spending limit (.00 USD via MCP_MAX_PAYMENT_PRICE) on paid_call - Permitted host allow-list restriction for paid endpoints - Explicit 'When NOT to use' boundaries to avoid redundant agent loops - Explicit argument schema descriptions and expected format constraints Closes #424 --- src/mcp/server.ts | 34 ++++++++++++++++++++++++++++------ tests/mcp.test.ts | 29 ++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 3f69c5b1..877e0262 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -34,17 +34,27 @@ export class BazaarMcpServer { tools: [ { name: 'bazaar_search', - description: 'Search for resources in a Stellar Bazaar node', + description: + 'Search for discovery resources indexed in a Stellar Bazaar node. ' + + 'Cost: Free (no payment or tokens required). ' + + 'Preconditions: Requires a reachable HTTP/HTTPS base URL of a Stellar Bazaar catalog node. ' + + 'When NOT to use: Do NOT use this tool to execute paid API requests or purchase data (use `paid_call`). ' + + 'Do NOT use this tool to query live SDEX order books or pricing feeds directly (use Lens REST/GraphQL APIs instead). ' + + 'Do NOT pass specific API resource paths in the URL argument (provide only the root base URL of the node).', inputSchema: { type: 'object', properties: { url: { type: 'string', - description: 'The base URL of the Bazaar API', + description: + 'The base URL of the Stellar Bazaar catalog node (e.g. "https://bazaar.example.com" or "http://localhost:3000"). ' + + 'Format: Absolute HTTP or HTTPS URL without subpaths or trailing slashes.', }, query: { type: 'string', - description: 'Optional search query', + description: + 'Optional search term to filter discovery catalog resources by keyword (e.g. "XLM price", "oracle"). ' + + 'Format: UTF-8 plain text search string. If omitted, all indexed resources are returned.', }, }, required: ['url'], @@ -52,17 +62,29 @@ export class BazaarMcpServer { }, { name: 'paid_call', - description: 'Make a paid API call to a Bazaar resource, handling the 402 loop automatically.', + description: + 'Make an authenticated paid API call to an x402-enabled Stellar Bazaar resource endpoint. ' + + 'Cost & Real Funds: SPENDS REAL CRYPTOGRAPHIC FUNDS (XLM/USDC) from the configured on-chain wallet. ' + + 'Enforces a strict per-call spending limit (default max $1.00 USD equivalent via MCP_MAX_PAYMENT_PRICE). ' + + 'Preconditions: Requires a funded Stellar signer with sufficient balance and valid trustlines. ' + + 'Host Allow-List: Payments are restricted strictly to permitted hosts; calls to unlisted hosts will fail. ' + + 'When NOT to use: Do NOT use this tool for free discovery searches (use `bazaar_search` instead). ' + + 'Do NOT use if the agent does not intend to spend real funds or if estimated cost exceeds the per-call limit ($1.00). ' + + 'Do NOT use for unverified, external, or non-Bazaar endpoints.', inputSchema: { type: 'object', properties: { url: { type: 'string', - description: 'The full URL of the resource to fetch', + description: + 'The full destination URL of the paid Bazaar resource endpoint (e.g. "https://api.example.com/v1/price"). ' + + 'Format: Valid absolute HTTP or HTTPS URL pointing to an allowed payment host.', }, method: { type: 'string', - description: 'HTTP method (default: GET)', + description: + 'HTTP request method for the target endpoint. ' + + 'Format: Uppercase HTTP verb string ("GET" or "POST"). Default: "GET".', }, }, required: ['url'], diff --git a/tests/mcp.test.ts b/tests/mcp.test.ts index 8b979ad6..c8ef82e6 100644 --- a/tests/mcp.test.ts +++ b/tests/mcp.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi } from 'vitest'; import { BazaarMcpServer } from '../src/mcp/server'; -import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; +import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; describe('BazaarMcpServer', () => { it('should map signature expiration ledger error to ERR_EXPIRED_AUTHORISATION', async () => { @@ -120,4 +120,31 @@ describe('BazaarMcpServer', () => { const parsedText = JSON.parse(response.content[0].text); expect(parsedText.success).toBe(true); }); + + it('should return clear tool descriptions with costs, preconditions, and argument formats in listTools', async () => { + const server = new BazaarMcpServer({ fetchWithPayment: vi.fn() }); + let listHandler: any = null; + (server as any).server.setRequestHandler = (schema: any, fn: any) => { + if (schema === ListToolsRequestSchema) { + listHandler = fn; + } + }; + (server as any).setupHandlers(); + + const result = await listHandler(); + expect(result.tools).toHaveLength(2); + + const searchTool = result.tools.find((t: any) => t.name === 'bazaar_search'); + expect(searchTool.description).toContain('Cost: Free'); + expect(searchTool.description).toContain('When NOT to use:'); + expect(searchTool.inputSchema.properties.url.description).toContain('Format:'); + + const paidTool = result.tools.find((t: any) => t.name === 'paid_call'); + expect(paidTool.description).toContain('SPENDS REAL CRYPTOGRAPHIC FUNDS'); + expect(paidTool.description).toContain('$1.00'); + expect(paidTool.description).toContain('Host Allow-List:'); + expect(paidTool.description).toContain('When NOT to use:'); + expect(paidTool.inputSchema.properties.url.description).toContain('Format:'); + expect(paidTool.inputSchema.properties.method.description).toContain('Format:'); + }); });