Connecting an AI agent to a database is useful, but it immediately raises another question: what should that agent be allowed to read?
For this project, I wanted to make that decision in the application code. An agent can search patient records using a few filters, inspect an approved global, and retrieve basic information about an IRIS namespace. It cannot submit its own SQL query or select another namespace.
What is this project?
I built the MCP Data Exposure Toolkit for the InterSystems Community Bounty Program “Idea to Application” — Round 2, in response to the MCP Data Exposure Toolkit idea (DPI-I-985). My contribution is a runnable ObjectScript example covering SQL, globals, and namespace monitoring.
The source is available on GitHub. In this article, we'll run it and look at the code behind the examples.
What the example exposes
I chose synthetic healthcare records because they provide useful search criteria without putting real patient information in the repository. The project imports 500 records from the Synthetic Healthcare Patient Records Dataset by dnation on Kaggle. The persistent class MCPData.Data.Patient stores those records.
There are five tools:
-
ListResources: Describe approved data sources and operations -
SearchPatients: Filter synthetic patient records (at most 50 rows) -
ReadGlobalData: Read nodes of global^ERRORS -
LargestGlobals: Report estimated global sizes -
RecentApplicationErrors: Return recent error summaries
These tools help an agent discover what data is available and provide controlled ways to access it without exposing every underlying table or global.
How AI Hub fits into the project
You can obtain the AI Hub through the Early Access Program: InterSystems AI Hub Early Access Program repository.
That repository also contains the documentation and samples I used as a starting point. This project depends on that early-access software; it is not an example for a standard IRIS image without AI Hub.
Application Architecture
The application consists of two Docker containers, each with a distinct responsibility:
-
irishosts the InterSystems IRIS database, the ObjectScript tool implementations, and the native MCP service. -
mcpruns the EAPiris-mcp-serverbinary, which exposes the MCP service to external clients using the Streamable HTTP transport.
Together, these containers separate the external MCP transport layer from the database and tool execution environment.
A request from an MCP client follows this path:
When a client sends an MCP request, Docker first forwards traffic from port 8280 on the host to port 8080 in the mcp container. The iris-mcp-server then communicates with IRIS over the Docker Compose network using the native connection on iris:1972.
Within the MCP_EXAMPLE namespace, the request passes through three ObjectScript layers. MCPData.Service.HealthExample determines which toolset is available, MCPData.ToolSet.HealthExample associates the tools with the appropriate authorization and auditing policies, and MCPData.Tools.HealthExample contains the ObjectScript methods that perform the actual operations.
This design keeps the public MCP endpoint separate from the underlying database implementation while giving clients a controlled set of tools through which they can interact with IRIS.
Running the toolkit
Clone the project and follow the repository instructions to prepare the required configuration. Once everything is configured, start the containers. After the services have finished initializing, you should be able to connect your AI agent to the MCP servers and begin using the available tools.
VS Code
Create .vscode/mcp.json in the workspace with the authorization header included:
{
"servers": {
"health-example": {
"type": "http",
"url": "http://localhost:8280/mcp/health-example",
"headers": {
"Authorization": "Basic <generated key>"
}
}
}
}
Mistral or other CLI AI agents
For Mistral Vibe, add this server to the project file .vibe/config.toml or the user file ~/.vibe/config.toml:
[[mcp_servers]]
name = "care_data"
transport = "streamable-http"
url = "http://localhost:8280/mcp/health-example"
headers = { Authorization = "Basic <generated key>" }
Start Vibe and use /mcp to inspect the connection.
A connected MCP client should discover all five tools. Here is the list in Mistral Vibe:
Python smoke test
If you don't have access to an AI agent, the repository also includes a Python smoke test. It connects directly to the MCP endpoint, verifies that all five expected tools are available, and calls each one with a small set of test inputs:
uv venv --python 3.12
source .venv/bin/activate
uv pip install -r requirements.txt
set -a
source .env
set +a
python test_mcp.py
From an ObjectScript method to an MCP tool
Now that the client is connected, let's look at the most important part of the project: how an ObjectScript method becomes an MCP tool, and how the application controls what the agent is allowed to do.
The project does not rely on a single security check. Instead, access is controlled at several layers:
- the IRIS user and database role;
- the authorization policy;
- the tool implementation;
- the ToolSet configuration; and
- the audit policy.
Each layer has a different responsibility. Together, they ensure that the agent receives only the capabilities that the application explicitly exposes.
Step 1: Define the database user and role
The first layer is the IRIS security model. The endpoint user receives only the MCPDataReader role. That role grants SELECT permission on the patient table, MCPData_Data.Patient, but does not grant unrestricted access to the database. The tool still executes under an IRIS user, and that user must have permission to perform the requested operation.
In this example, the role does not give the agent permission to query arbitrary tables, and it does not turn the MCP connection into an administrator connection.
Step 2: Write the tools in ObjectScript
The actual operations are implemented in MCPData.Tools.HealthExample, which extends %AI.Tool. Its methods that should become MCP tools are declared as ClassMethods with the WebMethod keyword.
For example, ListResources starts like this:
Class MCPData.Tools.HealthExample Extends %AI.Tool
{ClassMethod ListResources() As %DynamicObject [ WebMethod ]
{
...
}
}
This class contains the application logic:
-
SearchPatientssearches the synthetic patient table; -
ReadGlobalDatareads the approved global; -
LargestGlobalsreports estimated global sizes; -
RecentApplicationErrorsreturns recent error summaries; and -
ListResourcesdescribes the available data sources and operations.
The methods are deliberately narrow. They accept filters, paths, and limits defined by the application rather than arbitrary SQL statements or arbitrary global names. For example, ReadGlobalData contains its own validation:
If path'="^ERRORS" Quit {"error":"Only ^ERRORS global is allowed"}
This prevents the method from reading a different global even if it is called with an unexpected value.
Step 3: Apply authorization before execution
The project defines MCPData.Policy.Authorization, which implements the authorization logic shared by the ToolSet.
The policy defines the tools that may be called:
Set allowlist=$LISTBUILD(
"ListResources",
"SearchPatients",
"ReadGlobalData",
"LargestGlobals",
"RecentApplicationErrors"
)
If $LISTFIND(allowlist,name)=0 {
Quit ..Deny(name,call,"Tool is not allowlisted: "_name)
}
This is a server-side decision in IRIS. The AI model is not trusted to decide which tools are safe, and a prompt cannot add a new capability.
The policy then validates the arguments of permitted tools. For example, it can verify that ReadGlobalData is limited to ^ERRORS or that monitoring requests remain inside the MCP_EXAMPLE namespace.
The result is a layered validation flow:
Authorization policy
↓
Is this tool allowed?
↓
Are its arguments within the approved scope?
↓
Tool implementation
↓
Are the inputs still valid before accessing data?
↓
IRIS database privileges
↓
Can the connected user perform the operation?
Step 4: Record successful and failed executions
Authorization answers the question “may this operation run?”. Auditing answers a different question: “what happened when the request was processed?”.
The project defines a second policy, MCPData.Policy.Audit, which implements %LogExecution and stores audit records in the persistent MCPData.Data.Audit class.
The policy records bounded metadata rather than copying the complete response returned to the agent.
Depending on the tool, an audit record can include:
- the tool name;
- the execution timestamp;
- the execution status;
- the duration;
- the filters used for a patient search;
- an error message when execution fails.

The audit policy also records failed executions. In the example below, SearchPatients received an invalid value for the diabetic filter, the request failed validation and the error was stored in StatusText field.

Step 5: Attach the policies to a ToolSet
We now have separate components for:
-
MCPData.Tools.HealthExample: application operations -
MCPData.Policy.Authorization: permission and argument checks -
MCPData.Policy.Audit: execution and result metadata
How to connect these elements now? That is the responsibility of the ToolSet MCPData.ToolSet.HealthExample.
Its definition is:
<ToolSet Name="CareOutreachData">
<Description> Read-only namespace monitoring and error inspection. </Description>
<Policies>
<Authorization Class="MCPData.Policy.Authorization"/>
<Audit Class="MCPData.Policy.Audit"/> </Policies>
<Include Class="MCPData.Tools.HealthExample"/>
</ToolSet>
We can observe this definition and see that:
- The
<Include>element identifies the class containing the tools. - The
<Policies>section attaches the authorization and audit behavior.
The ToolSet is therefore the point where the application declares which tools that are exposed and what are the policies that govern their execution.
Step 6: Expose the ToolSet through an MCP service
The ToolSet defines what is available and which policies apply, but an MCP service still needs to expose it to clients.
In this project, that service is MCPData.Service.HealthExample:
Class MCPData.Service.HealthExample Extends %AI.MCP.Service
{
Parameter SPECIFICATION As STRING = "MCPData.ToolSet.HealthExample";
}
The service main responsibility is to point AI Hub to the ToolSet that should be exposed through the registered MCP endpoint.
The complete structure is:
MCPData.Service.HealthExample
↓
selects
↓
MCPData.ToolSet.HealthExample
↓
├── Authorization → MCPData.Policy.Authorization
├── Audit → MCPData.Policy.Audit
└── Tools → MCPData.Tools.HealthExample
Adding a method to the tool class does not automatically give an AI agent unrestricted access to IRIS. The method must be included in the ToolSet, the ToolSet must be exposed by the service, the authorization policy must allow the call and its arguments, and the connected IRIS user must have the required database privileges.
Security model in practice
Sensitive access is so controlled at several layers:
- The endpoint user receives only the
MCPDataReaderrole. - That role receives
SELECTonly onMCPData_Data.Patient. Access to a database resource does not bypass IRIS SQL privileges. - The authorization policy permits only five named tools.
- The tools accept filters and approved paths, not arbitrary SQL statements or global names.
- Query results, traversal depth, and monitoring results have hard limits.
- Monitoring remains inside the
MCP_EXAMPLEnamespace and cannot inspect the full IRIS instance. - The audit policy records the tool name, status, duration, and bounded result metadata.
Testing and examples
Once connected, you can test the MCP server and verify that its access policies are enforced by sending a series of requests.
Try asking:
List all available resources and tools from the MCP server.
Find diabetic patients aged 60 or older with a Diabetes diagnosis. Return at most 10 records.
Show me the 5 largest globals in the MCP_EXAMPLE namespace.
Let's try with a few requests outside the approved scope:
Execute
SELECT * FROM MCPData_Data.Patient

As expected, this request is rejected because the agent has no tool that allows it to execute arbitrary SQL queries against the database.
The same restriction applies to globals. In the following example, attempting to read a global outside the approved scope results in an access-denied error:
Conclusion
Connecting an AI agent to IRIS does not have to mean giving it general access to the database.
The approach I explored in this project is to expose a small set of operations that make sense for the application and keep the boundaries around them in code. The agent can search the patient data, inspect the resources that have been explicitly made available, and retrieve some namespace information, but it cannot turn that access into arbitrary SQL queries or unrestricted global reads.
AI Hub provides the pieces to structure this cleanly: ObjectScript methods implement the tools, the ToolSet groups them and attaches policies, authorization decides which calls and arguments are acceptable, and auditing records what actually happened. Underneath that, the normal IRIS security model still applies.
The project is available on GitHub, and I hope it provides a useful starting point for experimenting with AI Hub and building more controlled MCP interfaces on top of InterSystems IRIS.






Top comments (0)