Mainframe MCP Server
# Mainframe MCP Server
A **Model Context Protocol (MCP) server for IBM z/OS mainframes**. It exposes the
RSE (Remote System Explorer) REST API — read datasets and members (COBOL, JCL,
PROC, copybooks), manage datasets, and submit and monitor jobs — as MCP tools.
Because it speaks the MCP protocol, the **same server works in every
MCP-compatible client**: VS Code (GitHub Copilot), Cursor, Claude Desktop,
Gemini CLI, and more. Configure it once per client.
It ships as a **single pure-Python package** (`httpx`, no PowerShell) exposing
**31 tools**, so it runs anywhere — Linux containers, macOS, Windows — and is
publishable to PyPI and the MCP Registry.
## Install (native package)
```bash
pip install mainframe-mcp # or, from the mcp-server folder: pip install -e .
```
> If public PyPI is blocked, use a mirror, e.g.
> `pip install --index-url https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com mainframe-mcp`
This installs a `mainframe-mcp` command that speaks MCP over stdio.
## Configure (native)
| Variable | Required | Default | Purpose |
|----------|----------|---------|---------|
| `MF_HOSTNAME` | yes | — | RSE API hostname |
| `MF_PORT` | no | `443` | RSE API port |
| `MF_RSE_BASE_PATH` | no | `/rseapi/api/v1` | API base path |
| `MF_USERNAME` | yes | — | TSO user id |
| `MF_PASSWORD` | yes | — | TSO password |
| `MF_VERIFY_TLS` | no | *(off)* | `1` to verify the server certificate |
| `MF_PDS_LOCATIONS` | no | `pds_locations.txt` beside the module | Segment → dataset map for the `get_*` tools |
| `MF_SEG_*` | no | generic names | Per-site segment names (`MF_SEG_COBOL`, `MF_SEG_COPYBOOK`, `MF_SEG_CT1`, `MF_SEG_JCL`, `MF_SEG_PROC`, `MF_SEG_JOBDOC`) used in `pds_locations.txt` |
| `MF_READ_ONLY` | no | *(unset)* | `1` disables all write/destructive tools |
| `MF_ALLOWED_DSN` | no | *(unset)* | Comma-separated dataset prefixes; write/destructive tools may only target these |
| `MF_AUDIT_LOG` | no | `logs/audit.log` | JSONL audit trail (sensitive fields redacted) |
Copy `pds_locations.sample.txt` to `pds_locations.txt` and edit it for your site.
## Client configuration
The native package installs a `mainframe-mcp` command. Point your client at it
and pass the endpoint + credentials via `env`.
### VS Code (GitHub Copilot)
Add to `.vscode/mcp.json` in your workspace:
```json
{
"servers": {
"mainframe": {
"type": "stdio",
"command": "mainframe-mcp",
"env": {
"MF_HOSTNAME": "your.host.example.com",
"MF_USERNAME": "${input:mf_username}",
"MF_PASSWORD": "${input:mf_password}"
}
}
}
}
```
### Cursor
Add to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in the project):
```json
{
"mcpServers": {
"mainframe": {
"command": "mainframe-mcp",
"env": {
"MF_HOSTNAME": "your.host.example.com",
"MF_USERNAME": "you",
"MF_PASSWORD": "..."
}
}
}
}
```
### Claude Desktop
Add to `claude_desktop_config.json`
(`%APPDATA%\Claude\claude_desktop_config.json`):
```json
{
"mcpServers": {
"mainframe": {
"command": "mainframe-mcp",
"env": {
"MF_HOSTNAME": "your.host.example.com",
"MF_USERNAME": "you",
"MF_PASSWORD": "..."
}
}
}
}
```
## Available tools (31)
**Retrieval:** `get_cobol`, `get_copybook`, `get_ct1`, `get_jcl`, `get_proc`,
`get_job_docs`, `get_content`, `scan_source`
**Datasets:** `get_dataset_list`, `get_dataset_members`, `get_gdg_versions`,
`search_seq_datasets`, `create_dataset`, `create_dataset_like`, `delete_dataset`,
`rename_dataset`, `recall_dataset`, `copy_member`, `modify_module`, `upload_member`
**Jobs:** `submit_job`, `submit_jcl_string`, `get_job_status`,
`get_job_steps`, `get_job_files`, `get_job_file_content`, `get_job_jcl`,
`get_job_notification`, `get_spool_details`, `export_spool_to_dataset`
**TSO:** `run_tso_command`
## Architecture
Components:
| Layer | File | Role |
|-------|------|------|
| Transport / entry | `server_native.py` (`main()` → `mcp.run()`) | Registers tools, speaks MCP over stdio |
| Safety layer | `mcp_common.py` | Intent annotations, read-only mode, confirm guards, scope allowlist, audit |
| RSE client | `rse_client.py` | Pure-Python `httpx` calls to the z/OS RSE REST API |
| Config | env + `pds_locations.txt` | Endpoint, credentials, segment → dataset map |
What happens on a tool call:
```mermaid
sequenceDiagram
participant C as MCP client
participant S as server_native (FastMCP)
participant W as mf_tool + _run
participant SF as Safety (mcp_common)
participant R as RSEClient (httpx)
participant MF as RSE API
C->>S: tools/call get_cobol(["PROG1"])
S->>W: dispatch tool
W->>SF: _dsn_allowed? / _needs_confirmation?
alt blocked
SF-->>C: {status:"error"|"needs_confirmation"}
else allowed
W->>W: _client() builds creds (MF_* env)
W->>R: client.read_member(dsn)
R->>MF: GET /datasets/{dsn}/content (Basic auth, TLS)
MF-->>R: 200 + records
R-->>W: unwrap_records()
W->>SF: _audit(tool, args, "success", ms)
W-->>C: {status:"success", data:{...}}
end
```
Safety gates, in order:
1. **Registration filter** — `MF_READ_ONLY=1` means the 10 write + 4 destructive tools are never registered (clients see only the 20 read tools).
2. **Scope allowlist** — `MF_ALLOWED_DSN` rejects out-of-scope datasets before any network call.
3. **Confirm guard** — destructive tools return `needs_confirmation` unless called with `confirm=true`.
4. **Per-session credentials** — creds come from `MF_*` env (or a per-session override); missing creds return a clean error.
5. **Audit** — every call appends a redacted JSONL line (tool, args, status, duration).
The safety layer (`mcp_common.py`) and RSE client (`rse_client.py`) are
transport-agnostic, so the same tool bodies can be reused behind another
transport (e.g. an HTTP/OAuth front end) without changing the mainframe logic.
## Deploy
```bash
python -m build # -> dist/mainframe_mcp-<ver>-py3-none-any.whl + .tar.gz
twine upload dist/* # publish to PyPI
```
Then submit `server.json` to the MCP Registry. Users install with
`pip install mainframe-mcp` and point their MCP client at the `mainframe-mcp`
command with `MF_HOSTNAME` / `MF_USERNAME` / `MF_PASSWORD` set (see above).
## Hosting over HTTP (Render, Cloud Run, ...)
To run as a hosted service instead of stdio, `server_http.py` serves the same
tools over MCP **streamable-HTTP** at `/mcp`, guarded by a bearer token.
Local:
```bash
pip install -e ".[http]"
$env:MF_MCP_TOKEN="secret"; $env:MF_HOSTNAME="..."; $env:MF_USERNAME="..."; $env:MF_PASSWORD="..."
python server_http.py # clients connect to http://localhost:8000/mcp
# with header: Authorization: Bearer secret
```
Render: a `Dockerfile` and `render.yaml` are included. In Render →
**New → Blueprint / Web Service** from the (private) repo — it reads
`render.yaml`, generates `MF_MCP_TOKEN`, and you set `MF_HOSTNAME` /
`MF_USERNAME` / `MF_PASSWORD` in the dashboard. Endpoint:
`https://<your-app>.onrender.com/mcp`.
**Security:** these tools can modify datasets and submit jobs. Keep
`MF_MCP_TOKEN` set, serve only over HTTPS (Render terminates TLS), and prefer
`MF_READ_ONLY=1` (the `render.yaml` default). The host must also be able to
**reach your mainframe's RSE API** — a public host cannot reach an internal
z/OS system without a tunnel or VPN.
TDQS
Scored across 30 tools
Many tools follow a clear get_<artifact> pattern (COBOL, copybook, CT1, JCL, PROC), and the job-status/spool tools are distinct. However, generic get_content overlaps heavily with the specialized getters, and get_jcl vs get_job_jcl plus upload_member vs modify_module create potential confusion.
Tool names overwhelmingly use snake_case verb_noun, with clear verbs like get, submit, create, delete. Minor inconsistencies exist (get_cobol vs get_content, search_seq_datasets uses an abbreviation, create_dataset_like) but the pattern is predictable.
At 30 tools, the server is over-scoped for a coherent set. Many specialized getters could be consolidated into get_content, and the job-spool area has four overlapping tools. A more focused set of 15-20 would be stronger.
The set covers dataset lifecycle (create, delete, rename, recall, copy, modify), source retrieval, and full job submission/status/spool flow. Missing member-level deletion and a direct list-members operation are minor gaps.