Subchapter 6.8
references/rhai-scripting.mdMarkdown5 KBView on GitHub
Common use cases:
Security: Treat Rhai scripts as trusted code. Scripts can read environment variables, make outbound HTTP requests, and change the endpoint or headers of outgoing GraphQL requests. Restrict write access to the script directory and review script changes before deploying.
Create a rhai/ directory with a main.rhai entry point:
your-project/
├── config.yaml
└── rhai/
└── main.rhaiIf rhai/main.rhai does not exist, the server starts normally without scripting. Configure a custom directory with:
rhai:
scripts: /config/rhaiCalled before every outgoing GraphQL HTTP request. Define the function in main.rhai:
fn on_execute_graphql_operation(ctx) {
ctx.headers["x-custom-header"] = "my-value";
}| Property | Type | Access | Description |
|---|---|---|---|
endpoint | String | read/write | GraphQL endpoint URL for the request |
headers | HeaderMap | read/write | HTTP headers for the outgoing request |
incoming_request | HttpParts | read-only | Original HTTP request (streamable_http only) |
tool_name | String | read-only | MCP tool that triggered this operation |
trace_id | String | read-only | Current OpenTelemetry trace ID (32-char hex, or empty) |
| Property | Type | Description |
|---|---|---|
method | String | HTTP method (e.g., "POST") |
uri | String | Request URI path (e.g., "/mcp") |
headers | HeaderMap | HTTP headers from the incoming request |
Empty when using stdio transport.
fn on_execute_graphql_operation(ctx) {
let token = ctx.incoming_request.headers["authorization"];
if token != "" {
ctx.headers["x-forwarded-auth"] = token;
}
}let region = Env::get("GRAPHQL_REGION");
fn on_execute_graphql_operation(ctx) {
if region == "eu" {
ctx.endpoint = "https://eu.api.example.com/graphql";
}
}This only checks that the header is present. It does not validate the token. Use transport.auth (see auth.md) for real authentication.
fn on_execute_graphql_operation(ctx) {
if ctx.incoming_request.headers["authorization"] == "" {
throw #{
message: "Missing authorization header",
code: ErrorCode::INVALID_REQUEST
};
}
}Use throw with a map containing message and code:
| Constant | Description |
|---|---|
ErrorCode::INVALID_REQUEST | Client error (missing header, bad input) |
ErrorCode::INTERNAL_ERROR | Server error (default if no code provided) |
Throwing a non-map value logs the value server-side but returns a generic internal error to the client.
| Function | Description |
|---|---|
Env::get(name) | Read environment variable (returns empty string if unset) |
| Function | Description |
|---|---|
JSON::parse(input) | Parse JSON string into Rhai value |
JSON::stringify(value) | Convert Rhai value to JSON string |
| Function | Description |
|---|---|
Sha256::digest(input) | Hex-encoded SHA-256 hash |
| Function | Description |
|---|---|
Regex::is_match(string, pattern) | Test if pattern matches |
Regex::replace(string, pattern, replacement) | Replace all matches (supports capture groups) |
Regex::matches(string, pattern) | Return all matching substrings |
| Function | Description |
|---|---|
Http::get(url) / Http::get(url, options) | GET request, returns Promise |
Http::post(url) / Http::post(url, options) | POST request, returns Promise |
Options: headers (Map), body (String), timeout (Integer, default 30s).
Call .wait() on the promise. Response has .status (Integer), .text() (String), .json() (Dynamic).
Split scripts into multiple files:
// rhai/main.rhai
import "helpers" as helpers;
fn on_execute_graphql_operation(ctx) {
helpers::add_auth_headers(ctx);
}Top-level variables are available to every hook but are copied per invocation. Each hook gets its own copy. A value a hook writes to a variable is discarded when the hook returns. Hooks cannot carry state between requests.
Variables that a top-level closure captured are also copied per invocation, so concurrent calls never read each other’s values.
Hooks for concurrent requests run in parallel. Do not write hooks that assume they run to completion before the next request’s hook starts.