Drive the real mainframe engine from any MCP client — Claude Desktop, Claude Code, Cursor, a CI harness, or any remote-native connector. Two things you can do with it: write and run COBOL/JCL on the live engine (the developer loop), and use SteelFrame as a reference oracle for a modernization comparison — it returns files, joblogs, screens one keystroke at a time and scheduler cycles, and your harness does the comparing. Two servers, one engine: a persistent lane whose work lands in the shared store, and a private / ephemeral lane with the same tool surface that runs RAM-only with zero retention — one-shot sandboxes for a single answer, RAM-only workspaces for multi-step work including the whole oracle chain.
what it is which lane? setup authentication developer loop reference oracle worked oracle example modernization playbook kit VS Code MCP (your own VS Code) Git MCP (your own GitHub / GitLab) z/OS MCP (your own IBM mainframe) screen MCP (your own open page) pricing live tool reference REST API (same routes) language catalog back to the terminal
test/tw_mcp_oracle_battle.mjs) drives every oracle tool through
both doors against a scratch engine with the CardDemo estate — workspaces,
seeding, run_batch joblogs, screen-by-screen CICS with a pinned
clock, CA-7 DEMAND cycles — and the three MCP battles
(tw_mcp_stdio_battle, tw_mcp_http_battle,
tw_mcp_private_battle) cover both auth modes (incl. auto-re-logon
across a real engine restart), scope gating, JSON-RPC conformance, ephemeral
isolation/cleanup on the abend path and the injection/traversal sanitizers.
An adversarial QA pass on the MCP doors (solo Opus, 2026-09-01) returned
PUSH-READY with zero confirmed correctness or security bugs. Real counts and
dates are in notes/MCP-ORACLE-BUILD-LEDGER.md and
notes/MCP-TEST-FINDINGS.md. Compile diagnostics, abends, screens
and joblogs are the engine's genuine output, never fabricated.
One engine, one route registry, two projections: every REST route that opts in becomes exactly one MCP tool (name, description and input schema are generated from the route — nothing is hand-written per tool, so the catalog cannot drift from the dispatcher). What you do with the tools falls into two uses:
You write COBOL/JCL in your MCP client and compile, run and
inspect it on the REAL engine: check_source →
compile_program → run_program, the raw JES2 tools,
WTOR replies, datasets, SPUFI, the analysis suite. On the persistent lane
everything lands in the shared store — log into the SteelFrame web as the
same userid and your jobs are in SDSF, your members in
DSLIST, your load modules in <USERID>.LOADLIB.
You are modernizing an estate and need the mainframe's actual behaviour to compare against. SteelFrame returns what the mainframe holds and does: source + copybooks + JCL + maps + CSD slice, records decoded through copybooks beside their bytes, a batch run's whole joblog and output datasets, CICS screens one keystroke at a time, CA-7 / Control-M cycles — all on an isolated copy of the estate (a disk workspace on the persistent lane, a RAM-only workspace on the private lane). It never judges equivalence and never converts code; your harness replays the same inputs against the modernized system and compares on its side. Details below.
Both lanes expose the same engine and the same tool surface. The difference is one thing only: where the bytes live. Persistent work lands in the shared store and is web-visible; private work is RAM-only and zero-retention — a single-use sandbox per one-shot call, or a leased RAM-only workspace that survives across calls so a whole equivalence scenario runs inside it and then vanishes.
| Persistent default | Private / ephemeral lane=ephemeral | |
|---|---|---|
| What it is for | The developer loop on the real engine, and the whole reference-oracle workflow (workspaces, seeding, batch runs, CICS sessions, scheduler cycles, state dumps) — when the result should exist afterwards. | The same two uses when nothing may be retained: a one-shot
answer ("does this compile?", "what does this deck do?"), or the whole
oracle chain — find/export → seed → run_batch →
cics_send screen by screen → sched_wait_cycle →
export_state — inside a RAM-only workspace. |
| Where it runs | The shared engine, as your userid; or a disk workspace (a child engine on a copy of the estate under the data root, visible in the region grid). | A single-use sandbox per one-shot call (destroyed before the
response returns), or a RAM-only workspace: a leased sandbox-class
engine whose data root and scratch live on the RAM-backed base, never
under the shared data root, never in /api/instances;
destroyed by destroy_workspace, its TTL, or the parent's
exit. |
| Retention | Persistent and web-visible: SDSF, DSLIST, LOADLIB, run records, bundles, state dumps (with TTL/quota). Full audit as you. | Nothing. Nothing written to the shared engine, nothing visible in
the web, nothing after the session. Parent-side audit keeps sizes, return
codes and workspace ids only, never content. RAM-backed
(/dev/shm) on hosted Linux; results and workspace views carry
an honest ramBacked flag. |
| How results come back | Inline, and as persisted artifacts you fetch later
(get_run, get_bundle, get_state,
read_spool). |
One-shots: inline only. RAM workspace: inline plus the same artifacts —
but they live inside the workspace: fetch them before
destroy_workspace; the bundle tar.gz download (a REST
stream) is not reachable — page with get_bundle_part. |
| Tool count | The full catalog (the live count is on the button in the tool reference). | The full catalog plus five: the one-shots
(check_source, compile_inline,
run_inline, compile_and_run,
jcl_inline) first, then the RAM-workspace lifecycle, then
every persistent tool with workspace required. |
| The one rule | workspace:<id> is optional — without it a tool runs on
the live estate (except sched_reset, workspace-only; the
CICS definition tools cics_ceda / cics_cemt /
cics_define run live, decided by RACF for your userid). |
create_workspace first, then
workspace:<id> on every stateful tool; without it the
call is refused in-band (403) — nothing ever runs on the live estate
from this lane. A disk workspace id is invisible here (404). |
| Known limit | wait_job caps at 300 s (call it again on
timedOut:true); run_batch waits up to 3600 s. |
A WTOR-ing one-shot times out (no operator to reply); the one-shot
caps are engine env (ES_EPH_TIMEOUT_S,
ES_EPH_MAX_KB, ES_EPH_MAX). A
seed:"clone" RAM workspace copies the whole estate into RAM
(budget for it; seed:"fresh" + load_dataset is
the light path); disk + RAM workspaces share the per-user / plane caps
and the TTL (≤ 86400 s). |
| Scope | The developer scope set (and the harness additions for the oracle — see authentication). | ephemeral.exec to create the sandbox/workspace; inside a
workspace each routed tool is checked against your own scopes, so
an oracle harness token carries the same scopes as on the persistent
lane plus ephemeral.exec. |
| Pick it when | You want the result to exist afterwards, or you want it in the web. | You need a zero-retention guarantee — for a throwaway answer or for a whole equivalence run. |
Live on steelframe.cobolstack.com: /mcp (server steelframe,
both lanes), /mcp/vscode (steelframe-vscode, scope vscode.drive),
/mcp/screen (steelframe-screen, scope screen.drive), /mcp/git
(steelframe-git, scope connectors.use) and /mcp/zos
(steelframe-zos, scope zos.remote). One API key can carry all five scopes (a Custom
key); each door is a separate MCP server in your client. The sections below cover each one.
| Door | What |
|---|---|
| Door A — stdio bridge | bin/steelframe-mcp.mjs (zero-dep, Node 18+): the standard
local MCP client configuration. It fetches its tool catalog from the engine
(GET /api/mcp/tools) so it never hardcodes a tool; a
workspace:<id> argument is routed into that workspace's
child engine for you. |
| Door B — remote | POST /mcp on the engine (JSON-RPC over POST, bearer auth):
for remote-native MCP clients and CI harnesses. Append
?lane=ephemeral for the private lane. Same scopes/audit as the
REST API by construction. |
Claude Desktop / Claude Code (mcpServers), Cursor
(mcp.json). Point args at your checkout's bridge:
{
"mcpServers": {
"steelframe": {
"command": "node",
"args": ["/path/to/mainframe/bin/steelframe-mcp.mjs"],
"env": {
"SF_URL": "https://steelframe.cobolstack.com",
"SF_USER": "YOURUSERID",
"SF_PASSWORD": "YOURPASSWORD"
}
},
"steelframe-private": {
"command": "node",
"args": ["/path/to/mainframe/bin/steelframe-mcp.mjs"],
"env": {
"SF_URL": "https://steelframe.cobolstack.com",
"SF_TOKEN": "sf_...",
"SF_LANE": "ephemeral"
}
}
}
}
SF_USER / SF_PASSWORD are YOUR OWN SteelFrame
credentials — no shared account is published. Get a user ID with Get User ID on the
logon screen (you choose your own password at first sign-on), then put that
userid and password here.
SF_URL is your SteelFrame URL:
https://steelframe.cobolstack.com for the hosted estate (the
default above). Self-host only: use http://localhost:3270
if you run the engine from your own checkout — there is nothing listening on
localhost:3270 otherwise, and the bridge will report
connection refused.
Remote client (connector-style): for the hosted estate the door is
https://steelframe.cobolstack.com/mcp, header
Authorization: Bearer sf_... (or a session token). For a
self-hosted plane use https://<plane>/mcp.
No MCP client at all? Every tool is an ordinary REST route — the
binding (method + path) is shown on each tool in the
reference and the same routes are documented on
/api.html; e.g. POST /api/mcp/compile
with { "program": "HELLO", "lines": [...] } or
POST /api/run/batch with { "member": "CARDDEMO.JCL(POSTTRAN)" }.
Zowe, or your own app reading JES jobs and spool? SteelFrame also answers the
z/OSMF jobs and data set REST interface at https://steelframe.cobolstack.com/zosmf/
(HTTP Basic with your userid and password, or a Bearer API key) — see
z/OSMF-compatible REST on /api.html.
On a different machine — no repo, no node, no bridge process — register the hosted door as an HTTP transport straight from Claude Code. This needs only Claude Code + internet:
claude mcp add -s user --transport http steelframe \
https://steelframe.cobolstack.com/mcp \
--header "Authorization: Bearer sf_..."
-s user makes the server available in every folder (drop it for
project scope). --transport http selects the remote door;
--header carries your bearer token. The sf_... value is a
persistent scoped API key — mint one under
Authentication below. (Flag names verified against Claude Code's
claude mcp add --help; if yours differs, check that help for your CLI version.)
Contrast with Door A — stdio bridge above, which runs
bin/steelframe-mcp.mjs locally and therefore needs the repo checkout + node.
Get a key yourself: log on to the web terminal, open the
assistant panel → Credits → API keys, or open your
account page →
API keys. The key is shown once with ready-made setup snippets (the
claude mcp add line above, MCP JSON, mcp-remote, this bridge,
curl); pick a preset — Read-only (free tools only), Developer
(the write → compile → run loop), Full (everything your user id
can do in the terminal) — and set an expiry, a per-key daily spend cap
and an IP allow-list. Keys are never wider than your own rights; administrator
scopes (sched.admin, workspace.write, act-as) still
come from POST /api/tokens (admin). Set SF_TOKEN to
the key. The developer scope set:
datasets.write, jobs.write, db2.exec,
system.read, cics.read
(+ ephemeral.exec for the private lane)
A modernization harness using the oracle adds:
cics.exec # headless CICS sessions, scripts, LINK
sched.admin # DEMAND / ORDER / define / import
workspace.write # create / reset / destroy workspaces
(db2.exec already covers DB2 table dumps)
Set SF_USER + SF_PASSWORD. The bridge
performs POST /api/logon at startup and automatically re-logons
on any 401 — session tokens live in engine memory and die on a restart, and
the bridge rides through transparently.
The password stays in your client config/env; the engine's
logon audit records the userid only. A session token carries the ordinary
session scope set (which includes cics.exec); a RACF SPECIAL
user gets *.
http:// to
non-localhost engines unless SF_ALLOW_HTTP=1 (hosted planes
terminate TLS at the gateway). Administrator-scoped tools are reachable only
with a token an administrator minted explicitly with that scope
(POST /api/tokens; never in an API-key preset or a self-service
key) or a RACF SPECIAL user's own logon (*), and only on
/mcp: sched.admin — sched_import,
sched_define_job, sched_ca7_command,
sched_order, sched_demand, sched_symbol,
sched_options, sched_condition,
sched_job_action, sched_cancel,
sched_newday, sched_calendar,
sched_reset; cics.admin — no tool of its own: it
makes cics_ceda / cics_cemt / cics_define
act as a CICS administrator (any resource, no group); workspace.write — create_workspace,
reset_workspace, destroy_workspace. Never reachable
from any MCP door, whatever the token: security / RACF administration and
browsing, API-token management, listener, instance, snapshot, application
deploy, IMS and Endevor administration, TSO commands, account administration,
and connecting a Git account or a mainframe. Seeding tools (load_dataset, stage_gdg,
sched_import, sched_define_job) carry the MCP
destructive hint and refuse system HLQs; sched_reset is
workspace-only. The CICS definition tools (cics_ceda,
cics_cemt, cics_define) run on the live estate for
any key holding cics.exec, because the engine hands the key's
user to the one RACF authority (class SFCSDGRP) that also
decides for that user at a 3270 CEDA / CEMT screen: an ordinary user acts only
in its own CSD groups (its personal group, named after its userid, plus the
groups the security administrator granted), GROUP(g) is required
on DEFINE, system-owned entries and other users' groups are refused
NOT AUTHORIZED - reason and audited (cics.ceda, door
mcp) - so a key never does more than its user can on the 3270, and
no scope widens (cics.admin is never on a self-service key). On a plane shared by mutually-untrusting developers, enable
PROTECTALL + security.enforce (documented opt-in) so
the shared store isn't cross-user readable/destructible.
The persistent lane exposes the whole spine — compile-check, upload, compile+link, run, the raw JES2 job tools, WTOR, and the analysis suite. The private lane collapses this into five one-shot composites. Highlights:
| Tool | What it does |
|---|---|
| check_source | inline compile-check (real cobc + DB2 precompiler / CICS translator auto-detect), diagnostics with line numbers, no artifacts |
| upload_source | batch-write members: kind
cbl→<HLQ>.SOURCE,
cpy→<HLQ>.COPYLIB,
jcl→<HLQ>.JCL (allocate-on-first-use) |
| compile_program | ONE call: source → real
EXEC IGYWCL deck → JES2 →
{jobid, maxcc, per-step RCs, diagnostics} + listing excerpt on
failure + the generated JCL; load module → <HLQ>.LOADLIB.
parm:"SQL"/"CICS" for the precompiler/translator;
copybooks ride via SYSLIB. loadlib + syslib
install the module into an estate's library instead |
| run_program | GO deck with STEPLIB
<HLQ>.LOADLIB, DDs from a JSON spec
({lines} instream / {dsn,disp} /
{sysout} / {dummy}), returns RC + SYSOUT |
| JES2 spine | submit_job /
wait_job / get_job / list_jobs /
read_spool (tail-guards to the last 500 lines,
full:true overrides; wait_job caps at 300s — on
timedOut:true just call it again) |
| list_replies / reply_wtor | see and answer a blocked batch step's WTOR |
| cancel/purge/hold/release_job | queue actions (destructive ones carry MCP destructive hints) |
| datasets · analysis · query | list/search/allocate,
members and raw records (hex, binary-safe), vsam_info;
inventory_scan, analysis_model/graph/impact/metrics,
explain_program, bms_preview; sql
(SPUFI), whoami, system_info |
Against Door B (POST /mcp, JSON-RPC 2.0). Step 1
compiles; the response carries maxcc, per-step RCs, parsed
diagnostics and the load-module dsname:
curl -s https://<plane>/mcp \
-H "Authorization: Bearer sf_..." \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{
"name":"compile_program",
"arguments":{
"program":"HELLO",
"lines":[
" IDENTIFICATION DIVISION.",
" PROGRAM-ID. HELLO.",
" PROCEDURE DIVISION.",
" DISPLAY \"HELLO FROM MCP\".",
" STOP RUN."
]
}
}
}'
# → { "jobid":"JOB00021", "ok":true, "maxcc":0,
# "steps":[{"name":"COB","pgm":"IGYCRCTL","rc":0}, ...],
# "diagnostics":[], "loadModule":"IBMUSER.LOADLIB(HELLO)",
# "jcl":"//IBMUSERC JOB ..." }<HLQ>.LOADLIB:
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"run_program",
"arguments":{"program":"HELLO",
"dds":{"SYSOUT":{"sysout":"*"}}}}}'
# → { "ok":true, "maxcc":0, "sysout":"HELLO FROM MCP" }-d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"read_spool",
"arguments":{"jobid":"JOB00021"}}}'
# → { "content":"...JES2 job log...", "truncated":false }
# (pass "full":true or "tail":<n> for the whole spool)A compile error comes back honestly — e.g.
{ "ok":false, "maxcc":12, "diagnostics":[{"line":7,
"code":"IGYPS2121-S","message":"'WS-BOGUS' IS NOT DEFINED"}] } — the
line number and symbol are parsed from the genuine cobc listing. A program that
abends surfaces { "ok":false, "abend":"S0C7", "maxcc":"S0C7" }.
Point the client at SF_LANE=ephemeral (or
POST /mcp?lane=ephemeral). One shot: call
compile_and_run with the source, copybooks and data DDs inline —
the verdict, diagnostics, RC, SYSOUT and requested output datasets come back
in the one response and the sandbox is gone (jcl_inline is the
generalization lever for any one-shot batch deck). Multi-step:
create_workspace (a RAM-only workspace, seed fresh;
asynchronous — on status:"CREATING" poll get_workspace
until RUNNING), then the very same upload_source →
compile_program →
run_program → read_spool with
workspace:<id>, then destroy_workspace —
identical results, nothing retained.
GET /api/mcp/tools), audited as your userid.
search_source (content / regex /
kind-classified members) and find_program (source, transitive
copybooks, load modules, the JCL steps that run it, the CSD slice, mapsets, the
scheduler jobs that run those decks) tell you what the estate knows.
export_bundle hands it over in one call — programs + copybooks +
JCL + procs + BMS (source, parsed model, physical map) + CSD slice + schedule +
datasets as hex and copybook-decoded rows; text parts inline, dataset parts
paged by get_bundle_part, or the whole thing as
GET /api/export/bundles/{id}/download (tar.gz). Nothing is
converted.create_workspace {seed:'clone', name:'…'} boots a
child engine on a copy of the live estate (datasets, CSD, scheduler store;
the parent's spool is left behind unless includeSpool:true) — or
snapshot:<id> for a reproducible CI baseline, or
fresh. Creation is asynchronous: the workspace is
registered at once with status:"CREATING" (phase, progress) while
the copy and the child boot run in the background, and the call answers as
soon as it is ready or after waitSeconds (default 20, max 45 —
under a 60 s MCP client timeout). On CREATING, poll
get_workspace every 10–15 s until status:"RUNNING";
tool calls with workspace:<id> are refused (409 NOT READY)
until then. Never call create_workspace again meanwhile: the
same name returns the in-flight workspace (reused:true),
a different name counts against your cap (429 naming the one still
creating). A failed create shows status:"FAILED" with the error
(destroy and recreate). Then pass workspace:<id> on any
tool call to run it there as yourself; the live estate never changes.
reset_workspace re-baselines it the same asynchronous way
(optional jobNumber so JOBIDs line up across re-runs);
destroy_workspace when done. On a CREATING one it
cancels: the estate copy is stopped at once (a queued copy leaves the
queue, a running copy process is killed), no child engine boots and the
partial directory is removed; the row shows CANCELLING (or
DESTROYING for a running one) and counts toward your cap only
until that is done — the call waits for it (up to waitSeconds,
default 15) and answers settled:true when the row is gone, else
poll get_workspace until 404. A create/destroy loop therefore
never queues copies ahead of other users. Inside a workspace every tool is
checked against the scopes of the token making that call (not the one
that created the workspace): no datasets.write → no
write_member inside either; the one extra granted inside is
cics.admin to a cics.exec holder, so
cics_ceda / cics_cemt / cics_define act as
a CICS administrator in your own throwaway copy.load_dataset puts records in (rows + copybook, or
hex with the charset stated): PS replace/append, KSDS replace/append/upsert,
ESDS; allocate-on-demand. stage_gdg rolls a new generation through
a real IEBGENER DISP=(NEW,CATLG) step. define_cluster
for a new VSAM cluster; compile_program {loadlib, syslib} installs
a program into the estate's LOADLIB. A mapset is installed by running an inline
//S1 EXEC PGM=DFHBMSCP step through run_batch
(//SYSIN DD = the BMS source, //SYSPUNCH DD = the
symbolic copybook target). Load the same rows into your candidate system.run_batch {member:'CARDDEMO.JCL(POSTTRAN)'} (or
inline lines) submits, waits, and returns the run record: step
RCs / abend, the whole joblog keyed STEP.DD, the output
datasets (auto-derived from the deck's DDs) as hex + decoded rows, GDG (+1)
resolved after the run, optional DB2 tables, an optional identical dump
taken before the run (verbatim — no diff), WTORs auto-answered.
clock:{fixed:'2022-07-18 10:20:30'} pins the COBOL runtime clock
for that job. Persisted as a steelframe-run/1 record
(get_run).cics_open_session gives you a
headless terminal (the same engine terminal the web and TN3270E use) and its
initial screen. cics_send sends one AID per call with fields
addressed by BMS field name (or row/col) and returns every screen the
engine painted during the step plus the final screen as
steelframe-screen/1: named fields + attributes, cursor, keyboard
state, 24×80 text (raw:true adds the TN3270E data stream). It
settles on the engine's keyboard lock → unlock; task.alive:true
means a CONVERSE/RECEIVE is waiting for your next send; 409 while the keyboard
is still locked (re-read with cics_screen or type ahead with
queue:true). cics_run_script runs a whole step list in
one call and returns the transcript + a state dump. Both take
clock:{fixed:…} to pin EIBDATE/EIBTIME, ASKTIME/FORMATTIME and
FUNCTION CURRENT-DATE for that terminal's tasks only, so a re-run paints
the same screens. cics_link LINKs a program headlessly with a
COMMAREA (hex, or rows + copybook) and returns the COMMAREA back.sched_import takes a real
LJOB report or DEFTABLE XML; sched_define_job adds one job with
triggers/conditions from JSON; sched_jobs returns the dependency
graph as data. sched_demand {job} (CA-7) or
sched_order {folder} (Control-M) starts a cycle and returns its
id; sched_wait_cycle {id} waits for it to end and returns the
cycle log (the order the jobs ran, the triggers that fired), every job's
state / jobid / maxcc / steps and joblog, and optional output datasets.
sched_ca7_command speaks CA-7's own syntax (RUN,JOB=,
LJOB, LQ, LPRRN, POST, …).export_state {datasets, db2, spool} is a pure dump of what the
mainframe holds right now (hex + rows, charset stated, sha256 per artifact),
persisted for get_state. Take one before and one after, or one
from the oracle and one from your candidate, and diff them in your
harness. decode_records / encode_records convert
between record images and JSON rows through a copybook whenever you need to
cross the representation boundary yourself.| Group | Tools | What you get back |
|---|---|---|
| find + export | search_source · find_program · export_bundle · get_bundle · get_bundle_part · list_bundles · delete_bundle | source text inline; datasets as parts paged (hex + rows); the manifest carries a sha256 per part |
| data both ways | decode_records · encode_records · export_state · get_state · list_states · delete_state | {charset, hex[], rows[], layout, sha256}; charset is
stated every time — PS/PDS = EBCDIC at rest, KSDS/ESDS = the program's
record image; numerics as exact scaled-decimal strings
("-123.45"), OCCURS expanded, FILLER omitted |
| seed | load_dataset · stage_gdg · define_cluster · compile_program{loadlib,syslib} | counts written / deleted / updated, duplicates reported, sha256 after the load, the absolute GnnnnV00 name + joblog for a GDG roll |
| batch | run_batch · run_capture · list_runs · get_run · delete_run | steelframe-run/1: steps[],
joblog{STEP.DD}, outputs{dsn:{charset,total,sha256,hex,rows,layout}},
db2, before, wtorsAnswered |
| online, screen by screen | cics_open_session · cics_send · cics_screen · cics_close_session · cics_list_sessions · cics_run_script · cics_link · cics_csd | steelframe-screen/1: named fields + attributes, cursor,
keyboard, 24×80 text, raw:true = the TN3270E data stream;
task{transid,num,alive}; LINK returns the COMMAREA hex
(+ rows) and containers |
| CICS definitions (CEDA / CEMT) | cics_ceda · cics_cemt · cics_define · cics_csd | cics_ceda {"command":"DEFINE LIBRARY(RABILIB) GROUP(RABIAPP) DSNAME01(C826RHA.LOADLIB)"}
— ONE CEDA command line (DEFINE / ALTER / DELETE / INSTALL / COPY /
DISPLAY / VIEW / CHECK / EXPAND / ADD / REMOVE / LIST; every kind
including LIBRARY with DSNAME01..16,
RANKING, CRITICAL) through the CEDA transaction's
own parser, executor and RACF authority; { ok, lines }.
cics_cemt {"command":"SET LIBRARY(RABILIB) DISABLED"} — ONE
CEMT line (INQUIRE open; SET / PERFORM / DISCARD authorized; SET FILE
also passes CASFILE). cics_define = the JSON form of DEFINE
(+install) of one resource, group required for an ordinary
user, answering only the entry it defined. A refused or failed CEDA /
CEMT command is an MCP error result whose content is still
{ ok:false, lines } — the E NOT AUTHORIZED - …
line plus YOUR CSD GROUPS: …; over REST the same routes
answer 200 with ok:false (the 3270 result-list contract the
web panel and SDSF rely on). Read-only CEDA verbs need cics.read, everything else
cics.exec (which implies cics.read: whoever may
run CICS work may look at it); an administrator needs cics.admin
only to touch system-owned entries. The AI assistant's direct mode
("via MCP") has the same four tools, acting as the signed-in user. The student flow: DEFINE LIBRARY →
PROGRAM → MAPSET → FILE → TRANSACTION (all GROUP(me)) →
CHECK GROUP(me) → INSTALL GROUP(me) → run it with
cics_open_session / cics_send. |
| scheduler | sched_import · sched_define_job · sched_jobs · sched_demand · sched_order · sched_ca7_command · sched_wait_cycle · sched_status · sched_symbol / options / condition / calendar / job_action / cancel / newday / alerts | the order the oracle ran the jobs, verbatim, with every job's RC and joblog |
| isolation | create_workspace · reset_workspace · destroy_workspace · list_workspaces · get_workspace (+ workspace-only: sched_reset; inside a workspace cics_ceda / cics_cemt / cics_define act as an administrator) | a child engine on a copy of the estate; the live estate never
changes. Creation is asynchronous: CREATING →
RUNNING | FAILED, polled with
get_workspace; name is an idempotency key |
CURDATE/CURTIME on a
screen, EIBTASKN, JOBIDs and the timestamps in JESMSGLG all come
back exactly as composed — decide on your side what to ignore, or pin what can
be pinned (clock:{fixed} on run_batch,
cics_open_session, cics_run_script;
jobNumber on a workspace so JOBIDs line up). Two CardDemo facts
the gate pins, as examples of the kind of thing the oracle will faithfully
report rather than smooth over: POSTTRAN needs the
AWS.M2.CARDDEMO.DALYREJS GDG base to exist, and a DEMAND cycle
that runs CLOSEFIL leaves the CICS files closed until
OPENFIL runs (signon then answers "Unable to verify the User").
One harness run, JSON-RPC over POST /mcp (every call
below is the params of a tools/call). Result shapes are
the routes' documented returns, abbreviated. Which door: with a
self-service key (Developer preset) run this chain on
POST /mcp?lane=ephemeral — a RAM-only workspace needs only
ephemeral.exec, which the preset carries; create_workspace
on the persistent /mcp needs the administrator scope
workspace.write and answers 403 KEY_SCOPE to a
Developer key. The same calls, the same results; nothing survives the workspace.
A ready-made project that does exactly this, with Claude Code skills and the
CardDemo POSTTRAN job ported and tested end to end, is the
modernization playbook kit.
{"name":"create_workspace",
"arguments":{"seed":"clone","ttlSeconds":3600,"jobNumber":1000,"name":"posting-v1"}}
# → { "workspace":"W1", "seed":"clone", "status":"READY", "jobNumber":1000,
# "expires":"..." } # from here on: "workspace":"W1" on every call{"name":"load_dataset",
"arguments":{"workspace":"W1",
"dsn":"AWS.M2.CARDDEMO.DALYTRAN.PS",
"copybook":"CARDDEMO.COPYLIB(CVTRA06Y)",
"mode":"replace",
"rows":[{"DALYTRAN-ID":"...","DALYTRAN-AMT":"125.00", "...":"..."}]}}
# → { "ok":true, "dsorg":"PS", "charset":"ebcdic", "given":N, "written":N,
# "total":N, "sha256":"..." } # load the same rows into your candidate{"name":"run_batch",
"arguments":{"workspace":"W1",
"member":"CARDDEMO.JCL(POSTTRAN)",
"copybooks":{"AWS.M2.CARDDEMO.TRANSACT.VSAM.KSDS":"CARDDEMO.COPYLIB(CVTRA05Y)",
"AWS.M2.CARDDEMO.ACCTDATA.VSAM.KSDS":"CARDDEMO.COPYLIB(CVACT01Y)"},
"records":"rows",
"clock":{"fixed":"2022-07-18 10:20:30"}}}
# → { "runId":"...", "jobid":"JOB01000", "maxcc":0, "abend":null,
# "steps":[{"name":"STEP15","pgm":"CBTRN02C","rc":0}],
# "joblog":{"JESMSGLG":{...},"JESYSMSG":{...},"STEP15.SYSPRINT":{"text":"..."}},
# "outputs":{"AWS.M2.CARDDEMO.TRANSACT.VSAM.KSDS":{"charset":"program",
# "total":N,"sha256":"...","rows":[...]}, ...},
# "sha256":"..." } # run YOUR posting job on the same seed; compare rows + RCs{"name":"cics_open_session",
"arguments":{"workspace":"W1","clock":{"fixed":"2022-07-18 10:20:30"}}}
# → { "session":"S1", "termid":"...", "keyboard":"unlocked",
# "screen":{ "format":"steelframe-screen/1", "fields":[...], "text":[24 lines] } }
{"name":"cics_send",
"arguments":{"workspace":"W1","id":"S1","aid":"ENTER","text":"CC00"}}
# → { "step":1, "screens":[...every screen painted...], "screen":{...COSGN00...},
# "task":{"transid":"CC00","alive":false}, "settled":true }
{"name":"cics_send",
"arguments":{"workspace":"W1","id":"S1","aid":"ENTER",
"fields":[{"name":"USERID","text":"USER0001"},{"name":"PASSWD","text":"PASSWORD"}],
"expect":{"text":"MAIN MENU"}}}
# → { "screen":{ "fields":[{"name":"CURDATE","text":"07/18/22"}, ...] },
# "expectMet":true, ... } # drive YOUR UI with the same inputs; compare fields
…then cics_close_session {"id":"S1"}. Or the same steps as one
cics_run_script {"steps":[...], "state":"auto"} → the transcript
plus a dump of every CSD FILE dataset.{"name":"sched_demand","arguments":{"workspace":"W1","job":"POSTTRAN"}}
# → { "id":"CYC00004", "jobs":[...] }
{"name":"sched_wait_cycle",
"arguments":{"workspace":"W1","id":"CYC00004","joblogs":"all"}}
# → { "cycle":"CYC00004", "ended":true, "result":"OK",
# "jobs":[{"name":"POSTTRAN","state":"ENDED-OK","jobid":"JOB01001","maxcc":0,
# "steps":[...],"joblog":{...}},
# {"name":"WAITSTEP","state":"ENDED-OK", ...}],
# "runId":"..." } # compare job order, RCs and outputs with your orchestration{"name":"export_state",
"arguments":{"workspace":"W1","name":"after-posting","records":"rows",
"datasets":[{"dsn":"AWS.M2.CARDDEMO.ACCTDATA.VSAM.KSDS","copybook":"CARDDEMO.COPYLIB(CVACT01Y)"},
{"dsn":"AWS.M2.CARDDEMO.TRANSACT.VSAM.KSDS","copybook":"CARDDEMO.COPYLIB(CVTRA05Y)"}]}}
# → { "id":"...", "sha256":"...",
# "artifacts":[{"kind":"dataset","name":"AWS.M2.CARDDEMO.ACCTDATA.VSAM.KSDS",
# "charset":"program","count":N,"sha256":"..."}, ...],
# "datasets":{"AWS.M2.CARDDEMO.ACCTDATA.VSAM.KSDS":{"rows":[...]}, ...} }{"id":"W1"} — nothing on the live
estate changed.MCP and API-key calls draw on the same prepaid SteelFrame credit as the AI assistant. Working by hand in the web terminal, Studio and VS Code is never charged.
what the agent does on /mcp | class | price |
|---|---|---|
| reads, lists, status, job output, analysis | read | free |
writes: members, data sets, records, SQL, job control, CEDA / CEMT changes (cics_ceda DEFINE / ALTER / DELETE / INSTALL / COPY / ADD / REMOVE, cics_cemt SET / PERFORM / DISCARD, cics_define; the read-only verbs DISPLAY / VIEW / CHECK / EXPAND / LIST and CEMT INQUIRE are reads, and a refused command is not charged), exports | write | $0.03 per call |
the headless 3270: cics_send (one AID key, the screens it painted) | screen | $0.01 per key — cics_open_session, cics_screen and cics_close_session are free; cics_run_script is a run |
compile_program, check_source | compile | $0.03 |
run_program, submit_job, run_batch, run_capture, cics_link, cics_run_script | run | $0.03 + $0.99/core-hour after the first 10 s of CPU |
compile_and_run (private lane) | compile + run | $0.06 + core time |
create_workspace, reset_workspace, destroy_workspace | workspace | unmetered |
Failed and refused calls are free, an identical retry within 60 s is free when nothing of yours changed in between, and every billable result ends with
its cost and your remaining balance (_meta['io.steelframe/billing']; each tool's class and price is in
its _meta['io.steelframe/price'] in tools/list). At $0 a call answers
CREDIT_REQUIRED (do not retry; top up). Each key has its own rolling 24-hour spending cap
(DAILY_CAP). The other doors' prices are in their sections below: the same $0.03 write / compile and
run rule, and $0.01 for a screen action on /mcp/screen.
A second server, steelframe-vscode at POST /mcp/vscode
(same transport and keys as /mcp), lets your agent (Claude Code,
Cursor, Claude Desktop, any MCP client) drive your own SteelFrame VS Code seat
(code-server + Zowe Explorer, logged on as you) — exactly the tools the AI assistant
panel uses there. Every action happens visibly in your VS Code tab: text is typed at
human speed, saves go through Zowe and are verified against the mainframe, Java builds
and runs stream into the Output panel SteelFrame AI: Java, Python runs and tests into
SteelFrame AI: Python, SQL into SteelFrame AI: SQL, and the page shows an
amber ring with Your AI agent is working in VS Code (MCP).
vscode_status says whether it is
connected; vscode_start starts your container seat and waits for the
window. Without one, a call fails fast with VSCODE_NOT_CONNECTED and the
page to open — never a hang, never a charge.vscode.drive and
vscode.headless (in no preset) lets vscode_start, or the first vscode_* call, start your
container seat and open a headless VS Code window for the agent on the SteelFrame engine, with no website logon.
vscode_status says window: "headless" or "browser". The window closes after
10 minutes without a vscode_* call, at the operator's maximum lifetime, when the key is revoked or expires, and when
you log on to the website VS Code tab (your window takes over). One seat stays reserved for website users
(VSCODE_HEADLESS_BUSY, free). The window itself is free; the tools cost what they cost.vscode.drive — in the Developer and Full presets, not in
Read-only (keys minted before 2026-10-14 do not have it: mint a new one). A key without
it gets 403 KEY_SCOPE; REST-only keys 403 KEY_SURFACE.java/, python/, dotnet/, node/, c/, ...) with only its
required setup and ONE hello-world project (java/hello-java, python/hello-python with the venv,
node/hello-js, angular/carddemo-ui, ...); the result says so. The other starters are
samples, copied from the seat image when a tool names one as the project (e.g. vscode_java_build
hello-postgres). In VS Code the user runs SteelFrame: New project…. Existing folders are never touched.
The host seat (the operator's shared VS Code) gets the editing tools only: no
vscode_run_command, no Java, no Python, none of the language folders, no database.vscode_dotnet_open / _write /
_build / _run / _test: the .NET 10 SDK under dotnet/, packages only from
the seat's offline NuGet folder (Npgsql, Dapper, EF Core, xUnit), run starts the project's own built app.
vscode_node_open / _write / _install / _run / _test /
_build: Node.js 22 under node/ with a built-in TypeScript / tsx / Vitest / ESLint / pg
toolchain (no install needed), and Angular 22 under angular/ (ng build, ng test
with Vitest + jsdom, no browser; starter angular/carddemo-ui); install is an offline,
script-less npm install of what the toolchain lacks.
Fixed commands only; NuGet.Config, .npmrc, node_modules, lock files, URL / git / file:
dependencies and install scripts are refused (free). The seat also has Prettier and the Vitest explorer (the
Testing view), @types/express in the offline cache, the plain-JavaScript hello world
node/hello-js (TypeScript: the sample node/hello-node) and Run and Debug configurations for tsx, node and Vitest.vscode_lang_open / _write / _build /
_install / _run / _test on c/, go/, shell/,
php/, lua/, kotlin/, rust/ or pwsh/<project>.
C / C++: gcc / g++ 12, make, CMake 3.25, gdb 13, clangd 16 in the editor; build = CMake if the project has a
CMakeLists.txt, else its Makefile, else gcc -std=c17 / g++ -std=c++20
of its sources into build/<project>; test = ctest, make test or the
tests/test_*.c build; run = the built program. Go 1.27 with gopls, Delve and staticcheck: build =
go build -o bin/ ./... + go vet, test = go test, run = the built program (never
go run), install = go mod tidy OFFLINE from the seat's read-only module cache (pgx, chi,
testify, cobra, shopspring/decimal, yaml.v3, x/text). Shell: bash 5.2; build = bash -n +
shellcheck, test = bats, run = bash <script>. PHP 8.2 with Composer,
Phpactor and Xdebug: build = php -l + composer validate, run = php <script>,
install = composer update OFFLINE from the seat's package repository (PHPUnit 11, brick/math, monolog,
league/csv; no package scripts or plugins), test = vendor/bin/phpunit. Lua 5.4: build =
luac5.4 -p, run = lua5.4 <script>, test = tests/run.lua. Kotlin 2.4
on JDK 25 with Maven (kotlin-maven-plugin, kotlin-test + JUnit 5 from the offline repository): build = Maven package,
run = java with the main class (e.g. hello.HelloKt), test = mvn test.
Rust 1.99 with clippy, rustfmt and rust-analyzer: build = cargo build (limit 300 s; one build folder
rust/.target for all projects), test = cargo test, install = cargo fetch against the
seat's vendored crates (serde, serde_json, anyhow, thiserror, clap, tokio, postgres, rust_decimal, chrono, regex, rand,
itertools, csv), run = the built program. PowerShell 7.6: build = the PowerShell parser + PSScriptAnalyzer,
run = pwsh -File <script>, test = Pester 6. Runs stop after 60 s, builds and tests after 150 s
(Rust 300 s); no stdin, no internet; something the seat's offline sets lack fails at once (free); build/,
bin/, target/, vendor/, go.sum, go.work,
Cargo.lock, composer.lock and auth.json are never written. Starters:
c/hello-c, c/record-reader (a COBOL copybook record: packed decimal, zoned decimal and EBCDIC
decoded in C), go/hello-go, go/go-postgres, shell/hello-shell,
shell/jcl-to-shell (a SORT / SUM / report batch job as a shell pipeline), php/hello-php,
php/php-postgres, lua/hello-lua, kotlin/hello-kotlin, rust/hello-rust,
rust/rust-postgres, pwsh/hello-pwsh (the *-postgres ones use your own PostgreSQL). In the
editor: the debuggers gdb (Native Debug; rust-gdb for Rust), Delve, bashdb, Xdebug (PHP Debug), Lua Debug and the
PowerShell debugger; the language servers clangd, gopls, Bash IDE, Phpactor, Lua, rust-analyzer and PowerShell Editor
Services (Kotlin: highlighting only). vscode_status returns langs, the language folders your
seat has. Not in the seats (ask the operator): Scala, Swift, Dart, Haskell, Elixir, Ruby, R.vscode_db_schema and vscode_db_query (ONE read-only statement in a
read-only transaction, at most 200 rows) are free; vscode_db_exec runs a DDL/DML script as one
transaction (all or nothing); vscode_db_import brings a DB2 table over (DSNTIAUL unload into your
own data sets through your z/OSMF door, EBCDIC and packed decimals decoded in the seat, CREATE TABLE + COPY)
and reports PARITY OK or the mismatches (row counts, per-column sums and value hashes, DB2 vs
PostgreSQL); vscode_db_import_vsam brings a VSAM cluster (KSDS, ESDS, RRDS) or a sequential data
set over through its COBOL copybook (decoded by the mainframe's own copybook codec as you; PIC X to varchar,
zoned and COMP-3 to numeric(p,s), COMP to integers, OCCURS to arrays, the KSDS key to the primary key) and reports
PARITY OK with a byte proof (every PostgreSQL row re-encodes to the source record). The tools connect as your non-superuser role: no COPY, no server files or programs, no role,
extension (outside a trusted list) or time-limit changes; 200 MB per database.python -u <file>, python -m pytest, basedpyright and offline
pip install -r requirements.txt templates in your venv (requirements.txt
holds package names and versions only; packages come from the seat's wheelhouse); vscode_run_command is an
allow-list of 15 UI commands (no terminals, tasks, debugging, extensions or settings).tools/list). Where an operator switches them on (container seats): vscode_java_serve,
vscode_dotnet_serve and vscode_node_serve start your BUILT app with the run tools' fixed
command and keep it running on an app port of your seat (3000-3009, 5000-5009, 8082-8089; at most 2 apps; stopped
after 15 minutes without a request, after 2 hours, and when your seat ends). The app opens on a private address of
its own (https://<port>-<you>-<nonce>.<apps host>/, only you can open it) and in the
App Preview tab. vscode_app_list, vscode_app_logs and vscode_app_stop are free.
No live reload: build, stop, serve again.snapshot:true on an action tool appends
what the VS Code tab shows afterwards.Setup (your account page shows these with your key filled in):
claude mcp add -s user --transport http steelframe-vscode https://steelframe.cobolstack.com/mcp/vscode \
--header "Authorization: Bearer sf_…"
# .mcp.json / Cursor / Windsurf
{ "mcpServers": { "steelframe-vscode": { "type": "http",
"url": "https://steelframe.cobolstack.com/mcp/vscode",
"headers": { "Authorization": "Bearer sf_…" } } } }
Tools and prices (same credit, caps, loop guard and retry rule as /mcp; ledger kind mcp):
| tool | class | price |
|---|---|---|
vscode_status, vscode_start, vscode_screen, vscode_open_member, vscode_zowe_filter, vscode_refresh, vscode_job_output, vscode_java_open, vscode_python_open, vscode_dotnet_open, vscode_node_open, vscode_lang_open, vscode_db_schema, vscode_db_query | read | free (vscode_open_member with create:true is a write) |
vscode_type, vscode_set_text, vscode_insert_lines, vscode_save, vscode_java_write, vscode_python_write, vscode_dotnet_write, vscode_node_write, vscode_lang_write | write | $0.03 |
vscode_run_command | read | free (the save / undo / redo commands are writes, $0.03) |
vscode_db_exec, vscode_db_import, vscode_db_import_vsam | write | $0.03 (a SQL script in one transaction; a DB2 table or a VSAM / sequential data set imported with its parity check) |
vscode_java_build | compile | $0.03 (a build that ran and found compile errors counts, like a compile with RC 8) |
vscode_python_install, vscode_python_typecheck | compile | $0.03 (offline pip install from the seat's wheelhouse; a basedpyright type check) |
vscode_lang_build, vscode_lang_install | compile | $0.03 (cmake / make / gcc / g++; go build + go vet; bash -n + shellcheck; php -l + composer validate; luac -p; Maven (Kotlin); cargo build; PowerShell parser + PSScriptAnalyzer; go mod tidy, composer update or cargo fetch from the seat's offline sets; a build that ran and found errors counts) |
vscode_dotnet_build, vscode_node_build, vscode_node_install | compile | $0.03 (dotnet build with packages only from the seat's offline NuGet folder; tsc or ng build; an offline, script-less npm install of what the seat's toolchain lacks; a build that ran and found errors counts) |
vscode_java_run, vscode_java_test, vscode_python_run, vscode_python_test, vscode_dotnet_run, vscode_dotnet_test, vscode_node_run, vscode_node_test, vscode_lang_run, vscode_lang_test | run | $0.03 + $0.99/core-hour beyond the first 10 s of the run time the seat measured |
vscode_submit_job | run | $0.03 + the JES job's core time beyond 10 s, settled when the job ends |
vscode_java_serve, vscode_dotnet_serve, vscode_node_serve (coming soon) | run | $0.03 + $0.99/core-hour beyond the first 10 s of the CPU time the app used, settled when it stops |
vscode_app_list, vscode_app_logs, vscode_app_stop (coming soon) | read | free |
Failed or refused calls are free; the same failing call five times in ten minutes is
refused with LOOP_GUARD. The free identical-retry rule (60 s) applies only to tools
whose arguments name the action (vscode_java_write, vscode_python_write, the Java build/run/test and
the Python run/test/install/type check of a project, the .NET / Node write, build, run, test and install, vscode_open_member); the tools that act on the active editor
(vscode_type, vscode_set_text, vscode_insert_lines,
vscode_save, vscode_run_command, vscode_submit_job) and vscode_db_exec are charged every time. The same routes exist as REST
(POST /api/vscode/<tool>, GET /api/vscode/status) for scripts.
A third server, steelframe-git at POST /mcp/git (same transport and keys as
/mcp; only where the operator switched the Connectors on), lets your agent use the GitHub and
GitLab accounts you connected in the Connectors tab and the repositories you linked there — to your own
data sets (a repository folder ↔ one of your PDSs), your VS Code seat and your seat PostgreSQL database.
connectors.use — in no preset: create a Custom key and tick it
(a push publishes your code on GitHub / GitLab with your identity). A key without it gets
403 KEY_SCOPE.git_push is one commit whose parent is the head it compared; a
branch that moved answers 409. git_pull is all-or-nothing (a line longer than the record or a
character the code page cannot hold is refused, never cut) and never deletes a member.git_sync turns it on for a mapping —
member saves are committed and pushed (batched), provider pushes are pulled (webhook or polling). If a member
changed on both sides it stops with a conflict; git_resolve keeps the mainframe, keeps the repository,
or puts your mainframe versions on a new branch and takes the repository versions.openWorldHint, so your client asks you
before each call.claude mcp add -s user --transport http steelframe-git https://steelframe.cobolstack.com/mcp/git \
--header "Authorization: Bearer sf_…"
| tool | class | price |
|---|---|---|
git_accounts, git_repos, git_status, git_diff, git_log, git_branches, git_seat, git_pg_migrations | read | free |
git_link, git_unlink, git_create_repo, git_branch_create, git_branch_switch, git_pull, git_push, git_sync, git_resolve, git_seat_clone, git_pg_migrate, git_pg_export | write | $0.03 |
Refused and failed calls are free; an identical retry within 60 s is free when nothing of yours changed in between. The AI assistant panel has the same
operations as git_* tools; there, everything that publishes or replaces mainframe text waits for your
click on Allow.
A fourth server, steelframe-zos at POST /mcp/zos (same transport and keys as
/mcp; only where the operator switched Mainframes on), lets your agent work on your own
real IBM mainframe — your company's z/OS, an IBM Z Xplore account, a test system — that you connected in the
Mainframes tab (z/OSMF, directly or through a Zowe API ML gateway).
zos.remote — in no preset: create a Custom key and tick it. A key
without it gets 403 KEY_SCOPE.claude mcp add -s user --transport http steelframe-zos https://steelframe.cobolstack.com/mcp/zos \
--header "Authorization: Bearer sf_…"
| tool | class | price |
|---|---|---|
zos_connections, zos_list_datasets, zos_list_members, zos_read_member, zos_list_jobs, zos_job_status, zos_list_spool, zos_read_spool | read | free |
zos_write_member, zos_submit_job, zos_cancel_job, zos_purge_job, zos_copy_to_steelframe, zos_copy_to_mainframe | write | $0.03 |
Refused and failed calls are free; an identical retry within 60 s is free when nothing of yours changed in between. The AI assistant panel has the same
operations as zos_* tools, behind the connection's AI chat switches; every save, copy and job waits for
your click on Allow unless you turned that off.
A fifth server, steelframe-screen at POST /mcp/screen (same transport and keys as
/mcp; only where the operator switched it on), lets your agent drive your own open
SteelFrame page the way the AI assistant does: the 3270 terminal, the Postgres tab and the tab bar — visibly, with
the amber ring, the cursor and typing you can watch.
screen.drive — in no preset: create a Custom key and tick it. Not available to a RACF SPECIAL or OPERATIONS user id (refused with SCREEN_PRIVILEGED): use an ordinary user id.claude mcp add -s user --transport http steelframe-screen https://steelframe.cobolstack.com/mcp/screen \
--header "Authorization: Bearer sf_…"
| tool | class | price |
|---|---|---|
screen_read, wait_for, read_member, job_output, show_tab, say, pg_tables, pg_open_table, pg_query, pg_explain, pg_export_csv | read | free |
type_field, press_key, select_row, editor_set_text | screen | $0.01 |
app_open, app_read (coming soon, with the web apps) | read | free (your running web app, in a headless browser that reaches only your own app addresses) |
app_click, app_type (coming soon, with the web apps) | screen | $0.01 (never into a password field) |
pg_exec | write | $0.03 |
Refused and failed calls are free; a screen action twice is two actions (no retry waiver).
Where the operator enabled it (it is on here), Anthropic's own Claude Code extension can also run inside your own VS Code seat (you install it and sign in with your own Claude account or your own API key; it is Anthropic's software under Anthropic's terms, billed to your Claude account — SteelFrame credit is not used for it): it then gets these servers with your own rights, and reaches only Anthropic's hosts on the internet (Anthropic's published addresses, and only TLS connections that name the allowed host).
Fetched live from GET /api/mcp/tools — this list is
always current and is never baked into this page. It is exactly what an
MCP client receives from tools/list: each tool shows its
description, the read/write/destructive annotation, the required scope, the
bound HTTP method/path, and the full input schema (parameters, types, required
fields). The "instructions" box is the text the client receives from
initialize. Click a tool to expand.