Skip to main content
Glama
thezeivier

mcp-reliable-adapter

by thezeivier

MCP Reliable Adapter

MCP request flowing through a SQLite outbox, bounded retries, SaaS delivery, and dead-letter handling

A small, clean-room reference implementation of an MCP server that accepts support tickets even when a downstream SaaS is temporarily unavailable. It demonstrates a transactional outbox, semantic idempotency, bounded exponential retries, dead-letter handling, recovery after restart, and an inspectable audit trail—with only SQLite as infrastructure.

Portfolio scope: this is deliberately compact and uses a fictional SaaS. It illustrates the delivery boundary and its failure modes; it is not presented as a production-ready help desk.

Why this exists

An MCP tool call and a third-party API call cannot share a transaction. If the process stops at the wrong instant, a naive adapter can lose the request or create the same ticket twice. This project first commits the request and an outbox item atomically, then performs the external side effect. Every retry carries the original idempotency key.

sequenceDiagram
    participant C as MCP client
    participant A as Adapter
    participant DB as SQLite
    participant S as Support SaaS
    C->>A: submit_support_ticket(..., idempotency_key)
    A->>DB: BEGIN IMMEDIATE
    A->>DB: INSERT ticket + outbox + audit
    A->>DB: COMMIT
    A-->>C: pending + ticket_id
    A->>DB: claim due outbox item
    A->>S: create_ticket(..., same key)
    alt success
        S-->>A: external_id
        A->>DB: delivered + audit
    else transient failure
        A->>DB: retry_at + bounded backoff + audit
    else retry budget exhausted
        A->>DB: dead_letter + audit
    end

Related MCP server: Support Ticket Triage MCP

Quickstart

Requires Python 3.12+.

python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[dev]'
pytest
python -m build
mcp-reliable-adapter

The final command starts the official MCP Python SDK's stdio transport. A client can configure the command directly; set ADAPTER_DB_PATH when the database should live elsewhere:

{
  "mcpServers": {
    "reliable-support": {
      "command": "/absolute/path/.venv/bin/mcp-reliable-adapter",
      "env": {"ADAPTER_DB_PATH": "/absolute/path/adapter.db"}
    }
  }
}

Tools

submit_support_ticket

Inputs: subject, description, and a caller-generated idempotency_key. It commits a ticket, outbox item, and audit event in one local transaction and returns a stable ticket_id. Repeating the same payload and key returns the original ticket. Reusing the key for a different payload returns a conflict instead of silently conflating two requests.

get_delivery_status

Input: ticket_id. It advances due demo deliveries, then returns pending, delivered, dead_letter, or not_found, plus delivery metadata when available.

Local demo without an MCP client

python - <<'PY'
from pathlib import Path
from tempfile import TemporaryDirectory
from mcp_reliable_adapter import FakeSupportSaaS, ReliableAdapter

with TemporaryDirectory() as directory:
    adapter = ReliableAdapter(Path(directory) / "demo.db", FakeSupportSaaS(failures_before_success=1))
    ticket = adapter.submit_support_ticket(
        subject="Synthetic demo",
        description="No real customer data.",
        idempotency_key="demo-001",
    )
    adapter.process_due()                 # simulated outage; retry scheduled
    print(adapter.get_delivery_status(ticket["ticket_id"]))
    print(adapter.get_audit_trail(ticket["ticket_id"]))
PY

Guarantees and failure semantics

  • Durable acceptance: the tool returns only after the ticket, outbox item, and first audit record commit together.

  • Same-key consistency: a key identifies one canonical request body; conflicting reuse fails.

  • At-least-once delivery attempts: claimed work is requeued on startup, so a crash does not strand it permanently.

  • Effectively-once downstream creation—conditional: duplicate network attempts produce one logical ticket only if the downstream system honors the forwarded idempotency key.

  • Bounded retrying: exponential delay is capped, the attempt budget is finite, and exhausted work is visible as dead_letter rather than looping forever.

  • Auditability: acceptance, duplicate submissions, retry decisions, successful delivery, and dead-letter transitions are written as insert-only events by the application. SQLite does not make the table tamper-evident; a process with direct database access can still alter it.

Limitations and production path

  • The bundled SaaS is an in-memory fake; replace it with an authenticated HTTP adapter, explicit timeouts, response classification, and safe credential injection.

  • The demo processes due work during a status call. A real deployment should run a supervised worker independently of MCP request traffic.

  • SQLite is appropriate for a single-host example. Multiple workers need stronger claiming/lease semantics; a production service would commonly use PostgreSQL FOR UPDATE SKIP LOCKED.

  • There is a small crash window after the SaaS accepts a request but before local success commits. The downstream idempotency contract is therefore essential.

  • Restart recovery immediately requeues every processing item. A multi-process deployment needs expiring leases so one live worker's claim is not stolen.

  • The audit trail has no retention policy, tamper-evident signatures, or PII redaction layer.

  • Authentication, authorization, rate limiting, observability export, schema migrations, and operator-driven dead-letter replay are intentionally outside this compact example.

  • Tool inputs are intentionally small demo strings but are not size-limited. Do not expose this server to untrusted clients without authentication, authorization, input limits, and resource quotas.

Tests

The ten focused test scenarios cover:

  1. identical idempotent submissions;

  2. conflicting key reuse;

  3. successful delivery and audit state;

  4. scheduled exponential backoff;

  5. retry exhaustion and dead-letter state;

  6. persistence and recovery after process restart;

  7. stale concurrent claims respecting attempt counts and retry deadlines;

  8. invalid retry configuration;

  9. rejection of SQLite's connection-local :memory: mode;

  10. generated MCP input/output schema compatibility.

Español (resumen)

Este proyecto muestra cómo aceptar una solicitud MCP de forma durable antes de llamar a un SaaS inestable. La solicitud y el mensaje de salida se guardan juntos en SQLite; luego un worker intenta la entrega con la misma clave de idempotencia. Los fallos temporales generan reintentos con espera exponencial y límite, y los fallos agotados quedan visibles en una cola muerta. El historial registra cada transición. El alcance es educativo: para producción faltan autenticación, un worker separado, leases multiworker, observabilidad y políticas de datos.

See PROVENANCE.md for the clean-room statement and SECURITY.md for the demo's trust boundary. Licensed under MIT.

Available Tools

2 tools
get_delivery_statusA

Return the current durable delivery state for a submitted ticket.

ParametersJSON Schema
NameRequiredDescriptionDefault
ticket_idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
statusNo
attemptsNo
ticket_idNo
created_atNo
last_errorNo
updated_atNo
external_idNo
next_attempt_atNo

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description must convey behavior on its own. 'Return' clearly indicates a read-only operation, which is helpful, but it does not disclose behavior around invalid ticket IDs, missing tickets, or delivery-state semantics. Still, it gives a basic and accurate behavioral profile.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, focused sentence with no filler or redundant restatement. It front-loads the action ('Return') and the target resource, which is ideal for a simple one-parameter tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

This is a low-complexity tool with one required parameter and an output schema available, so the description need not explain return structure. It covers the essential purpose and resource, with the only notable gap being explicit usage context relative to the sibling submission tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description should compensate for the single parameter. It adds context that the ticket must already be submitted, which helps explain ticket_id's origin, but it does not specify format, where to obtain the ID, or any constraints beyond what the schema's type and required fields already state.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Return') and a specific resource ('durable delivery state for a submitted ticket'), making the tool's purpose clear. It is naturally distinguished from the sibling submit_support_ticket by being a read/status operation rather than a submission, though it does not explicitly name the sibling.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'for a submitted ticket' implies this tool should be used after a ticket has been submitted, and the sibling name suggests the alternative creation path. However, there is no explicit when-to-use or when-not-to-use guidance, and no alternative is named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

submit_support_ticketA

Durably accept a support ticket for asynchronous downstream delivery.

ParametersJSON Schema
NameRequiredDescriptionDefault
subjectYes
descriptionYes
idempotency_keyYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
statusNo
attemptsNo
ticket_idNo
created_atNo
last_errorNo
updated_atNo
external_idNo
next_attempt_atNo

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the behavioral disclosure burden. It does reveal two significant traits: acceptance is durable and delivery is asynchronous, which tells the agent this is not a synchronous end-to-end call. However, it does not disclose idempotency behavior, failure semantics, or what the returned output represents, which are material for a tool that requires an idempotency_key.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence with no filler. Every phrase earns its place: the core action, the durability guarantee, and the asynchronous nature of delivery are all packed into one concise, front-loaded statement.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given three required parameters, no annotations, 0% schema parameter coverage, and a sibling tool for status retrieval, this description is too sparse. It does not explain how idempotency_key should be used, what the expected response is, or how get_delivery_status relates to the asynchronous workflow. An agent can infer the basic action but not the full invocation contract.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description does not compensate: subject and description are self-explanatory, but idempotency_key's semantics, required format, and expected reuse behavior are entirely unexplained. The agent is left to guess the purpose of a required parameter that is central to correct invocation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('accept') with a clear resource ('support ticket') and adds meaningful qualifiers: 'durably' and 'asynchronous downstream delivery.' This makes the tool's submission-focused role distinct from the sibling get_delivery_status, which is clearly about checking status.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies when to use the tool—when submitting a support ticket—and the asynchronous wording hints at a later status check, but it never explicitly references the sibling get_delivery_status or states when not to use this tool. The routing between submit and status-check is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.1.0
    • First observedget_delivery_status
    • First observedsubmit_support_ticket

TDQS

A3.8/5.0

Scored across 2 tools

Disambiguation5/5

The two tools have clearly distinct purposes: one submits a support ticket, the other retrieves delivery status for a previously submitted ticket. There is no overlap or ambiguity between them.

Naming Consistency5/5

Both tool names follow a consistent verb_noun pattern: submit_support_ticket and get_delivery_status. The naming style is uniform and predictable.

Tool Count3/5

With only two tools, the server feels thin, but the tools form a natural pair for a reliable adapter focused on submission and status tracking. The count is borderline but not unreasonable for the narrow scope.

Completeness4/5

The core workflow of durably accepting a ticket and checking its delivery state is covered. A retry or cancellation operation could be useful, but the primary path has no dead ends.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    F
    maintenance
    An MCP-compliant server that enables AI assistants like Claude Desktop to access and analyze Intercom support tickets with full conversation history.
    4
    8
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    An MCP server that provides AI agents with a persistent, agent-native email mailbox for sending, receiving, and managing emails through bounded-context retrieval, idempotent operations, and explicit acknowledgement.
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    A production-grade MCP server over Postgres, providing secure data operations with tenant isolation, exact-once mutations, loop-aware rate limiting, and a tamper-evident audit trail.
    -