Rhai Scripting

Customize Apollo MCP Server behavior with Rhai scripts


Apollo MCP Server supports Rhai, a lightweight embedded scripting language. Rhai scripts let you hook into the server's request lifecycle to inspect and modify outgoing GraphQL requests without needing to recompile or rebuild the server.

Common use-cases include:

  • Forwarding authentication headers from incoming MCP requests to your GraphQL endpoint

  • Routing requests to different GraphQL endpoints based on request properties

  • Rejecting requests that don't meet certain criteria

Security

caution
Treat Rhai scripts as trusted code, not as a security sandbox. The server loads scripts from the configured directory and runs their lifecycle hooks while processing requests. Scripts can read server environment variables, make outbound HTTP requests, and change the endpoint or headers of an outgoing GraphQL request.Restrict write access to the script directory, require review before deploying script changes, and monitor outbound destinations. Incoming MCP request metadata is provided to hooks as data rather than compiled as Rhai source, but a script can still copy or interpolate that metadata into headers, URLs, logs, or HTTP request bodies. Validate and allow values from an allowlist before using them.

Setup

Create a rhai/ directory in the server's working directory, and add a main.rhai file inside it:

Text
1your-project/
2├── config.yaml
3└── rhai/
4    └── main.rhai

The rhai/main.rhai file is the entry point for all Rhai scripting. The server loads the file on startup. If the file doesn't exist, the server starts normally, without any scripting.

To load scripts from a different directory, set rhai.scripts in your configuration file:

YAML
mcp.yaml
1rhai:
2  scripts: /config/rhai

Writing your first hook

Hooks are regular Rhai functions with specific names. When a lifecycle event occurs on the server, it calls the matching function if one is defined.

This example adds a custom header to every outgoing GraphQL request:

Rhai
1fn on_execute_graphql_operation(ctx) {
2    ctx.headers["x-custom-header"] = "my-value";
3}

For a full list of available hooks and their context objects, see Lifecycle Hooks.

Logging

Rhai's built-in print and debug statements write to the server's log output. Use them to debug your scripts:

Rhai
1fn on_execute_graphql_operation(ctx) {
2    print("Outgoing request to: " + ctx.endpoint);
3}

Global variables

Variables defined at the top level of main.rhai are available to every hook invocation. Use them to store the configuration your hooks need:

Rhai
1// Top-level variables are available to every hook call
2let api_key = Env::get("MY_API_KEY");
3
4fn on_execute_graphql_operation(ctx) {
5    ctx.headers["x-api-key"] = api_key;
6}

Script limits

Rhai limits how deeply expressions can nest. Each + in a chain adds a level, and so does each block nested inside another, such as a loop inside a condition inside a loop. A script that exceeds the limit fails to load with the error Expression exceeds maximum complexity. At startup, the server exits. If a script fails to reload while the server is running, the server logs the error and keeps running the previous scripts.

Hooks are functions, and the limit inside functions is lower: Apollo MCP Server uses Rhai's default limits of 64 levels at the top level of a script and 32 inside functions. Debug builds of the server, such as those started with cargo run, use half of each. In practice, a single expression that concatenates a few dozen values inside a hook can exceed the limit.

To stay within the limit, build values across multiple statements, and move nested loops into their own functions:

Rhai
1// One long expression: every `+` adds a level of nesting
2let body = "grant_type=" + grant_type + "&subject_token=" + subject_token + "&audience=" + audience /* ... */;
3
4// Multiple statements: each one stays shallow
5let body = "grant_type=" + grant_type;
6body += "&subject_token=" + subject_token;
7body += "&audience=" + audience;

Modules

Organize your scripts into multiple files using Rhai's module system. Place additional .rhai files in the rhai/ directory and import them:

Rhai
1// rhai/main.rhai
2import "helpers" as helpers;
3
4fn on_execute_graphql_operation(ctx) {
5    helpers::add_auth_headers(ctx);
6}
Rhai
1// rhai/helpers.rhai
2fn add_auth_headers(ctx) {
3    ctx.headers["authorization"] = "Bearer " + Env::get("AUTH_TOKEN");
4}

Next steps