@raytio/mcp-server
v1.17.0
Published
MCP server for secure AI agent access to Raytio user data
Readme
Raytio MCP Server
An MCP (Model Context Protocol) server that enables AI agents to securely request and retrieve user data from the Raytio platform using end-to-end encryption.
Overview
This server exposes Raytio platform capabilities to AI agents via MCP. Tools are organised into domains, and the full catalogue (names, descriptions, parameters) is generated from source at docs/tools.md.
Current domains:
| Domain | Purpose | |---|---| | DSM — Data Sharing & Requests | Request and retrieve end-to-end encrypted user data via Raytio forms and access applications. | | PPM — Project & Program Management | Read and update Raytio work items and activities with semantic status transitions. | | PRM — Party Relationship Management | Manage parties (organisations, groups, and people) that the business entity interacts with. | | WRM — Worker Relationship Management | Manage AI worker identities, model stacks, MCP tool registrations, roles, role memories, and agent run steps/artifacts. |
All user data flowing through DSM is encrypted end-to-end using Raytio's Maxcryptor system (AES-256-GCM + WADEK key wrapping).
Adding MCP server to AI tool
Two install paths:
- npx (recommended for end users) — no clone, no build;
npxdownloads the published package from npm and runs it. Credentials come from env vars passed inline, or from a file pointed at byRAYTIO_CONFIG_FILE(see below). - Local clone (recommended for contributors) — clone this repo, run
npm install && npm run build, and point the MCP client atdist/index.js. Credentials come fromconfig.envcolocated with the build.
Recommended setup for npx installs
Keep all the credential env vars in a single file outside your project tree, and have the MCP client only pass RAYTIO_CONFIG_FILE pointing at it. This is shorter than inline -e blocks, keeps creds out of the MCP client's config file, and means rotating a password is a one-file edit instead of re-running claude mcp add.
# 1. Create the file (chmod 600 so only you can read it)
mkdir -p ~/.config/raytio
install -m 600 /dev/null ~/.config/raytio/mcp.env
# 2. Populate it — same keys as config.env.example
cat > ~/.config/raytio/mcp.env <<'EOF'
RAYTIO_API_BASE_URL=https://api.rayt.io
RAYTIO_COGNITO_USER_POOL_ID=us-east-1_xxxxxxxxx
RAYTIO_COGNITO_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
RAYTIO_COGNITO_REGION=us-east-1
[email protected]
RAYTIO_AGENT_PASSWORD=...
RAYTIO_AGENT_ENCRYPTION_PASSWORD=...
RAYTIO_AGENT_CUSTOMER_ID=00000000-0000-0000-0000-000000000000
[email protected]
# Optional: static override for tool domains (see "Tool domain selection" below).
# If unset, domains are resolved dynamically from the user's backend roles.
# RAYTIO_TOOL_DOMAINS=ppm-readonly
EOFThen register the MCP server, passing only the pointer:
# Claude Code
claude mcp add raytio -s user \
-e RAYTIO_CONFIG_FILE=$HOME/.config/raytio/mcp.env \
-- npx -y @raytio/mcp-server# Codex CLI (~/.codex/config.toml)
[mcp_servers.raytio]
command = "npx"
args = ["-y", "@raytio/mcp-server"]
[mcp_servers.raytio.env]
RAYTIO_CONFIG_FILE = "/home/you/.config/raytio/mcp.env"For multiple registrations with different credentials (see "Splitting tools" below), use one file per registration: ~/.config/raytio/dsm.env, ~/.config/raytio/ppm.env, etc., each with its own RAYTIO_CONFIG_FILE pointer.
Claude Code
Install via npx
claude mcp add raytio -s user \
-e RAYTIO_API_BASE_URL=https://api.rayt.io \
-e RAYTIO_COGNITO_USER_POOL_ID=... \
-e RAYTIO_COGNITO_CLIENT_ID=... \
-e RAYTIO_COGNITO_REGION=us-east-1 \
-e [email protected] \
-e RAYTIO_AGENT_PASSWORD=... \
-e RAYTIO_AGENT_ENCRYPTION_PASSWORD=... \
-e RAYTIO_AGENT_CUSTOMER_ID=00000000-0000-0000-0000-000000000000 \
-- npx -y @raytio/mcp-serverInstall from a local clone
Build first (npm install && npm run build), then register. The server resolves config.env relative to its own install location, so one registration works from every project on your machine:
claude mcp add raytio -s user -- node /absolute/path/to/mcp-server/dist/index.jsCredentials are picked up from config.env in the repo root (copy config.env.example and fill it in).
Shared settings
Verify: claude mcp list. Inside a session: /mcp. Remove: claude mcp remove raytio.
Scopes:
-s user— available in every project for the current OS user (recommended for personal use).-s project— writes to.mcp.jsonin the current repo, committed for teammates. Prefer the npx form at project scope, since absolute paths aren't portable.- (default, local) — private to your user, scoped to the current project.
Codex CLI
Codex CLI registers MCP servers via ~/.codex/config.toml.
Install via npx
[mcp_servers.raytio]
command = "npx"
args = ["-y", "@raytio/mcp-server"]
[mcp_servers.raytio.env]
RAYTIO_API_BASE_URL = "https://api.rayt.io"
RAYTIO_COGNITO_USER_POOL_ID = "..."
RAYTIO_COGNITO_CLIENT_ID = "..."
RAYTIO_COGNITO_REGION = "us-east-1"
RAYTIO_AGENT_USERNAME = "[email protected]"
RAYTIO_AGENT_PASSWORD = "..."
RAYTIO_AGENT_ENCRYPTION_PASSWORD = "..."
RAYTIO_AGENT_CUSTOMER_ID = "00000000-0000-0000-0000-000000000000"Install from a local clone
Build first (npm install && npm run build), then:
[mcp_servers.raytio]
command = "node"
args = ["/absolute/path/to/mcp-server/dist/index.js"]Credentials are picked up from config.env next to the server install. To override inline, add a [mcp_servers.raytio.env] block as in the npx example.
Restart Codex CLI after editing the config. Inside a session, /mcp lists loaded servers. Remove by deleting the block and restarting.
Tool domain selection
The server decides which tools to expose using the following precedence:
RAYTIO_TOOL_DOMAINSenv var (static override) — if set and non-empty, this value is used directly. No backend query is made.- Dynamic role resolution — if
RAYTIO_TOOL_DOMAINSis not set, the server queries the authenticated user's roles viafnd_authz_user_groups()at startup and maps them to tool domains automatically. - All domains (fallback) — if the role query fails or returns no recognised roles, every domain is exposed (matching pre-v1.4.0 behaviour).
The server logs which path was taken at startup (to stderr).
How dynamic resolution works
On startup the server calls the backend RPC endpoint fnd_authz_user_groups() with the agent's Cognito credentials. This returns all roles the user holds (direct assignments + inherited via the role hierarchy). The roles are mapped to tool domains:
| Role | Domain |
|---|---|
| PROJECT_USER | ppm (full read + write) |
| PROJECT_ADMINISTRATOR | ppm + wrm (full read + write) |
| PROJECT_VIEWER | ppm-readonly + wrm-readonly (read-only subset) |
| DATA_SHARING_USER, DATA_SHARING_ADMINISTRATOR | dsm |
| WORKER_ADMINISTRATOR | wrm (full read + write) |
| WORKER_VIEWER | wrm-readonly (read-only subset) |
If a user has both PROJECT_VIEWER and PROJECT_USER (or PROJECT_ADMINISTRATOR), ppm wins — it is a superset of ppm-readonly.
The backend's row-level security (RLS) remains the authoritative access control layer. Dynamic filtering is a UX optimisation: it keeps the LLM's tool list relevant and prevents wasted calls to endpoints the agent can't access.
Static override with RAYTIO_TOOL_DOMAINS
Set RAYTIO_TOOL_DOMAINS (comma-separated) to bypass dynamic resolution entirely. This is useful when you want to:
- Give each domain its own credentials (e.g. a DSM agent and a PPM agent), since each registration is a separate process with its own env.
- Restrict an agent below its actual role (e.g. force
ppm-readonlyeven though the account hasPROJECT_USER). - Lock in a known domain set regardless of backend role changes.
Available domains (source: src/tools/domains.ts, also listed at the top of docs/tools.md):
| Domain ID | Notes |
|---|---|
| dsm | Data Sharing & Requests |
| ppm | Project & Program Management (full read + write) |
| prm | Party Relationship Management |
| ppm-readonly | Read-only subset of PPM — no create/update/status transitions |
| wrm | Worker Relationship Management (full read + write) |
| wrm-readonly | Read-only subset of WRM — no create/update operations |
Example — separate DSM and PPM registrations with different credentials:
claude mcp add raytio-dsm -s user \
-e RAYTIO_TOOL_DOMAINS=dsm \
-e [email protected] \
-e RAYTIO_AGENT_PASSWORD=... \
-e RAYTIO_AGENT_ENCRYPTION_PASSWORD=... \
-e RAYTIO_AGENT_CUSTOMER_ID=... \
-- npx -y @raytio/mcp-server
claude mcp add raytio-ppm -s user \
-e RAYTIO_TOOL_DOMAINS=ppm \
-e [email protected] \
-e RAYTIO_AGENT_PASSWORD=... \
-e RAYTIO_AGENT_ENCRYPTION_PASSWORD=... \
-e RAYTIO_AGENT_CUSTOMER_ID=... \
-- npx -y @raytio/mcp-serverExample — read-only PPM for a separate audit agent:
claude mcp add raytio-ppm-ro -s user \
-e RAYTIO_TOOL_DOMAINS=ppm-readonly \
-e [email protected] \
...
-- npx -y @raytio/mcp-serverThe same pattern works in Codex CLI by varying RAYTIO_TOOL_DOMAINS and the credential env vars per [mcp_servers.<name>] block.
Prerequisites
All installs need a Raytio account with agent credentials, a customer ID, and Cognito access. Installing from a local clone additionally requires:
- Node.js 20 or higher (use
nvm use—.nvmrcpins the version) - npm or yarn
Installation (local clone)
Skip this if you registered the server via npx above.
npm installConfiguration
Environment Variables
Copy config.env.example to config.env in the server's root directory and fill in the values below. The server resolves config.env relative to its own install location, so it works regardless of which directory Claude Code (or any other MCP client) launches it from. To point at a different file, set RAYTIO_CONFIG_FILE=/absolute/path/to/creds.env. Real process.env variables always take precedence over anything in the file.
# Raytio API Configuration
RAYTIO_API_BASE_URL=https://api.rayt.io
RAYTIO_COGNITO_USER_POOL_ID=us-east-1_xxxxxxxxx
RAYTIO_COGNITO_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
RAYTIO_COGNITO_REGION=us-east-1
# Agent User Credentials
[email protected]
RAYTIO_AGENT_PASSWORD=your-agent-password
RAYTIO_AGENT_ENCRYPTION_PASSWORD=your-encryption-password
RAYTIO_AGENT_CUSTOMER_ID=00000000-0000-0000-0000-000000000000
# Target User
[email protected]
# Optional: Request Settings
RAYTIO_REQUEST_TIMEOUT_SECONDS=600
RAYTIO_POLL_INTERVAL_SECONDS=5
RAYTIO_REQUEST_EXPIRY_MINUTES=10See config.env.example for the full template.
Building
npm run buildThis compiles TypeScript to JavaScript in the dist/ directory.
Running
Development Mode
npm run devProduction Mode
npm startThe server runs in stdio mode for MCP communication.
Tools
The full catalogue of exposed tools — names, descriptions, and parameters — is generated from source at docs/tools.md. Regenerate it after adding or changing tools:
npm run docs:toolsError codes
Tools return structured responses; error codes common across domains:
PPM_NOT_FOUND— ID doesn't exist or RLS hides it (PPM only)PPM_VALIDATION— Input failed validation (PPM only)API_ERROR— Network or server error (all tools)
Usage example (DSM)
End-to-end flow: list schemas → create application → create form → send request → poll for the encrypted submission.
const schemas = await list_schemas();
const aa = await create_access_application({
name: 'Tax Filing Assistant',
description: 'Collecting tax information',
});
const form = await create_form({
access_application_id: aa.data.access_application_id,
system_schemas: ['ss_Right_To_Work', 'ss_Phone_Number'],
recipient_email: '[email protected]',
});
const request = await send_request({
access_application_id: aa.data.access_application_id,
short_link_key: form.data.short_link_key,
recipient_email: '[email protected]',
message: 'Please share your information',
expiry_minutes: 10,
});
let data = null;
while (!data) {
const result = await poll_and_retrieve({
request_short_code: request.data.request_short_code,
});
if (result.data) data = result.data;
else await sleep(5000);
}Architecture
Components
- Config Management: Zod-validated environment variable loading
- Authentication: AWS Cognito JWT token management with caching
- API Client: Axios-based REST client with automatic token refresh
- Schema Loader: File system-based schema loading and caching
- Encryption:
- Maxcryptor: AES-256-GCM with PBKDF2 key derivation
- WADEK: Wrapped Asymmetric DEK for key sharing
- Tool Registry: Unified
Toolinterface with Zod schema validation andzod-to-json-schemaconversion - DSM Tools: Data request workflow (in
src/tools/dsm/) - PPM Tools: Work item and activity management (in
src/tools/ppm/) - PRM Tools: Party relationship management (in
src/tools/prm/) - WRM Tools: Worker, model, MCP server/tool, role, guardrail, eval scorer, and agent run management (in
src/tools/wrm/) - MCP Server: Stdio-based server implementing MCP protocol
Security
- End-to-End Encryption: All data is encrypted with user's password
- WADEK Key Wrapping: DEKs are wrapped with recipient's password
- JWT Authentication: Cognito-based authentication with automatic refresh
- No Plaintext Storage: Encryption passwords never leave the client
Testing
# Run all tests
npm test
# Run tests with UI
npm run test:ui
# Run specific test file
npm test src/tools/list-schemas.test.tsDevelopment
Project Structure
src/
├── index.ts # MCP server entry point (dispatches via registry)
├── config.ts # Configuration management
├── raytio/
│ ├── auth.ts # Cognito authentication + getCurrentUserId()
│ ├── api-client.ts # REST API client (get/post/put/patch/delete + RequestOptions)
│ └── types.ts # Auth type definitions
├── crypto/
│ ├── maxcryptor.ts # AES-256-GCM encryption
│ └── wadek.ts # WADEK key wrapping
├── schemas/
│ └── loader.ts # Schema file loader
├── ppm/ # PPM domain helpers
│ ├── types.ts # WorkItem, Activity interfaces
│ ├── projection.ts # WORK_ITEM_SELECT, ACTIVITY_SELECT constants
│ └── filters.ts # buildPostgrestQuery()
├── prm/ # PRM domain helpers
│ ├── types.ts # Party interfaces
│ └── projection.ts # PRM SELECT constants
├── wrm/ # WRM domain helpers
│ ├── types.ts # WRM entity interfaces
│ └── projection.ts # WRM SELECT constants
└── tools/
├── types.ts # ToolResponse + error codes
├── registry.ts # Tool + ToolContext interfaces
├── domains.ts # Domain definitions + resolveDomains()
├── role-mapping.ts # Role → domain resolution
├── dsm/ # DSM tools
│ ├── index.ts
│ └── ...
├── ppm/ # PPM tools
│ ├── index.ts
│ └── ...
├── prm/ # PRM tools
│ ├── index.ts
│ └── ...
└── wrm/ # WRM tools
├── index.ts
└── ...Adding New Tools
- Create a tool file implementing the
Toolinterface fromsrc/tools/registry.ts - Add tests in a co-located
*.test.tsfile - Export from the relevant
index.ts(dsm, ppm, or wrm)
Troubleshooting
Authentication Errors
- Verify Cognito credentials are correct
- Check user has access to the Raytio API
- Ensure Cognito user pool ID and client ID match
Decryption Errors
- Verify encryption password matches user's password
- Check WADEK was created with correct password
- Ensure data format matches expected schema
API Errors
- Check API base URL is correct
- Verify network connectivity
- Review error messages in tool responses
Versioning and updates
This server follows semver. The current version is published in package.json and logged at startup.
Keeping your install up to date (local clone):
cd /path/to/mcp-server
git pull
npm install # if package.json changed
npm run buildRestart any running Claude Code sessions (exit/relaunch, or /mcp → reconnect) to pick up the new dist/. New sessions load the latest build automatically.
The server is published to npm on every merge to main (see .gitlab-ci.yml), so end users can run the latest release with:
npx -y @raytio/mcp-serveror register it in Claude Code / Codex CLI with npx instead of a local path.
Contributing
See AGENTS.md for contributor conventions — especially the requirement to bump package.json version on every branch / MR.
License
MIT
Support
For issues or questions, please open an issue in the repository.
