Home Applications IRIS MCP Server Suite

IRIS MCP Server Suite

Community Project
This project is maintained by its author and is not officially supported by InterSystems. For technical support, please contact the project developer.
0
0 reviews
0
Awards
315
Views
0
IPM installs
0
0
Details
Releases (1)
Reviews
Issues
IRIS MCP Server Suite: Comprehensive suite of MCP servers for InterSystems IRIS.

What's new in this version

Initial Alpha Release

IRIS MCP Server Suite

Pre-Release — This project is under active development and has not yet been published to npm or IPM. Install by cloning the repository (see Quick Start below). Package registry publishing is planned for a future release. See https://github.com/jbrandtmse/iris-execute-mcp-v2/blob/main/CHANGELOG.md for the 2026-04-19 bug-fix pass (six defects found during a manual MCP suite retest) and the 2026-04-09 pre-release breaking change to tool names.

Give AI assistants structured, safe access to InterSystems IRIS.

The IRIS MCP Server Suite is a collection of five specialized Model Context Protocol (MCP) servers that let AI coding assistants — Claude, Copilot, Cursor, and others — work directly with InterSystems IRIS. Each server covers a distinct operational domain so you can install only what you need.


Servers

Package Domain Tools Description
@iris-mcp/dev Development 28 ObjectScript document CRUD, compilation, SQL, globals, code execution, unit tests, package browsing, bulk export, macro-expanded routine lookup, SQL query analysis and performance advisories, lines-of-code metrics, cross-profile environment diff & promotion (iris_env_diff, iris_env_promote)
@iris-mcp/admin Administration 26 Namespace, database, user, role, resource (incl. SQL privileges), web-app, SSL/TLS, OAuth2, service, LDAP, X.509, and audit management
@iris-mcp/interop Interoperability 22 Ensemble/Health Connect production lifecycle, production item management, system default settings, credentials, lookups, rules, transforms, message-trace Mermaid diagrams, message resend/replay (duplication hazard — preview before executing)
@iris-mcp/ops Operations & Monitoring 21 Composite health check (iris_health_check — one call, verdict + findings), system metrics, jobs, locks, journals, mirrors, audit, database integrity, licensing, ECP, tasks, alert management, process control, database maintenance operations, backups
@iris-mcp/data Data & Analytics 7 DocDB document database, DeepSee analytics (MDX/cubes), REST API management

104 tools across 5 servers — install one or all. Each server additionally provides one framework tool, iris_server_profiles (see Discovering profiles and policy), so the advertised count per server is one greater than the package totals above.

Meta-package

Once published, all servers will be installable at once with npm install -g @iris-mcp/all. See the meta-package README for details.


Which Server Do I Need?

Your Role Recommended Server(s)
ObjectScript developer @iris-mcp/dev — compile, edit, execute code, run SQL, manage globals
System administrator @iris-mcp/admin — manage namespaces, databases, users, roles, web apps, SSL, OAuth
Integration engineer @iris-mcp/interop — control productions, configure credentials, manage business rules and transforms
Operations / SRE @iris-mcp/ops — check overall instance health in one call (iris_health_check), monitor metrics, inspect jobs and locks, review journals, audit events, manage tasks
Data / BI analyst @iris-mcp/data — query DocDB collections, run MDX against DeepSee cubes, manage REST APIs
Full-stack / getting started @iris-mcp/dev + @iris-mcp/admin, or all five servers

Prerequisites

  • Node.js 18 or later
  • InterSystems IRIS 2023.1 or later (including IRIS for Health, HealthShare)
  • IRIS web port accessible (default 52773)

Quick Start

Until packages are published to npm, install by cloning the repository and building from source.

1. Clone and Build

git clone https://github.com/jbrandtmse/iris-execute-mcp-v2.git
cd iris-execute-mcp-v2
pnpm install
pnpm turbo run build

Requires: Node.js 18+ and pnpm 9+. Install pnpm with npm install -g pnpm if needed.

2. Set Environment Variables

All servers use the same environment variables:

Variable Default Description
IRIS_HOST localhost IRIS hostname or IP
IRIS_PORT 52773 IRIS web server port
IRIS_USERNAME _SYSTEM IRIS username
IRIS_PASSWORD (required) IRIS password
IRIS_NAMESPACE USER Default IRIS namespace
IRIS_HTTPS false Use HTTPS instead of HTTP
IRIS_PROFILES (unset) Optional. JSON map of named IRIS instances for multi-server use. Omit for single-server — the IRIS_* vars above define the reserved default profile. See Multiple Servers & Governance.
IRIS_GOVERNANCE (unset) Optional. JSON policy that enables/disables individual tool actions per profile. Omit to leave every tool enabled (today’s behavior). See Multiple Servers & Governance.
IRIS_GOVERNANCE_PRESET (unset) Optional. "read-only" or "full" — a one-word safety preset that blocks every write action suite-wide. Omit (or "full") for today’s behavior. See Read-only mode.
IRIS_GOVERNANCE_FILE (unset — inert) Optional. Path to a JSON file of the same shape as IRIS_GOVERNANCE, so one policy is portable across every MCP client. The file’s layers sit strictly below the IRIS_GOVERNANCE env layers (an env setting always wins). Unset ⇒ inert: no file is ever read and behavior is byte-for-byte today’s. Read once at startup (restart to apply edits); a missing/unreadable/malformed file fails startup naming the var and the path. See Governance file.
IRIS_AUDIT_LOG (unset — OFF) Optional. Absolute path to a JSONL audit file recording every MCP tool call (who-ish/session, what, outcome, denials). Omit for today’s behavior — a mechanical no-op, zero filesystem writes. See Compliance & Auditability.
IRIS_AUDIT_LOG_MAX_MB 50 Optional. Rotates the audit file at this size — the current file is renamed to <path>.1 (single generation, overwriting a prior one) and a fresh file is started. Only relevant when IRIS_AUDIT_LOG is set.
IRIS_AUDIT_LOG_PARAMS false Optional. When true, audit entries also include each call’s (redacted) parameter values; the default logs parameter key names only. Only relevant when IRIS_AUDIT_LOG is set.
IRIS_SQL_MAX_ROWS (unset — no cap) Optional. Positive integer ceiling on the number of rows iris_sql_execute returns — a post-fetch cap on the response (it bounds the returned row count, not the server-side result set or transfer). Omit for today’s behavior (only the per-call maxRows/1000-row default apply).
IRIS_SQL_TIMEOUT (unset — no override) Optional. Positive number of seconds — a per-request timeout override for iris_sql_execute‘s HTTP call. Omit to use the connection’s default IRIS_TIMEOUT.
IRIS_TOOLS_PRESET full Optional. "full" (today’s behavior) | "core" (~10-tool small-model subset) | "developer" (persona filter, security/enterprise admin hidden). Unknown value fails fast at startup naming the valid values. Omit for today’s tools/list, byte-for-byte. See Tool Visibility Presets.
IRIS_TOOLS_DISABLE (unset) Optional. Comma-separated tool names to hide from tools/list. Trailing-* wildcard supported (iris_doc_*); a bare * alone is rejected. Omit to hide nothing beyond the active preset. See Tool Visibility Presets.
IRIS_TOOLS_ENABLE (unset) Optional. Comma-separated tool names to force-show, overriding both the preset and IRIS_TOOLS_DISABLE (punches a hole in a hidden family). Omit for no overrides. See Tool Visibility Presets.
IRIS_SERVER_MANAGER off Optional. off (default — today’s behavior, no settings file is ever read) | auto (import connection definitions from the InterSystems Server Manager VS Code extension) | required (as auto, but fail startup if no definition is found). Unknown value fails fast at startup naming the valid values. See Server Manager connections.
IRIS_SM_SERVERS (unset — import all) Optional. Comma-separated allow-list of intersystems.servers names to import, so you get the two servers you want instead of your whole roster. A listed name that matches nothing warns (it fails only under IRIS_SERVER_MANAGER=required, where nothing would be imported at all). Only relevant when IRIS_SERVER_MANAGER is auto/required.
IRIS_SM_SETTINGS_PATHS (unset — normal discovery) Optional. Escape hatch: a ;-separated (Windows) / :-separated (macOS, Linux) list of settings.json paths that replaces discovery entirely. Only relevant when IRIS_SERVER_MANAGER is auto/required.
IRIS_SM_WORKSPACE (unset — the process CWD) Optional. Directory whose .vscode/settings.json is used as the highest-precedence source. Set it explicitly when the MCP client’s working directory is not the workspace you mean. Only relevant when IRIS_SERVER_MANAGER is auto/required.
IRIS_CREDENTIAL_HELPER (unset) Optional. A command run with the Server-Manager profile name appended as its final argument; the command’s trimmed stdout is the password. Because the name is always appended, point this at a small wrapper script that uses it (e.g. a script running op read "op://vault/$1/password" or pass show "iris/$1") rather than at a bare op read …/pass show …, which would reject the extra argument. Run without a shell, so on Windows point it at a real .exe (a .cmd/.bat shim needs cmd /c <your-helper.cmd>). Runs synchronously per password-less profile with a 10s timeout, so keep IRIS_SM_SERVERS tight if the helper can hang. Link 3 of the credential chain — see Server Manager connections. Only relevant when IRIS_SERVER_MANAGER is auto/required and a profile still lacks a password after the OS-keychain link.

Single-server installs need no changes. IRIS_PROFILES, IRIS_GOVERNANCE, IRIS_GOVERNANCE_PRESET, IRIS_GOVERNANCE_FILE, IRIS_AUDIT_LOG, IRIS_AUDIT_LOG_MAX_MB, IRIS_AUDIT_LOG_PARAMS, IRIS_SQL_MAX_ROWS, IRIS_SQL_TIMEOUT, IRIS_TOOLS_PRESET, IRIS_TOOLS_DISABLE, IRIS_TOOLS_ENABLE, IRIS_SERVER_MANAGER, IRIS_SM_SERVERS, IRIS_SM_SETTINGS_PATHS, IRIS_SM_WORKSPACE, and IRIS_CREDENTIAL_HELPER are all optional and additive. With none set, the suite behaves exactly as it always has — the six IRIS_* variables above are all you need.

Server Manager connections (optional)

If you already curate IRIS connections in the InterSystems Server Manager VS Code extension, set IRIS_SERVER_MANAGER=auto and the suite reads those intersystems.servers definitions straight out of your VS Code settings files — no re-typing host/port/username into every MCP client config. Each imported definition becomes a named profile you address with the server parameter, exactly like an IRIS_PROFILES entry.

⚠️ Read this first — Server Manager import is ADDITIVE, and the keychain does NOT cover your base credentials.

The six base variables are still required. IRIS_HOST, IRIS_PORT, IRIS_USERNAME, IRIS_PASSWORD, IRIS_NAMESPACE and IRIS_HTTPS configure the reserved default profile, and default is built before any Server Manager import. Setting IRIS_SERVER_MANAGER=auto does not let you omit them — the server fails at startup with IRIS_USERNAME environment variable is required (then IRIS_PASSWORD…) if you try. Server Manager adds extra addressable profiles; it does not replace your primary connection.

The OS keychain serves Server-Manager-discovered profiles ONLY. iris-mcp-credentials set <name> stores a password that the credential chain uses for an imported definition that has none. It is not consulted for IRIS_PASSWORD: storing an entry named default has no effect, because loadConfig reads the base credentials from the environment and never calls the chain. So today the keychain reduces plaintext for your additional servers — it cannot remove IRIS_PASSWORD from your client config.

Worked example: with IRIS_HOST/IRIS_USERNAME/IRIS_PASSWORD set for default, IRIS_PROFILES adding sademo, IRIS_SERVER_MANAGER=auto importing local (inline password) and localhost2 (keychain, via iris-mcp-credentials set localhost2), all four resolve and route — but removing IRIS_PASSWORD breaks startup for all of them.

  • Default is off. Nothing is read, nothing is imported, and no settings file is touched unless you opt in.
  • Discovery order (highest precedence first), mirroring VS Code’s own folder > workspace > user scope ranking: (1) the workspace .vscode/settings.json (from IRIS_SM_WORKSPACE, else the process working directory); (2) every *.code-workspace file directly inside that same directory, sorted by filename — multi-root workspaces commonly keep intersystems.servers only there; (3) each product’s user settings — Code, Code - Insiders, VSCodium, Cursor (%APPDATA%\<Product>\User\settings.json on Windows, ~/Library/Application Support/<Product>/User/settings.json on macOS, $XDG_CONFIG_HOME/<Product>/User/settings.json then ~/.config/<Product>/User/settings.json on Linux); (4) on Linux only, Flatpak sandboxes — ~/.var/app/{com.visualstudio.code,com.visualstudio.code.insiders,com.vscodium.codium}/config/<Product>/User/settings.json. The first file to define a name wins. A name already defined by IRIS_* or IRIS_PROFILES always wins over a Server Manager definition.
  • Linux specifics. VS Code is Electron, whose Linux config root is $XDG_CONFIG_HOME or ~/.config — so both are searched, XDG first. A Flatpak install cannot write ~/.config at all (Flatpak redirects the app’s XDG_CONFIG_HOME into its sandbox), so the three published Flathub apps are searched too, after the native paths. Cursor is not published on Flathub and therefore has no Flatpak entry; use IRIS_SM_SETTINGS_PATHS for any install layout not listed here, including VS Code portable mode.
  • Both settings-file shapes are understood. A settings.json holds intersystems.servers at the top level; a .code-workspace file nests every setting under a settings key. Either works, including when named explicitly in IRIS_SM_SETTINGS_PATHS.
  • Passwords are not readable directly, but a credential chain completes them. Server-Manager-saved passwords live in VS Code SecretStorage and are cryptographically unreachable from outside VS Code — by design, with no supported extraction path. For any imported definition that lacks a password, the suite tries, in order: (1) the OS keychain (Windows Credential Manager / macOS Keychain / libsecret), service iris-mcp, account <name> — set with the iris-mcp-credentials CLI below; (2) IRIS_CREDENTIAL_HELPER. A definition carrying a legacy inline password still works too (with a deprecation warning), provided it also declares its own username — see the next bullet. If every link is exhausted, the profile is excluded at startup with a log line naming the remediations; under IRIS_SERVER_MANAGER=required this fails startup instead.
  • IRIS_PROFILES replaces a Server Manager definition — it does not complete one. If a name exists in both, the environment wins and the Server Manager definition is discarded entirely, host and username included (a warning names the collision). So an IRIS_PROFILES entry that supplies only a password will quietly give you a profile pointed at your local IRIS_HOST/IRIS_USERNAME, not at the Server Manager host. To supply a password for a Server Manager definition, use the OS keychain or IRIS_CREDENTIAL_HELPER; use IRIS_PROFILES only when you are defining the whole connection there.
  • A Server-Manager entry needs its own username. An entry that omits username (relying on the local default’s username) is not imported at all — a warning names the file and the fix. Pairing an inherited username with a password destined for a different remote host risks repeated authentication failures that can lock out the account there. Add "username" to the intersystems.servers entry to opt in. (Changed in this release: such an entry previously imported successfully when it carried an inline password.)
  • The same name in two settings files. The first (highest-precedence) file wins. The one exception: if that definition has no password of its own and a lower-precedence file’s definition carries an inline password, the lower-precedence definition is imported instead — including its host, port and username — and a warning names both hosts. Store a password for the name (iris-mcp-credentials set <name>) to make the higher-precedence definition win.
  • auto reads the working directory. Because the workspace candidate defaults to the process CWD, a repository you have merely cloned can contribute connection definitions. Set IRIS_SM_WORKSPACE explicitly, or pin IRIS_SM_SERVERS, when working in repositories you do not control.
  • Changes require a restart, exactly like the IRIS_* environment variables.
  • See where every profile came from. iris_server_profiles (the discovery tool) reports a source field on every roster entry — "env" (from IRIS_*/IRIS_PROFILES) or "server-manager" (imported from a settings file) — plus sourceFile, the exact settings file a Server-Manager-sourced profile came from. Both are attribution only: they never affect which actions governance allows, only where you look to change a connection’s host/port/username. The optional IRIS_AUDIT_LOG audit trail (see Compliance & Auditability) likewise records profileSource per call, for the same reason.
  • sourceFile is a local path, and your AI client sees it. It never contains a password — but it is a real filesystem path, so when a profile comes from a user-scope settings file it includes your OS account name (e.g. C:\Users\jsmith\AppData\Roaming\Code\User\settings.json), and when it comes from a workspace it includes that directory. Because the roster is returned to the connected MCP client, that path is visible to the model. This is deliberate: a bare filename would be useless — every candidate file is called settings.json, so the folder is the whole point. If you would rather not disclose local paths, define the connections you expose at workspace scope (IRIS_SM_WORKSPACE), or leave IRIS_SERVER_MANAGER off and use IRIS_PROFILES.

VS Code extension: IRIS MCP Launcher (optional, Copilot-family only)

If you’re inside VS Code with GitHub Copilot (or another client that consumes VS Code’s built-in MCP server
registry), there’s a fourth way to connect that sidesteps the credential chain above entirely:
extensions/iris-mcp-launcher/, a standalone, optional VS Code
extension (outside this repo’s npm workspace) that registers the @iris-mcp/* servers directly with VS Code and
resolves credentials from Server Manager’s vscode.authentication session inside the extension host — the
one place a Server-Manager-saved SecretStorage password actually is reachable. No OS keychain setup, no
IRIS_CREDENTIAL_HELPER, no IRIS_PROFILES password entry required.

This only helps Copilot-family agents for launching servers. Claude Code manages its own MCP configuration
(claude mcp add / .mcp.json) and does not consume VS Code’s MCP registry — Claude Code users should use the
IRIS_SERVER_MANAGER/credential-chain path documented above instead. See the extension’s own README for the
verified client-coverage boundary and citations.

The extension carries three surfaces, and two of them help every client, not just Copilot:

Command (category “IRIS MCP Launcher”) What it does
Select Servers… Choose which Server Manager servers become MCP servers; a status bar item shows how many are registered
Manage MCP Clients… Wire the suite into any of the 13 supported MCP clients — detect, per-server enable/disable, diff preview, backup/restore, doctor. The GUI front-end for iris-mcp-clients; useful to Claude Code / Cursor / Cline users too
Open Governance Editor Visually edit the shared governance policy file that every client’s servers read

Installing it (not yet on any Marketplace). Build a VSIX from this checkout and install it, then point it at
your checkout — full steps, including the reload/reinstall gotchas, are in
the extension’s install section:

cd extensions/iris-mcp-launcher && npm install && npm run build
npx vsce package --allow-missing-repository        # -> iris-mcp-launcher-0.1.0.vsix
code --install-extension "$PWD/iris-mcp-launcher-0.1.0.vsix"
# then: Developer: Reload Window, and set irisMcpLauncher.developmentRepoPath to this repo's absolute path

Where do Server Manager passwords come from? (the canonical answer)

Short version: a Server Manager password is never readable outside VS Code, by design — but the suite can still get one from you through three other doors.

VS Code’s Server Manager extension stores passwords in its own encrypted SecretStorage (backed by the OS’s safeStorage/keychain, app-bound to VS Code itself). There is no supported API, file, or database an external process — including this suite — can read to recover that value. If you set IRIS_SERVER_MANAGER=auto/required and a Server-Manager-sourced profile still shows no password at startup, this is why, and it is expected. Complete it one of these three ways instead:

  1. OS keychain (recommended). Run iris-mcp-credentials set <name> once per machine (see the CLI below) — it stores the password in the same class of OS-native secure storage (Windows Credential Manager / macOS Keychain / libsecret) that VS Code itself uses, just under this suite’s own service name (iris-mcp). This is the credential chain’s link 2 and needs no config file.
  2. IRIS_CREDENTIAL_HELPER. Point this environment variable at a command (e.g. a wrapper around op, pass, or your organization’s secret manager) that prints the password to stdout when given the server name. Useful when you already have a secrets tool and don’t want a second, suite-specific store.
  3. IRIS_PROFILES — but only as a full replacement, never a patch. An IRIS_PROFILES entry for the same name does not “add a password” to the Server Manager definition; it replaces it entirely, including host/port/username. Use this only when you want to define the connection outside Server Manager altogether.

If none of the three is set up, the profile is excluded at startup (a log line names all three remediations) rather than started with a missing or wrong credential; under IRIS_SERVER_MANAGER=required this fails startup instead of degrading silently.

iris-mcp-credentials CLI

A small command-line tool — ships as a bin in @iris-mcp/shared, built to packages/shared/dist/cli/credentials-cli.js — for the one-time per-machine setup referenced above: storing, listing, testing, and removing IRIS passwords in the OS keychain, so the OS-keychain link of the credential chain never needs a hand-edited config file containing a password. Not yet published to npm (see Quick Start), so invoke it directly with node:

node packages/shared/dist/cli/credentials-cli.js set myserver
Command Effect
set <name> Store a password for <name> in the OS keychain. Interactive by default — prompts with echo suppressed; --stdin reads the password from a pipe or file instead (a leading UTF-8 BOM and a single trailing newline are stripped) for scripted/CI use. --stdin is refused when stdin is a terminal, because reading a TTY does not suppress echo and the password would appear in cleartext. A password is never accepted as a command-line argument — it would land in shell history and any same-user process listing.
delete <name> Remove the stored password for <name>. A missing entry is exit code 1, not an error.
list [--json] List stored server names only — no password value is ever printed, logged, or serialized. (The native findCredentials(service) API returns {account, password} pairs and offers no account-only entry point, so passwords do transit process memory during enumeration; the CLI discards them at the first opportunity and never touches them again.) Enumeration is verified against the Windows Credential Manager; macOS Keychain and libsecret back-ends may enumerate differently. set, delete and test are unaffected.
test <name> [--connect] [--json] Run the real credential chain for <name> (env → OS keychain → credential helper) and report which link resolved it. --connect additionally performs a live Atelier HEAD /api/atelier/ check against the mapped profile, reusing the same health-check path the servers use at startup. --connect needs <name> to map to a connection profile in the shell running the CLI — i.e. IRIS_SERVER_MANAGER=auto (or an IRIS_PROFILES entry), plus IRIS_USERNAME/IRIS_PASSWORD for the reserved default profile. Without them the credential check still reports correctly and only the connectivity half fails, naming this remedy.

A <name> that is empty, whitespace-only, or contains a newline or control character is rejected (exit 2) before the keychain is touched. Use -- to address a name that begins with -, e.g. iris-mcp-credentials delete -- --json.

Every password is stored under OS-keychain service iris-mcp, account <name> — the exact key the credential chain’s OS-keychain link (link 2, above) reads, so a name you set here is immediately usable once IRIS_SERVER_MANAGER=auto/required is set (default off — see the variable table above).

Exit codes are consistent across all four commands: 0 success · 1 not found / credential unresolved / OS keychain unavailable (or a failed --connect check) · 2 usage or input error (unknown command, missing or extra argument, unknown option, invalid server name, empty password, aborted prompt).

--json output is available on list and test. Its contract: an operational outcome (exit 0 or 1) always writes exactly one JSON object to stdout — on failure that is {"error": "..."} for list, and an error field alongside the usual fields for test. Usage errors (exit 2) are always plain text on stderr, because the flags themselves were not understood. Human-readable failures always go to stderr, so ... --json 2>/dev/null | jq works while a human still sees the error.

Every output path — human text, --json, --help, and error/failure messages, including a failed --connect — is secret-free: known secrets are substring-redacted, and a secret too short to redact safely causes the whole message body to be withheld. Unlike the credential chain’s own OS-keychain link (which silently skips to the next link when the native keychain module is unavailable), this CLI’s set/delete/list fail loudly with a non-zero exit and an actionable message, since operating on the OS keychain is the whole point of the command. Run node packages/shared/dist/cli/credentials-cli.js --help for full usage.

iris-mcp-governance CLI

A small command-line tool — ships as a second bin in @iris-mcp/shared, built to packages/shared/dist/cli/governance-cli.js — for validating, inspecting, and editing a governance file with the SAME engine the servers enforce with: every parse goes through the server’s own loader (so validate prints the exact error text a server would fail startup with), and effective/diff compose the shared cascade functions directly rather than reimplementing them. Opt-in and agent-agnostic — with no file configured, the servers are unaffected. Not yet published to npm (see Quick Start), so invoke it directly with node:

node packages/shared/dist/cli/governance-cli.js validate --file C:\governance\iris-policy.json
Command Effect
validate [--file <path>] [--json] Parse and validate the file with the same loader the servers use at startup. Exit 1 (the server’s exact error text, naming IRIS_GOVERNANCE_FILE + the path) when invalid.
get <key> [--profile <name>] [--json] Print the key’s explicit value in the file (true/false/unset) — the file’s own layer, not the cascade.
set <key> true|false [--profile <name>] Write the key into the file (creating the file when missing). Atomic write (temp + rename in the same directory), existing key order preserved, the written file automatically re-validated with rollback on failure. Reserved keys (__proto__ etc.) are rejected; a key outside the frozen baseline warns on stderr but is still written (post-foundation keys like iris_env_promote:execute are legitimate).
unset <key> [--profile <name>] Remove the key from the file. Exit 1 when the key is not set (the iris-mcp-credentials delete not-found convention).
preset read-only|full Print the env-level wiring for IRIS_GOVERNANCE_PRESETwrites nothing: the servers source the preset from their process environment only, never from a governance file.
effective [--profile <name>] [--json] Render the SAME cascade the servers compute (env.profile ?? env.global ?? file.profile ?? file.global ?? presetSeed ?? defaultSeed) for the CLI’s own environment plus the resolved file, with the per-key configSource (env|file|preset|default).
diff [--json] Compare every key the file sets against its default-seed value.
universe [--profile <name>] [--root <path>] [--json] Render the full governed-key universe — the frozen baseline ∪ the five server packages’ registered tool keys (derived from their built dist, the same derivation the suite’s cross-package tests use) ∪ the framework iris_server_profiles tool — with the real mutates/default-enabled classifications, per-key effective value, and configSource. This is the render a running server’s iris_server_profiles computes over its own registered subset; post-foundation write keys seed default-DISABLED here (unlike effective/diff). Needs the packages’ built dist locatable: --root names a monorepo root (or package container dir); otherwise auto-detected from the CLI’s own install location.

Default file resolution--file <path> wins; otherwise IRIS_GOVERNANCE_FILE from the environment. Explicit path only: the CLI never discovers or searches for a file (architecture decision J1). Exit codes: 0 success · 1 operational failure (file unreadable/invalid per the server loader, a key not set for unset) · 2 usage error. --json on the read commands (validate, get, effective, diff, universe) always emits exactly one parseable JSON object to stdout on an operational outcome — including failures; usage errors (exit 2) are always plain text on stderr.

One caveat: the full governance-key universe is the frozen baseline plus each server’s registered tool keys, which validate/get/set/unset/effective/diff cannot enumerate (importing a server package would be a circular dependency). effective/diff therefore render over the baseline plus keys mentioned in any config layer — a post-foundation key mentioned nowhere renders with the read-default seed (enabled) even when a real server would seed it disabled. The universe command closes that gap by deriving the registered half from built dist, and iris_server_profiles on a running server remains the authoritative render. Run node packages/shared/dist/cli/governance-cli.js --help for full usage.

3. Configure Your MCP Client

Point your MCP client at the built server using node and the local dist/index.js path. Replace /path/to/iris-execute-mcp-v2 with the actual path where you cloned the repo.

📖 MCP client configuration index — copy-pasteable config for 13 clients (Claude Code, VS Code/Copilot, Cline, Kimi Code, Kimi CLI, Codex CLI, Cursor, Claude Desktop, Windsurf, Roo Code, Gemini CLI, Zed, Goose), a format/root-key matrix (three formats and four different root keys between them), and the two credential caveats above restated where you need them.

The manager: iris-mcp-clients (recommended)

The @iris-mcp/client-config package ships the iris-mcp-clients CLI — one tool that wires any of the 13 supported clients for you instead of hand-editing config files: it detects which clients are installed, previews the exact edit, writes through a backup-on-write engine, toggles entries, rolls back, and diagnoses the result. Not yet published to npm (see Quick Start), so invoke the built bin directly with node:

node packages/client-config/dist/cli/clients-cli.js detect                    # which clients are installed
node packages/client-config/dist/cli/clients-cli.js diff --client claude-code --servers iris-dev-mcp
node packages/client-config/dist/cli/clients-cli.js apply --client claude-code --servers iris-dev-mcp
node packages/client-config/dist/cli/clients-cli.js doctor                    # env refs, parseability, drift, backups, stashes

apply prints the pending diff first and requires confirmation (--yes to skip); every successful write takes a timestamped backup and prints the client’s restart hint.

The zero-secrets end state — three composable pieces, each covering a different secret class:

  1. Server Manager + OS keychain (Epic 31): --mode server-manager writes entries carrying IRIS_SERVER_MANAGER=auto — connections come from your InterSystems Server Manager profiles, passwords from the OS keychain via iris-mcp-credentials set <name>; nothing secret in the config file.
  2. Governance file (Epic 32): --mode governance-file writes entries carrying IRIS_GOVERNANCE_FILE=<path> — one shared policy file governs every server’s tool surface; the file holds no credentials.
  3. VS Code native inputs (default env-reference mode on VS Code): the password is never stored — the manager merges a native inputs prompt (${input:iris-password}) so VS Code asks once and keeps it out of the file.

Which clients/config surfaces are proven: the adapter certification table (Claude Code, VS Code, Cline and Kimi Code are certified-live as of 2026-07-28; the rest carry explicit fixture-only-with-residual-risk dispositions — re-provable anywhere with node packages/client-config/scripts/certify.mjs run <client> --real-config).

The manual per-client snippets below remain as the fallback (and as the reference for what the manager writes).

Claude Code (.mcp.json)

Create a .mcp.json file in your project root:

{
  "mcpServers": {
    "iris-dev-mcp": {
      "command": "node",
      "args": ["/path/to/iris-execute-mcp-v2/packages/iris-dev-mcp/dist/index.js"],
      "env": {
        "IRIS_HOST": "localhost",
        "IRIS_PORT": "52773",
        "IRIS_USERNAME": "_SYSTEM",
        "IRIS_PASSWORD": "SYS",
        "IRIS_NAMESPACE": "USER"
      }
    }
  }
}

Note: Replace "SYS" with your actual IRIS password. Avoid committing real credentials to version control.

Claude Desktop (claude_desktop_config.json)

Add to your Claude Desktop configuration:

{
  "mcpServers": {
    "iris-dev-mcp": {
      "command": "node",
      "args": ["/path/to/iris-execute-mcp-v2/packages/iris-dev-mcp/dist/index.js"],
      "env": {
        "IRIS_HOST": "localhost",
        "IRIS_PORT": "52773",
        "IRIS_USERNAME": "_SYSTEM",
        "IRIS_PASSWORD": "SYS",
        "IRIS_NAMESPACE": "USER"
      }
    }
  }
}

To add more servers, include additional entries under mcpServers using the same pattern. For example, to add admin tools:

{
  "mcpServers": {
    "iris-dev-mcp": {
      "command": "node",
      "args": ["/path/to/iris-execute-mcp-v2/packages/iris-dev-mcp/dist/index.js"],
      "env": { "IRIS_PASSWORD": "SYS" }
    },
    "iris-admin-mcp": {
      "command": "node",
      "args": ["/path/to/iris-execute-mcp-v2/packages/iris-admin-mcp/dist/index.js"],
      "env": { "IRIS_PASSWORD": "SYS" }
    }
  }
}

All server entry points:

Server Path (relative to repo root)
dev packages/iris-dev-mcp/dist/index.js
admin packages/iris-admin-mcp/dist/index.js
interop packages/iris-interop-mcp/dist/index.js
ops packages/iris-ops-mcp/dist/index.js
data packages/iris-data-mcp/dist/index.js

4. Verify

Ask your AI assistant:

“List the IRIS namespaces” (with @iris-mcp/admin)

or

“Show me the globals in the USER namespace” (with @iris-mcp/dev)

If the assistant returns results from your IRIS instance, you are connected.


Multiple Servers & Governance

Two optional environment variables — IRIS_PROFILES and IRIS_GOVERNANCE — let one MCP server process target several IRIS instances and restrict which tool actions are allowed per instance. Both are JSON values set in your MCP client’s env block (or, for governance, in a JSON file referenced by IRIS_GOVERNANCE_FILE — see Governance file). Neither is required: with both unset, the suite behaves exactly as a single-server, fully-enabled install (see Backward Compatibility below).

Where to put the escaped JSON: because IRIS_PROFILES/IRIS_GOVERNANCE are JSON strings that live inside your client’s JSON config, the inner quotes must be escaped. See the per-client guides for copy-pasteable, correctly-escaped blocks: Claude Code, Claude Desktop, Cursor.

Programmatic config updates: if you write the config file with a JSON serializer (Python json.dump, Node JSON.stringify, etc.), pass IRIS_PROFILES/IRIS_GOVERNANCE as a plain string — the serializer escapes the inner quotes automatically. Do not pre-escape the string yourself; that produces double-escaped output (\\") and the server will fail to parse the value at startup.

Profiles (IRIS_PROFILES)

A profile is a named IRIS instance — a host, port, and credentials. IRIS_PROFILES is a JSON object keyed by profile name:

{
  "prod":  { "host": "iris-prod.example.com",  "port": 443,   "username": "svc_mcp", "password": "...", "namespace": "HSCUSTOM", "https": true },
  "stage": { "host": "iris-stage.example.com", "port": 52773, "username": "svc_mcp", "password": "...", "namespace": "USER" }
}

Each profile may carry host, port, username, password, namespace, and https. Omitted fields are inherited from the reserved default profile (which is synthesized from your IRIS_HOST / IRIS_PORT / IRIS_USERNAME / IRIS_PASSWORD / IRIS_NAMESPACE / IRIS_HTTPS variables). So a profile that only overrides the host inherits the rest of the default’s connection settings.

Selecting a profile per call. Every tool gains an optional server parameter that carries only the profile name — credentials never travel over the wire, they stay in the server process. Omit server and the call runs against the default profile (today’s behavior).

server and the existing per-call namespace parameter compose cleanly: server picks the instance, namespace picks the namespace within it. For example, iris_global_list({ server: "prod", namespace: "%SYS" }) lists globals in %SYS on the prod instance, regardless of prod’s default namespace.

Governance (IRIS_GOVERNANCE)

IRIS_GOVERNANCE is a JSON policy that enables or disables individual tool actions, optionally per profile. It has a global baseline and per-profiles overrides:

{
  "global":   { "": true, ":": false },
  "profiles": { "prod": { ":": false } }
}

A governance key is the tool name for single-operation tools (e.g. iris_metrics_system) or tool:action for multi-action tools (e.g. iris_database_manage:delete). The value is a boolean: true = allowed, false = blocked.

Effective policy for a given action on a given profile resolves in this order:

effective = env.profile(key) ?? env.global(key) ?? file.profile(key) ?? file.global(key) ?? presetSeed(key) ?? defaultSeed(key)

That is: a per-profile IRIS_GOVERNANCE setting wins; otherwise the global IRIS_GOVERNANCE setting; otherwise the governance file’s per-profile setting; otherwise the file’s global setting (see Governance file — with no file configured, both file layers are simply absent); otherwise the preset; otherwise the default seed. All env layers sit above all file layers — a pre-existing IRIS_GOVERNANCE setting can never be overridden by a governance file introduced later.

The default seed (what happens when neither global nor profiles mentions a key):

  • Every existing tool action (everything shipped before governance) → enabled.
  • Every new read action → enabled.
  • Every new write / change action → disabled (opt-in). Newly-added mutating capability stays off until you explicitly enable it.

The “existing action” baseline is generated mechanically from the shipped tool catalog, so no pre-existing action is ever disabled by default.

Enforcement is call-time. The policy is checked in the shared framework after the server profile is resolved and before the tool handler runs — it has to be call-time, because the governing profile is chosen per call via server. All tools stay advertised in tools/list; a blocked action does not run the handler and instead returns a structured error:

{
  "isError": true,
  "structuredContent": {
    "code": "GOVERNANCE_DISABLED",
    "action": "iris_backup_manage:run",
    "server": "prod"
  }
}

(the human-readable text reads action 'iris_backup_manage:run' is disabled by governance policy for server 'prod').

Governance file (IRIS_GOVERNANCE_FILE)

IRIS_GOVERNANCE_FILE points at a JSON file of exactly the same shape as IRIS_GOVERNANCE ({"global": {...}, "profiles": {...}}, booleans only). One file can be referenced from every MCP client’s env block, so a single policy stays portable across Claude Code, Cursor, Copilot, and any other client that can pass a plain environment string — while the servers remain the sole enforcement authority.

{ "env": { "IRIS_GOVERNANCE_FILE": "C:\\governance\\iris-policy.json" } }
  • Default state: unset ⇒ inert. With the variable unset, no file is ever read (zero filesystem access) and behavior is byte-for-byte identical to an install without the feature. This channel is opt-in.
  • Env always wins. The file contributes two cascade layers strictly below both IRIS_GOVERNANCE env layers (env.profile ?? env.global ?? file.profile ?? file.global ?? preset ?? default seed): a file introduced later can never silently override a policy you already set inline.
  • Explicit path only — never discovered. The value is used literally; nothing is searched for in the working directory. A relative path resolves against the server process’s current working directory, which the MCP client chooses — prefer an absolute path.
  • Restart semantics (no hot-reload in v1). The file is read once at startup; edits take effect on the next server restart.
  • Fail-fast, never silently permissive. A missing, unreadable, malformed, or invalid-shape file aborts startup with an error naming IRIS_GOVERNANCE_FILE, the path, and the underlying reason — an operator who pointed at a policy file never runs ungoverned by mistake.
  • Attribution. iris_server_profiles and the iris-governance://<profile> resource report a configSource per key (env | file | preset | default), so you can see which channel resolved each setting.

Manage the file without hand-editing JSON: the iris-mcp-governance CLI (validate / get / set / unset / effective / diff) uses the same loader and cascade the servers enforce with, so what you write is what the servers will compute.

Read-only mode — point it at production with one environment variable

Point it at production in read-only mode with one environment variable. Set IRIS_GOVERNANCE_PRESET=read-only and every write-classified tool action — across all five servers — is blocked, while every read action keeps working, with zero IRIS_GOVERNANCE JSON to write:

{ "env": { "IRIS_GOVERNANCE_PRESET": "read-only" } }

What it does:

  • Blocks every write. Any action a tool’s mutates classification marks "write" (deletes, creates, sets, starts/stops, purges, defaultEnabled writes like iris_production_control:clean included — read-only means read-only, there is no “but this one’s safe” exception) is denied.
  • Allows every read. Queries, lists, gets, status/health checks, diagnostics — anything classified "read" — run exactly as they do today.
  • Sits UNDER your explicit IRIS_GOVERNANCE overrides, never over them. The cascade is profile.explicit ?? global.explicit ?? preset ?? defaultSeed: an explicit true in IRIS_GOVERNANCE still wins over read-only if you deliberately want one specific write enabled even in a read-only deployment (and an explicit false still wins too — it just doesn’t need to, since the preset already denies it).
  • Explains itself. A call blocked because of the preset (not an explicit false) returns structuredContent.presetApplied: "read-only" alongside the standard GOVERNANCE_DISABLED denial, so an operator or AI client can tell “blocked by the preset” apart from “blocked by an explicit override” at a glance.
  • Is opt-in and additive. IRIS_GOVERNANCE_PRESET unset (or set to "full", an explicit alias for today’s behavior) is byte-for-byte the pre-preset suite — nothing changes until you set it.

Pair it with the SQL resource caps for an extra safety margin on iris_sql_execute against a large production table: IRIS_SQL_MAX_ROWS (a ceiling on the number of rows a call returns — the response carries rowsCapped: true when it clamps a caller’s request; note this bounds the returned row count post-fetch, not the server-side result set or transfer) and IRIS_SQL_TIMEOUT (a per-request timeout in seconds). Both are independent of the preset — they apply to iris_sql_execute regardless of IRIS_GOVERNANCE_PRESET — and both are opt-in (unset = no cap, today’s behavior):

{ "env": { "IRIS_GOVERNANCE_PRESET": "read-only", "IRIS_SQL_MAX_ROWS": "1000", "IRIS_SQL_TIMEOUT": "30" } }

See the client configuration index — covering 13 clients — or the detailed guides for Claude Code, Claude Desktop and Cursor, for copy-pasteable env blocks.

Worked example — enable a write action globally, block it on prod

Suppose you want the run action of iris_backup_manage (a write action shipping in a later release — the canonical example) available everywhere except the prod profile, where backups are managed out-of-band. Set:

{
  "global":   { "iris_backup_manage:run": true },
  "profiles": { "prod": { "iris_backup_manage:run": false } }
}

Result, by the cascade above:

  • iris_backup_manage({ action: "run" }) → uses defaultglobal says trueallowed.
  • iris_backup_manage({ action: "run", server: "stage" })stage has no override → global says trueallowed.
  • iris_backup_manage({ action: "run", server: "prod" })prod override says falseblocked (GOVERNANCE_DISABLED).

The same shape governs any action you can name today. To try it against a write action that exists in the current release, substitute iris_database_manage:delete for iris_backup_manage:run — e.g. "profiles": { "prod": { "iris_database_manage:delete": false } } blocks database deletion on prod while leaving it enabled elsewhere. The JSON shape is identical; only the key changes.

Discovering profiles and policy (call this first)

Every server provides a framework tool, iris_server_profiles, that an AI client should call first to learn its operating environment without reading the client’s config files:

  • Profile roster — for each configured profile: name, isDefault, host, port, username, namespace, https, baseUrl, timeout, source ("env" or "server-manager" — where the connection fields came from), and sourceFile (the settings file, for a "server-manager"-sourced profile only). The password is never included (an allow-list of non-secret fields). Use this to pick the right server profile for subsequent calls.
  • Effective governance policy — the enabled/disabled action map for a selected profile (optional profile arg; defaults to default), or for every profile with allProfiles: true. Computed from the same engine the governance resource uses, so the two never disagree.

It does not connect to IRIS (it reports in-memory config), so it works even when the target instance is unreachable. It is a read tool, enabled by default — an operator can still disable it explicitly via IRIS_GOVERNANCE. The same call-first guidance is also surfaced via the MCP server instructions field at connect time.

Note: the tool’s optional profile arg selects which profile’s policy to report; the framework server arg (which selects the connection target on every other tool) is irrelevant here, since discovery does not connect.

Inspecting effective policy — the governance resource

The suite exposes an MCP resource (alongside its tools) so a client can read the effective policy for a profile before attempting a call:

  • iris-governance://default — the default/global effective policy (also listed in resources/list).
  • iris-governance://{profile} — the effective policy for any named profile (a resource template). Its list callback enumerates one concrete iris-governance://<profile> entry per configured profile, so resource-reading clients can also discover the profile roster by name via resources/list.

Reading the resource returns the effective policy map as JSON. It is advisory — a convenience so a client can avoid issuing calls it knows are blocked. The call-time gate remains the authoritative boundary; the resource never grants or denies anything on its own.

Default-disabled write actions in the current release

Per the default-seed rule above, the new write actions added after governance shipped are disabled by default (opt-in via IRIS_GOVERNANCE); their sibling reads are enabled by default. The set shipped to date:

Server Tool Default-disabled (write) actions Default-enabled (read/pre-existing)
admin iris_service_manage enable, disable, set list, get
admin iris_ldap_manage create, modify, delete list, get, test
admin iris_x509_manage import, delete list, get
admin iris_audit_manage enable, disable, configureEvent, purge, export status, view
admin iris_resource_manage (SQL privileges) grant, revoke listPrivileges (resource create/modify/delete are pre-governance, enabled)
ops iris_process_manage terminate, suspend, resume get
ops iris_database_action mount, dismount, compact, defragment, truncate, expandVolume (all six)
ops iris_backup_manage run, freeze, thaw listHistory
interop iris_default_settings_manage set, delete list, get
interop iris_production_item add, remove (new) enable, disable, get, set (pre-governance baseline)
interop iris_message_diagram — (flat read tool, no actions) whole tool (message-trace Mermaid diagrams, Epic 21)
interop iris_message_resend resend, resendFiltered (message resend/replay, Epic 26) preview
dev iris_sql_analyze — (all five actions are reads) explain, stats, indexUsage, running, advise (Epic 28)
dev iris_loc_count — (flat read tool, no actions) whole tool (namespace lines-of-code metrics, Epic 22)
dev iris_env_diff — (flat read tool, no actions) whole tool (cross-profile environment drift detection, Epic 27)
dev iris_env_promote execute plan

Every pre-governance tool action (everything shipped before the governance layer) stays enabled by default. The authoritative per-tool catalog with endpoints and governance notes is https://github.com/jbrandtmse/iris-execute-mcp-v2/blob/main/tool_support.md.

“Write, default-enabled” actions (the defaultEnabled mechanism)

A small number of new write actions ship enabled by default even though they are truthfully classified write. This is the defaultEnabled mechanism (Epic 20, architecture decision F2): a tool can mark specific write actions as default-enabled so a recovery/operational action an operator expects available does not require an opt-in, without mislabelling it as a read and without modifying the frozen governance baseline. Such an action still carries mutates: "write" (and its truthful destructiveHint), and an operator can still disable it with an explicit IRIS_GOVERNANCE override — the cascade honors an explicit false.

Server Tool Write action, but enabled by default Why
interop iris_production_control clean Recovery operation (unwedge a stopped production); parity with the grandfathered lifecycle actions. Its destructive killAppData persistent-wipe is separately double-gated behind confirm:true.

Absent any tool opting in, this mechanism is inert (the governance seed is byte-for-byte its pre-F2 behavior — every other new write still default-disabled).

iris_env_promote:execute safety model (Epic 27)

iris_env_promote’s execute action is a genuine environment-mutating write, so it carries a richer safety story than a single default-disabled flag — worth its own callout:

  • Default-disabled, not defaultEnabled. Unlike iris_production_control:clean (a recovery-of-last-resort action, Epic 20), execute is a real promotion write and deliberately does not use the defaultEnabled mechanism above — enable it explicitly via IRIS_GOVERNANCE, e.g. {"global": {"iris_env_promote:execute": true}}.
  • No-deletions guarantee. onlyInTarget diff entries (something exists on the target only) are always emitted as informational warnings, never as steps. No delete/remove operation exists anywhere in any plan, in this or any future version — the one exception is updateMapping’s intra-step delete+create replace of a mapping the source also has (Config.cls has no in-place update); it never targets a target-only item.
  • Secrets exclusion. A System Default Settings value whose setting name looks credential-ish (password/secret/key/token/pwd/passphrase/credential/cert/private/salt, case-insensitive) is redacted in both iris_env_diff and iris_env_promote output — the plaintext never appears in any tool result, on either the plan or the execute path (including error messages). Credentials/users/roles promotion is out of scope entirely.
  • Four refuse-before-any-write gates, each mutating nothing on failure: (1) confirm: true required; (2) a non-empty steps allowlist whose every index exists in plan.steps; (3) plan-hash freshness — the SAME diff that produced plan is re-hashed and compared, refusing a stale plan; (4) the target profile’s own governance policy must enable every write family the allowlisted steps use (iris_doc_put/iris_doc_compile, iris_mapping_manage:create/:delete, iris_default_settings_manage:set, iris_webapp_manage:modify, iris_config_manage:set) — this is what stops a caller on an unrestricted profile from writing into a governance-locked target, independent of the outer iris_env_promote:execute gate.

Backward Compatibility

Existing single-server IRIS_* setups require no changes. This is a release-gate promise:

  • With neither IRIS_PROFILES nor IRIS_GOVERNANCE set, behavior is byte-for-byte identical to before — one instance from your IRIS_* vars, every tool enabled.
  • With IRIS_GOVERNANCE_PRESET unset (the default), the governance cascade’s preset layer is a pure pass-through — behavior is unchanged whether or not IRIS_GOVERNANCE/IRIS_PROFILES are set.
  • With IRIS_SQL_MAX_ROWS/IRIS_SQL_TIMEOUT unset (the default), iris_sql_execute is byte-for-byte today’s behavior — no rowsCapped field, no per-request timeout override.
  • With IRIS_AUDIT_LOG unset (the default), the tool-call audit log is a mechanical no-op — no audit file is created, no fs write is attempted, and every tool call’s result is byte-for-byte identical to before the audit interceptor existed.
  • The server parameter is an optional addition to each tool’s input schema. Calls that omit it are unchanged; existing prompts and automations keep working.
  • No BOOTSTRAP_VERSION change is involved — these are TypeScript-layer capabilities; nothing on the IRIS side changes.

Tool Visibility Presets

Trim any server to a ~10-tool core for small models with one environment variable:

IRIS_TOOLS_PRESET=core                    # ~10-tool everyday subset per server (small-model sweet spot)
IRIS_TOOLS_PRESET=developer               # persona filter: dev-relevant tools, security/enterprise admin hidden
IRIS_TOOLS_PRESET=full                    # explicit alias for today's behavior (default when unset)
IRIS_TOOLS_DISABLE=iris_doc_export,iris_env_*   # hide individual tools / families
IRIS_TOOLS_ENABLE=iris_env_diff           # punch a hole: re-show a tool the preset/disable hid

A hidden tool is never registered with the MCP SDK: it is absent from tools/list, and calling it by name returns the SDK’s standard unknown-tool error — indistinguishable from a tool that never existed, zero context cost. IRIS_TOOLS_PRESET unset (the default, full) is byte-for-byte today’s tools/list for every server — a release-gate promise, mechanically proven by a back-compat snapshot test plus a live smoke against the built dist/ output (Epic 30 closing story).

Visibility vs. governance — two orthogonal layers

Tool visibility (this section) is a different concern from governance, and the two compose in a specific order:

Visibility (this section) Governance (existing, unchanged)
Question “Does the agent know this exists?” “Is this call allowed?”
Granularity per tool per action (tool / tool:action)
Enforcement point registration (never enters the SDK registry / tools/list) dispatchToolCall runtime gate
Failure surface standard MCP unknown-tool error structured GOVERNANCE_DISABLED error
Purpose context ergonomics, tool-selection accuracy safety / authorization

Rules of interaction:

  • Visibility is evaluated first, at server construction: a hidden tool can never reach the governance gate — it was never registered.
  • An IRIS_GOVERNANCE key naming a hidden tool is legal and inert (the same IRIS_GOVERNANCE JSON is shared across all 5 servers, and governance already tolerates keys for tools a given server doesn’t host — this is unchanged, just now also true for a tool your preset hid).
  • Visibility is per-tool only. Hiding a single action of a multi-action tool (e.g. only iris_env_promote:execute, not :plan) remains governance’s job — no schema surgery on any action enum.
  • Governance is the safety layer; visibility is ergonomics. read-only safety still means IRIS_GOVERNANCE_PRESET=read-only — there is no “safe” visibility preset, and hiding a tool is never a substitute for disabling its write actions.
  • Per-profile visibility is impossible by protocol. An MCP server process serves exactly one tools/list regardless of which IRIS_PROFILES connection a call later targets. Visibility env vars are process-global — unlike IRIS_GOVERNANCE, there is no profiles sub-structure here.

Env vars & resolution

  • IRIS_TOOLS_PRESETfull (default) | core | developer. An unrecognized value fails fast at startup, naming the valid values.
  • IRIS_TOOLS_DISABLE — comma-separated tool names to hide. A trailing-* wildcard matches a whole family (iris_doc_*); a bare * alone is rejected (use IRIS_TOOLS_PRESET for that). Whitespace is trimmed.
  • IRIS_TOOLS_ENABLE — comma-separated tool names to force-show, overriding both the preset and IRIS_TOOLS_DISABLE. Same syntax. This is what makes the “family except one” pattern work: IRIS_TOOLS_DISABLE=iris_doc_* + IRIS_TOOLS_ENABLE=iris_doc_get hides every iris_doc_* tool except iris_doc_get.

Resolution precedence per tool: ENABLE > DISABLE > preset > default-visible.

Edge-case semantics (all deterministic):

  • The same literal tool name in both IRIS_TOOLS_DISABLE and IRIS_TOOLS_ENABLE → a startup warning (ENABLE wins per precedence) — not an error, since a wildcard/literal overlap is the intended hole-punch usage.
  • An unknown tool name in either list → a startup warning, not an error (the same env block is shared across all 5 servers, so a name valid on another server is “unknown” here). A wildcard matching zero registered tools also warns.
  • iris_server_profiles is reserved and always visible — it is the discovery surface every server’s MCP instructions say to call first, and the diagnostic for this very feature. A wildcard that would match it silently skips it; naming it literally in IRIS_TOOLS_DISABLE fails startup (a deliberate misconfiguration, not cross-server sharing).
  • Hiding every package tool is legal (the server then serves only iris_server_profiles) but logs a startup warning.
  • Matching is case-sensitive exact/prefix; tool names are already all-lowercase iris_*.

iris_server_profiles reports the active configuration back to the agent: a toolVisibility block — { "preset": "core" | "developer" | "full", "visibleTools": <n>, "hiddenTools": <n> } — deliberately never the hidden tool names (invisible means invisible; diagnose “why is tool X missing?” via this count plus the env vars and the roster tables below). The effective-governance report (in iris_server_profiles and the iris-governance://{profile} resource) also omits keys belonging to hidden tools, so the agent’s view of both layers stays self-consistent. A startup stderr log line records the active preset plus visible/hidden counts and any of the warnings above.

The rosters

Design intents differ per preset:

  • core attacks the tool-count cliff (research: LLM tool-selection accuracy degrades sharply past ~20 tools per server) — the everyday ~80% loop, ≤13 runtime tools per server, tuned for small/cheap models. Destructive-and-rare and bulk/specialist tools are hidden.
  • developer attacks persona relevance — everything a developer touches (full dev server, production lifecycle + monitoring, namespace/database/webapp self-service config, runtime/task tools, all data tools) while hiding security & enterprise administration (users/roles/resources/SSL/OAuth/LDAP/X509/audit, backup/mirror/ECP). Counts stay above the cliff on dev/interop under developer — that’s accepted; core is the count answer.

iris_server_profiles is additionally visible on every server under every preset (the +1 in every “runtime” count below). Two tools designed as a unit — iris_env_diff / iris_env_promote — are always co-visible in every preset (never one without the other, so an agent is never stranded mid-workflow).

Preset dev admin interop ops data Package total Runtime total
full (default) 28 26 22 21 7 104 109
developer 28 10 22 9 7 76 81
core 12 12 9 9 7 49 54

Every core server lands at ≤13 runtime tools — inside the researched 5-15-tool window. The exact per-tool include/exclude disposition for each server is declared explicitly in that package’s src/tools/presets.ts (transcribed from and kept in lockstep with research/feature-specs/11-tool-visibility-presets.md#2.5 — the authoritative per-tool table; a mismatch fails a per-package unit test AND a construction-time assert, so it cannot drift silently). In short:

  • @iris-mcp/dev core = the authoring loop (get/put/list/compile/load) + execution & debug loop (command/classmethod/tests, global get/set/kill) + SQL execute. developer = the full 28-tool server (every dev tool is dev-relevant).
  • @iris-mcp/admin core = the everyday admin loop (namespaces, databases, users, webapps, permission checks). developer = only what developers self-serve (namespace/db/mapping/webapp config) — no user/security administration.
  • @iris-mcp/interop core = the troubleshoot-a-production loop (status/summary/control, item config, logs/queues/messages, trace diagram, resend) — iris_production_control stays visible so the recover-first MCP guidance holds under every preset. developer = the full 22-tool server.
  • @iris-mcp/ops core = monitoring-persona basics (health, system metrics, alert metrics, jobs/locks/processes, tasks, license). developer = the runtime debugging slice (interop metrics + task history) — no backup/mirror/ECP/config surface.
  • @iris-mcp/data — already inside the 5-15 sweet spot: both core and developer include all 7 tools.

Measured tools/list payload (per server × preset)

Produced by pnpm measure:tools-payload (https://github.com/jbrandtmse/iris-execute-mcp-v2/blob/main/scripts/measure-tools-payload.mjs), which constructs a real server per preset and drives the real tools/list request handler — the bytes below are exactly what a connected client receives (the SDK’s own Zod→JSON-schema tool conversion), not a hand-rolled estimate. ~tokens is a bytes / 4 heuristic (no tokenizer dependency).

Server full (count / bytes / ~tokens) core (count / bytes / ~tokens) developer (count / bytes / ~tokens)
@iris-mcp/dev 29 / 53,404 / ~13,351 13 / 17,749 / ~4,437 29 / 53,404 / ~13,351
@iris-mcp/admin 27 / 44,873 / ~11,218 13 / 16,613 / ~4,153 11 / 15,973 / ~3,993
@iris-mcp/interop 23 / 38,332 / ~9,583 10 / 20,103 / ~5,026 23 / 38,332 / ~9,583
@iris-mcp/ops 22 / 30,563 / ~7,641 10 / 13,565 / ~3,391 10 / 14,016 / ~3,504
@iris-mcp/data 8 / 10,937 / ~2,734 8 / 10,937 / ~2,734 8 / 10,937 / ~2,734

@iris-mcp/dev/interop show no fulldeveloper reduction (their developer roster includes every tool); @iris-mcp/data is unaffected by any preset (its 7 tools are all core+developer-visible). core is the biggest win across every server that defines one — up to ~67% fewer bytes (@iris-mcp/dev: 53,404 → 17,749).


Compliance & Auditability

For regulated environments — healthcare, finance, or any shop that has to answer “what did the AI actually do to this system?” — the suite ships an opt-in, secrets-free tool-call audit log: a structured JSONL record of every MCP tool call, across all five servers, switched on with a single environment variable.

What gets recorded. Every tool call — whether it succeeded, errored, or was blocked by governance — becomes one JSON line:

{"ts":"2026-07-11T14:03:22.117Z","session":"1f2e3a9c-...","seq":42,"serverPkg":"@iris-mcp/ops","tool":"iris_database_action","action":"truncate","profile":"prod","namespace":"HSCUSTOM","outcome":"denied","denyReason":"GOVERNANCE_DISABLED","presetApplied":"read-only","durationMs":3,"paramKeys":["action","database"]}

session is a UUID generated once per server process, so every line from one running server shares it; seq is a per-session monotonic counter (1, 2, 3, …), so a log file can be replayed in exact call order even under concurrent calls. outcome is one of ok, error (the entry’s error field carries the same sanitized message the caller received — never a raw stack trace or an internal ^global reference), or denied (the governance gate blocked it — see Multiple Servers & Governance — with a structured denyReason and, when a safety preset rather than an explicit override caused the block, presetApplied). Each entry also carries profileSource"server-manager" when the resolved profile came from Server Manager connections, "env" for an IRIS_*/IRIS_PROFILES profile — so an auditor can filter by where a connection was defined, not just by its name. It mirrors the roster’s source field exactly and is attribution only: it is never an input to any governance decision. The field is omitted entirely in one case only — when the call’s server parameter named a profile that does not exist in the registry (an unknown-profile call, which is rejected). So treat a missing profileSource as “no profile resolved”, not as "env".

Secrets-free by construction, not by convention. Before an entry is ever written — before it even reaches the write queue — the logger recursively walks the call’s arguments (through nested objects and arrays of objects) and replaces the value of any key that looks like a credential (password, passwd, secret, token, credential, apikey/api_key, authorization — case-insensitive) with "[REDACTED]", and truncates any other string over 2 KB to its first 256 characters. IRIS_AUDIT_LOG_PARAMS defaults to false: the safest posture, logging parameter key names only and never writing a single parameter value, redacted or not. Setting it to true includes the (still-redacted) values for deeper forensics.

A crashing or full disk never breaks a tool call. Writing to the audit file is fire-and-forget: if the sink becomes unwritable mid-session, the write is swallowed, counted, and logged once as a warning — the tool call itself always completes and returns to the client normally. A startup-time misconfiguration is treated differently and deliberately less forgiving: if IRIS_AUDIT_LOG is set but its directory isn’t writable, the server fails fast at launch naming the variable — an operator who asked for auditing must never end up silently running unaudited. On shutdown, the writer flushes its queue and appends a final line recording how many entries (if any) were dropped due to a degraded sink.

Governance cannot turn this off. This is deliberate, and it is the whole point for a regulated deployment: IRIS_AUDIT_LOG is server-side configuration, set by whoever deploys the MCP server — it is not a tool and not a governed action, so there is no IRIS_GOVERNANCE key for it and none will be added. An AI client operating through the MCP protocol has no path to disable or tamper with its own audit trail; only an operator with access to the server’s environment can turn it off, by unsetting IRIS_AUDIT_LOG and restarting.

Disambiguation — this is not iris_audit_manage / iris_audit_events. The suite already ships two IRIS server-side security-audit tools: iris_audit_manage (@iris-mcp/admin) manages IRIS’s own %SYS.Audit* security-audit subsystem (login events, privilege changes, and similar — a feature of the IRIS instance itself), and iris_audit_events (@iris-mcp/ops) reads events from that same IRIS-native audit database. IRIS_AUDIT_LOG is a completely different thing: it is the MCP server process’s own record of the tool calls an AI client made through it. It has nothing to do with IRIS’s built-in auditing feature, doesn’t read from or write to %SYS.Audit*, and works whether or not IRIS-native auditing is enabled at all. Don’t conflate the two when scoping a compliance review — they answer different questions (“what did IRIS’s security subsystem observe” vs. “what did the AI, through this MCP server, actually do”).

See the client configuration index — covering 13 clients — or the detailed guides for Claude Code, Claude Desktop and Cursor, for copy-pasteable env blocks.


Workflow Prompts & Agent Skills

Beyond individual tools, the suite ships a pack of MCP prompts (Epic 25) — parameterized, workflow-shaped instructions that teach an MCP client the sequence of tool calls an expert would use for a task, not just the tools themselves. This is a separate MCP protocol capability from tools: prompts are discoverable via prompts/list and rendered via prompts/get, on any client that supports the MCP prompts capability. A server only advertises prompts when it has at least one registered — servers with none behave exactly as before (Rule #19 back-compat).

Prompts do not change the 104-tool count anywhere. They are a framework/protocol surface, not tools — no mutates classification, no governance key, no package tool-array change (Rule #31). See Backward Compatibility above.

The v1 pack — 11 prompts, grouped by owning server

Server Prompt What it does
@iris-mcp/ops check-system-health Runs iris_health_check, interprets every non-ok finding, and names the fixing tool for each one.
@iris-mcp/ops run-external-backup Freezes the instance for an external (OS/SAN-level) snapshot and thaws it safely afterward — thaw always runs, even if the snapshot step failed.
@iris-mcp/dev diagnose-slow-query Runs iris_sql_analyze (explainindexUsagestats) and recommends a fix — never auto-applies one.
@iris-mcp/dev objectscript-review A concise pre-write checklist distilling this project’s ObjectScript conventions ($$$ macros, Quit in try/catch, %OnNew/initvalue, no-underscore names, storage sections untouchable).
@iris-mcp/dev deploy-and-test-class Deploys an ObjectScript class or package (iris_doc_load, glob-path form), resolves compile errors, then runs its unit tests (iris_execute_tests) with a total-count check.
@iris-mcp/dev promote-environment-change Reviews and promotes configuration/code drift from a source IRIS environment to a target using the review-before-write iris_env_diffiris_env_promote workflow — scoped diff, review with the user, plan, an explicit user-selected step allowlist, confirmed execute, then re-diff to verify. Never acts on onlyInTarget warnings; states the no-deletions guarantee and that execute is default-disabled.
@iris-mcp/interop trace-message-flow Traces a message’s flow through a production using iris_production_messages, iris_message_diagram, and iris_production_logs for any erroring items.
@iris-mcp/interop recover-stuck-production Diagnoses and recovers a troubled/wedged production, following the recover-first, clean-last-resort escalation ladder — never suggests killAppData without the user’s explicit acceptance of persistent business-state loss.
@iris-mcp/interop resend-failed-messages Resends failed messages for a config item using the dry-run-first iris_message_resend workflow — preview the match count, review with the user, execute only on explicit approval, then verify the new headers. States the duplication hazard and that the write actions are default-disabled.
@iris-mcp/admin provision-project-environment Provisions a new project environment (two databases, a namespace, a user, a web application), verifying each step before the next, with rollback notes.
@iris-mcp/admin audit-security-posture Audits users, roles, service authentication settings, SSL/TLS configs, and instance auditing status; reports default passwords, %All holders, and insecure services.

@iris-mcp/data ships no prompts in v1.

Using the prompts

  • Via the MCP protocol directly (recommended when your client supports it): call prompts/list on the relevant server to see its prompts, then prompts/get with the prompt’s name and any arguments to render the workflow text.
  • As installable Agent Skills: the same content is generated into a repo-root skills/ directory — one SKILL.md per prompt, with YAML frontmatter (name, description) and the workflow body. Copy the skills you want into your project’s .claude/skills/ directory (see https://github.com/jbrandtmse/iris-execute-mcp-v2/blob/main/skills/README.md for details). Every tool name referenced in a prompt or skill is validated against the live tool catalog in CI, so a renamed or removed tool breaks the build rather than shipping a broken workflow.

Architecture

All five servers share a common connection layer (@iris-mcp/shared) that handles:

  • HTTP(S) connection to the IRIS web port using Basic Auth
  • Session cookie reuse and CSRF token handling for efficient request batching
  • Atelier REST API (built into IRIS) for document and code operations
  • Custom REST dispatch (ExecuteMCPv2.REST.Dispatch) for execution, globals, security, interoperability, and analytics — auto-bootstrapped, and self-healing, on connection (see Known Limitations)
  • Built-in IRIS REST APIs for DocDB (/api/docdb/v1/) and REST management (/api/mgmnt/v2/)

Servers communicate over the MCP protocol (spec v2025-11-25) using either stdio or Streamable HTTP transport. Every tool returns both structuredContent (machine-readable) and text content (human-readable), and includes tool annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) so clients can make informed decisions about tool usage.

┌────────────────────────────────────────────────────────┐
│                    MCP Client                          │
│          (Claude, Copilot, Cursor, etc.)               │
└──────────┬──────────┬──────────┬──────────┬───────────┘
           │          │          │          │
     ┌─────▼──┐ ┌─────▼──┐ ┌────▼───┐ ┌───▼────┐ ┌─────▼──┐
     │  dev   │ │ admin  │ │interop │ │  ops   │ │  data  │
     │(28)    │ │(26)    │ │(22)    │ │(21)    │ │(7)     │
     └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘
         │          │          │          │          │
         └──────────┴──────┬───┴──────────┴──────────┘
                           │
                  ┌────────▼────────┐
                  │  @iris-mcp/shared │
                  │  (HTTP client,   │
                  │   auth, config)  │
                  └────────┬────────┘
                           │  HTTP(S)
                  ┌────────▼────────┐
                  │  InterSystems   │
                  │     IRIS        │
                  └─────────────────┘

Per-Package Documentation

Each server has its own README with a complete tool reference:


Known Limitations

Web Application Gateway Registration

When the MCP server auto-bootstraps its custom REST endpoint, it creates the web application via Security.Applications.Create(). However, this ObjectScript API call does not notify the CSP Gateway of the new application. As a result, requests to the new web app may return 404 until one of the following steps is taken:

  1. Save via System Management Portal (SMP): Navigate to System Administration > Security > Applications > Web Applications, open the newly created web application, and click Save. This triggers the gateway registration automatically.
  2. Restart the CSP Gateway: If SMP access is not available, restart the CSP Gateway service (or restart the IRIS instance) to force the gateway to reload its application table.

Migrated or %SYS-Reset Instances (Self-Healing)

The auto-bootstrap detects deployment state by checking both the deployed class version and whether the /api/executemcp/v2 web application is actually registered — not the class version alone. This matters when an instance’s %SYS state diverges from the code database that holds the ExecuteMCPv2 classes:

  • Container migration / %SYS restore / remounting the code DB into a fresh instance. The ExecuteMCPv2.* classes (and their embedded version stamp) live in the namespace’s code database and survive intact, but the web-application registration lives in %SYS and is lost. The result is “class version present, web app absent” — which the class-version check alone cannot detect.
  • A first install whose privileged Configure step failed (e.g., the connecting user lacked %Admin_Manage) leaves the same state.

On the next server start, the bootstrap recognizes this unconfigured state and self-heals: it re-registers the web application (and package mapping), then recompiles the classes. The recompile is deliberate — a class-version hash matching the build proves the source is current, but it does not prove the compiled objects are valid. A code database migrated across IRIS versions keeps the source while carrying stale or version-incompatible compiled objects, which otherwise dispatch as <NULL VALUE> HTTP 500 errors until recompiled. No manual steps are required, provided the connecting user has %Admin_Manage.

If the connecting user lacks %Admin_Manage, the web application cannot be created; the bootstrap reports configured: false with manual instructions, and a later launch by a privileged user self-heals automatically.


License

MIT

Contributing

Contributions are welcome. Please open an issue to discuss proposed changes before submitting a pull request.

Last checked by moderator
20 May, 2026Impossible to Test
Made with
Version
0.1.015 Apr, 2026
Category
Developer Environment
Works with
InterSystems IRISInterSystems IRIS for HealthHealthShareEnsemble
First published
16 Apr, 2026
Last edited
16 Apr, 2026