Backends reference
MCPG's 32 backend plugins — HTTP, command, NATS, gRPC, GraphQL, SOAP, Kafka, SQL, OpenAPI, warehouse & analytics connectors (BigQuery, Snowflake, ClickHouse, DuckDB, DynamoDB, Elasticsearch), enterprise connectors (LDAP, MSSQL, AMQP, email, FTP, SFTP, SMB), Twilio, mock, the native pipeline, plus an LLM provider family — each selected by an open 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 withcargo 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. BackendImpl is an open { kind, spec } pair: kind names a
loaded backend plugin's registered kind(), and spec carries that plugin's
own config. The gateway enumerates no kinds of its own — every backend is a
runtime-loaded cdylib plugin that registers its own kind at load. MCPG ships
32 backend plugins:
- 27 connectors spanning messaging, databases, warehouses, and enterprise
systems:
http,command,nats,grpc,graphql,soap,kafka,sql,openapi,dynamodb,elasticsearch,bigquery,snowflake,clickhouse,duckdb,hana,odbc,oracle,ldap,mssql,amqp,email,ftp,sftp,smb,twilio,mock - 5 LLM provider plugins — Anthropic, OpenAI, OpenAI-compatible, Gemini,
and Stability — each registering multiple model kinds (chat, embeddings,
image, text-to-speech, speech-to-text, depending on provider) with
underscore discriminators like
openai_chat,anthropic_chat,gemini_embedding,stability_image,openai_tts— see the LLM backend family below.
pipeline is a gateway-native composite backend (no plugin) selected via
backend.kind: pipeline; it chains multiple steps into one capability — see
pipeline steps. 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:
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.
- 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 # names a loaded backend plugin's kind
# …kind-specific fields are flattened siblings of `kind:`
- Governance.
minimum_trustandallow_ifare evaluated by the pre-dispatch policy gate before the backend runs. See identity and policy. - Input validation. When
input_schemais 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).
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. Setgateway.server.allow_private_backends: true(or per-bindingallow_private_backends: true) to permit private targets in trusted networks.
Command
Runs a local subprocess and captures its output.
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.
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 W3Ctraceparent/tracestatevia NATS headers. - The plugin's connection (
url/credentials_path) is the single source of truth across all NATS bindings, so theplugins[]entry needs noconfig:block. The single entry registers both thenatsbinding and thenats_topicwatch strategy.
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.
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-coreHTTP 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.
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 responsedatafield is extracted as output. - A non-200 status or a non-empty
errorsarray produces an error result.
SOAP
Dispatches a tool call as a SOAP 1.1 / 1.2 envelope over an outbound HTTP request, for calling legacy SOAP web services.
backend:
kind: soap
endpoint: "https://services.example.com/UserService" # http(s) URL (required); CEL + cred:// supported
soap_version: "1.1" # "1.1" | "1.2" (default: "1.1")
soap_action: "http://example.com/GetUser" # SOAPAction URI
body_template: | # XML for <soap:Body> (required); CEL ${arguments.*}, XML-escaped
<GetUser xmlns="http://example.com/">
<id>${arguments.user_id}</id>
</GetUser>
header_template: null # optional XML for <soap:Header>
timeout_ms: 30000
max_response_bytes: 1048576
expected_status_codes: [200]
headers: # CEL + cred:// supported per value
Authorization: "Bearer ${cred://vault-oauth/api}"
- The plugin (
dev.mcpg.backend.soap) builds the envelope, placessoap_actionper version (theSOAPActionheader in 1.1, anaction=Content-Type parameter in 1.2), and POSTs it toendpoint. - Tool arguments are XML-escaped as they interpolate into
body_template/header_template, so values cannot break out of the envelope (injection-safe). - A
<soap:Fault>(an HTTP 500/400 carrying a fault) is parsed into a structured downstream error, so the tool result is flaggedisErrorrather than surfacing an opaque 500. - Built on the shared
net-coreHTTP core (per-credential client cache, DNS-rebinding guard, per-call CEL /cred://), the same core HTTP, gRPC, and GraphQL use.cred://resolves inendpointand HTTPheaders(per caller) but is rejected insoap_action/body_template/header_template— pass per-caller secrets through anAuthorizationheader instead. - Beyond a tool, a SOAP binding can surface as a resource or a pipeline step. WSDL-driven auto-tool-generation is intentionally deferred; declare one binding per operation.
LDAP
Runs an LDAP / Active Directory search as MCP tools and resources — binds a service account and returns the matching directory entries as JSON.
backend:
kind: ldap
url: "ldaps://dc1.corp.example.com:636" # ldap:// or ldaps:// (required)
bind_dn: "cn=svc-mcpg,ou=svc,dc=corp,dc=example,dc=com" # service-account DN
bind_password: "${cred://vault-ad/svc-mcpg}" # literal or a whole cred:// URI
base_dn: "ou=people,dc=corp,dc=example,dc=com" # search base (required)
scope: subtree # base | one | subtree (default: subtree)
filter: "(&(objectClass=person)(cn=*${arguments.q}*))" # CEL ${arguments.*}, LDAP-escaped
attributes: [cn, mail, department, memberOf] # [] = all
size_limit: 100
timeout_ms: 10000
- The plugin (
dev.mcpg.backend.ldap) bindsbind_dnand searches underbase_dnatscope, returning each entry as{ dn, attributes }. - Argument values are RFC 4515 LDAP-escaped as they interpolate into
filter, so they cannot break out of it (injection-safe). - Bind / search rejections and connect / timeout failures become a structured
downstream error, so the tool result is flagged
isError. cred://resolves inbind_password(a whole-URI service-account secret) but is rejected infilter(which is CEL-templated from caller arguments).- LDAPS uses rustls. Beyond a tool, an LDAP binding can surface as a resource or a pipeline step (e.g. enrich another backend's result with a directory lookup). Write operations are deferred; v1 is read / search only.
MSSQL
Runs a parameterised Microsoft SQL Server (TDS) statement as MCP tools and
resources — the SQL-Server complement to the sql backend (which sqlx can't
drive for MSSQL).
backend:
kind: mssql
host: "sql1.corp.example.com" # required (operator-configured)
port: 1433
database: "HR" # required
user: "svc_mcpg" # required
password: "${env.MSSQL_HR_PASSWORD}" # ${env.X} / vault:// (no per-caller cred://)
trust_server_certificate: true # trust a self-signed / internal-CA cert
op: query # query (rows) | execute (rows-affected)
query: "SELECT id, full_name FROM dbo.employees WHERE id = @P1" # @P1.. placeholders
params: ["arguments.id"] # CEL exprs → bound to @P1.. (injection-safe)
size_limit: 100
pool_max_size: 8
timeout_ms: 10000
- The plugin (
dev.mcpg.backend.mssql) evaluates eachparams[i]CEL expression against the call arguments and binds the result to@P{i+1}as a TDS parameter — caller data is never concatenated into the statement, so SQL injection is structurally impossible. op: queryreturns{ rows, count }(each row projected to JSON by column);op: executereturns{ rowsAffected }.- SQL rejections and connect / login / pool / timeout failures become a
structured downstream error, so the tool result is flagged
isError. - The login
passwordresolves through the gateway secret-resolver; per-callercred://is rejected (the pool is one service identity). - TLS uses rustls. Beyond a tool, an MSSQL binding can surface as a resource or a pipeline step.
AMQP
Runs AMQP 0.9.1 (RabbitMQ) messaging operations as MCP tools and resources —
publish, request/reply (RPC), or pull-one — complementing the kafka/nats
backends.
backend:
kind: amqp
uri: "amqps://mcpg:${env.RABBIT_PASSWORD}@rabbit.internal:5671/%2f" # amqp:// or amqps://
op: rpc # publish (default) | rpc | get
exchange: "" # "" = the default direct exchange
routing_key: "rpc.pricing" # publish/rpc target (operator-fixed)
queue: "" # source queue for op: get
content_type: application/json
timeout_ms: 8000
- The plugin (
dev.mcpg.backend.amqp) sends the tool's arguments JSON as the message body.op: publishconfirms + mandatory-routing-checks the send;op: rpcawaits the correlated reply on a private exclusive queue;op: getpulls one message (auto-acked). - Replies / fetched messages decode to one of
body.json/body.text/body.base64. - Broker / publish failures become a structured downstream error, so the tool
result is flagged
isError. - The broker URI + password resolve through the gateway secret-resolver; the
connection is reused per binding. Per-caller
cred://is rejected. amqps://uses rustls. Beyond a tool, an AMQP binding can surface as a resource or a pipeline step (e.g. enqueue a job after another step).
Sends mail over SMTP and reads a mailbox over IMAP — let an agent send notifications and triage an inbox.
# op: send (SMTP) — the message is built from the call arguments
backend:
kind: email
op: send
host: "smtp.corp.example.com"
port: 587
tls: starttls # none | starttls | implicit
username: "svc-mcpg"
password: "${env.SMTP_PASSWORD}"
from: "alerts@corp.example.com"
# op: read (IMAP) — fetch the most recent messages
backend:
kind: email
op: read
host: "imap.corp.example.com"
port: 993
tls: implicit
username: "shared-inbox@corp.example.com"
password: "${env.IMAP_PASSWORD}"
mailbox: INBOX
limit: 20
- The plugin (
dev.mcpg.backend.email) sends via lettre and reads via async-imap.op: sendtakesto/subject/bodyfrom the call arguments and returns{ sent: true };op: readreturns the recent messages (envelope + text body), read non-destructively (BODY.PEEK). - Connect / login / protocol failures become a structured downstream error, so
the tool result is flagged
isError. - TLS is rustls (STARTTLS / implicit / none); the login password resolves
through the gateway secret-resolver. Per-caller
cred://is rejected.
SFTP
Lists a directory, reads a file, or writes a file over SSH — an on-ramp for partner drop-folders and legacy file exchange.
backend:
kind: sftp
op: get # list (default) | get | put
host: "sftp.partner.example.com"
port: 22
username: "svc-mcpg"
password: "${env.SFTP_PASSWORD}"
host_key_sha256: "SHA256:abc123…" # pin the server key (fail-closed)
root: "/outbound" # caller path joins under here
max_bytes: 10485760
- The plugin (
dev.mcpg.backend.sftp, russh + russh-sftp) takes the targetpathfrom the call arguments and joins it underroot—..segments are rejected, so a caller cannot escape the configured root. op: listreturns directory entries;op: getreturns the file's content ({text}/{base64}) capped atmax_bytes;op: putwrites thecontent(base64) /textargument.- Host-key verification fails closed: pin
host_key_sha256, or setaccept_unknown_host_key: truefor dev only. - Pure-Rust SSH crypto (no OpenSSL); connect / auth failures become a structured downstream error. Public-key auth is deferred (SFTP authenticates with a password).
Kafka
Sends a message and waits for a correlated response.
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 torequest_topicwith that ID (and forwards W3Ctraceparent), then filters replies by correlation ID. - Connection config (
bootstrap_servers/group_id) is the single source of truth across all Kafka bindings, so theplugins[]entry needs noconfig:block. The single entry registers both thekafkabinding and thekafka_topicwatch strategy.
SQL
Executes a parameterized query (or stored procedure) against PostgreSQL,
MySQL/MariaDB, or SQLite, returning rows shaped by row_mode.
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-callercred://in the URL or anysession_varsvalue; or cloud-DB IAM via anauth:block.rds_iamis shipped (Cargo featuresql-rds-iam);azure_ad,gcp_iam, andaurora_failoverparse 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). Withinputorboth, the plugin introspects prepared-statement parameter types (Postgres today) and merges a JSON Schema fragment intotools/list; operator-suppliedinput_schemafields win at every key. - Streaming (
row_mode: stream). Stateless keyset-cursor pagination — clients pass the previous response'snext_cursoras the_cursorargument. Multi-instance gateways must setstream.signing_key_envto 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 CELpredicateuntil 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), orsqlite3_interrupt(SQLite). - Resources and watch strategies. Add the binding under
mcp.capabilities.resources[](staticuri) orresource_templates[](uri_templatelikesqldoc://{slug});row_mode: resource_contentsauto-wraps SELECTeduri/text/mime_typecolumns into the MCPcontentspayload. Awatch.strategyofsql_polling(any engine) orpostgres_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.
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:
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: trueon the source to fan out every (filtered) operation as a tool; read-by-idGETs become resource templates by default.filter(allow/deny) andtool_prefixscope and namespace the generated tools.
Mock
Returns a configured response with no I/O — for testing and development.
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:
responseis JSON-stringified into a text content block plus structured metadata. error: truereturns a simulated error result.passthrough: truesurfacesresponseas a literalCallToolResult(its owncontent/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).
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 24 step kinds.
LLM backend family
Five LLM provider plugins — Anthropic, OpenAI, OpenAI-compatible, Gemini, and
Stability — register the model kinds below: managed bindings to hosted and
self-hosted model APIs across chat, embeddings, image, TTS, and STT surfaces.
Each surface 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://).
backend:
kind: openai_chat
model: gpt-4o-mini
api_key: "${cred://vault-openai/key}"
backend.kind | Surface | Plugin |
|---|---|---|
openai_chat | OpenAI chat completions | backend-llm-openai |
azure_openai_chat | Azure OpenAI chat (per-deployment URL) | backend-llm-openai |
anthropic_chat | Anthropic Messages API | backend-llm-anthropic |
gemini_chat | Google Gemini AI Studio chat | backend-llm-gemini |
compat_chat | Any OpenAI-compatible endpoint (vLLM, LocalAI, Together, Groq, OpenRouter, llama.cpp, Vertex compat) | backend-llm-compat |
openai_embedding | OpenAI embeddings | backend-llm-openai |
azure_openai_embedding | Azure OpenAI embeddings | backend-llm-openai |
gemini_embedding | Gemini embeddings | backend-llm-gemini |
compat_embedding | OpenAI-compatible embeddings | backend-llm-compat |
openai_image | OpenAI image generation (DALL-E) | backend-llm-openai |
azure_openai_image | Azure OpenAI image | backend-llm-openai |
gemini_image | Google Imagen | backend-llm-gemini |
stability_image | Stability AI Stable Image (Core / SD3 / Ultra) | backend-llm-stability |
openai_tts | OpenAI text-to-speech | backend-llm-openai |
azure_openai_tts | Azure OpenAI TTS | backend-llm-openai |
openai_stt | OpenAI speech-to-text (Whisper) | backend-llm-openai |
azure_openai_stt | Azure OpenAI STT | backend-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).
| Tool | Description |
|---|---|
mcpg.runtime.snapshot | Runtime metadata: service info, uptime, session count |
mcpg.request.echo | Echo request context and arguments |
mcpg.debug.command_probe | Execute a configured command profile |
mcpg.debug.network_probe | HTTP GET to a configured endpoint |
mcpg.debug.network_json_call | HTTP POST JSON to a configured endpoint |
See also
- Configuration reference — the full
AppConfigkey tree, includingplugins[]andmcp.capabilities. - Pipeline steps — all 24 pipeline step kinds.
- Identity and policy — how
governance.minimum_trustandallow_ifare enforced before dispatch.