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`); }
        }
      },
    );

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