Skip to main content
Glama
rogerix4

abap-adt-mcp-gateway

by rogerix4
README.md
# abap-adt-mcp-gateway

[![CI](https://github.com/rogerix4/abap-adt-mcp-gateway/actions/workflows/ci.yml/badge.svg)](https://github.com/rogerix4/abap-adt-mcp-gateway/actions/workflows/ci.yml)
[![Docs](https://img.shields.io/badge/docs-rogerix4.github.io-blue)](https://rogerix4.github.io/abap-adt-mcp-gateway/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org/)
[![MCP](https://img.shields.io/badge/MCP-compatible-blue)](https://modelcontextprotocol.io/)

**One MCP endpoint in front of every SAP system you work with.**

The SAP ABAP ADT server ([`@babamba2/abap-mcp-adt-powerup`](https://github.com/babamba2/abap-mcp-adt-powerup)) binds one SAP destination per process. That means one MCP server entry, one tool list, one login **per system** — and an assistant that keeps mixing up which system it is talking to.

This gateway keeps one child process per system but exposes the tool set **once**, with a mandatory `system` argument on every tool. Adding an eighth system does not add an eighth MCP server to your editor.

```
             ┌──────────────────────────────┐
MCP client ──►  abap-adt-mcp-gateway        │
(VS Code,     │  · one tool surface         │
 Claude,      │  · `system` routing         │──────► child: powerup  ──► SAP dev-100  (DEV, writable)
 Cursor, …)   │  · tier + read-only guard   │──────► child: powerup  ──► SAP qa-200   (QA, read-only)
              │  · audit log                │──────► child: powerup  ──► SAP prd-300  (PRD, read-only)
              └──────────────────────────────┘
```

## Why this exists

| Problem with one-server-per-system | What the gateway does |
|---|---|
| 8 systems = 8 × ~300 tools in the client, and the model picks the wrong one | one tool list; the target is an argument, not a separate tool namespace |
| every new system means editing `mcp.json` | one entry, one config file, no client change |
| a QA/PRD system is writable as long as someone adds `SAP_TIER=DEV` | tiers are evaluated per call, unknown tiers are **read-only**, and the name is not trusted as proof of "read-only" |
| credentials are copied into per-system env files | secrets stay in the environment or in the OS keychain |
| a tool call lands on the wrong system and nobody can tell afterwards | every call is recorded in a JSON Lines audit log (tool, system, outcome, duration — never argument values) |

## Features

- **One MCP server, many SAP systems** — stable tool count, `system` argument on every tool.
- **VS Code style configuration** — a single `adt-mcp.config.jsonc` with comments, trailing commas and a JSON Schema for autocompletion; edited while the server is running, picked up without a restart.
- **No secrets on disk** — `${env:NAME}` or `keychain:<service>/<account>`; credentials are materialised only into a private env file that is removed on shutdown.
- **Fail-closed safety** — anything that is not `DEV` is read-only; unknown tiers become `PRD`; a write that cannot be proven read-only will not guess between two writable systems.
- **Per-system serialisation** — one child process holds one SAP session, so calls to the same system are queued instead of colliding (`HTTP 400`, `Session Timed Out`, half-applied writes).
- **Audit trail** — append-only JSONL with redaction; argument *names* are logged, values never are.
- **Live reload** — editing the config or patching the child server recycles the affected child on the next call; other systems keep running.
- **Offline tool list** — `tools/list` works without a SAP login thanks to a per-system cache keyed by package, exposition and connection settings.
- **Diagnostics** — `adt-mcp-gateway doctor` checks paths, tiers, keychain entries, secret storage and leftover files.

## Requirements

- Node.js **20+**
- the ABAP ADT MCP server: `npm i -g @babamba2/abap-mcp-adt-powerup` (or a local install — it is auto-detected)
- network access to your SAP systems

## Install

```bash
git clone https://github.com/rogerix4/abap-adt-mcp-gateway.git
cd abap-adt-mcp-gateway
npm install
npm run build
```

Run it straight from the repository:

```bash
node dist/cli.js serve --config ./adt-mcp.config.jsonc
```

or install the CLI globally:

```bash
npm link          # exposes `adt-mcp-gateway`
adt-mcp-gateway --help
```

## Quick start

1. **Create a configuration** (interactive; writes to the per-user config folder):

   ```bash
   adt-mcp-gateway init
   ```

   Or copy [`adt-mcp.config.example.jsonc`](./adt-mcp.config.example.jsonc) next to your workspace and edit it.

2. **Verify it** — this reads everything except SAP itself:

   ```bash
   adt-mcp-gateway doctor --config ./adt-mcp.config.jsonc
   ```

3. **Point your MCP client at it** (`mcp.json` / `settings.json`, VS Code flavour):

   ```json
   {
     "servers": {
       "sap": {
         "type": "stdio",
         "command": "node",
         "args": ["D:\\tools\\abap-adt-mcp-gateway\\dist\\cli.js", "serve"],
         "env": {
           "SAP_DEV_100_PASSWORD": "${input:sapDevPassword}"
         }
       }
     }
   }
   ```

4. **Ask for a system.** Every tool takes `system`; `GatewayListSystems` tells you the ids:

   ```text
   GatewayListSystems()
   → [{ "system": "dev-100", "tier": "DEV", "writable": true, "connected": true, "tools": 302 }]

   ReadClass({ "system": "dev-100", "class_name": "ZCL_HELLO" })
   ```

## Configuration

The full reference lives in [docs/configuration.md](./docs/configuration.md); the schema is
[`schema/adt-mcp.config.schema.json`](./schema/adt-mcp.config.schema.json) (add `"$schema"` for completion and inline documentation).

Minimal file:

```jsonc
{
  "defaultSystem": "dev-100",
  "systems": [
    {
      "id": "dev-100",
      "tier": "DEV",
      "endpoint": { "url": "https://sap-dev.example.com:44300", "client": "100" },
      "auth": { "type": "basic", "username": "DEVELOPER", "password": "${env:SAP_DEV_100_PASSWORD}" }
    },
    {
      "id": "prd-300",
      "tier": "PRD",
      "endpoint": { "url": "https://sap-prd.example.com:44300", "client": "300" },
      "auth": { "type": "basic", "username": "READER", "password": "keychain:adt-mcp/prd-300" }
    }
  ]
}
```

Lookup order: `--config` → `$ADT_MCP_CONFIG` → `./adt-mcp.config.jsonc` → `./.adt-mcp/config.jsonc` → the per-user config folder.

## Gateway tools

Besides the ~300 ABAP tools, four tools belong to the gateway itself:

| Tool | Purpose |
|---|---|
| `GatewayListSystems` | systems, tiers, writability, connection state, tool count, queued calls |
| `GatewayListTools` | search the whole surface (including hidden tools) and see which systems provide each tool |
| `GatewayCallTool` | call any tool by name, even if it is hidden from `tools/list` |
| `GatewayRestartSystem` | recycle one child process after changing its settings or upgrading the child |

`toolPolicy.allow` / `toolPolicy.deny` only change what `tools/list` advertises. They are **not** an authorisation boundary — the tier guard and `GatewayCallTool` are.

## Safety model

1. **Tier first.** `tier` must be `DEV` for writes. `QA`, `QAS`, `PRD`, `PROD`, a missing tier and a typo all mean read-only.
2. **Read-only is proven, not guessed.** A tool counts as read-only only when its name starts with a read verb (`Get`, `Read`, `List`, `Check`, `Validate`, `Search`, `Describe`, `Analyze`, `Inspect`, `Show`, `Query`, `Preview`, or `Runtime` + read verb). Everything else is treated as a write on non-DEV systems. Unknown tools are blocked, not assumed harmless.
3. **No silent rerouting.** A call for a system that does not provide the tool fails; it is never redirected to another system.
4. **Ambiguity is an error.** With several writable systems and no `defaultSystem`, a call that cannot be proven read-only must pass `system` explicitly.
5. **Conflicting tool definitions disable the tool.** If two systems expose the same tool name with different schemas (typically different child versions), the tool is hidden and rejected until the versions are aligned.
6. **Everything is audited.** `audit.jsonl` records tool, system, tier, outcome, duration and queue wait; argument names only.

## Documentation

- [Getting started](./docs/getting-started.md)
- [Configuration reference](./docs/configuration.md)
- [Architecture and lifecycle](./docs/architecture.md)
- [Security model](./docs/security.md)
- [Changelog](./CHANGELOG.md)

## Development

```bash
npm run check     # type-check only
npm run build     # tsc → dist/
npm test          # node:test suites (no SAP, no network)
```

The test suite covers the routing policy, the configuration loader, the per-system queue and redaction.
Nothing in it needs a SAP system.

Optional end-to-end check against a development system (creates, activates and deletes `ZTMP_LIVECHECK`
in `$TMP`):

```bash
ADT_LIVE_SYSTEM=dev-100 node tools/live-check.mjs --config ./adt-mcp.config.jsonc
```

## License

MIT — see [LICENSE](./LICENSE). This project builds on
[`@babamba2/abap-mcp-adt-powerup`](https://github.com/babamba2/abap-mcp-adt-powerup) (MIT); see [NOTICE](./NOTICE) for attribution.