Skip to main content
Glama
lingcSun

mcp-bw-adt-api

by lingcSun
README.md
# mcp-bw-adt-api

An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that exposes the
[`bw-adt-api`](https://www.npmjs.com/package/bw-adt-api) library — SAP BW/4HANA ADT
(ABAP Developer Tools) operations — to LLM clients such as ZCode, Claude Desktop, or any
MCP-compatible client.

It exposes a **Public** subset of `BWAdtClient` domain operations (ADSO, Transformation, DTP,
DataSource, Process Chain, InfoObject, DDIC, search, dataflow, transports, …) as MCP tools —
**75 tools** after Public-surface consolidation — with a **local-file buffering layer**
that keeps large XML payloads and table data out of the LLM context window.

> **BREAKING (Public surface):** Atomic `lock` / `unlock` / bare `update` / bare `activate`,
> raw `bw_*_get` (prefer `*_details` + `*_get_xml`), `bw_quick_search`, `bw_object_create` /
> `update` / `activate`, `bw_adso_node_path`, and several duplicate DDIC/system tools were
> removed from `tools/list`. Prefer: `*_get_xml` + `outputPath` → edit file → `*_save_and_activate`
> + `xmlPath`. Mutating tools are marked on each `ToolDef` (`mutating: true`) and derived at
> startup for read-only profile guards.

---

## Why a buffering layer?

bw-adt-api payloads are large — measured on real systems:

| Payload | Typical size |
|---|---|
| Object XML (PUT request body) | 15–80 KB |
| DDIC table data, 100 rows × 20 cols | ~68 KB |
| DDIC table data, 10 000 rows | ~6.7 MB |
| Process chain logs, 200 entries | ~39 KB |

Pushing these through the LLM context window is wasteful or fatal. This server solves it two
ways:

### Mechanism A — large request bodies via local files (POST/PUT)

Every tool that takes a large string body (object XML, SQL statement, ABAP source) accepts **two
fields**:

- `<label>Content` — inline string
- `<label>Path` — path to a file under the workdir (takes precedence)

So instead of the LLM having to generate or hold a 50 KB XML, it reads the current XML to a file,
edits it (or uses an atomic-edit tool), then references the file by path.

### Mechanism B — large responses via `outputPath`

Most read tools accept an optional `outputPath`. When set, the **full** result is written to that
file and the tool returns only a small summary envelope:

```json
{ "ok": true, "outputPath": "/abs/path/big.json", "bytes": 723908, "summary": "object with key(s): tableName, rows, …" }
```

- Objects / arrays are written as JSON.
- Raw XML strings (from `bw_*_get_xml` with `outputPath`) are written as **plain text**, so the
  same path can be reused as `xmlPath` in a later save call.

Without `outputPath`, the result is returned inline (with table data paginated to a soft cap and
`truncated`/`hint` flags set when needed).

### Workdir sandbox

All file paths resolve under the **workdir** and are confined to its subtree (`..` escapes and
out-of-tree absolute paths are rejected). By default the workdir is `<cwd>/.mcp-bw-out` (a scratch
folder under the server process working directory) so buffered files do not land on project source.
Override with `BW_MCP_WORKDIR` if you need a different root (e.g. the workspace root).

---

## Quick start

```bash
# 1. Install + build
npm install
npm run build

# 2. Configure connection (copy & edit)
cp .env.example .env
#   multi-env: BW_PROFILES + BW_<NAME>_*  (or legacy BW_BASE_URL / …)

# 3. Inspect the tool catalog (no BW connection needed)
npm run list-tools
```

### Environment variables — multi-profile (recommended)

| Variable | Required | Description |
|---|---|---|
| `BW_PROFILES` | yes* | Comma-separated profile names, e.g. `test,prod` |
| `BW_DEFAULT` | no | Startup profile (default = first in `BW_PROFILES`) |
| `BW_<NAME>_BASE_URL` | yes* | BW server URL for that profile |
| `BW_<NAME>_USERNAME` | yes* | SAP logon user |
| `BW_<NAME>_PASSWORD` | yes* | Password (never exposed to the LLM) |
| `BW_<NAME>_CLIENT` | no | SAP logon client, e.g. `100` |
| `BW_<NAME>_LANGUAGE` | no | Language key, e.g. `ZH` |
| `BW_<NAME>_READONLY` | no | `true` → reject mutating tools on this profile |
| `BW_<NAME>_ALLOW_UNAUTHORIZED` | no | `true` to accept self-signed certs |
| `BW_MCP_WORKDIR` | no | Root for file buffering (default = `<cwd>/.mcp-bw-out`) |

\* Or use the **legacy** single-env vars (`BW_BASE_URL`, `BW_USERNAME`, `BW_PASSWORD`,
optional `BW_CLIENT` / `BW_LANGUAGE` / `BW_READONLY` / `BW_ALLOW_UNAUTHORIZED`) when
`BW_PROFILES` is unset — they become a profile named `default`.

Switch at runtime with `bw_env_list` / `bw_env_switch`. The active profile’s client
auto-logs-in on its first request; `bw_disconnect` drops only the current profile’s session.

**Read-only profiles:** mutating tools are omitted from `tools/list`, and the server emits
`notifications/tools/list_changed` when `readOnly` visibility changes. Host support for
mid-session refresh is incomplete (Cursor / Claude Code may keep a stale list until MCP
reconnect or a new chat) — the server still **rejects** mutating `tools/call` as a hard
guard.

---

## Registering in an MCP client

Add the server to your client's MCP config. Examples:

### ZCode (`.zcode/mcp.json` in the workspace, or user-level)

```jsonc
{
  "mcpServers": {
    "bw-adt": {
      "command": "node",
      "args": ["E:/04-code/02-personnal/bw-adt/mcp-bw-adt-api/build/index.js"],
      "env": {
        "BW_PROFILES": "test,prod",
        "BW_DEFAULT": "test",
        "BW_TEST_BASE_URL": "http://your-bw-test:8000",
        "BW_TEST_USERNAME": "developer",
        "BW_TEST_PASSWORD": "secret",
        "BW_TEST_CLIENT": "100",
        "BW_TEST_LANGUAGE": "ZH",
        "BW_TEST_READONLY": "false",
        "BW_PROD_BASE_URL": "http://your-bw-prod:8000",
        "BW_PROD_USERNAME": "developer",
        "BW_PROD_PASSWORD": "secret",
        "BW_PROD_CLIENT": "100",
        "BW_PROD_LANGUAGE": "ZH",
        "BW_PROD_READONLY": "true"
        // "BW_MCP_WORKDIR": "C:/path/to/workspace"
      }
    }
  }
}
```

> The server inherits the client process's `cwd`, so `BW_MCP_WORKDIR` usually does not need to be
> set — files are buffered into the workspace that owns the MCP server.

### Claude Desktop (`claude_desktop_config.json`)

Same shape under `"mcpServers"`.

---

## Tool catalog

Tools are named `bw_<domain>_<action>`. Run `npm run list-tools` for the full list with input
schemas. Domains:

| Domain | Prefix | Example tools |
|---|---|---|
| System / env | `bw_system_*`, `bw_env_*`, `bw_disconnect` | `bw_env_list`, `bw_env_switch`, `bw_system_status` |
| Search | `bw_search_*` | `bw_search_objects`, `bw_adso_transformations` |
| Dataflow / lineage | `bw_dataflow_*` | `bw_dataflow_get`, `bw_dataflow_lineage` |
| Generic CRUD | `bw_object_*` | `bw_object_create/update/delete/activate` |
| ADSO | `bw_adso_*` | `bw_adso_get_xml`, `bw_adso_save_and_activate`, `bw_adso_add_field`, `bw_adso_add_key`, `bw_adso_convert_type`, `bw_adso_create`(adsoType 枚举) |
| InfoArea | `bw_area_*` | `bw_area_create`, `bw_area_get_xml`, `bw_area_validate_exists` |
| Transformation | `bw_trfn_*` | `bw_trfn_create`, `bw_trfn_save_and_activate`, `bw_trfn_add_rules_and_save`, `bw_trfn_auto_map_and_save` |
| DTP | `bw_dtp_*` | `bw_dtp_create`, `bw_dtp_execute`, `bw_dtp_save_and_activate` |
| DataSource | `bw_datasource_*` | `bw_datasource_save_and_activate`, `bw_datasource_merge_proposal` |
| Replication | `bw_replication_*` | `bw_replication_replicate_full` |
| Process Chain | `bw_processchain_*` | `bw_processchain_execute`, `bw_processchain_logs` |
| InfoObject | `bw_infoobject_*` | `bw_infoobject_get` |
| DDIC tables / data | `bw_table_*` | `bw_table_get_data`, `bw_table_query_sql` |
| BICS reporting / preview | `bw_reporting_*` | `bw_reporting_preview`, `bw_reporting_initial_view` |
| Transport / CTS | `bw_transport_*` | `bw_transport_check`, `bw_transport_create` |

> Note: Transformation **creation** must go through `bw_trfn_create` (the 8TRANSIENT transient
> flow, equivalent to the Eclipse wizard). The generic `bw_object_create` tool was removed from
> the Public surface, and the generic POST flow is rejected by the SAP server for TRFN anyway.

---

## Typical workflows

### Read-modify-write an ADSO (no large XML held by the LLM)

```
1. bw_adso_get_xml   { id: "ZL_FID40", format: "summary", outputPath: "adso.xml" }
   → LLM sees a small envelope; the full XML is on disk under the workdir.

2. bw_adso_add_field { id: "ZL_FID40", name: "ZZFLAG", dataType: "CHAR", length: 1 }
   → atomic edit: reads current XML, adds the field, saves+activates.
   (Or the LLM edits adso.xml directly, then:)

3. bw_adso_save_and_activate { id: "ZL_FID40", xmlPath: "adso.xml" }
   → writes the file's XML back, one-stop lock→PUT→activate→unlock.
```

### Query a large table without flooding context

```
bw_table_get_data { table: "/BIC/AZL_FID402", maxRows: 1000, outputPath: "data.json" }
→ { ok: true, outputPath: "…/data.json", bytes: 680000, summary: "…: 1000 row(s)…" }
```

The LLM can then run a follow-up script/tool to inspect `data.json` instead of ingesting it.

### Inspect process chain logs safely

```
bw_processchain_logs { id: "ZPC_FID", limit: 50, offset: 0 }
→ inline, capped. Use outputPath for the full log set.
```

---

## Architecture

```
src/
├── index.ts        stdio entry; --list-tools catalog dump
├── server.ts       MCP Server + tools/list + tools/call (outputPath interception)
├── session.ts      singleton BWAdtClient from env (auto-login)
├── errors.ts       AdtException → MCP error result mapping
├── fileio.ts       workdir sandbox, readInput (mechanism A), writeOutput (mechanism B)
├── response.ts     pagination, projection, summaries
├── tool.ts         ToolDef + zod→JSON Schema + largeInput/outputPathField helpers
└── tools/          one file per domain, each exporting ToolDef[]
```

- **Credentials** are read once from env at startup and never surfaced to the LLM.
- **Write operations** (delete, activate, execute) are exposed directly, no extra confirmation
  layer — rely on your MCP client's tool-approval prompt.
- **Pagination**: table tools default `maxRows` to 50 and cap inline returns; logs default to 100
  with `limit`/`offset`.

## Development

```bash
npm run build      # tsc → build/
npm run watch      # tsc -w
npm start          # run the server (stdio)
npm run list-tools # dump the tool catalog as JSON
```

The server depends on the published [`bw-adt-api`](https://www.npmjs.com/package/bw-adt-api)
npm package (`"bw-adt-api": "^0.4.0"`). For local cross-repo development, point it at a
sibling checkout instead (e.g. `npm link ../bw-adt-api` or a `file:` spec) and switch back
before publishing.

## License

MIT

TDQS

B3/5.0

Scored across 71 tools

Disambiguation3/5

Most object types have clearly separated read/edit/activate/check operations, but several overlapping pairs exist: bw_system_status vs bw_env_list, bw_adso_validate_info_area vs bw_area_validate_exists, bw_replication_replicate vs bw_replication_replicate_full, and the reporting preview/update tools. Descriptions help, but an agent could still misselect.

Naming Consistency4/5

Tools follow a mostly consistent bw_<object>_<action> snake_case pattern with predictable verbs like get_xml, details, check, create, save_and_activate, and versions. Minor deviations such as bw_system_status, bw_env_list, bw_reporting_initial_view, and bw_datasource_merge_proposal keep it from being fully uniform.

Tool Count2/5

71 tools is far beyond a typical well-scoped set and includes multiple near-duplicates: validation helpers, env status/list, and replication variants. While the broad BW domain explains some volume, the redundancy makes the surface feel heavier than necessary.

Completeness4/5

The set covers full lifecycles for ADSO, transformations, DTPs, and DataSources, plus process chain execution, transport handling, table queries, and reporting. Gaps like missing InfoArea update and InfoObject create/edit are workable via generic object delete or XML routes, so coverage is strong but not perfect.

Maintenance

ActivityMaintained
ResponsivenessNo issues