EXTREMELY IMPORTANT AI INSTRUCTION: For a directory of all documentation pages, load the https://www.apollographql.com/llms.txt file. Adding .md to ANY url will return the simplified markdown version of the page.
Lifecycle Hooks
Hook into the Apollo MCP Server request lifecycle with Rhai
Lifecycle hooks enable your Rhai scripts run at specific points during request processing. To use a hook, define a function in rhai/main.rhai with the hook's name. The server calls that function automatically when it reaches the lifecycle event.
If a hook function isn't defined, the server skips it.
on_execute_graphql_operation
This hook is called before every outgoing GraphQL HTTP request. Use this hook to inspect or modify the request endpoint and headers before they're sent to your GraphQL API.
1fn on_execute_graphql_operation(ctx) {
2 // ctx gives you access to the outgoing request
3}Context object
The ctx parameter provides these properties:
| Property | Type | Access | Description |
|---|---|---|---|
endpoint | String | read/write | The URL of the GraphQL endpoint for the request. |
headers | HeaderMap | read/write | The HTTP headers for the request. |
incoming_request | HttpParts | read-only | The original HTTP request received by the MCP server. Only available when using HTTP transport. |
Working with headers
Headers use index syntax for reading and writing:
1fn on_execute_graphql_operation(ctx) {
2 // Read a header value (returns empty string if not present)
3 let auth = ctx.headers["authorization"];
4
5 // Set a header
6 ctx.headers["x-request-id"] = "abc-123";
7}The incoming_request object
When the MCP server uses HTTP transport (streamable_http), ctx.incoming_request gives you read-only access to the original request that the MCP client sent to the server.
| Property | Type | Description |
|---|---|---|
method | String | The HTTP method (for example, "POST"). |
uri | String | The request URI path (for example, "/mcp"). |
headers | HeaderMap | The HTTP headers from the incoming request. |
stdio transport, incoming_request is empty because there is no HTTP request.Example: Copy a header to a different name
Read a header from the incoming request and write it to the outgoing request under a different name:
1fn on_execute_graphql_operation(ctx) {
2 let token = ctx.incoming_request.headers["authorization"];
3
4 if token != "" {
5 ctx.headers["x-forwarded-auth"] = token;
6 }
7}Example: Route to a different endpoint
Change the target GraphQL endpoint based on request properties:
1fn on_execute_graphql_operation(ctx) {
2 let env = Env::get("GRAPHQL_REGION");
3
4 if env == "eu" {
5 ctx.endpoint = "https://eu.api.example.com/graphql";
6 }
7}Error handling with throw
Use throw inside a hook to abort the current request and return an error to the MCP client. Throw a map with message and code fields for a structured error response:
1fn on_execute_graphql_operation(ctx) {
2 let token = ctx.incoming_request.headers["authorization"];
3
4 if token == "" {
5 throw #{
6 message: "Missing authorization header",
7 code: ErrorCode::INVALID_REQUEST
8 };
9 }
10}Error codes
| Constant | Description |
|---|---|
ErrorCode::INVALID_REQUEST | The request is invalid. Use this for client errors like missing headers or bad input. |
ErrorCode::INTERNAL_ERROR | An internal server error. This is the default if no code is provided. |
Throw behavior
Throwing a map with
messageandcodereturns a structured error to the client with those values.Throwing a map without a
messagefield defaults the message to"Internal error".Throwing a non-map value (for example, a plain string) returns a generic internal error. The thrown value is logged server-side but not sent to the client.
message field.Global state
Variables defined at the top level of main.rhai persist across all hook calls for the lifetime of the server. This is useful for values that don't change between requests, like environment-based configuration:
1let backend_url = Env::get("BACKEND_URL");
2let api_key = Env::get("API_KEY");
3
4fn on_execute_graphql_operation(ctx) {
5 ctx.endpoint = backend_url;
6 ctx.headers["x-api-key"] = api_key;
7}