#!/usr/bin/env node
// agentsvc.io MCP server (stdio)
// Exposes every agentsvc.io service as an MCP tool and pays per call with USDC on Base via x402.
//
// Install once:   npm i @x402/fetch @x402/evm viem
// Run:            EVM_PRIVATE_KEY=0x... node mcp-server.mjs
//
// Claude Desktop / any MCP client (claude_desktop_config.json):
// {
//   "mcpServers": {
//     "agentsvc": {
//       "command": "node",
//       "args": ["/path/to/mcp-server.mjs"],
//       "env": { "EVM_PRIVATE_KEY": "0x..." }
//     }
//   }
// }
//
// EVM_PRIVATE_KEY: key of a wallet holding USDC on Base mainnet (chain 8453). No ETH needed.
// Without a key the server still works but only calls the free trial endpoints (limited per day).

import { createInterface } from 'readline';

const BASE_URL = process.env.AGENTSVC_URL || 'https://agentsvc.io';
const PRIVATE_KEY = process.env.EVM_PRIVATE_KEY || process.env.X402_PRIVATE_KEY;

function send(msg) {
  process.stdout.write(JSON.stringify(msg) + '\n');
}

let services = [];
let payFetch = null;
let payError = null;

async function initPayment() {
  if (!PRIVATE_KEY) return;
  try {
    const { wrapFetchWithPayment, x402Client } = await import('@x402/fetch');
    const { registerExactEvmScheme } = await import('@x402/evm/exact/client');
    const { privateKeyToAccount } = await import('viem/accounts');
    const client = new x402Client();
    registerExactEvmScheme(client, { signer: privateKeyToAccount(PRIVATE_KEY) });
    payFetch = wrapFetchWithPayment(fetch, client);
  } catch (err) {
    payError = `x402 client not available (${err.message}). Run: npm i @x402/fetch @x402/evm viem`;
  }
}

async function loadServices() {
  try {
    const res = await fetch(`${BASE_URL}/api/v1/services?limit=100`);
    const json = await res.json();
    services = json.data || [];
  } catch {
    services = [];
  }
}

function tools() {
  return services.map(svc => ({
    name: svc.slug.replace(/-/g, '_'),
    description: `${svc.description} Cost: $${svc.price_usdc} USDC per call${payFetch ? '' : ' (no wallet configured: free trial only, limited per day)'}. Example input: ${JSON.stringify(svc.example_input || {})}`,
    inputSchema: svc.input_schema,
  }));
}

async function callService(slug, input) {
  const body = JSON.stringify(input || {});
  const headers = { 'Content-Type': 'application/json' };
  if (payFetch) {
    const res = await payFetch(`${BASE_URL}/api/v1/proxy/${slug}`, { method: 'POST', headers, body });
    const json = await res.json();
    if (!res.ok || json.success === false) {
      throw new Error(`${json.error || `HTTP ${res.status}`}: ${json.hint || json.message || ''}`.trim());
    }
    return json;
  }
  if (payError) throw new Error(payError);
  const res = await fetch(`${BASE_URL}/api/v1/try/${slug}`, { method: 'POST', headers, body });
  const json = await res.json();
  if (!res.ok || json.success === false) {
    throw new Error(`${json.error || `HTTP ${res.status}`}: ${json.message || ''} Set EVM_PRIVATE_KEY with USDC on Base to use the paid endpoint without limits.`.trim());
  }
  return json;
}

const rl = createInterface({ input: process.stdin, terminal: false });

rl.on('line', async line => {
  if (!line.trim()) return;
  let msg;
  try { msg = JSON.parse(line); } catch { return; }
  const { id, method, params } = msg;

  if (method === 'initialize') {
    await Promise.all([loadServices(), initPayment()]);
    send({
      jsonrpc: '2.0', id,
      result: {
        protocolVersion: '2024-11-05',
        capabilities: { tools: {} },
        serverInfo: { name: 'agentsvc', version: '2.0.0', description: `agentsvc.io: ${services.length} pay-per-call web services for agents (x402, USDC on Base).` },
      },
    });
  } else if (method === 'notifications/initialized') {
    // no response for notifications
  } else if (method === 'tools/list') {
    if (!services.length) await loadServices();
    send({ jsonrpc: '2.0', id, result: { tools: tools() } });
  } else if (method === 'tools/call') {
    const slug = String(params?.name || '').replace(/_/g, '-');
    try {
      const result = await callService(slug, params?.arguments || {});
      const text = JSON.stringify(result.data ?? result, null, 2);
      send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text }] } });
    } catch (err) {
      send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true } });
    }
  } else if (method === 'ping') {
    send({ jsonrpc: '2.0', id, result: {} });
  } else if (id !== undefined) {
    send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } });
  }
});
