MCPG
Reference
Reference

Backends reference

The 27 backend kinds MCPG ships — HTTP, command, NATS, gRPC, GraphQL, Kafka, SQL, OpenAPI, mock, pipeline, plus a 17-kind LLM family — each selected by a nested backend.kind discriminator on an MCP capability.

Adapted from apps/gateway/docs/backends.md. Every config example uses the real nested binding shape. Validate any full config with cargo run -q -p mcpg --bin mcpg-config-check -- <file>, and see the configuration reference for the full key tree.

A backend maps one MCP capability (a tool, prompt, resource, or resource template) to one downstream integration. You attach it as a nested backend: block on the capability, and a kind: discriminator selects the implementation. The gateway's BackendImpl enum defines 27 kinds:

  • 10 general-purpose: http, command, nats, grpc, graphql, kafka, mock, pipeline, openapi, sql
  • 17 LLM kinds: managed bindings to hosted and self-hosted model APIs, with underscore discriminators like openai_chat, anthropic_chat, gemini_embedding, stability_image, openai_tts — see the LLM backend family below.

Every backend ships as a runtime-loaded cdylib plugin — the mcpg binary hard-wires no backend. A binding selecting a kind whose plugin is not declared in plugins[] fails fast at boot.

Where bindings live

Bindings sit under mcp.capabilities.{tools,prompts,resources,resource_templates}[]. There is no top-level bindings: block. A minimal tool binding:

yaml
mcp:
  capabilities:
    tools:
      - name: dev.echo
        description: Echo a value back for testing.
        backend:
          kind: mock
          response: { ok: true }

Common binding fields

Every binding shares these fields. The implementation lives under backend:; governance, retry, and quota policy are sibling blocks.

yaml
- name: "tool_name"            # string — unique MCP capability name (required)
  description: "What it does"  # string — MCP tool description (required)
  title: null                  # optional — display title
  input_schema: null           # optional — JSON Schema for argument validation
  output_schema: null          # optional — JSON Schema for the result
  governance:                  # optional — pre-dispatch policy gate
    minimum_trust: verified    # unauthenticated | header_asserted | verified
    allow_if: '"platform" in $identity.groups'   # optional CEL predicate
  retry:                       # optional — see the configuration reference
    max_attempts: 3
  backend:                     # required — implementation, discriminated by kind:
    kind: http                 # one of 27 BackendImpl kinds
    # …kind-specific fields are flattened siblings of `kind:`
  • Governance. minimum_trust and allow_if are evaluated by the pre-dispatch policy gate before the backend runs. See identity and policy.
  • Input validation. When input_schema is a valid JSON Schema, tool arguments are validated against it before dispatch; invalid arguments produce a JSON-RPC error without invoking the backend.
  • Per-call CEL. Many backend fields (url, header values, SQL params) accept CEL templates against the standard variable bag — ${$arguments.X}, ${$context.principal_id}, ${$tool_name}. Templates compile at config load and evaluate per call.
  • Per-caller credentials. cred://<plugin>/<target> URIs in supported fields resolve per request, so each caller's downstream call carries its own credential.

HTTP

Executes an HTTP request to a downstream endpoint (POST or GET).

yaml
backend:
  kind: http
  url: "https://api.example.com/accounts/${$arguments.account_id}"  # required; CEL + cred:// supported
  method: get                       # post | get (default: post)
  timeout_ms: 5000
  max_response_bytes: 1048576
  expected_status_codes: [200]
  require_json_response: true
  headers:                          # CEL + cred:// supported per value
    Authorization: "Bearer ${cred://vault-oauth/api}"
  • POST sends tool arguments as the JSON body; GET sends them as query parameters.
  • Non-matching status codes produce an error result with retry classification.
  • The plugin (dev.mcpg.backend.http) owns the reqwest client, a per-credential client cache, and a DNS-rebinding guard that refuses loopback / RFC1918 / link-local / ULA destinations after resolution. Set gateway.server.allow_private_backends: true (or per-binding allow_private_backends: true) to permit private targets in trusted networks.

Command

Runs a local subprocess and captures its output.

yaml
backend:
  kind: command
  command: "/usr/bin/my-tool"       # executable path (required, never templated)
  args: ["--format", "json"]        # CEL templating supported against $arguments / $context
  timeout_ms: 10000
  max_output_bytes: 1048576
  require_json_stdout: true
  • Tool arguments are written to stdin as JSON; stdout is captured up to max_output_bytes.
  • A non-zero exit code, a timeout, or non-JSON stdout (when require_json_stdout) produces an error result.
  • Runs locally — no network or SSRF surface, so the plugin entry needs no config: block.

NATS

Sends a request via the NATS Core request/reply pattern.

yaml
backend:
  kind: nats
  url: "nats://broker:4222"             # all nats bindings must agree
  credentials_path: "/etc/mcpg/nats.creds"   # optional
  subject: "backend.request"            # required
  timeout_ms: 5000
  max_response_bytes: 1048576
  • Publishes arguments as JSON to subject, waits for a reply on an auto-generated inbox, and propagates W3C traceparent / tracestate via NATS headers.
  • The plugin's connection (url / credentials_path) is the single source of truth across all NATS bindings, so the plugins[] entry needs no config: block. The single entry registers both the nats binding and the nats_topic watch strategy.
yaml
plugins:
  - id: dev.mcpg.backend.nats
    source:
      oci: ghcr.io/mcpg-dev/source-code/plugins/backend-nats:<version>-linux-amd64
      # or: path: /opt/mcpg/plugins/libmcpg_plugin_backend_nats.so

gRPC

Invokes a gRPC service method using proto-less JSON mapping over HTTP/2.

yaml
backend:
  kind: grpc
  url: "https://grpc.example.com"       # endpoint base (required)
  service: "example.UserService"        # fully-qualified service (required)
  method: "GetUser"                     # method name (required)
  timeout_ms: 5000
  max_response_bytes: 1048576
  headers: {}
  • Sends arguments as a JSON body via HTTP POST to {endpoint}/{service}/{method}; expects a JSON response mapped from protobuf.
  • Built on the shared net-core HTTP core (per-credential client cache, DNS-rebinding guard, per-call CEL / cred://), the same core HTTP and GraphQL use.

GraphQL

Executes a GraphQL query or mutation.

yaml
backend:
  kind: graphql
  url: "https://api.example.com/graphql"            # required
  operation: "query GetUser($id: ID!) { user(id: $id) { name } }"  # required
  timeout_ms: 5000
  max_response_bytes: 1048576
  headers: {}
  • Sends { "query": "...", "variables": <arguments> } as a JSON POST; tool arguments become GraphQL variables, and the response data field is extracted as output.
  • A non-200 status or a non-empty errors array produces an error result.

Kafka

Sends a message and waits for a correlated response.

yaml
backend:
  kind: kafka
  bootstrap_servers: "broker:9092"      # all kafka bindings must agree
  group_id: "mcpg"                      # all kafka bindings must agree
  request_topic: "requests"
  response_topic: "responses"
  timeout_ms: 10000
  max_response_bytes: 1048576
  • Generates a correlation ID, subscribes to response_topic, publishes arguments to request_topic with that ID (and forwards W3C traceparent), then filters replies by correlation ID.
  • Connection config (bootstrap_servers / group_id) is the single source of truth across all Kafka bindings, so the plugins[] entry needs no config: block. The single entry registers both the kafka binding and the kafka_topic watch strategy.

SQL

Executes a parameterized query (or stored procedure) against PostgreSQL, MySQL/MariaDB, or SQLite, returning rows shaped by row_mode.

yaml
backend:
  kind: sql
  driver: postgres                      # postgres | mysql | mariadb | sqlite
  url: "postgres://app:${env.ORDERS_DB_PW}@db:5432/orders"   # creds via ${env.X}
  pool:
    max_connections: 10
    min_idle: 1
    acquire_timeout_ms: 5000
  query:
    sql: "SELECT id, total FROM orders WHERE tenant = :tenant"  # or procedure / sql_file
    params: ["tenant"]                  # named placeholders bind in this order
    row_mode: many                      # single | many | scalar | affected_rows | resource_contents | stream
    max_rows: 1000
    timeout_ms: 3000
  schema:
    derive: input                       # off | input | output | both — opt-in
  session_vars:                         # Postgres only (SET LOCAL via set_config)
    app.current_tenant: "${identity.tenant}"

The SQL backend is the deepest of the set. Highlights:

  • Three credential surfaces, exactly one per binding. A static password in the url (with ${env.VAR} expanded at load); per-caller cred:// in the URL or any session_vars value; or cloud-DB IAM via an auth: block. rds_iam is shipped (Cargo feature sql-rds-iam); azure_ad, gcp_iam, and aurora_failover parse and validate today but return a clear "not yet implemented" error at boot. Combining two surfaces is a config error caught at startup.
  • Schema derivation (schema.derive). With input or both, the plugin introspects prepared-statement parameter types (Postgres today) and merges a JSON Schema fragment into tools/list; operator-supplied input_schema fields win at every key.
  • Streaming (row_mode: stream). Stateless keyset-cursor pagination — clients pass the previous response's next_cursor as the _cursor argument. Multi-instance gateways must set stream.signing_key_env to a shared secret so any replica can decode and resume a cursor.
  • Fire-and-wait (await). Submit work, then poll a check query and evaluate a CEL predicate until it matches or times out — for async provisioning flows.
  • Circuit breaker, schema-drift retry, driver-level cancel. Per-backend fail-fast; transparent re-prepare on stale-plan SQLSTATEs; in-flight cancel via pg_cancel_backend (Postgres), KILL QUERY (MySQL), or sqlite3_interrupt (SQLite).
  • Resources and watch strategies. Add the binding under mcp.capabilities.resources[] (static uri) or resource_templates[] (uri_template like sqldoc://{slug}); row_mode: resource_contents auto-wraps SELECTed uri / text / mime_type columns into the MCP contents payload. A watch.strategy of sql_polling (any engine) or postgres_listen_notify (Postgres-only, lower overhead) re-emits change events to subscribed MCP sessions.

For transactional multi-statement flows inside a pipeline, see the sql_tx pipeline step.


OpenAPI

Surfaces one operation of a registered OpenAPI spec as an MCP tool. The operator names only a source + operation; the plugin parses the spec, derives the tool's input/output schema, and dispatches an outbound HTTP request.

yaml
backend:
  kind: openapi
  source: petstore        # a source declared in the plugin's own config (required)
  operation: getPetById   # the OpenAPI operationId to surface (required)

The source is declared in the plugin's own config — the spec, upstream base_url, per-scheme auth, and limits live there:

yaml
plugins:
  - id: dev.mcpg.backend.openapi
    source:
      oci: ghcr.io/mcpg-dev/source-code/plugins/backend-openapi:<version>-linux-amd64
    config:
      sources:
        - name: petstore
          spec: "file:///etc/mcpg/specs/petstore.yaml"   # file:// URI or { inline: {...} }
          base_url: "https://api.petstore.example.com"
          auth:
            apiKeyAuth: "${cred://vault-api/petstore}"     # keyed by the spec's securityScheme name

Two surfacing modes:

  • Reference-only. One explicit backend: { kind: openapi, source, operation } binding per operation you choose to expose.
  • Bulk auto-expose. Set expose.tools: true on the source to fan out every (filtered) operation as a tool; read-by-id GETs become resource templates by default. filter (allow/deny) and tool_prefix scope and namespace the generated tools.

Mock

Returns a configured response with no I/O — for testing and development.

yaml
backend:
  kind: mock
  response:                  # configured response
    message: "hello from mock"
    status: "ok"
  delay_ms: 0                # simulated latency
  error: false               # simulate a tool error
  error_message: null        # error text (error mode)
  passthrough: false         # treat `response` as a literal CallToolResult
  • Default: response is JSON-stringified into a text content block plus structured metadata.
  • error: true returns a simulated error result.
  • passthrough: true surfaces response as a literal CallToolResult (its own content / isError / structuredContent) — for image, audio, embedded-resource, or mixed-content shapes. Validated at registration.

The mock plugin has no network surface, so its plugins[] entry needs no config: block.


Pipeline

Orchestrates multiple steps into a single tool call — data flow between steps, conditional gates, transactional SQL groups, and client interaction (elicitation / sampling / roots).

yaml
backend:
  kind: pipeline
  pipeline_timeout_ms: 30000            # total pipeline timeout
  steps:
    - id: fetch_order
      kind: http
      url: "https://orders.internal/v1/orders/${original_args.order_id}"
      method: get
    - id: check_balance
      kind: cel_gate
      expression: 'completed_steps.fetch_order.output.balance >= 0'
      error_message: "Order balance is negative"
    - id: confirm
      kind: elicitation
      message: "Confirm the charge?"
    - id: shape
      kind: transform
      expression: 'completed_steps.fetch_order.output'
  • Steps execute sequentially, sharing a context (original_args, request_context, completed_steps); the first error aborts the pipeline.
  • Suspending steps (elicitation, sampling, roots, gather) persist state and resume on the client's response — durable across load-balanced instances.

The pipeline backend has its own full reference: see pipeline steps for all 18 step kinds.


LLM backend family

Beyond the 10 general-purpose kinds, BackendImpl defines a 17-kind LLM family — managed bindings to hosted and self-hosted model APIs. Each is a separate backend.kind: (note the underscore discriminators), backed by an LLM cdylib. Like sql, these bindings forward a raw spec to the plugin, which owns the per-kind schema (model, generation parameters, streaming, and the API key via cred://).

yaml
backend:
  kind: openai_chat
  model: gpt-4o-mini
  api_key: "${cred://vault-openai/key}"
backend.kindSurfacePlugin
openai_chatOpenAI chat completionsbackend-llm-openai
azure_openai_chatAzure OpenAI chat (per-deployment URL)backend-llm-openai
anthropic_chatAnthropic Messages APIbackend-llm-anthropic
gemini_chatGoogle Gemini AI Studio chatbackend-llm-gemini
compat_chatAny OpenAI-compatible endpoint (vLLM, LocalAI, Together, Groq, OpenRouter, llama.cpp, Vertex compat)backend-llm-compat
openai_embeddingOpenAI embeddingsbackend-llm-openai
azure_openai_embeddingAzure OpenAI embeddingsbackend-llm-openai
gemini_embeddingGemini embeddingsbackend-llm-gemini
compat_embeddingOpenAI-compatible embeddingsbackend-llm-compat
openai_imageOpenAI image generation (DALL-E)backend-llm-openai
azure_openai_imageAzure OpenAI imagebackend-llm-openai
gemini_imageGoogle Imagenbackend-llm-gemini
stability_imageStability AI Stable Image (Core / SD3 / Ultra)backend-llm-stability
openai_ttsOpenAI text-to-speechbackend-llm-openai
azure_openai_ttsAzure OpenAI TTSbackend-llm-openai
openai_sttOpenAI speech-to-text (Whisper)backend-llm-openai
azure_openai_sttAzure OpenAI STTbackend-llm-openai

compat_chat and compat_embedding require an explicit base_url; api_key is optional for them. Each LLM kind requires its cdylib declared in plugins[].


Built-in debug tools

When feature_flags.debug_tools_enabled: true, these tools register automatically (gated by debug.tools.exposure flags). They are all off when feature_flags.debug_tools_enabled: false (the default).

ToolDescription
mcpg.runtime.snapshotRuntime metadata: service info, uptime, session count
mcpg.request.echoEcho request context and arguments
mcpg.debug.command_probeExecute a configured command profile
mcpg.debug.network_probeHTTP GET to a configured endpoint
mcpg.debug.network_json_callHTTP POST JSON to a configured endpoint

See also