MCP Integration Bridge
by Randika97
README.md
# MCP Integration Bridge
An MCP server that connects **any two systems** so an agent can explore both,
map between them, and run real data transfers — then shows its working in a run
folder you can audit afterwards.
Nothing in the codebase names a product. Which systems the bridge talks to is
decided by a `.env` file (addresses and credentials) and a **profile** (the shape
of each system: its endpoints, entities, queries, and documentation tree).
Pointing the bridge at a different pair of systems is a configuration change.
```mermaid
flowchart LR
MCP["MCP client<br/>(Cursor, Claude Desktop)"] -- stdio --> B
UI["Browser UI"] -- HTTP --> B
B["Integration bridge"] <--> SRC["SOURCE system<br/>records read from"]
B <--> TGT["TARGET system<br/>records written to"]
B <--> HUB["HUB (optional)<br/>orchestration API"]
ENV[".env"] --> B
PROF["profiles/<id>/"] --> B
```
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full design.
---
## What it gives an agent
| Capability | How |
| ---------------------- | ----------------------------------------------------------------------------------- |
| **Explore live APIs** | Authenticated tools for each system: REST/OData reads, GraphQL queries, introspection |
| **Explore the docs** | Offline keyword search across each system's source or specification tree |
| **Read named entities**| `source_query_data` / `target_query_data` over operations the profile declares |
| **Transfer records** | `execute_workflow` runs forward, reverse, or a full round trip |
| **Self-bootstrap** | A missing workflow is built from a template, a codegen pipeline, or generated modules |
| **Audit everything** | Every tool call, payload, and skipped field lands in a timestamped run folder |
---
## Quickstart
```bash
python -m venv .venv
.venv/Scripts/pip install -r requirements.txt # Linux/macOS: .venv/bin/pip
cp .env.example .env # then fill in the SOURCE_* and TARGET_* values
python scripts/selfcheck.py
```
`selfcheck.py` validates the profile, catalog, workflow and ingest wiring without
contacting either system, so it works before you have credentials.
**As an MCP server** (stdio) — copy `mcp.json.example` into your MCP client
config and adjust the paths:
```bash
.venv/Scripts/python server.py
```
**As an HTTP API** for a browser UI:
```bash
.venv/Scripts/python http_server.py # http://127.0.0.1:8765
```
Start with `bridge_info` — it reports the active profile, both roles, and exactly
which environment variables are still missing.
---
## Configuration
Every connected system is a **role**, and all roles are configured the same way:
`<ROLE>_<OPTION>`, where the role is `SOURCE`, `TARGET`, or `HUB`. Environment
variables override the profile's defaults, so a profile ships the shape of a
system and `.env` supplies the instance.
### Minimum viable `.env`
```ini
ACTIVE_PROFILE=erp-to-tms
SOURCE_BASE_URL=https://your-tenant.example.com
SOURCE_TOKEN_URL=https://your-tenant.example.com/auth/realms/main/protocol/openid-connect/token
SOURCE_CLIENT_ID=your-client-id
SOURCE_CLIENT_SECRET=your-client-secret
SOURCE_DOCS_PATH=c:/Repositories/your-erp/workspace
TARGET_BASE_URL=https://your-platform.example.com
TARGET_USERNAME=you@example.com
TARGET_PASSWORD=your-password
TARGET_DOCS_PATH=c:/Repositories/your-platform
```
`.env.example` documents the full surface, including connector selection, path
prefixes, login form field names, bootstrap behaviour, and the HTTP bridge.
### Connectors
A role picks its protocol with `<ROLE>_CONNECTOR`:
| Value | Authentication | Suits |
| ----------------- | ---------------------------------- | ----------------------------------------- |
| `oauth2_rest` | OAuth2 client credentials → bearer | REST / OData service catalogues |
| `session_graphql` | CSRF-protected form login → cookie | GraphQL web applications |
| `token_api` | Credentials → JWT bearer | JSON APIs that issue a token from a login |
### Searching documentation
Each role's offline search is described entirely by settings, so it works against
a source checkout, a specification bundle, or a folder of docs:
```ini
SOURCE_DOCS_PATH=c:/Repositories/your-erp/workspace
SOURCE_DOCS_GLOBS=*/model/**/*.projection,*/model/**/*.entity
SOURCE_DOCS_INDEX_GLOB=*/model/**/*.projection
TARGET_DOCS_PATH=c:/Repositories/your-platform
TARGET_DOCS_GLOBS=**/graphql/*.py,**/schema.py
TARGET_DOCS_INDEX_FILE=your_app/schema.py
TARGET_DOCS_INDEX_REGEX=(\w+Query)
```
`DOCS_GLOBS` selects what is searchable; the index settings produce the list of
named API surfaces that `*_search_docs` returns alongside raw matches.
---
## MCP tools
**Bridge and runs**
| Tool | Purpose |
| ------------- | ------------------------------------------------------------------ |
| `bridge_info` | Active profile, both roles, paths, and what is still unconfigured |
| `run_new` | Start a new run folder |
| `run_info` | Active run id, folder, and counters |
**Workflows**
| Tool | Purpose |
| -------------------------- | ----------------------------------------------------------- |
| `list_workflows` | Registered workflows and whether each is ready |
| `workflow_bootstrap_hints` | Search hints and a starter manifest for a missing workflow |
| `register_workflow` | Register a manifest plus `forward.py` and optional `reverse.py` |
| `bootstrap_workflow` | Build a workflow automatically |
| `execute_workflow` | Run `forward`, `reverse`, or `round_trip` |
**Source role** — `source_connection_info`, `source_search_docs`, `source_http`,
`source_service_query`, `source_service_metadata`, `source_query_data`
**Target role** — `target_connection_info`, `target_search_docs`,
`target_graphql`, `target_introspect`, `target_query_data`
**Hub role** — `hub_connection_info`, `hub_login`, `hub_trigger_run`,
`hub_poll_run`, `hub_answer_run`, `hub_graphql`, `hub_http`
### Example agent flow
```
bridge_info → confirm both roles are configured
source_search_docs("Shipment") → find the source API surface
target_introspect() → see what the target accepts
source_query_data("shipments", limit=5) → sample real records
execute_workflow(workflow_id="booking", source_id="12345")
run_info → the folder holding the evidence
```
---
## Profiles
A profile describes one concrete pair of systems. `profiles/erp-to-tms/` ships as
a worked reference — copy it, edit it, and set `ACTIVE_PROFILE`.
```
profiles/<id>/
profile.json role defaults (connector, path prefixes, docs globs) + workflow manifests
catalog.json named read operations behind source_query_data / target_query_data
ingest.json write operations that generated mappings call
reconcile.json entities to compare across both systems
smoke.json the end-to-end pipeline and its pass criteria
discovery.json discovery step labels and the outcome catalogue
templates/ bundled forward.py / reverse.py for bootstrap
```
Only `profile.json` is required.
### Adding a readable entity
```json
"source": { "operations": {
"purchase_orders": {
"description": "Purchase order headers.",
"service": "PurchaseOrderHandling",
"entity_set": "PurchaseOrderSet",
"filter_template": "OrderNo eq '{identifier}'",
"search_field": "Description"
}
}}
```
`source_query_data("purchase_orders", identifier="PO-1")` works immediately, with
no code change.
### Adding a write operation
Generated mappings call methods that no Python file defines — the adapter
resolves the name against `ingest.json` at call time:
```json
"operations": {
"create_order": {
"document": "mutation CreateOrder($input: OrderInput!) { createOrder(input: $input) { order { id state } } }",
"wrap_positional": "input",
"variables": { "input": "{input}" },
"root": "createOrder.order",
"required": true,
"flatten": { "id": "id", "state": "state" }
}
}
```
Mapping logic too complex to express declaratively belongs in the workflow's
`forward.py` / `reverse.py`, which is ordinary Python. That is the intended
boundary: profiles describe *what the systems offer*; workflows describe *how
this business mapping works*.
---
## Workflows and runs
A workflow is a manifest (how to fetch from the source, what the target entity is
called, whether a reverse callback exists) plus a `forward.py` and optional
`reverse.py`. Asking for one that does not exist is not an error — the bridge
tries a bundled template, then a codegen pipeline, then modules already generated
under `CODEGEN_ROOT`, and only then returns `needs_bootstrap` with search hints
and a starter manifest.
Every tool call runs inside a run folder:
```
runs/2026-08-11/143022-booking-12345/
run.json tool_calls.jsonl transfer_log.jsonl skips.jsonl
inputs/ outputs/ mappings/
```
---
## HTTP bridge
For browser UIs that cannot speak MCP stdio. Routes are role-shaped:
| Method | Path | Purpose |
| ------ | --------------------------------------------- | ----------------------------------- |
| GET | `/api/health`, `/api/info` | Liveness and active configuration |
| GET | `/api/workflows`, `/api/runs` | Registry and run history |
| POST | `/api/sessions` | Create a session |
| POST | `/api/sessions/{id}/configure` | Supply credentials |
| POST | `/api/sessions/{id}/configure-env` | Use the server's `.env` |
| GET | `/api/sessions/{id}/source/test`, `/target/test` | Prove each connection |
| GET | `/api/sessions/{id}/source/services`, `/target/types` | Discovered API surfaces |
| POST | `/api/sessions/{id}/discovery/start` | Walk both systems |
| POST | `/api/sessions/{id}/reconcile/run`, `/reconcile/lookup` | Master-data comparison |
| POST | `/api/sessions/{id}/smoke/run` | End-to-end proof |
| POST | `/api/sessions/{id}/agent/*` | Agent-planned discovery and builds |
---
## Repository layout
```
server.py MCP entry point (stdio)
http_server.py HTTP bridge entry point
integration_mcp/
config/ .env loading, profiles, per-role settings
connectors/ oauth2_rest, session_graphql, token_api, graphql_ingest
catalog.py profile-declared read operations
search/ offline documentation and source-tree search
transfer/ workflow execution
workflows/ registry, manifests, bootstrap strategies
runs/ run folders, tool-call logging, spec snapshots
http/ sessions, discovery, reconcile, smoke, agent
profiles/ per-integration configuration
workflows/ registered forward/reverse modules (gitignored)
runs/ run artefacts (gitignored)
scripts/ selfcheck.py
docs/ARCHITECTURE.md design, diagrams, extension points
```
---
## Security notes
- Each connector may only reach the hosts it was configured with. Add more with
`<ROLE>_ALLOWED_HOSTS`, which keeps an authenticated connector from becoming an
SSRF primitive.
- Credentials are read from the environment only; `.env` and `.env.*` are
gitignored. Cached cookies and tokens live under `STATE_DIR`.
- `*_connection_info` tools report whether a credential is present, never its
value.
- Writing back to the source system is off by default; it requires
`post_callback=true` or `TRANSFER_POST_CALLBACK=1`.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues