Skip to main content
Glama

azeth_smart_pay

Discover and automatically pay for services by capability, selecting high-reputation providers and handling fallbacks if needed.

Instructions

Discover the best service for a capability and pay for it automatically.

Use this when: You need a service by CAPABILITY (e.g., "price-feed", "market-data", "translation") and want Azeth to pick the highest-reputation provider, handle payment, and fall back to alternatives if needed.

How it differs from azeth_pay:

  • azeth_smart_pay: "I need price-feed data" → Azeth discovers the best service, pays it, returns the data.

  • azeth_pay: "I need data from https://specific-service.com/api" → You know which service, Azeth pays it.

Flow: Discovers services ranked by reputation → tries the best one → if it fails, tries the next. Set autoFeedback: true to automatically submit a reputation opinion based on service quality after payment. Note: autoFeedback defaults to false in MCP context (ephemeral client). Enable it if the MCP server has a bundler configured.

Returns: The response data, which service was used, how many attempts were needed, and payment details.

Example: { "capability": "price-feed" } or { "capability": "translation", "maxAmount": "0.50", "method": "POST", "body": "{"text": "hello"}" }

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
chainNoTarget chain. Defaults to AZETH_CHAIN env var or "baseSepolia". Accepts "base", "baseSepolia", "ethereumSepolia", "ethereum" (and aliases like "base-sepolia", "eth-sepolia", "sepolia", "eth", "mainnet").
capabilityYesService capability to discover (e.g., "price-feed", "market-data", "translation", "compute").
methodNoHTTP method. Defaults to "GET".
bodyNoRequest body for POST/PUT/PATCH requests (JSON string, max 100KB).
maxAmountNoMaximum USDC amount willing to pay per service (e.g., "1.00"). Rejects if service costs more.
minReputationNoMinimum reputation score (0-100) to consider. Services below this are excluded.
autoFeedbackNoAutomatically submit a reputation opinion after payment based on service quality. Defaults to false.
smartAccountNoSmart account to pay from. Use "#1", "#2", etc. (index from azeth_accounts) or a full address. Defaults to your first smart account.

Implementation Reference

  • The handler implementation for the azeth_smart_pay MCP tool, which discovers services by capability, executes a request, and handles payments.
    server.registerTool(
      'azeth_smart_pay',
      {
        description: [
          'Discover the best service for a capability and pay for it automatically.',
          '',
          'Use this when: You need a service by CAPABILITY (e.g., "price-feed", "market-data", "translation")',
          'and want Azeth to pick the highest-reputation provider, handle payment, and fall back to alternatives if needed.',
          '',
          'How it differs from azeth_pay:',
          '- azeth_smart_pay: "I need price-feed data" → Azeth discovers the best service, pays it, returns the data.',
          '- azeth_pay: "I need data from https://specific-service.com/api" → You know which service, Azeth pays it.',
          '',
          'Flow: Discovers services ranked by reputation → tries the best one → if it fails, tries the next.',
          'Set autoFeedback: true to automatically submit a reputation opinion based on service quality after payment.',
          'Note: autoFeedback defaults to false in MCP context (ephemeral client). Enable it if the MCP server has a bundler configured.',
          '',
          'Returns: The response data, which service was used, how many attempts were needed, and payment details.',
          '',
          'Example: { "capability": "price-feed" } or { "capability": "translation", "maxAmount": "0.50", "method": "POST", "body": "{\"text\": \"hello\"}" }',
        ].join('\n'),
        inputSchema: z.object({
          chain: z.string().optional().describe('Target chain. Defaults to AZETH_CHAIN env var or "baseSepolia". Accepts "base", "baseSepolia", "ethereumSepolia", "ethereum" (and aliases like "base-sepolia", "eth-sepolia", "sepolia", "eth", "mainnet").'),
          capability: z.string().min(1).max(256).describe('Service capability to discover (e.g., "price-feed", "market-data", "translation", "compute").'),
          method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).optional().describe('HTTP method. Defaults to "GET".'),
          body: z.string().max(100_000).optional().describe('Request body for POST/PUT/PATCH requests (JSON string, max 100KB).'),
          maxAmount: z.string().max(32).optional().describe('Maximum USDC amount willing to pay per service (e.g., "1.00"). Rejects if service costs more.'),
          minReputation: z.coerce.number().min(0).max(100).optional().describe('Minimum reputation score (0-100) to consider. Services below this are excluded.'),
          autoFeedback: z.boolean().optional().describe('Automatically submit a reputation opinion after payment based on service quality. Defaults to false.'),
          smartAccount: z.string().optional().describe('Smart account to pay from. Use "#1", "#2", etc. (index from azeth_accounts) or a full address. Defaults to your first smart account.'),
        }),
      },
      async (args) => {
        let client;
        try {
          client = await createClient(args.chain);
    
          // Apply smart account selection if specified
          if (args.smartAccount) {
            const selectionErr = applySmartAccountSelection(client, args.smartAccount);
            if (selectionErr) return selectionErr;
          }
    
          let maxAmount: bigint | undefined;
          if (args.maxAmount) {
            try {
              maxAmount = parseUnits(args.maxAmount, 6);
            } catch {
              return error('INVALID_INPUT', 'Invalid maxAmount format — must be a valid decimal number (e.g., "1.00")');
            }
          }
    
          // Disable autoFeedback in MCP context: the client is ephemeral (destroyed
          // after this call) and may not have a bundler URL for UserOp submission.
          // Feedback should be submitted by long-lived AzethKit instances instead.
          const result = await client.smartFetch402(args.capability, {
            method: args.method,
            body: args.body,
            maxAmount,
            minReputation: args.minReputation,
            autoFeedback: args.autoFeedback ?? false,
          });
    
          // Stream response body with size limit (same pattern as azeth_pay)
          const chunks: Uint8Array[] = [];
          let totalBytes = 0;
          const reader = result.response.body?.getReader();
          if (reader) {
            try {
              while (totalBytes < MAX_RESPONSE_SIZE) {
                const { done, value } = await reader.read();
                if (done) break;
                chunks.push(value);
                totalBytes += value.byteLength;
              }
            } finally {
              reader.cancel().catch(() => {});
            }
          }
          const merged = new Uint8Array(Math.min(totalBytes, MAX_RESPONSE_SIZE));
          let offset = 0;
          for (const chunk of chunks) {
            const remaining = merged.byteLength - offset;
            if (remaining <= 0) break;
            const slice = chunk.byteLength <= remaining ? chunk : chunk.subarray(0, remaining);
            merged.set(slice, offset);
            offset += slice.byteLength;
          }
          const responseBody = new TextDecoder().decode(merged);
          // For non-JSON responses (e.g., HTML pages), strip tags and truncate aggressively
          let truncatedBody: string;
          const trimmedSmart = responseBody.trimStart();
          if (trimmedSmart.startsWith('{') || trimmedSmart.startsWith('[')) {
            truncatedBody = safeTruncate(responseBody, MAX_RESPONSE_SIZE);
          } else {
            const stripped = responseBody.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
            truncatedBody = safeTruncate(stripped, 2_000);
          }
    
          return success({
            paid: result.paymentMade,
            amount: result.amount?.toString(),
            paymentMethod: result.paymentMethod,
            statusCode: result.response.status,
            body: truncatedBody,
            service: {
              name: result.service.name,
              endpoint: result.service.endpoint,
              tokenId: result.service.tokenId.toString(),
              reputation: result.service.reputation,
            },
            attemptsCount: result.attemptsCount,
            autoFeedback: args.autoFeedback ?? false,
          });
        } catch (err) {
          if (err instanceof Error && /AA24/.test(err.message)) {
            return guardianRequiredError(
              'Payment amount exceeds your standard spending limit.',
              { operation: 'smart_payment' },
            );
          }
          // Format raw USDC amounts in guardian/payment errors for readability
          if (err instanceof AzethError && err.details) {
            const formatted = { ...err.details };
            let changed = false;
            for (const [key, val] of Object.entries(formatted)) {
              if (/amount/i.test(key) && typeof val === 'bigint') {
                formatted[key] = formatTokenAmount(val, 6, 2) + ' USDC';
                changed = true;
              } else if (/amount/i.test(key) && typeof val === 'string' && /^\d{7,}$/.test(val)) {
                try {
                  formatted[key] = formatTokenAmount(BigInt(val), 6, 2) + ' USDC';
                  changed = true;
                } catch { /* keep original */ }
              }
            }
            if (changed) {
              return handleError(new AzethError(err.message, err.code, formatted));
            }
          }
          return handleError(err);
        } finally {
          try { await client?.destroy(); } catch (e) { process.stderr.write(`[azeth-mcp] destroy error: ${e instanceof Error ? e.message : String(e)}\n`); }
        }
      },
    );
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Without annotations, the description carries full disclosure burden. It explains the discovery→rank→fallback flow, mentions autoFeedback defaults specific to MCP context (ephemeral client), and details return contents (service used, attempts needed, payment details). Minor gap: doesn't explicitly state transaction finality or failure modes beyond fallback.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well-structured with clear section headers (Use this when, How it differs, Flow, Note, Returns, Example). Length is justified by complexity—every section serves a distinct purpose in distinguishing siblings or explaining discovery behavior. Front-loaded with clear purpose statement.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Comprehensive for a complex discovery/payment tool with no output schema or annotations. Covers discovery algorithm, reputation filtering, fallback behavior, payment authorization context (maxAmount), and return value structure. Missing only explicit warning about irreversible transactions, though implied by 'pay'.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema coverage, baseline is 3. Description adds value by providing complex examples showing parameter interactions (e.g., capability + maxAmount + method + body used together) and clarifying MCP-specific defaults (autoFeedback false in ephemeral context) not evident in schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific action 'Discover the best service for a capability and pay for it automatically' (verb + resource + mechanism). It explicitly distinguishes from sibling tool azeth_pay using concrete examples showing capability-based vs. URL-based invocation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit 'Use this when' section defines exact trigger condition (need service by CAPABILITY). The 'How it differs from azeth_pay' section provides clear comparative guidance with syntax examples, eliminating ambiguity between the two payment tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/azeth-protocol/mcp-azeth'

If you have feedback or need assistance with the MCP directory API, please join our Discord server