Skip to main content
Version: ERPBridge + bridgectl ยท v0.5.0-alpha.2

ERPBridge V2 architecture: declarative control plane

ERPBridge V2 adopts a Declarative Control Plane architecture, inspired by Kubernetes. This design moves away from static, file-system-bound configurations toward a live API-managed resource system for MCP tools.

High-level overviewโ€‹

The system is divided into three distinct layers:

  1. Management Layer (The CLI): Developers use bridgectl to declare the desired state of the system by "applying" YAML/JSON resource definitions.
  2. Control Plane (The Server): A centralized API server stores tool definitions in a persistent SQLite database, validates them against strict admission rules, and manages versioning.
  3. Runtime Layer (MCP Engine): A background reconciliation controller keeps the active MCP server aligned with the desired state in the database.

Visual architectureโ€‹

The detailed view shows the control plane, MCP access, plugin integration, and the protected connections to ERP and external services.

ERPBridge high-level architecture

Presentation overviewโ€‹

Use this simplified view to explain ERPBridge to a non-technical audience. Developers choose the tools. AI agents use those tools. ERPBridge connects the business systems.

ERPBridge presentation architecture

Endpoint and tool: key conceptsโ€‹

One of the most important concepts in ERPBridge V2 is the distinction between registering an API Endpoint and applying an MCP Tool.

FeatureAPI Endpoint (api register)MCP Tool (tool apply)
Mental ModelTechnical DiscoveryDeclarative Management
Primary FocusThe ERP System (technical)The AI Agent (semantic)
StorageContext-scoped local registry (~/.bridgectl/registries/<context>.json)Server Registry (erpbridge.db)
VisibilityHidden from AIVisible to AI (as an MCP Tool)
StabilityExperimental / InternalVersioned / Stable
Commandbridgectl api register ...bridgectl tool apply -f ...

Why separate them?โ€‹

  • Technical vs. Semantic: A single ERP API endpoint (e.g., /api/v1/resource/Employee) might be used by multiple MCP tools with different filters or versions.
  • Safety: Registering an API is a developer-only technical step. Applying a tool is a conscious decision to expose functionality to an AI agent.
  • Lifecycle: You can "discover" and test 100 API endpoints locally, but only "apply" the 5 that are safe and ready for the LLM to use.

Core componentsโ€‹

1. Tool resource registry (the source of truth)โ€‹

Instead of loading files from a directory, the server maintains an internal Tool Registry backed by SQLite. This registry stores multiple versions of the same tool, allowing for safe rollouts and rollbacks.

Each admitted name and version is immutable executable content identified by a canonical SHA-256 digest and server-authored admission record. Changed content requires a new version. IsActive controls exact execution eligibility. IsServing selects the active revision used by future unqualified calls. Soft deletion retains the resource but removes execution authority.

2. Version resolver and invocation snapshotโ€‹

An unqualified request such as list_employees selects the operator-controlled serving revision, not the highest semantic version. Every active revision is also exposed through a protocol-safe exact name. A client can bind an unqualified call with the digest in MCP _meta; exact requests never fall forward.

ERPBridge clones the selected resource before schema validation and middleware construction. Validation, authorization, ERP mapping, revision-scoped caching, response processing, and output use that snapshot if the serving pointer changes concurrently.

3. Visibility filtering (JSON-RPC interception)โ€‹

Because the underlying MCP runtime does not always support dynamic removal of tools from an active session, ERPBridge implements a Visibility Filtering Layer.

  • The Problem: Once a tool is registered in memory, standard libraries often provide no way to "unregister" it without a restart.
  • The Solution: ERPBridge wraps the MCP server's HTTP handler and intercepts the tools/list response. Before the JSON-RPC result reaches the client, ERPBridge parses the list and removes any tools marked as IsActive = false in the internal registry.
  • Result: The client receives a truthful list of tools. The list matches the desired state of the control plane. This works even when the underlying runtime still knows the "ghost" tools.

4. Final execution authority and target originโ€‹

Immediately before connector entry, ERPBridge validates the final effective request origin against the origins bound at admission. This includes ERP_BASE_URL rewrites. The authority boundary forces the production connector to return redirect responses without following them.

Withdrawal and connector commitment share one process-local synchronization boundary. Withdrawal first prevents connector entry. Connector commitment first lets that call complete before withdrawal is acknowledged. This is not a distributed-revocation guarantee. Protected cache-hit revocation requires a separate release check and is outside this boundary.

5. Reconciliation controllerโ€‹

The server runs a background reconciliation controller. It keeps the in-memory MCP registry in sync with the SQLite database.

The controller runs every 10 seconds. It compares the database state against the desired state:

  • If a tool exists in SQLite but not in the registry, the controller registers it.
  • If a tool is inactive (soft-deleted) or missing from SQLite, the controller deregisters it from the MCP runtime.
  • If a tool changes, the controller re-registers the new version.

Each check uses a SHA-256 state hash over ordered tool, plugin, and binding identity, activity, update timestamp, and stored resource data. If the hash is unchanged, the controller skips the pass. This keeps the check cheap.

The authenticated /api/info response exposes process-local reconciliation attempt count, last attempt, last success, safe error state, desired-state hash, observed generation, and convergence. Desired resources and soft-delete tombstones remain durable in SQLite; cycle timestamps and counters reset with the process. The periodic scan continues after failed intervals and can converge when dependencies recover without a restart.

When the registry changes, the controller sends the notifications/tools/list_changed notification to all active MCP sessions.

The controller also runs immediately after a tool apply HTTP request. The tool is visible to agents right after apply. It does not wait for the next 10-second tick.

External response pluginsโ€‹

ERPBridge invokes an externally operated HTTP plugin. The control plane stores the endpoint and exact release, but never installs, starts, upgrades, or schedules plugin code.

A PluginBinding connects one exact plugin version to one exact tool version. A raw_response binding runs first and receives a bounded ERP response with status, normalized content type, and a tagged JSON or base64 body. It can adapt media, such as an ERP image to OCR text. The plugin cannot change status or receive ERP headers, URLs, credentials, caller identity, or original arguments. The developer owns the final object-shaped MCP output schema; plugins never change it dynamically. An incompatible output meaning requires a new MCP-visible tool name and exact tool version.

For successful responses, the fixed order is:

Terminal non-2xx responses can be inspected by raw plugins while retaining ERP retry and circuit-breaker accounting. They remain errors, including 3xx, and skip success-only normalization and after-response processing.

The pipeline runs only on cache misses. The cache stores the final transformed MCP result and never stores an error result, so cache hits bypass ERP and plugins. Plugin or binding lifecycle changes flush affected tool cache entries. continue uses the original captured response only when it satisfies the final schema; otherwise it returns a safe error. Use fail for media conversion unless a compatible fallback is known.

Plugin resources support api and docker metadata types; both describe an external deployment. ERPBridge does not own plugin images or their lifecycle. Credentialed calls support bearer and API-key headers only. A credentialRef is an environment-variable name with a PLUGIN_ prefix; the resolved value is read at request time and is never persisted or logged. Credentialed endpoints require HTTPS, except for exact development-only host:port entries in INSECURE_AUTH_ALLOWED_HOSTS, and exact endpoint admission in PLUGIN_ENDPOINT_ALLOWLIST. Raw bindings require the allowlist even without plugin authentication, plus authenticated admin admission and an explicit object-shaped tool schema. Redirects, URL userinfo, raw secrets, and broad wildcards are not accepted.

Tool generation and templatingโ€‹

ERPBridge uses a "Template-Based Generation" approach. When generating tools (especially in bulk from OpenAPI):

  1. The Template: A Registered API acts as the source of technical truth (URL, Auth, Module).
  2. The Spec: An OpenAPI Definition acts as the source of semantic truth (Paths, Parameters, Descriptions).
  3. The Result: The generator merges these, creating production-ready MCP tools that are pre-configured with your environment's connectivity and security settings.

Use the same OpenAPI specification to generate tools for different environments (Dev, Staging, Prod). Point the generator to a different registered API.

Generation is pure until an explicit output or apply operation. The reviewed manifest under manifests/<module>/ is the single applied source; generated YAML is a temporary draft and does not create a competing schemas/ tree or per-tool JSON authority. CLI control-plane calls use the configured host root; only the exact /mcp or /mcp/ transport suffix is normalized away. The /mcp/ endpoint itself remains the Streamable HTTP MCP transport.

Lifecycle of a tool changeโ€‹

  1. Define: Developer creates a V2 YAML schema for a new tool.
  2. Validate: Runs bridgectl tool validate -f tool.yaml to check for syntax and admission rules (e.g., no raw secrets).
  3. Apply: Runs bridgectl tool apply -f tool.yaml.
  4. Store: The ERPBridge API validates the payload again and saves it in the SQLite tools table.
  5. Reconcile: The background controller detects the new DB entry and registers it with the mcp-go runtime.
  6. Execute: AI agents now see the new tool and can invoke it immediately.

Security designโ€‹

  • Secret decoupling: Schemas and plugin resources never contain raw tokens or keys. They contain only a credentialRef; the middleware resolves it from the environment at request time. Legacy local API registries require an explicit scrub before state changes continue.
  • Transport Admission: Credentialed ERP and plugin endpoints require HTTPS, except exact development-only host:port entries. Plugin endpoints also require exact PLUGIN_ENDPOINT_ALLOWLIST membership. Userinfo and redirects cannot be used to bypass the policy.
  • Admission Controllers: The API server rejects suspicious endpoint content, unknown authentication fields, reserved headers, raw-secret-looking values, and invalid credential references.
  • Redaction: All logs produced by tool executions are automatically filtered to redact sensitive keys defined in internal/types/sensitive.go; ERP request and response bodies are not logged.
  • Deployment Boundary: ERPBridge stores plugin configuration and invokes the protocol, but does not install, start, update, or schedule plugin code.
  • Structured failures: Control-plane HTTP errors use bounded error, message, suggestion, and numeric code fields. Stable identifiers such as VALIDATION_FAILED, AUTHORIZATION_DENIED, UPSTREAM_UNREACHABLE, and API_PROBE_FAILED provide recovery keys without exposing upstream bodies.
  • Sensitivity admission: Tools may declare security.dataClass as public, internal, pii, or restricted; the latter two require opaque allowedRoles. Development-only system.*_test tools are gated, and RedisInsight is loopback-only by default.