This guide covers how to install, configure, and run iris-mcp-server to expose IRIS tools to LLM clients via the Model Context Protocol (MCP).
For detailed information about creating tools and toolsets in ObjectScript, see the ObjectScript SDK User Guide.
Note
Some of the advanced features explained in this guide, including but not limited to Vault integration, multi-server setup and the container version of this tool are experimental and may change significantly between builds, or may not be part of the build you downloaded.
iris-mcp-serverUser Guide- Table of Contents
- Installation
- Overview
- Quick Start
- Architecture
- Configuring
iris-mcp-server - Transport Modes
- Security & Credentials
- InterSystems IRIS Backend Setup
- Service Discovery
- The
iris_statusDiagnostic Tool - Smart Discovery (RAG)
- Monitoring & Telemetry
- Production Deployment
- Troubleshooting
- Next Steps
- Migrating from
iris-mcp-serverVersion 1
iris-mcp-server is a standalone binary included in the bin directory of your IRIS installation. No installation step is required — copy or reference it directly.
Upgrading from v0.1? The configuration file format changed in v2 (as of build ~140). See Migrating from v0.1 Config at the end of this guide.
iris-mcp-server is a Rust-based MCP gateway that bridges LLM clients (Claude Desktop, remote MCP clients) to IRIS MCP Server endpoints, handling tool discovery and execution transparently.
┌─────────────────────┐
│ Claude Desktop │ (LLM Client)
│ or other MCP │
│ compatible client │
└──────────┬──────────┘
│ MCP Protocol (stdio or HTTPS streaming)
▼
┌─────────────────────┐
│ iris-mcp-server │ (Rust binary)
│ │
│ - MCP protocol │
│ - Tool discovery │
│ - Connection pool │
│ - RAG discovery │
└──────────┬──────────┘
│ InterSystems IRIS Web Gateway Protocol
▼
┌─────────────────────┐
│ %AI.MCP.Service │ (InterSystems IRIS ObjectScript)
│ │
│ - Tool dispatch │
│ - Policy layer │
│ - ToolManager │
└──────────┬──────────┘
▼
┌─────────────────────┐
│ Your Tools & │
│ ToolSets │
└─────────────────────┘
iris-mcp-server uses the native Web Gateway Transport Protocol (wgproto) to communicate to InterSystems IRIS; no external web server is required.
- Create an MCP service in InterSystems IRIS (see InterSystems IRIS Backend Setup for details):
Class MyApp.MCP.SimpleService Extends %AI.MCP.Service
{
Parameter SPECIFICATION As STRING = "MyApp.Tools.Calculator";
}-
Create an MCP server that points to your service class. To do this, open the InterSystems IRIS Management Portal and go to System Administration > Security > Applications > MCP Servers and click on Create New MCP Server.
For example, you can create an MCP server called
/mcp/simplethat points to theMyApp.MCP.SimpleServicedispatch class:- Name:
/mcp/simple - Dispatch Class:
MyApp.MCP.SimpleService - Authentication: Password (or Unauthenticated for development; OAuth 2.0 for Remote MCP with Bearer tokens)
- Name:
-
Run
iris-mcp-server. To run with a configuration file (recommended):iris-mcp-server.exe --transport=stdio --config=config.toml run
To run with a configuration file:
iris-mcp-server.exe --config=config.toml run
Example minimal
config.toml:[mcp] transport = "stdio" [[iris]] name = "local" server = { host = "localhost", port = 1972, username = "CSPSystem", password = "SYS" } pool = { min = 2, max = 5 } endpoints = [ { path = "/mcp/simple" }, ] [logging] level = "info" output = "file" file = "iris-mcp.log"
-
Configure Claude Desktop to point to
iris-mcp-serverby adding the following to your%APPDATA%\Claude\claude_desktop_config.json:{ "mcpServers": { "iris": { "command": "C:\\path\\to\\iris-mcp-server.exe", "args": [ "--config=C:\\path\\to\\config.toml", "run" ] } -
Restart Claude Desktop. A gear icon appears when MCP servers are active. Claude logs are in
%APPDATA%\Claude\logs.
┌─────────────────────────┐
│ MCP Protocol │ (JSON-RPC 2.0)
├─────────────────────────┤
│ iris-mcp-server │ (Rust — protocol translation)
├─────────────────────────┤
│ STP Protocol │ (JSON: request / response / error)
├─────────────────────────┤
│ wgproto │ (IRIS native gateway protocol — binary)
├─────────────────────────┤
│ TCP │
└─────────────────────────┘
Tool discovery:
LLM Client → MCP list_tools → iris-mcp-server
iris-mcp-server → wgproto GET /v1/services → InterSystems IRIS
InterSystems IRIS → ToolManager.%Discover() → JSON tool catalog
iris-mcp-server → MCP response → LLM Client
Tool execution:
LLM Client → MCP call_tool → iris-mcp-server
iris-mcp-server → wgproto+ws /v1/ws → InterSystems IRIS
InterSystems IRIS → ToolManager.ExecuteTool() → (Auth → Execute → Audit) → result
result → STP response → iris-mcp-server
iris-mcp-server → MCP response → LLM Client
The behavior and features of iris-mcp-server are determined by a combination of CLI flags, a .toml configuration file, and its default settings. When the same value is specified in multiple locations, the highest priority (shown below) source applies:
CLI flags > TOML config file > built-in defaults
Credentials in the .toml file are not overridden by environment variables directly. Instead, use @(env:VAR) references inside the .toml file to read the environment variables during startup.
# ── MCP Transport ─────────────────────────────────────────────────────────────
[mcp]
transport = "stdio" # stdio | http | https | sse
host = "0.0.0.0" # bind address for HTTP/HTTPS transports
port = 8080 # bind port
base_route = "/mcp" # HTTP route prefix (default: /mcp)
# TLS for the MCP HTTP transport (required when transport = "https")
# [mcp.tls]
# cert = "/etc/certs/server.crt" # path to PEM certificate
# key = "/etc/certs/server.key" # path to PEM private key
# Secret references are also accepted:
# cert = "@{vault:tls/iris-mcp#certificate}"
# key = "@{vault:tls/iris-mcp#private_key}"
# ── IRIS Servers ──────────────────────────────────────────────────────────────
# Use [[iris]] (double brackets) — one entry per InterSystems IRIS instance.
# Multiple instances can be declared in the same file.
[[iris]]
name = "production"
# wgproto super-server connection credentials.
# Credential fields accept a literal value, @{env:VAR}, or @{vault:path#field}.
server = { host = "iris.example.com", port = 1972, username = "@{env:WG_USER}", password = "@{env:WG_PASS}" }
# WebSocket session pool for this instance.
pool = { min = 2, max = 10 }
# MCP Server paths (endpoints) on this InterSystems IRIS instance.
# Each entry is an MCP Server path, with optional application-layer auth.
# Auth options per endpoint:
# username + password -> HTTP Basic (Authorization: Basic ...)
# bearer -> Bearer token (Authorization: Bearer ...)
# (no auth fields) -> unauthenticated endpoint
endpoints = [
{ path = "/mcp/myapp" },
{ path = "/mcp/secure", username = "@{env:APP_USER}", password = "@{env:APP_PASS}" },
{ path = "/mcp/api", bearer = "@{vault:iris/prod#api_token}" },
]
# Seconds between reconnect attempts when the connection is lost (default: 30)
reconnect_interval_secs = 30
# Seconds between tool-list refresh polls (default: 300)
tool_refresh_interval_secs = 300
# Maximum bytes to accumulate from InterSystems IRIS for a single tool response (default: 10 MiB).
# Increase for tools that return very large payloads; decrease if your LLM has a
# small context window and is being overwhelmed by large results.
max_response_bytes = 10485760
# Seconds a pooled WebSocket session may sit idle before it is closed.
# Closing an idle session causes the InterSystems IRIS job to Halt, freeing its license slot.
# Lower values free licenses faster; higher values reduce reconnection overhead.
# Default: 300 (5 minutes).
idle_timeout_secs = 300
# Maximum number of concurrent pooled sessions per (endpoint, auth_context) pair.
# Each OAuth user or opaque token gets its own pool up to this limit.
# Prevents runaway license consumption when many distinct identities are active.
# Default: same as pool.max.
max_sessions_per_auth_context = 10
# Hard cap on total session lifetime in seconds, regardless of activity.
# When set, a session older than this is dropped the next time it becomes idle
# (InterSystems IRIS job Halts, license freed). Off by default — only idle_timeout_secs applies.
# Useful in high-churn environments to guarantee periodic license recycling.
# max_age_secs = 3600 # e.g. 1 hour
# TLS for the wgproto connection to this instance (optional).
# Presence of the tls field enables TLS; absence means plaintext.
# tls = {} # system CA roots
# tls = { ca_cert = "/etc/certs/iris-ca.crt" } # custom CA
# tls = { ca_cert = "/etc/certs/iris-ca.crt", # mutual TLS
# cert = "/etc/certs/client.crt",
# key = "/etc/certs/client.key" }
# ── Secret Provider ──────────────────────────────────────────────────────────
[secrets]
provider = "env" # env | vault (default: env)
# Required only when provider = "vault":
# vault_addr = "http://127.0.0.1:8200"
# vault_token = "s.xxxx" # literal token
# vault_token_file = "/var/run/vault/token" # or path to token file
# vault_mount = "secret" # KV v2 mount (default: secret)
# ── Logging ──────────────────────────────────────────────────────────────────
[logging]
level = "info" # error | warn | info | debug | trace
output = "stderr" # stderr | file
# file = "/var/log/iris-mcp.log" # required when output = "file"
# ── Optional Feature Toggles ─────────────────────────────────────────────────
[features]
smart_discovery = false # enable RAG tool search (requires smart-discovery feature)
telemetry = false # enable OpenTelemetry tracing
vault = false # enable Vault secret providerCredential fields (username, password, bearer, cert, key, etc.) accept one of three formats:
| Format | Example | How it resolves |
|---|---|---|
| Literal | "CSPSystem" |
Used exactly as written |
| Environment variable | "@{env:IRIS_PASS}" |
Reads $IRIS_PASS at startup |
| Vault KV2 | "@{vault:iris/prod#password}" |
Fetches from Vault: path <mount>/iris/prod, field password |
To include a literal @{ in a value, escape it as @@{.
The Vault format is @{vault:path#field} where path is relative to the configured vault_mount. Vault is only available when built with the vault feature and [secrets] provider = "vault" with vault_addr configured.
All flags before the subcommand are global:
| Flag | Description |
|---|---|
--transport=<spec> |
stdio, http://host:port, https://host:port, sse://host:port |
--config=<path> |
Path to TOML configuration file |
--log-level=<level> |
error / warn / info / debug / trace (default: info) |
--log-output=<out> |
stderr / file (default: stderr) |
--log-file=<path> |
Log file path — required when --log-output=file |
--http-tls-cert=<path> |
Server TLS certificate (PEM) — required with https:// |
--http-tls-key=<path> |
Server TLS private key (PEM) — required with https:// |
--http-base-route=<path> |
HTTP route prefix (default: /mcp) |
run subcommand flags (all optional; override the [[iris]] config):
| Flag | Description |
|---|---|
--iris-host=<host> |
IRIS hostname or IP |
--iris-port=<port> |
IRIS super-server port |
--iris-user=<user> |
IRIS connection username |
--iris-password=<pass> |
IRIS connection password |
--iris-endpoint=<path> |
MCP Server path — may be repeated for multiple endpoints |
--auto-discover-interval=<secs> |
Poll for local IRIS instances every N seconds (0 = disabled) |
--status-tool=<bool> |
Expose iris_status diagnostic tool (default: true) |
monitor subcommand flags (one of --pid or --socket is required):
| Flag | Description |
|---|---|
--pid=<pid> |
Connect to the iris-mcp-server process with this PID |
--socket=<path> |
Connect to the IPC socket at this explicit path |
The following sections demonstrate how to set the transport mode for iris-mcp-server.
iris-mcp-server.exe --config=config.toml run[mcp]
transport = "stdio"
[logging]
output = "file"
file = "C:\\logs\\iris-mcp.log"When using
stdiotransport, setoutput = "file"so logs do not mix with the MCP protocol stream onstderr.
iris-mcp-server.exe --transport=http://0.0.0.0:8080 --config=config.toml run[mcp]
transport = "http"
host = "0.0.0.0"
port = 8080iris-mcp-server.exe --transport=https://0.0.0.0:8443 --config=config.toml run[mcp]
transport = "https"
host = "0.0.0.0"
port = 8443
[mcp.tls]
cert = "/etc/certs/server.crt"
key = "/etc/certs/server.key"TLS cert and key can also be supplied from Vault — see Server-Side TLS.
When --auto-discover-interval is set, iris-mcp-server polls for local running InterSystems IRIS instances (via iris qlist on Linux/Mac, or the Windows Registry) and automatically connects to any that appear:
iris-mcp-server.exe --transport=http://0.0.0.0:8080 --config=config.toml run --auto-discover-interval=60iris-mcp-server has two independent authentication layers that serve different purposes:
| Layer | What it secures | Configuration location |
|---|---|---|
| IRIS server auth | iris-mcp-server connecting to the IRIS server | [[iris]] server.username / server.password |
| MCP endpoint auth | Per-request identity presented to each IRIS MCP Server | [[iris]] endpoints[].username/password/bearer |
Understanding both layers is essential; authenticating the connection to InterSystems IRIS does not automatically authenticate individual requests to the MCP endpoint inside it.
The [iris] fields username and password authenticate iris-mcp-server to your InterSytems IRIS instance. These credentials must be those of a privileged gateway user such as CSPSystem (the same credential that IIS/Apache web gateway modules use).
[[iris]]
name = "local"
server = { host = "localhost", port = 1972, username = "CSPSystem", password = "SYS" }These credentials are used once when the connection is established. For production, use secret references instead of literals:
[[iris]]
name = "production"
server = { host = "iris.example.com", port = 1972,
username = "@{env:IRIS_GW_USER}", password = "@{vault:iris/gateway#password}" }Never commit credentials to version control. Use
@{env:...}references or HashiCorp Vault integration.
Each request targets an MCP Server in InterSystems IRIS. These MCP servers map a URL path to a %AI.MCP.Service subclass. If that endpoint requires authentication, InterSystems IRIS returns 403 Forbidden unless credentials accompany each request.
Credentials are configured per endpoint in the endpoints array:
[[iris]]
name = "production"
server = { host = "iris.example.com", port = 1972, username = "@{env:WG_USER}", password = "@{env:WG_PASS}" }
pool = { min = 2, max = 10 }
endpoints = [
# Unauthenticated endpoint
{ path = "/mcp/public" },
# HTTP Basic auth — sends Authorization: Basic base64(username:password)
{ path = "/mcp/secure", username = "@{env:APP_USER}", password = "@{env:APP_PASS}" },
# Bearer token — sends Authorization: Bearer <token>
{ path = "/mcp/api", bearer = "@{vault:iris/prod#api_token}" },
]For Remote MCP (HTTP transport), the Authorization header from the incoming MCP client session is automatically forwarded to IRIS per-request and takes priority over any configured endpoint credentials. See OAuth Passthrough.
Different authentication modes are used for different scenarios. The following table details these scenarios and their configurations:
| Scenario | Configuration |
|---|---|
| Remote MCP + OAuth Bearer tokens | Omit endpoint auth, the header is forwarded automatically |
| stdio transport (HTTP Basic) | { path = "...", username = "...", password = "..." } |
| stdio transport (API key) | { path = "...", bearer = "..." } |
| Unauthenticated endpoint | { path = "..." } (s)et endpoint to Unauthenticated in InterSystems IRIS) |
Security: Unauthenticated endpoints expose tools to anyone who can reach the IRIS server. Only use
Unauthenticatedfor local development; all production environments should require authentication.
When iris-mcp-server runs in HTTP/HTTPS mode, each MCP session from a remote client carries its own Authorization header (commonly an OAuth 2.0 Bearer token). iris-mcp-server forwards the header value unchanged to IRIS on every request within that session. Any valid scheme (Bearer, API-key-as-Authorization, etc.) works without server-side changes.
OAuth passthrough is always active for the HTTP/HTTPS transport. Endpoint username/password/bearer configuration acts as a fallback only when no Authorization header arrives from the client.
For IRIS to validate incoming Bearer tokens automatically, OAuth authentication must be enabled on the MCP Server endpoint in the IRIS Management Portal. When enabled, IRIS validates the token at WebSocket session open time — by the time a tool call runs, the end-user identity is already established. Use OnPreServer() in your %AI.MCP.Service subclass to perform claim-based authorization (scope, audience, groups) after authentication has already succeeded.
Requests carrying a Bearer token whose exp claim is already in the past are rejected by iris-mcp-server before reaching IRIS.
You can integrate iris-mcp-server with HashiCorp Vault by adding references to your vault in the [secrets] section of your configuration file.
All secret references are resolved once at startup before any connections are established. If any secret fails to resolve, the server exits with an error.
The following example configures iris-mcp-server to use secrets from a local vault:
-
Configure the
[secrets]section of your configuration file:[secrets] provider = "vault" vault_addr = "http://127.0.0.1:8200" vault_token = "@{env:VAULT_TOKEN}" # token as env var reference # vault_token_file = "/var/run/vault/token" # or path to a token file vault_mount = "secret" # KV v2 mount name (default: "secret") [features] vault = true
Kubernetes: use
vault_token_filewith a projected service account token volume rather than storing a static token in a Secret. Mount the projected token at a path like/var/run/secrets/vault/tokenand setvault_token_fileto that path. The token is automatically rotated by Kubernetes and re-read byiris-mcp-serveron the next startup. -
Reference Vault secrets in credential fields using the format The path format is
@{vault:path#field}wherepathis relative tovault_mount.For example, with
vault_mount = "secret", the reference@{vault:iris/gateway#password}reads the fieldpasswordfrom the Vault KV2 secret atsecret/data/iris/gateway.[[iris]] name = "production" server = { host = "iris.example.com", port = 52773, username = "@{vault:iris/gateway#username}", password = "@{vault:iris/gateway#password}" } pool = { min = 10, max = 50 } endpoints = [ { path = "/mcp/prod", bearer = "@{vault:iris/prod#app_token}" }, ]
-
Set up the Vault secrets.
# Enable KV v2 secrets engine (if not already enabled) vault secrets enable -path=secret kv-v2 # Store InterSystems IRIS gateway credentials vault kv put secret/iris/gateway \ username=CSPSystem \ password=SYS # Store InterSystems IRIS application token vault kv put secret/iris/prod \ app_token=eyJ...
-
Run with the token in the environment:
export VAULT_TOKEN="s.xxxx" iris-mcp-server --config=config.toml run
iris-mcp-server is associated with two connections:
wgproto:iris-mcp-server→ InterSystems IRIS- Server-side: LLM clients →
iris-mcp-server
The sections below detail how to encrypt each connection.
You can encrypt the wgproto connection between iris-mcp-server and InterSystems IRIS by adding the tls field to the [[iris]] entry of your configuration file.
The presence of the tls field enables TLS, and its value determines which certificates and keys to use for the connection:
tls = {}- Use the system's default CA certificates.tls = { ca_cert = path/to/ca.crt }- Use the CA certificateca.crtto verify the identity of the InterSystems IRIS server.tls = { ca_cert = path/to/ca.crt, cert = path/to/client.crt, key = /path/to/client.key- (Mutual TLS only) Use the CA certificateca.crtto verify the identity of the InterSystems IRIS server and present the certificateclient.crtto identify the client to the server.
By default, iris-mcp-server uses the system CA certificates by default. The InterSystems IRIS gateway must also be configured for TLS.
The following examples demonstrate how to use the tls field:
# TLS with system roots — simplest form
[[iris]]
name = "production"
server = { host = "iris.example.com", port = 1972, username = "CSPSystem", password = "SYS" }
pool = { min = 2, max = 10 }
tls = {}
endpoints = [{ path = "/mcp/prod" }]
# TLS with a custom or self-signed IRIS CA certificate
[[iris]]
name = "production"
server = { host = "iris.example.com", port = 1972, username = "CSPSystem", password = "SYS" }
pool = { min = 2, max = 10 }
tls = { ca_cert = "/etc/certs/iris-ca.crt" }
endpoints = [{ path = "/mcp/prod" }]
# Mutual TLS — iris-mcp-server presents a client certificate to IRIS
[[iris]]
name = "production"
server = { host = "iris.example.com", port = 1972, username = "CSPSystem", password = "SYS" }
pool = { min = 2, max = 10 }
tls = { ca_cert = "/etc/certs/iris-ca.crt",
cert = "/etc/certs/client.crt",
key = "/etc/certs/client.key" }
endpoints = [{ path = "/mcp/prod" }]If you've integrated iris-mcp-server with HashiCorp Vault, you can reference your secrets instead:
tls = { ca_cert = "@{vault:tls/iris#ca_cert}" }You can encrypt the connection between LLM clients and iris-mcp-server (that is, the MCP HTTP transport endpoint) by adding the cert and key to [mcp.tls]:
# Both from files
[mcp.tls]
cert = "/etc/certs/server.crt"
key = "/etc/certs/server.key"If you've integrated iris-mcp-server with HashiCorp Vault, you can reference your secrets instead:
# HashiCorp Vault integration
[mcp.tls]
cert = "@{vault:tls/iris-mcp#certificate}"
key = "@{vault:tls/iris-mcp#private_key}"
Class MyApp.Tools.Calculator Extends %AI.Tool
{
Method Add(a As %Integer, b As %Integer) As %Integer
{
Return a + b
}
Method Multiply(a As %Integer, b As %Integer) As %Integer
{
Return a * b
}
}This example composes tools with policies using XML DSL. Notice that it extends %AI.ToolSet:
Class MyApp.ToolSet.Database Extends %AI.ToolSet
{
XData Definition [ MimeType = application/xml ]
{
<ToolSet Name="DatabaseTools">
<Description>SQL and database operations</Description>
<Policies>
<Authorization Class="MyApp.Policy.ReadOnlyAuth" />
<Audit Class="%AI.Policy.ConsoleAudit" />
</Policies>
<Include Class="%AI.Tools.SQL">
<Requirement Name="ReadOnly" Value="1"/>
</Include>
</ToolSet>
}
}MCP service classes can reference tools, toolsets, or tool references:
Class MyApp.MCP.DatabaseService Extends %AI.MCP.Service
{
/// SPECIFICATION lists tools, toolsets, or tool refs (comma-separated)
Parameter SPECIFICATION As STRING = "MyApp.ToolSet.Database";
}You can then add an MCP server to InterSystem IRIS that uses your service class:
- Open IRIS Management Portal
- Navigate to: System Administration → Security → Applications → MCP Servers
- Create a new application:
- Name:
/mcp/database - Namespace: USER
- Dispatch Class:
MyApp.MCP.DatabaseService(this is your%AI.MCP.Servicesubclass) - Enabled: Yes
- Authentication: Password (or Unauthenticated for dev/internal use; OAuth 2.0 for Remote MCP with Bearer tokens)
- Name:
Tool discovery is deferred until the first MCP client connects. At that point iris-mcp-server fetches the tool list from each configured endpoint:
- Sends
GET {endpoint}/v1/servicesto IRIS (conditional GET withIf-None-MatchETag) - InterSystems IRIS MCP Service returns a JSON tool catalog
iris-mcp-serverregisters the tools, computing a per-tool SHA-256 hash- A service-level ETag is stored for future conditional GETs
If discovery fails (for example, because IRIS is temporarily unreachable), the existing registration is retained so already-registered tools remain available. A fresh discovery attempt is made on the next tool call.
The background tool-refresh loop re-fetches tool lists every tool_refresh_interval_secs (default: 300 seconds). A 304 Not Modified response means no change and nothing is re-registered. On a 200 response, per-tool hashes are compared; only changed tools trigger an MCP tools/list_changed notification to connected clients.
To authenticate with a configuration file:
[[iris]]
name = "dev"
server = { host = "localhost", port = 1972, username = "CSPSystem", password = "SYS" }
pool = { min = 2, max = 5 }
tool_refresh_interval_secs = 60 # more frequent polling during development
endpoints = [{ path = "/mcp/myapp" }]If endpoints is omitted from [[iris]], iris-mcp-server queries the InterSystems IRIS CSP application registry for all apps matching /mcp* and connects to each one automatically.
[[iris]]
name = "local"
server = { host = "localhost", port = 1972, username = "CSPSystem", password = "SYS" }
pool = { min = 2, max = 5 }
# endpoints omitted -> auto-discover /mcp* appsWhen multiple MCP Servers are connected, tools from each service are prefixed with a service ID derived from the MCP Server's path. This prevents name collisions and tells the LLM which service a tool belongs to.
The service ID is the endpoint path of the MCP Server with the leading slash stripped and remaining slashes replaced with underscores:
| MCP Server name | Service ID prefix | Example tool name |
|---|---|---|
/mcp |
mcp |
mcp_ExecuteQuery |
/mcp/database |
mcp_database |
mcp_database_ExecuteQuery |
/mcp/myapp |
mcp_myapp |
mcp_myapp_GetCustomer |
When there is only one endpoint, tools still carry the prefix. Keep this in mind when writing system prompts or instructions that refer to tools by name.
If the connection to InterSystems IRIS is lost, iris-mcp-server automatically attempts to reconnect in the background using the interval configured by reconnect_interval_secs (default: 30 seconds). You do not need to restart iris-mcp-server.
The example below configures iris-mcp-server to attempt to reconnect every 10 seconds:
[[iris]]
name = "dev"
server = { host = "localhost", port = 1972, username = "CSPSystem", password = "SYS" }
pool = { min = 2, max = 5 }
reconnect_interval_secs = 10 # retry faster during development
endpoints = [{ path = "/mcp/myapp" }]When iris-mcp-server encounters connection errors or startup failures, it exposes a special MCP tool called iris_status that the LLM can call to report the problem. This tool only appears in the tool list when there are active errors.
When the LLM sees iris_status, it means something went wrong. Calling it returns a structured report of all current errors:
iris_status result:
- mcp_database: connection failed — refused at localhost:1972
- mcp_analytics: authentication error — 403 Forbidden (check endpoint credentials)
This allows the LLM to proactively report issues rather than silently failing when tools are called.
iris_status should only be used in development; in a production environment, you should disable this tool with the --status-tool=false flag to ensure internal state is hidden from the LLM:
iris-mcp-server.exe --config=config.toml run --status-tool=falseSmart discovery uses a local embedding model (fastembed / AllMiniLML6V2) to perform semantic search across all registered tool descriptions. When an LLM asks for tools matching a natural-language query, relevant tools are ranked by cosine similarity rather than exact name matching.
The embedding model (~25 MB) is downloaded from HuggingFace on first use.
To enable this feature in your configuration file:
[features]
smart_discovery = trueSmart discovery is indexed automatically as tools are registered. No additional CLI flags are required.
iris-mcp-server includes a live terminal dashboard that shows connection pool status, active sessions, tool call throughput, and a live log feed — all updated every 500 ms without interrupting the running server.
Starting the monitor:
The server prints its IPC socket path at startup:
INFO IPC server listening on \\.\pipe\iris-mcp-1234 (local-only, owner DACL)
Connect to it by PID or by the socket path:
# Windows — connect by PID
iris-mcp-server.exe monitor --pid 1234
# Windows — connect by explicit socket path
iris-mcp-server.exe monitor --socket \\.\pipe\iris-mcp-1234# Unix — connect by PID
iris-mcp-server monitor --pid 1234
# Unix — connect by explicit socket path
iris-mcp-server monitor --socket /run/user/1000/iris-mcp-1234.sockPress q or Ctrl-C to exit. The iris-mcp-server process continues running normally.
Dashboard panels:
| Panel | Content |
|---|---|
| GATEWAY | Active MCP sessions (HTTP and stdio), authenticated contexts (distinct OAuth identities), WebSocket sessions, error counts, and discovery cache hit rates |
| CONNECTIONS | Per-endpoint WebSocket pool state (active, queued, idle, total), P50/P99 latency per tool, session eviction counts by reason, IRIS connection failure breakdown by reason, HTTP pool self-heal counters (stale/healed/failed), and WS session error breakdown (send, receive, stale-session) |
| TOOLS | Per-tool call counts, success/error rates, P50/P99 execution latency, and input/output byte totals |
| LOG | Live stream of recent INFO/WARN/ERROR log lines, color-coded by severity |
Monitor security:
The monitor communicates with the server through a local IPC channel — a named pipe on Windows and a Unix domain socket on Unix. The channel is strictly one-way: the monitor receives periodic read-only metrics snapshots and cannot send commands to the server.
Windows: The named pipe is not accessible over the network. On the local machine, access is restricted to the user who started iris-mcp-server, the SYSTEM account, and members of the local Administrators group. No other local user account can connect to the monitor.
Unix: The socket file is created with mode 0600. Only the user who started iris-mcp-server and root can connect to the monitor. Unix domain sockets are not accessible over the network.
In both cases the process ID is embedded in the socket path, preventing collisions when multiple iris-mcp-server instances run on the same host.
iris-mcp-server has robust logging capabilities. It collects and outputs the following information:
- Connection events (connect, disconnect, reconnect)
- Tool discovery requests and ETag comparisons
- STP request/response JSON
- Smart discovery indexing
Logging has several levels. These are ordered by least to most severe:
debug(least severe, most verbose)infowarnerror(most severe, least verbose)
The log level expresses the minimum severity you want to know about. This means that if you set the log level to info, the log will contain messages of severity info and above, which includes warn and error.
To set the log level with flags (overrides the configuration file):
iris-mcp-server.exe --log-level=debug --log-output=stderr --config=config.toml runTo set the log level in config.toml:
[logging]
level = "debug" # error | warn | info | debug | trace
output = "file"
file = "C:\\logs\\iris-mcp.log"For file output, file is required. For stderr output, file is ignored.
You can also set RUST_LOG=debug (or any env_logger-compatible filter) to override the log level from the environment:
RUST_LOG=debug iris-mcp-server --config=config.toml runTo enable OpenTelemetry Tracing:
- Enable the
telemetryfeature in theconfig.toml:
[features]
telemetry = true- Set the OTLP endpoint via the standard environment variable. Traces are exported via OTLP (gRPC):
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"- Run
Jaegerfor local trace visualization:
docker run -d --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
jaegertracing/all-in-one:latest
# View traces at http://localhost:16686The easiest way to confirm whether iris-mcp-server is discovering tools is to check the logs at startup:
iris-mcp-server.exe --log-level=debug --config=config.toml runLook for lines like registered N tools from /mcp/database. If no tools appear, enable debug logging and check for discovery errors.
If you have access to an IRIS web gateway (not the super-server port), you can also call the IRIS health endpoint directly:
curl http://<iris-webgateway-host>/mcp/database/v1/healthA pre-built container image is available. Run it with your config file mounted and credentials supplied via environment:
docker run -d \
--name iris-mcp \
-e WG_USER=CSPSystem \
-e WG_PASS=SYS \
-e VAULT_TOKEN=${VAULT_TOKEN} \
-v /path/to/config.toml:/etc/iris-mcp/config.toml:ro \
-p 8080:8080 \
intersystems/iris-mcp-server:latestTo deploy iris-mcp-server on Kubernetes, create a Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: iris-mcp-server
spec:
replicas: 3
selector:
matchLabels:
app: iris-mcp-server
template:
metadata:
labels:
app: iris-mcp-server
spec:
containers:
- name: iris-mcp-server
image: iris-mcp-server:latest
args: ["--config", "/etc/iris-mcp/config.toml", "run"]
ports:
- containerPort: 8080
env:
- name: VAULT_TOKEN
valueFrom:
secretKeyRef:
name: vault-credentials
key: token
volumeMounts:
- name: config
mountPath: /etc/iris-mcp
readOnly: true
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: config
configMap:
name: iris-mcp-config
---
apiVersion: v1
kind: Service
metadata:
name: iris-mcp-server
spec:
selector:
app: iris-mcp-server
ports:
- port: 8080
targetPort: 8080
type: LoadBalancerEach InterSystems IRIS instance has its own WebSocket session pool, the size of which is determined by pool = { min, max }. Each pool slot is one WebSocket connection to InterSystems IRIS (one concurrent tool call in flight per slot).
The following example shows how to use the pool field:
[[iris]]
name = "production"
server = { host = "iris.example.com", port = 1972, username = "CSPSystem", password = "SYS" }
pool = { min = 5, max = 20 } # up to 20 concurrent tool calls
endpoints = [{ path = "/mcp/prod" }]As a general rule, max should be at least as large as the number of tool calls you expect to run simultaneously. min sets the number of sessions kept warm at idle.
Session lifetime tuning: Each pooled session corresponds to one InterSystems IRIS license slot (job). Three settings control how long sessions live:
| Setting | Default | Effect |
|---|---|---|
idle_timeout_secs |
300 | Close sessions idle longer than this (InterSystems IRIS job halts and its license is freed) |
max_sessions_per_auth_context |
pool.max |
Cap on concurrent sessions per OAuth user / token identity |
max_age_secs |
off | Hard cap on total session lifetime; session dropped on next idle regardless of activity |
OAuth deployments: IRIS validates a Bearer token once, at WebSocket session open. It does not re-validate the token while the session is running — a session stays alive even after the token expires. idle_timeout_secs is therefore the primary mechanism for ensuring stale-token sessions are cleaned up. Set it to no more than your OAuth token lifetime so that expired sessions are recycled before the next token rotation.
In deployments with many OAuth users (each user gets their own session pool), lower idle_timeout_secs and set max_sessions_per_auth_context to prevent license exhaustion:
[[iris]]
name = "production"
server = { host = "iris.example.com", port = 1972, username = "CSPSystem", password = "SYS" }
pool = { min = 2, max = 10 }
idle_timeout_secs = 120 # free licenses after 2 minutes idle
max_sessions_per_auth_context = 3 # each user may have at most 3 concurrent sessions
max_age_secs = 3600 # recycle sessions after 1 hour regardless
endpoints = [{ path = "/mcp/prod" }]For multiple InterSystems IRIS instances, each gets its own pool:
[[iris]]
name = "primary"
server = { host = "iris1.example.com", port = 1972, username = "CSPSystem", password = "SYS" }
pool = { min = 5, max = 20 }
endpoints = [{ path = "/mcp/prod" }]
[[iris]]
name = "analytics"
server = { host = "iris2.example.com", port = 1972, username = "CSPSystem", password = "SYS" }
pool = { min = 2, max = 10 }
endpoints = [{ path = "/mcp/analytics" }]This section covers common errors and how to diagnose/resolve them. For more general information about logging and monitoring, see Monitoring & Telemetry.
Problem: Failed to connect / ConnectionClosed
- Verify the InterSystems IRIS superserver is running on the configured port.
- Verify that the MCP server in InterSystems IRIS (System Administration > Security > Applications > MCP Servers) exists and is enabled.
- Check the Dispatch Class name is correct and the class is compiled in InterSystems IRIS.
- Confirm
server.username/server.passwordin[[iris]]are correct — these are gateway-level credentials (CSPSystemor equivalent), not IRIS application user credentials. - Check firewall rules allow TCP to the InterSystems IRIS superserver port.
Problem: iris-mcp-server connects successfully but the LLM cannot see your tools (or the LLM only sees iris_status)
The server's initialize response already instructs the LLM: "If InterSystems IRIS tools appear to be missing or something seems wrong, call iris_status," so you should check the output of that first.
If iris_status reports a clean connection but zero tools, the problem is on the InterSystems IRIS side:
- Verify that the
SPECIFICATIONparameter on the MCP Service class is non-empty and references the correct class names. - Confirm all listed tool/toolset classes are compiled in the correct IRIS namespace.
- Confirm the that the MCP server's Namespace matches where the classes are compiled.
- Run
iris-mcp-serverwith--log-level=debugand look for the tool count logged during discovery — if it shows 0 tools, the issue is on the IRIS side (emptySPECIFICATION, uncompiled classes, or wrong namespace).
Problem: Tool appears in discovery but call fails, or tool not listed
- Check the
SPECIFICATIONparameter on the MCP Service class includes the tool/toolset. - Ensure the method is public (not marked
PrivateorInternal). - Tool names are case-sensitive — check the exact names logged during discovery with
--log-level=debug.
Problem: Tool call error: 403 Forbidden
This error means that InterSystems IRIS is reachable (Layer 1) but the MCP endpoint is rejecting the request (Layer 2).
- Verify the endpoint entry in
[[iris]] endpointshas the correctusername/passwordorbearerfor that endpoint. - For HTTP Basic: confirm the username/password are valid IRIS credentials with access to the endpoint.
- For OAuth passthrough: confirm the MCP client is sending a valid
Authorizationheader. - Verify the MCP server's authentication settings in the Management Portal.
Problem: Server exits at startup with a secret error
@{env:VAR}- Verify the environment variable is set before starting the process.@{vault:path#field}- Verifyvault_addris reachable,vault_tokenis valid, the path exists, and the field name matches exactly.- Verify Vault token permissions:
vault token lookup. - Vault KV2 path format:
@{vault:iris/gateway#password}maps to Vault pathsecret/data/iris/gateway→ fieldpassword.
Enable verbose logging to see detailed protocol activity:
iris-mcp-server.exe --log-level=debug --log-output=stderr --config=config.toml runOr via environment:
RUST_LOG=debug iris-mcp-server --config=config.toml runDebug output includes:
- Connection events (connect, disconnect, reconnect)
- Tool discovery requests and ETag comparisons
- STP request/response JSON
- Smart discovery indexing
- Create your IRIS backend — See IRIS Backend Setup and ObjectScript User Guide
- Write a config.toml — Use the Full TOML Reference as a template
- Test locally — Use stdio transport with Claude Desktop
- Secure credentials — Use
@{env:...}references or configure Vault - Deploy — Docker or Kubernetes using the examples above
For more information:
- ObjectScript User Guide — Creating tools and toolsets in IRIS
The configuration file format changed between iris-mcp-server version 1 and 2. These changes are as follows:
| Old | New |
|---|---|
[server] |
[mcp] |
[iris] (single) |
[[iris]] (Supports multiple instances) |
host, port, username, password at top level |
Moved inside server = { ... } |
namespace |
Removed |
mcp_endpoints = ["/mcp/myapp"] |
endpoints = [{ path = "/mcp/myapp" }] |
pool_size = 5 |
pool = { min = 2, max = 10 } |
[iris.user_auth] section |
Inline per-endpoint: { path = "...", username = "...", password = "..." } |
"env:VAR" secret syntax |
"@{env:VAR}" |
Below are a set of sample configuration files which demonstrate these changes and should help you migrate to version 2.
Sample version 1 configuration file:
[server]
transport = "stdio"
[iris]
host = "localhost"
port = 1972
namespace = "USER"
username = "CSPSystem"
password = "SYS"
mcp_endpoints = ["/mcp/myapp"]
pool_size = 5
[iris.user_auth]
username = "myuser"
password = "mypass"
[logging]
level = "info"
output = "stderr"Sample version 2 configuration file:
[mcp]
transport = "stdio"
[[iris]]
name = "local"
server = { host = "localhost", port = 1972, username = "CSPSystem", password = "SYS" }
pool = { min = 2, max = 10 }
endpoints = [
{ path = "/mcp/myapp", username = "myuser", password = "mypass" },
]
[logging]
level = "info"
output = "stderr"What changed:
| Old | New |
|---|---|
[server] |
[mcp] |
[iris] (single) |
[[iris]] (double brackets — supports multiple instances) |
host, port, username, password at top level |
Moved inside server = { ... } |
namespace |
Removed |
mcp_endpoints = ["/mcp/myapp"] |
endpoints = [{ path = "/mcp/myapp" }] |
pool_size = 5 |
pool = { min = 2, max = 10 } |
[iris.user_auth] section |
Inline per-endpoint: { path = "...", username = "...", password = "..." } |
"env:VAR" secret syntax |
"@{env:VAR}" |