@mcp-layer/graphql 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,788 @@
1
+ # @mcp-layer/graphql
2
+
3
+ `@mcp-layer/graphql` exposes MCP catalogs through GraphQL using shared runtime primitives from `@mcp-layer/gateway`. It supports generated per-item operations and generic fallback operations in the same schema.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [What this package provides](#what-this-package-provides)
9
+ - [Quick Start](#quick-start)
10
+ - [API Reference](#api-reference)
11
+ - [Error Handling](#error-handling)
12
+ - [Runtime Error Reference](#runtime-error-reference)
13
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ pnpm add @mcp-layer/graphql
18
+ # or
19
+ npm install @mcp-layer/graphql
20
+ # or
21
+ yarn add @mcp-layer/graphql
22
+ ```
23
+
24
+ ## What this package provides
25
+
26
+ - Fastify plugin for GraphQL endpoint registration on MCP sessions.
27
+ - Framework-agnostic schema builder export.
28
+ - Deterministic MCP-to-GraphQL field mapping.
29
+ - Shared validation/resilience/telemetry/session-manager behavior through `@mcp-layer/gateway`.
30
+
31
+ ### Contract defaults
32
+
33
+ - Endpoint: `/{version}/graphql`
34
+ - IDE route: disabled by default
35
+ - Operation surface: generated operations + generic fallback operations
36
+ - Subscription root: intentionally omitted in v1
37
+
38
+ ## Quick Start
39
+
40
+ This example demonstrates the plugin surface when you need a production GraphQL endpoint with minimal setup. This matters for teams that already run Fastify and want GraphQL over MCP without writing adapter boilerplate.
41
+
42
+ Expected behavior: the plugin mounts `POST /v0/graphql` (or other derived version), supports generic and generated operations, and reuses gateway runtime validation and breaker behavior.
43
+
44
+ ```js
45
+ import Fastify from 'fastify';
46
+ import { attach } from '@mcp-layer/attach';
47
+ import mcpGraphql from '@mcp-layer/graphql';
48
+ import { build } from '@mcp-layer/test-server';
49
+
50
+ const server = build();
51
+ const session = await attach(server, 'primary');
52
+
53
+ const app = Fastify({ logger: true });
54
+ await app.register(mcpGraphql, { session });
55
+
56
+ await app.listen({ port: 3000 });
57
+ ```
58
+
59
+ This example demonstrates framework-agnostic schema generation. This matters when you need custom hosting, custom execution pipelines, or prebuilt schema inspection in tooling.
60
+
61
+ Expected behavior: `schema(...)` returns executable schema + typeDefs + resolvers + deterministic mapping metadata.
62
+
63
+ ```js
64
+ import { schema } from '@mcp-layer/graphql';
65
+
66
+ const built = schema(catalog, {
67
+ operations: {
68
+ generated: true,
69
+ generic: true
70
+ }
71
+ });
72
+
73
+ console.log(built.typeDefs);
74
+ console.log(built.mapping.findField('tool', 'echo'));
75
+ ```
76
+
77
+ ## API Reference
78
+
79
+ ### Default export: Fastify plugin
80
+
81
+ Registers GraphQL routes from MCP sessions.
82
+
83
+ ```ts
84
+ fastify.register(mcpGraphql, options)
85
+ ```
86
+
87
+ Options:
88
+
89
+ | Option | Type | Default | Description |
90
+ | --- | --- | --- | --- |
91
+ | `session` | `Session` or `Session[]` | required | Bootstrap session(s) used for catalog extraction and static mode. |
92
+ | `manager` | `{ get(request), close?() }` | optional | Per-request session manager (requires bootstrap `session`). |
93
+ | `prefix` | `string` or `(version, info, name) => string` | derived | Per-session route prefix. |
94
+ | `endpoint` | `string` | `"/graphql"` | GraphQL POST endpoint path under prefix. |
95
+ | `ide.enabled` | `boolean` | `false` | Enables GraphiQL route. |
96
+ | `ide.path` | `string` | `"/graphiql"` | IDE alias route path when IDE is enabled (redirect stays within the session prefix). |
97
+ | `operations.generated` | `boolean` | `true` | Include generated per-item operations. |
98
+ | `operations.generic` | `boolean` | `true` | Include generic fallback operations. |
99
+ | `validation.*` | object | gateway defaults | Validation trust/safety options. |
100
+ | `resilience.*` | object | gateway defaults | Breaker options. |
101
+ | `telemetry.*` | object | gateway defaults | OpenTelemetry API options. |
102
+ | `errors.exposeDetails` | `boolean` | `false` | Include raw upstream error message in GraphQL error text. |
103
+
104
+ ### `schema(catalog, options?)`
105
+
106
+ Builds executable GraphQL schema primitives from an extracted catalog.
107
+
108
+ ```ts
109
+ schema(catalog, {
110
+ operations?: {
111
+ generated?: boolean;
112
+ generic?: boolean;
113
+ }
114
+ }) => {
115
+ schema: GraphQLSchema;
116
+ typeDefs: string;
117
+ resolvers: Record<string, unknown>;
118
+ mapping: ReturnType<typeof map>;
119
+ }
120
+ ```
121
+
122
+ ### `map(catalog, options?)`
123
+
124
+ Returns deterministic mapping metadata used by generated operations.
125
+
126
+ Returns:
127
+
128
+ - `entries[]` with `{ type, root, name, field, item }`
129
+ - `find(type, name)`
130
+ - `findField(type, name)`
131
+ - typed lists (`tools`, `prompts`, `resources`, `templates`)
132
+
133
+ ### GraphQL operation surface
134
+
135
+ #### Generic operations
136
+
137
+ - `Mutation.callTool(name: String!, input: JSON): ToolResult!`
138
+ - `Mutation.getPrompt(name: String!, input: JSON): PromptResult!`
139
+ - `Query.readResource(uri: String!): ResourceResult!`
140
+ - `Query.readTemplate(uriTemplate: String!, params: JSON): ResourceResult!`
141
+
142
+ `readTemplate` uses RFC6570 expansion semantics through `uri-template`, so operator/modifier forms like `{+name}` and `{name*}` are expanded using the provided `params`.
143
+
144
+ #### Generated operations
145
+
146
+ Generated fields mirror MCP catalog items and are deterministic/sanitized.
147
+
148
+ Examples:
149
+
150
+ - Tool `echo` -> `Mutation.echo(input: JSON): ToolResult!`
151
+ - Tool `fail-gracefully` -> `Mutation.fail_gracefully(input: JSON): ToolResult!`
152
+
153
+ #### Response types
154
+
155
+ - `ToolResult { content, isError, structuredContent }`
156
+ - `PromptResult { messages, payload }`
157
+ - `ResourceResult { contents, text, mimeType, payload }`
158
+ - `Catalog` + `CatalogEntry` metadata for schema discovery
159
+
160
+ ## Error Handling
161
+
162
+ GraphQL execution errors are returned in `errors[]` with machine-readable extensions. Successful transport responses may still include resolver failures (`HTTP 200` with `errors` present), so client-side handling must inspect both `data` and `errors`.
163
+
164
+ Response contract highlights:
165
+
166
+ - `extensions.code` carries the primary classification (`BAD_USER_INPUT`, `UNAUTHENTICATED`, `SERVICE_UNAVAILABLE`, `TOOL_ERROR`, etc.).
167
+ - `extensions.instance` and `extensions.requestId` provide request correlation metadata.
168
+ - Validation failures include `extensions.errors[]` payloads with path/keyword/message metadata.
169
+ - Tool failures include `extensions.toolError` with preserved MCP payload content.
170
+
171
+ Operational debugging checklist:
172
+ 1. Capture `extensions.code`, `extensions.instance`, and `extensions.requestId`.
173
+ 2. Distinguish transport success from resolver failure (`HTTP 200` plus `errors`).
174
+ 3. For validation failures, inspect `extensions.errors` before retrying.
175
+ 4. For startup failures, resolve configuration errors before plugin registration.
176
+
177
+ ## Runtime Error Reference
178
+
179
+ This section is written for high-pressure debugging moments. Entries are split into request-time GraphQL errors, request-time internal `LayerError` guards, and startup configuration errors.
180
+ Every `LayerError` entry uses a hash anchor (`error-xxxxxx`) from `@mcp-layer/error` so incidents can link directly to a deterministic remediation block.
181
+
182
+ <a id="graphql-validation"></a>
183
+ ### Validation failures (`BAD_USER_INPUT`)
184
+
185
+ When it happens: input validation fails against tool/prompt schemas or template parameter limits.
186
+
187
+ Step-by-step resolution:
188
+ 1. Read `extensions.errors[]` and identify failing `path`/`message`.
189
+ 2. Compare payload against tool/prompt schema from catalog discovery.
190
+ 3. Re-submit with corrected input shape and types.
191
+ 4. Add client-side validation for common invalid payloads.
192
+
193
+ This example demonstrates extracting validation details from GraphQL responses. This matters because GraphQL transport can be `200` while operations fail semantically. Expected behavior: client logs structured validation hints and blocks invalid retries.
194
+
195
+ <details>
196
+ <summary>Fix Example: inspect GraphQL validation extension payload</summary>
197
+
198
+ ```js
199
+ const response = await gqlClient.request(query, vars);
200
+ if (Array.isArray(response.errors) && response.errors.length > 0) {
201
+ const first = response.errors[0];
202
+ if (first.extensions?.code === 'BAD_USER_INPUT') {
203
+ console.error(first.extensions.errors);
204
+ }
205
+ }
206
+ ```
207
+
208
+ </details>
209
+
210
+
211
+ <a id="graphql-auth"></a>
212
+ ### Authorization failures (`UNAUTHENTICATED`)
213
+
214
+ When it happens: manager mode requires auth header and request is missing/invalid.
215
+
216
+ Step-by-step resolution:
217
+ 1. Verify `Authorization` header is present for protected routes.
218
+ 2. Confirm `Bearer <token>` formatting is correct.
219
+ 3. Validate manager auth mode (`required` vs `optional`) for your environment.
220
+ 4. Add request middleware tests for missing and malformed auth headers.
221
+
222
+ This example shows a bearer-authenticated GraphQL request. This matters because manager mode may resolve different sessions based on identity. Expected behavior: request resolves session and executes operation without `UNAUTHENTICATED`.
223
+
224
+ <details>
225
+ <summary>Fix Example: provide authorization header in manager mode</summary>
226
+
227
+ ```js
228
+ await fastify.inject({
229
+ method: 'POST',
230
+ url: '/v0/graphql',
231
+ headers: {
232
+ authorization: 'Bearer token-value',
233
+ 'content-type': 'application/json'
234
+ },
235
+ payload: { query, variables }
236
+ });
237
+ ```
238
+
239
+ </details>
240
+
241
+ <a id="graphql-circuit-open"></a>
242
+ ### Circuit open failures (`SERVICE_UNAVAILABLE`)
243
+
244
+ When it happens: breaker is open for the selected session and request is failed fast.
245
+
246
+ Step-by-step resolution:
247
+ 1. Check recent upstream failures and breaker thresholds.
248
+ 2. Reduce request pressure or isolate failing operations.
249
+ 3. Tune `resilience.errorThresholdPercentage`, `volumeThreshold`, and `resetTimeout`.
250
+ 4. Re-run traffic after reset window and monitor for repeat opens.
251
+
252
+ This example demonstrates conservative breaker tuning for unstable upstreams. This matters because GraphQL adapters often multiplex many clients through one session surface. Expected behavior: breaker opens less aggressively while still protecting upstream.
253
+
254
+ <details>
255
+ <summary>Fix Example: tune resilience options for unstable upstreams</summary>
256
+
257
+ ```js
258
+ await fastify.register(mcpGraphql, {
259
+ session,
260
+ resilience: {
261
+ enabled: true,
262
+ timeout: 20000,
263
+ errorThresholdPercentage: 60,
264
+ resetTimeout: 10000,
265
+ volumeThreshold: 10
266
+ }
267
+ });
268
+ ```
269
+
270
+ </details>
271
+
272
+ <a id="graphql-tool"></a>
273
+ ### Tool payload errors (`TOOL_ERROR`)
274
+
275
+ When it happens: MCP tool returns `isError: true`.
276
+
277
+ Step-by-step resolution:
278
+ 1. Read `extensions.toolError.content` for upstream tool diagnostics.
279
+ 2. Inspect input arguments for domain/business constraint failures.
280
+ 3. Retry only after correcting payload or upstream state.
281
+ 4. Surface structured tool error content to API consumers.
282
+
283
+ This example shows handling tool-level failure payloads in GraphQL clients. This matters because tool failures are semantically different from protocol/runtime faults. Expected behavior: client presents tool-specific corrective guidance.
284
+
285
+ <details>
286
+ <summary>Fix Example: branch on TOOL_ERROR and show tool payload</summary>
287
+
288
+ ```js
289
+ const first = response.errors?.[0];
290
+ if (first?.extensions?.code === 'TOOL_ERROR') {
291
+ console.error(first.extensions.toolError?.content);
292
+ }
293
+ ```
294
+
295
+ </details>
296
+
297
+ <a id="graphql-runtime"></a>
298
+ ### Upstream runtime failures (`INTERNAL_SERVER_ERROR`, `TIMEOUT`, etc.)
299
+
300
+ When it happens: MCP/JSON-RPC errors bubble from upstream tool/prompt/resource calls.
301
+
302
+ Step-by-step resolution:
303
+ 1. Inspect `extensions.mcpErrorCode` and classify retryability.
304
+ 2. Correlate with upstream logs via `requestId` and `instance`.
305
+ 3. Enable `errors.exposeDetails` temporarily in controlled environments.
306
+ 4. Add retry/backoff only for transient categories (`TIMEOUT`, service pressure).
307
+
308
+ This example demonstrates temporary detail exposure during incident diagnosis. This matters because opaque runtime failures are hard to triage without short-term enhanced diagnostics. Expected behavior: richer error detail is available for debugging, then disabled again.
309
+
310
+ <details>
311
+ <summary>Fix Example: temporarily expose upstream error details</summary>
312
+
313
+ ```js
314
+ await fastify.register(mcpGraphql, {
315
+ session,
316
+ errors: {
317
+ exposeDetails: true
318
+ }
319
+ });
320
+ ```
321
+
322
+ </details>
323
+
324
+ ### Runtime Guard Errors (LayerError)
325
+
326
+ These entries are emitted from resolver guard paths before the final GraphQL error mapping. They are still useful for root-cause analysis because they identify the failing internal method and deterministic hash.
327
+
328
+ <a id="error-633b3e"></a>
329
+ ### `Request payload failed schema validation.` (`callTool`)
330
+
331
+ Thrown from: `callTool`
332
+
333
+ This happens when tool input fails schema validation before invoking MCP `tools/call`.
334
+
335
+ Step-by-step resolution:
336
+ 1. Inspect the GraphQL `BAD_USER_INPUT` response and collect `extensions.errors`.
337
+ 2. Compare tool variables against the tool schema exported in catalog metadata.
338
+ 3. Coerce client values to the expected shape/types before dispatch.
339
+ 4. Add tests that submit invalid and valid payloads for the same tool.
340
+
341
+ <details>
342
+ <summary>Fix Example: pass a schema-compliant tool payload</summary>
343
+
344
+ ```js
345
+ const mutation = `
346
+ mutation Call($input: JSONObject!) {
347
+ callTool(name: "echo", input: $input) {
348
+ isError
349
+ content
350
+ }
351
+ }
352
+ `;
353
+
354
+ await gqlClient.request(mutation, {
355
+ input: { text: 'hello world' }
356
+ });
357
+ ```
358
+
359
+ </details>
360
+
361
+ <a id="error-97d14a"></a>
362
+ ### `Tool "{tool}" reported an error.` (`callTool`)
363
+
364
+ Thrown from: `callTool`
365
+
366
+ This happens when MCP responds with `isError: true` for a tool invocation. GraphQL then maps this into `TOOL_ERROR`.
367
+
368
+ Step-by-step resolution:
369
+ 1. Inspect `extensions.toolError.content` for the upstream tool failure details.
370
+ 2. Validate tool arguments and domain constraints against server-side expectations.
371
+ 3. Fix upstream state or request payload before retrying.
372
+ 4. Add consumer handling that surfaces tool-level errors distinctly from transport/runtime failures.
373
+
374
+ <details>
375
+ <summary>Fix Example: branch on TOOL_ERROR and inspect tool payload</summary>
376
+
377
+ ```js
378
+ const first = response.errors?.[0];
379
+ if (first?.extensions?.code === 'TOOL_ERROR') {
380
+ const content = first.extensions.toolError?.content ?? [];
381
+ console.error(content);
382
+ }
383
+ ```
384
+
385
+ </details>
386
+
387
+ <a id="error-c0448a"></a>
388
+ ### `Request payload failed schema validation.` (`getPrompt`)
389
+
390
+ Thrown from: `getPrompt`
391
+
392
+ This happens when prompt arguments fail schema validation before invoking MCP `prompts/get`.
393
+
394
+ Step-by-step resolution:
395
+ 1. Inspect GraphQL `BAD_USER_INPUT` metadata for prompt argument path/message.
396
+ 2. Compare client variables with prompt input schema from catalog.
397
+ 3. Normalize optional/required prompt fields in client payload builders.
398
+ 4. Add prompt-specific request validation tests in your GraphQL client or gateway layer.
399
+
400
+ <details>
401
+ <summary>Fix Example: submit prompt arguments that match schema</summary>
402
+
403
+ ```js
404
+ const mutation = `
405
+ mutation Prompt($input: JSONObject!) {
406
+ getPrompt(name: "welcome", input: $input) {
407
+ messages
408
+ payload
409
+ }
410
+ }
411
+ `;
412
+
413
+ await gqlClient.request(mutation, {
414
+ input: { topic: 'launch' }
415
+ });
416
+ ```
417
+
418
+ </details>
419
+
420
+ ### Startup Configuration Errors (LayerError)
421
+
422
+ The following entries are thrown before route registration when plugin options are invalid.
423
+
424
+ <a id="error-07b5a1"></a>
425
+ ### `endpoint must start with "/".`
426
+
427
+ Thrown from: `validateOptions`
428
+
429
+ This happens when `endpoint` does not begin with `/`.
430
+
431
+ Step-by-step resolution:
432
+ 1. Inspect `endpoint` option source.
433
+ 2. Normalize path to absolute route format.
434
+ 3. Keep endpoint formatting logic in one config utility.
435
+ 4. Add startup tests for invalid and valid endpoint paths.
436
+
437
+ <details>
438
+ <summary>Fix Example: configure endpoint with absolute path</summary>
439
+
440
+ ```js
441
+ await fastify.register(mcpGraphql, {
442
+ session,
443
+ endpoint: '/graphql'
444
+ });
445
+ ```
446
+
447
+ </details>
448
+
449
+ <a id="error-8cd512"></a>
450
+ ### `ide.path must start with "/".`
451
+
452
+ Thrown from: `validateOptions`
453
+
454
+ This happens when `ide.path` is not absolute.
455
+
456
+ Step-by-step resolution:
457
+ 1. Ensure `ide.path` starts with `/`.
458
+ 2. Keep IDE route path separate from endpoint path.
459
+ 3. Validate defaults and env overrides together.
460
+ 4. Add registration tests for custom IDE path.
461
+
462
+ <details>
463
+ <summary>Fix Example: configure IDE path as absolute route</summary>
464
+
465
+ ```js
466
+ await fastify.register(mcpGraphql, {
467
+ session,
468
+ ide: {
469
+ enabled: true,
470
+ path: '/graphiql'
471
+ }
472
+ });
473
+ ```
474
+
475
+ </details>
476
+
477
+ <a id="error-a1c144"></a>
478
+ ### `operations.generated and operations.generic cannot both be false.`
479
+
480
+ Thrown from: `validateOptions`
481
+
482
+ This happens when both operation surfaces are disabled.
483
+
484
+ Step-by-step resolution:
485
+ 1. Enable at least one operation mode.
486
+ 2. Use `generic: true` during migration phases.
487
+ 3. Disable generated operations only when explicitly required.
488
+ 4. Add tests covering each allowed operation combination.
489
+
490
+ <details>
491
+ <summary>Fix Example: enable at least one operation surface</summary>
492
+
493
+ ```js
494
+ await fastify.register(mcpGraphql, {
495
+ session,
496
+ operations: {
497
+ generated: true,
498
+ generic: false
499
+ }
500
+ });
501
+ ```
502
+
503
+ </details>
504
+
505
+ <a id="error-7b8c55"></a>
506
+ ### `"{option}" must be a positive number.`
507
+
508
+ Thrown from: `requirePositiveNumber` (via `@mcp-layer/gateway` `validateRuntimeOptions`)
509
+
510
+ This happens when any numeric runtime limit is `<= 0`, `NaN`, or non-finite.
511
+
512
+ Step-by-step resolution:
513
+ 1. Read the failing `{option}` name from the thrown message.
514
+ 2. Trace that value from env/config parsing into plugin registration.
515
+ 3. Coerce and validate value ranges before calling `fastify.register(...)`.
516
+ 4. Add startup tests for invalid and valid values.
517
+
518
+ Canonical gateway reference: [`@mcp-layer/gateway` runtime errors](../gateway/README.md#error-688cda).
519
+
520
+ <details>
521
+ <summary>Fix Example: preflight-check numeric GraphQL runtime options</summary>
522
+
523
+ ```js
524
+ const timeout = Number(process.env.MCP_TIMEOUT_MS ?? 30000);
525
+ if (!Number.isFinite(timeout) || timeout <= 0)
526
+ throw new Error('MCP_TIMEOUT_MS must be a positive number');
527
+
528
+ await fastify.register(mcpGraphql, {
529
+ session,
530
+ resilience: { timeout }
531
+ });
532
+ ```
533
+
534
+ </details>
535
+
536
+ <a id="error-e88459"></a>
537
+ ### `validation.trustSchemas must be "auto", true, or false.`
538
+
539
+ Thrown from: `trustMode` (via `@mcp-layer/gateway` `validateRuntimeOptions`)
540
+
541
+ This happens when `validation.trustSchemas` is set to a value outside `"auto"`, `true`, or `false`.
542
+
543
+ Step-by-step resolution:
544
+ 1. Inspect runtime plugin options for `validation.trustSchemas`.
545
+ 2. Replace stringified booleans (for example `"true"`) with real booleans.
546
+ 3. Default to `"auto"` unless you need a strict trust override.
547
+ 4. Add config tests covering all allowed values.
548
+
549
+ Canonical gateway reference: [`@mcp-layer/gateway` runtime errors](../gateway/README.md#error-a103aa).
550
+
551
+ <details>
552
+ <summary>Fix Example: configure supported trust mode</summary>
553
+
554
+ ```js
555
+ await fastify.register(mcpGraphql, {
556
+ session,
557
+ validation: {
558
+ trustSchemas: 'auto'
559
+ }
560
+ });
561
+ ```
562
+
563
+ </details>
564
+
565
+ <a id="error-b1960f"></a>
566
+ ### `prefix must be a string or function.`
567
+
568
+ Thrown from: `validateRuntimeOptions` (via `@mcp-layer/gateway`)
569
+
570
+ This happens when `prefix` is neither a string nor a callback.
571
+
572
+ Step-by-step resolution:
573
+ 1. Use a static string for fixed mounting.
574
+ 2. Use a callback for version/session-aware prefixes.
575
+ 3. Remove unsupported injected types from config loaders.
576
+ 4. Add tests for both valid prefix modes.
577
+
578
+ Canonical gateway reference: [`@mcp-layer/gateway` runtime errors](../gateway/README.md#error-a74b20).
579
+
580
+ <details>
581
+ <summary>Fix Example: use supported prefix callback shape</summary>
582
+
583
+ ```js
584
+ await fastify.register(mcpGraphql, {
585
+ session,
586
+ prefix: function prefix(version, info, name) {
587
+ return `/${version}/${name}`;
588
+ }
589
+ });
590
+ ```
591
+
592
+ </details>
593
+
594
+ <a id="error-3b828a"></a>
595
+ ### `manager does not support multiple sessions. Register multiple plugins instead.`
596
+
597
+ Thrown from: `validateRuntimeOptions` (via `@mcp-layer/gateway`)
598
+
599
+ This happens when `manager` is provided while `session` is an array.
600
+
601
+ Step-by-step resolution:
602
+ 1. In manager mode, pass one bootstrap `session`.
603
+ 2. For multi-session static mounts, remove `manager`.
604
+ 3. Register separate plugin instances per static session surface.
605
+ 4. Add startup tests for both architecture modes.
606
+
607
+ Canonical gateway reference: [`@mcp-layer/gateway` runtime errors](../gateway/README.md#error-712f62).
608
+
609
+ <details>
610
+ <summary>Fix Example: use one bootstrap session with manager</summary>
611
+
612
+ ```js
613
+ await fastify.register(mcpGraphql, {
614
+ session: bootstrapSession,
615
+ manager
616
+ });
617
+ ```
618
+
619
+ </details>
620
+
621
+ <a id="error-126799"></a>
622
+ ### `session is required when manager is provided (used for catalog bootstrap).`
623
+
624
+ Thrown from: `validateRuntimeOptions` (via `@mcp-layer/gateway`)
625
+
626
+ This happens when `manager` is configured without a bootstrap `session`.
627
+
628
+ Step-by-step resolution:
629
+ 1. Always provide a bootstrap `session` with `manager`.
630
+ 2. Ensure bootstrap session is connected before registration.
631
+ 3. Keep bootstrap and managed sessions aligned in capability surface.
632
+ 4. Add tests that fail fast without bootstrap session.
633
+
634
+ Canonical gateway reference: [`@mcp-layer/gateway` runtime errors](../gateway/README.md#error-c773d9).
635
+
636
+ <details>
637
+ <summary>Fix Example: include bootstrap session in manager mode</summary>
638
+
639
+ ```js
640
+ await fastify.register(mcpGraphql, {
641
+ session: bootstrapSession,
642
+ manager
643
+ });
644
+ ```
645
+
646
+ </details>
647
+
648
+ <a id="error-66da32"></a>
649
+ ### `session or manager option is required.`
650
+
651
+ Thrown from: `validateRuntimeOptions` (via `@mcp-layer/gateway`)
652
+
653
+ This happens when plugin options omit both `session` and `manager`.
654
+
655
+ Step-by-step resolution:
656
+ 1. Pass a connected `session` for static mode.
657
+ 2. Or pass `manager` plus bootstrap `session` for dynamic mode.
658
+ 3. Assert required options before `register`.
659
+ 4. Add tests that validate registration failure on missing session source.
660
+
661
+ Canonical gateway reference: [`@mcp-layer/gateway` runtime errors](../gateway/README.md#error-6e22f2).
662
+
663
+ <details>
664
+ <summary>Fix Example: provide GraphQL session source</summary>
665
+
666
+ ```js
667
+ await fastify.register(mcpGraphql, { session });
668
+ ```
669
+
670
+ </details>
671
+
672
+ <a id="error-e996df"></a>
673
+ ### `manager must be an object with a get(request) function.`
674
+
675
+ Thrown from: `validateRuntimeOptions` (via `@mcp-layer/gateway`)
676
+
677
+ This happens when `manager` is not an object implementing `get(request)`.
678
+
679
+ Step-by-step resolution:
680
+ 1. Ensure `manager` is an object.
681
+ 2. Implement `async get(request)` returning a `Session`.
682
+ 3. Optionally add `close()` for lifecycle cleanup.
683
+ 4. Add contract tests for malformed manager values.
684
+
685
+ Canonical gateway reference: [`@mcp-layer/gateway` runtime errors](../gateway/README.md#error-b78e6d).
686
+
687
+ <details>
688
+ <summary>Fix Example: implement required manager contract</summary>
689
+
690
+ ```js
691
+ const manager = {
692
+ async get(request) {
693
+ return resolveSession(request);
694
+ }
695
+ };
696
+ ```
697
+
698
+ </details>
699
+
700
+ <a id="error-d5f1ec"></a>
701
+ ### `errors must be an object.`
702
+
703
+ Thrown from: `validateRuntimeOptions` (via `@mcp-layer/gateway`)
704
+
705
+ This happens when `errors` is provided as a primitive instead of an object.
706
+
707
+ Step-by-step resolution:
708
+ 1. Ensure `errors` is an object literal.
709
+ 2. Keep only supported keys under `errors`.
710
+ 3. Remove primitive shorthand from environment mappers.
711
+ 4. Add shape-validation tests.
712
+
713
+ Canonical gateway reference: [`@mcp-layer/gateway` runtime errors](../gateway/README.md#error-6fc055).
714
+
715
+ <details>
716
+ <summary>Fix Example: provide GraphQL error options object</summary>
717
+
718
+ ```js
719
+ await fastify.register(mcpGraphql, {
720
+ session,
721
+ errors: { exposeDetails: false }
722
+ });
723
+ ```
724
+
725
+ </details>
726
+
727
+ <a id="error-785cc4"></a>
728
+ ### `validation must be an object.`
729
+
730
+ Thrown from: `validateRuntimeOptions` (via `@mcp-layer/gateway`)
731
+
732
+ This happens when `validation` is provided as a non-object value.
733
+
734
+ Step-by-step resolution:
735
+ 1. Move validation keys under a `validation` object.
736
+ 2. Keep values typed correctly (number/boolean/allowed enums).
737
+ 3. Validate env/config coercion before registration.
738
+ 4. Add tests for malformed and valid validation objects.
739
+
740
+ Canonical gateway reference: [`@mcp-layer/gateway` runtime errors](../gateway/README.md#error-349758).
741
+
742
+ <details>
743
+ <summary>Fix Example: pass validation options as object</summary>
744
+
745
+ ```js
746
+ await fastify.register(mcpGraphql, {
747
+ session,
748
+ validation: {
749
+ trustSchemas: 'auto',
750
+ maxSchemaDepth: 10
751
+ }
752
+ });
753
+ ```
754
+
755
+ </details>
756
+
757
+ <a id="error-73e3ed"></a>
758
+ ### `normalizeError must be a function.`
759
+
760
+ Thrown from: `validateRuntimeOptions` (via `@mcp-layer/gateway`)
761
+
762
+ This happens when `normalizeError` is provided with a non-callable value.
763
+
764
+ Step-by-step resolution:
765
+ 1. Ensure `normalizeError` is a function reference.
766
+ 2. Keep function signature aligned with gateway runtime usage.
767
+ 3. Remove object/string placeholders from config loaders.
768
+ 4. Add tests validating custom normalizer behavior.
769
+
770
+ Canonical gateway reference: [`@mcp-layer/gateway` runtime errors](../gateway/README.md#error-b72182).
771
+
772
+ <details>
773
+ <summary>Fix Example: provide callable normalizeError handler</summary>
774
+
775
+ ```js
776
+ await fastify.register(mcpGraphql, {
777
+ session,
778
+ normalizeError: function normalizeError(error, instance, requestId, options) {
779
+ return mapGraphQLError(error, instance, requestId, options);
780
+ }
781
+ });
782
+ ```
783
+
784
+ </details>
785
+
786
+ ## License
787
+
788
+ MIT