Sign in →

MCP Server

Configure Model Context Protocol servers as an Aforo product type — tool registry, per-tool pricing, and session lifecycle.

Updated 2026-07-29Suggest edits

MCP Server

Best when your product exposes tools over the Model Context Protocol and you want to bill each tool at a different rate. MCP Server is one of the four generally available product types in Aforo. This page covers what to configure in the catalog. The wire-level metering path — how a tools/call becomes a billable event, how gateway plugins detect it, and how the SDK decorators emit it — lives on the MCP Servers protocol guide.

When to use the MCP Server product type

Pick MCP Server when:

  • Your product answers MCP tools/call JSON-RPC requests, and
  • Different tools cost you different amounts to run (a semantic search vs. a database write vs. an LLM call), and
  • You want the invoice line item to say "SmartSearch.query — 1,200 calls" rather than "API — 3,400 requests".

If your product is a normal REST API you also want to sell to AI agents, pick Agentic API instead. If it is a fully autonomous agent that runs long sessions, pick AI Agent. See the Products page for the full type comparison.

Configuring the tool registry

Every MCP Server product carries a tool registry — the list of tools clients can call, stored in the product's tool_registry JSONB column. Two ways to populate it:

Discover from a running MCP server. Aforo calls the server's tools/list JSON-RPC and returns the tools it advertises:

curl -X POST "https://catalog.aforo.ai/api/v1/products/discover?productType=MCP_SERVER" \
  -H "Authorization: Bearer $AFORO_API_KEY" \
  -H "X-Tenant-Id: $TENANT_ID" \
  -H "Content-Type: application/json" \
  -d '{ "serverUrl": "https://mcp.example.com" }'

The response includes every tool's name, description, and inputSchema. Save what you want in the product's tool registry — you don't have to bill on every tool the server exposes.

ℹ

An older endpoint POST /api/v1/products/discover-mcp still works but is deprecated (2026-04-13). New integrations use POST /api/v1/products/discover?productType=MCP_SERVER.

Author it directly. Set type: "MCP_SERVER" on create and pass a toolRegistry object:

curl -X POST https://catalog.aforo.ai/api/v1/products \
  -H "Authorization: Bearer $AFORO_API_KEY" \
  -H "X-Tenant-Id: $TENANT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "SmartSearch",
    "type": "MCP_SERVER",
    "toolRegistry": {
      "tools": [
        { "name": "SmartSearch.query",  "description": "Semantic search over indexed documents" },
        { "name": "SmartSearch.index",  "description": "Index new documents for search" },
        { "name": "SmartSearch.delete", "description": "Remove documents from the index" }
      ]
    },
    "sessionConfig": {
      "idle_timeout_sec": 3600,
      "concurrent_sessions": 100
    },
    "integrationMode": "DIRECT"
  }'

Field notes:

FieldNotes
typeMust be MCP_SERVER (uppercase, case-sensitive).
toolRegistry.tools[]Each tool needs a non-blank name and a non-null description. inputSchema is optional; if present, must be a JSON object. Server validates on create.
sessionConfigFree-form JSONB blob for session policy. The descriptor's conventional keys are idle_timeout_sec, max_duration_sec, and concurrent_sessions — note the units are seconds, not minutes.
integrationModeHow Aforo meters the server: DIRECT, APIM_NATIVE, or HYBRID. This is distinct from the MCP transport (SSE or stdio), which is the wire protocol the server speaks. Together they determine which gateway plugin path or proxy sidecar you use — see the MCP protocol guide.
ℹ

Tool names in the registry must match exactly what your MCP server sends — SmartSearch.query, not smart_search_query. This is the same string the gateway plugin extracts from the JSON-RPC body and the key dimensionPricing uses at billing time.

Per-tool pricing

Aforo rate plans weight individual tools with different multipliers via the plan's dimensionPricing map. The map is a top-level field on the rate plan; keys are tool names, values are multipliers applied to the per-metric base rate.

curl -X POST https://pricing.aforo.ai/api/v1/rate-plans \
  -H "Authorization: Bearer $AFORO_API_KEY" \
  -H "X-Tenant-Id: $TENANT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "SmartSearch — Standard",
    "pricingModel": "PER_UNIT",
    "currency": "USD",
    "productIds": ["prod_smartsearch_uuid"],
    "dimensionPricing": {
      "SmartSearch.query":  1.0,
      "SmartSearch.index":  3.0,
      "SmartSearch.delete": 0.5
    },
    "metricConfigs": [{
      "metricName": "Tool Invocations",
      "model": "PER_UNIT",
      "rate": 0.01,
      "includedFree": 0
    }]
  }'

With this plan, a customer who fires 1,000 SmartSearch.query calls and 100 SmartSearch.index calls in a billing period sees two line items:

Line itemQuantityRateAmount
SmartSearch.query1,000$0.010$10.00
SmartSearch.index100$0.030$3.00
⚠

Per-tool multipliers only fan out into separate line items when the metric config uses model: "PER_UNIT" with includedFree: 0. On FLAT_RATE, INCLUDED_QUOTA, GRADUATED, VOLUME_TIERED, STAIRCASE, or OUTCOME_BASED, the plan bills the aggregate as a single line item at the base rate — fanning per-tool across those models would either triple-count a flat fee or hand each tool its own full allowance. If per-tool matters for you, use PER_UNIT.

Attach billable units that make sense for the metric shape you care about — the shipped MCP templates are MCP Tool Calls, MCP Session Duration, MCP Active Sessions, MCP Connected Agents, MCP Input Tokens, MCP Output Tokens, MCP Errors, and MCP P95 Latency.

Session lifecycle

MCP sessions are long-lived. A single session may fire fifty tool calls, span an hour, and consume thousands of reasoning tokens. Aforo tracks the full lifecycle server-side:

StateMeaning
ACTIVEThe session is running. The first tools/call with a new session_id opens it; subsequent calls within the idle window meter tool invocations and aggregate token counts.
TIMED_OUTNo activity for the configured idle window — the session closed on timeout (terminal).
COMPLETEDThe session closed normally or reached its max lifetime (terminal).
FAILEDThe session ended in error (terminal).

sessionConfig on the product holds the idle-window and concurrency policy — set idle_timeout_sec too low and sessions the customer expected to be a single conversation split into several; set it too high and concurrency reporting under-counts.

The current session list for a customer is available via the storefront BFF at GET /api/v1/portal/mcp/sessions — this is what the customer-facing dashboard reads.

Troubleshooting

SymptomCauseFix
A tool fires but no line item appears on the invoiceThe tool name in your MCP server's JSON-RPC response doesn't match a key in dimensionPricing. Case-sensitive.Log the exact params.name your server emits, then align either the registry entry or the pricing key. Unmatched dimensionPricing keys silently fall through to the base rate.
Per-tool multipliers land the aggregate on the base rate instead of splitting per toolRate plan's metric config uses model: "FLAT_RATE", INCLUDED_QUOTA, or a tiered model. Fan-out is intentionally suppressed for those (see the warning callout above).Switch the metric config to model: "PER_UNIT" with includedFree: 0, or keep the model and bill the aggregate as one line item.
POST /api/v1/products/discover?productType=MCP_SERVER returns 502 with a discovery errorThe MCP server is unreachable from Aforo's egress, the URL is wrong, or the server responded with a JSON-RPC error to tools/list.Reach the server from Aforo's egress IPs, then re-run. For dev, call the server locally, capture the tools/list response, and paste it into the toolRegistry.tools array on create.
A session that ran for 40 minutes shows two sessions on the invoiceThe idle-window in sessionConfig (idle_timeout_sec) is shorter than the agent's pause between tool calls.Raise idle_timeout_sec on the product's sessionConfig. New sessions inherit the new value; in-flight ones close on their original setting.
The gateway plugin logs a tools/call but Aforo shows no usage eventThe plugin needs the tenant and product mapping — the JSON-RPC body alone doesn't carry either.Verify the gateway plugin config injects X-Tenant-Id and the correct product mapping. See the gateway guides for the per-gateway variant.
Create returns 400 "tool_registry.tools[0].description must not be null"Server-side validation of the tool registry (see validateToolRegistry in ProductServiceImpl). Each tool needs a non-blank name and a non-null description.Fill in every tool's description, then retry. inputSchema is optional; if you include it, it must be a JSON object, not a string.

What this guide does NOT cover

  • On-the-wire metering. How tools/call requests get intercepted, how sessions are correlated, how gateway plugins vs. SDK decorators emit the event — that's the MCP Servers protocol guide.
  • Billable unit definitions. Aggregation types, unit types, filter conditions, custom units — see Billable Units.
  • Customer credential lifecycle. How end-customers register agents, rotate client_id / client_secret, or bind keys to subscriptions — see Customers.
  • Operator MCP analytics dashboards. Aggregate views by tool, by agent, and by capability live inside the Aforo product console, not on this docs site.
  • The MCP protocol itself. Transport specs, tool schemas, and JSON-RPC framing are defined by the Model Context Protocol project; Aforo tracks the spec but doesn't restate it here.