redhat-datalayer-mcp 0.1.0__tar.gz

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.
Files changed (31) hide show
  1. redhat_datalayer_mcp-0.1.0/.gitignore +12 -0
  2. redhat_datalayer_mcp-0.1.0/CONTEXT.md +23 -0
  3. redhat_datalayer_mcp-0.1.0/PKG-INFO +563 -0
  4. redhat_datalayer_mcp-0.1.0/README.md +547 -0
  5. redhat_datalayer_mcp-0.1.0/docs/adr/0001-datalayer-extension-as-lifespan-not-provider.md +19 -0
  6. redhat_datalayer_mcp-0.1.0/docs/adr/0002-filesystem-provider-with-ast-collision-detection.md +19 -0
  7. redhat_datalayer_mcp-0.1.0/pyproject.toml +42 -0
  8. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/__init__.py +27 -0
  9. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/config.py +70 -0
  10. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/env.py +18 -0
  11. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/errors.py +53 -0
  12. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/extension.py +119 -0
  13. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/parse.py +93 -0
  14. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/py.typed +0 -0
  15. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/runtime.py +84 -0
  16. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/scan.py +77 -0
  17. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/schema.py +60 -0
  18. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/server.py +26 -0
  19. redhat_datalayer_mcp-0.1.0/src/datalayer_mcp/tools.py +75 -0
  20. redhat_datalayer_mcp-0.1.0/tests/conftest.py +105 -0
  21. redhat_datalayer_mcp-0.1.0/tests/fixtures/operations/Cves.graphql +12 -0
  22. redhat_datalayer_mcp-0.1.0/tests/test_config.py +67 -0
  23. redhat_datalayer_mcp-0.1.0/tests/test_env.py +35 -0
  24. redhat_datalayer_mcp-0.1.0/tests/test_errors.py +31 -0
  25. redhat_datalayer_mcp-0.1.0/tests/test_extension.py +216 -0
  26. redhat_datalayer_mcp-0.1.0/tests/test_parse.py +156 -0
  27. redhat_datalayer_mcp-0.1.0/tests/test_registry.py +24 -0
  28. redhat_datalayer_mcp-0.1.0/tests/test_runtime.py +71 -0
  29. redhat_datalayer_mcp-0.1.0/tests/test_schema.py +17 -0
  30. redhat_datalayer_mcp-0.1.0/tests/test_server.py +123 -0
  31. redhat_datalayer_mcp-0.1.0/tests/test_tools.py +27 -0
@@ -0,0 +1,12 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ .env
@@ -0,0 +1,23 @@
1
+ # datalayer-mcp
2
+
3
+ MCP server factory that turns curated `.graphql` operation files and user-defined tools into a FastMCP server backed by the Red Hat supergraph.
4
+
5
+ ## Language
6
+
7
+ **Operation**: A named GraphQL query or mutation defined in a single `.graphql` file. Becomes one auto-tool on the MCP server. One operation per file; duplicate names across files are a hard startup error.
8
+ _Avoid_: query, request, command
9
+
10
+ **Auto-tool**: An MCP tool generated automatically from an operation file. Executes the raw GraphQL operation against the supergraph. No user code required.
11
+ _Avoid_: generated tool, operation tool
12
+
13
+ **Custom tool**: A user-defined MCP tool in the tools directory, decorated with `@tool` from `fastmcp.tools`. Calls the supergraph via `execute_operation` or `get_client`.
14
+ _Avoid_: hand-written tool, user tool, manual tool
15
+
16
+ **DatalayerExtension**: The central library object. Acts as a FastMCP `Lifespan` (manages the supergraph client) and mounts both auto-tools and the `FileSystemProvider` for custom tools onto a FastMCP server via `mount()`.
17
+ _Avoid_: plugin, middleware, adapter, provider
18
+
19
+ **Operation registry**: Module-level dict mapping operation names to raw query strings. Populated at `mount()` time. Enables `execute_operation` to resolve query strings without a reference to the extension instance.
20
+ _Avoid_: query cache, operations map
21
+
22
+ **Tools directory**: The directory scanned by FastMCP's `FileSystemProvider` for custom tools. Configured via `tools.dir` in `mcp-config.yaml`.
23
+ _Avoid_: custom tools folder, plugins directory
@@ -0,0 +1,563 @@
1
+ Metadata-Version: 2.5
2
+ Name: redhat-datalayer-mcp
3
+ Version: 0.1.0
4
+ Summary: FastMCP runtime — turn GraphQL operations into MCP tools
5
+ Author: Red Hat DataLayer Team
6
+ Maintainer-email: Mayur Deshmukh <mdeshmuk@redhat.com>, Pranav Advani <padvani@redhat.com>
7
+ License-Expression: Apache-2.0
8
+ Requires-Python: >=3.13
9
+ Requires-Dist: fastmcp>=3.0
10
+ Requires-Dist: graphql-core>=3.2
11
+ Requires-Dist: pydantic>=2
12
+ Requires-Dist: python-dotenv>=1.2.2
13
+ Requires-Dist: pyyaml>=6
14
+ Requires-Dist: redhat-datalayer-graphql>=0.1.0
15
+ Description-Content-Type: text/markdown
16
+
17
+ # datalayer-mcp
18
+
19
+ MCP runtime library for the Datalayer platform. Turns curated GraphQL operation files into [FastMCP](https://gofastmcp.com/) tools backed by the Red Hat GraphQL **supergraph** via [`datalayer-graphql`](../datalayer-graphql/).
20
+
21
+ Each `.graphql` file in your operations directory becomes one MCP tool. Custom tools can reuse the same supergraph client for computed or aggregated logic.
22
+
23
+ ## Features
24
+
25
+ - **Config-driven bootstrap** — `mcp-config.yaml` for supergraph URL, auth, and paths
26
+ - **Operation discovery** — scans a directory for `*.graphql` files at startup
27
+ - **Auto tool registration** — one MCP tool per named GraphQL operation
28
+ - **Variable schemas** — GraphQL variables mapped to Pydantic models → JSON Schema for MCP clients
29
+ - **Custom tool support** — `@tool` handlers (from `fastmcp.tools`) with shared `get_client()`
30
+ - **`.env` loading** — bearer token from a file next to your config (no manual `export`)
31
+ - **Structured errors** — federation-aware `GraphQLError` wrapped as `OperationError`
32
+ - **Session-managed client** — reuses HTTP connections via `async with SupergraphClient`
33
+
34
+ ## Requirements
35
+
36
+ - Python 3.13+
37
+ - [uv](https://docs.astral.sh/uv/) (recommended)
38
+ - [`datalayer-graphql`](../datalayer-graphql/) (workspace dependency)
39
+ - VPN access for internal endpoints (e.g. `vpn.graphql.stage.redhat.com`)
40
+ - Valid GraphQL bearer token with `api.graphql` scope
41
+
42
+ ## Installation
43
+
44
+ From the monorepo root:
45
+
46
+ ```bash
47
+ uv sync --package datalayer-mcp
48
+ ```
49
+
50
+ With dev dependencies (for running tests):
51
+
52
+ ```bash
53
+ uv sync --package datalayer-mcp --group dev
54
+ ```
55
+
56
+ As a workspace dependency in another package:
57
+
58
+ ```toml
59
+ dependencies = ["datalayer-mcp"]
60
+
61
+ [tool.uv.sources]
62
+ datalayer-mcp = { workspace = true }
63
+ ```
64
+
65
+ ## Quick start
66
+
67
+ ### 1. Project layout
68
+
69
+ ```
70
+ my-mcp-server/
71
+ ├── mcp-config.yaml
72
+ ├── .env # SUPERGRAPH_TOKEN (gitignored)
73
+ ├── server.py
74
+ ├── operations/
75
+ │ └── Cves.graphql
76
+ └── tools/
77
+ ├── __init__.py
78
+ └── custom.py # optional
79
+ ```
80
+
81
+ A complete working example lives in [`examples/mcp/`](../../examples/mcp/) at the monorepo root.
82
+
83
+ ### 2. Configure supergraph connection
84
+
85
+ **`mcp-config.yaml`**
86
+
87
+ ```yaml
88
+ supergraph:
89
+ url: https://vpn.graphql.stage.redhat.com
90
+ token_env: SUPERGRAPH_TOKEN
91
+ client_name: rh-datalayer-platform-poc
92
+ verify_ssl: false
93
+ extra_headers: {}
94
+
95
+ operations:
96
+ directory: operations
97
+
98
+ tools:
99
+ dir: tools # optional directory of custom @tool files
100
+ include_response_headers: false
101
+ ```
102
+
103
+ **`.env`** (same directory as `mcp-config.yaml`):
104
+
105
+ ```env
106
+ SUPERGRAPH_TOKEN=eyJ...
107
+ ```
108
+
109
+ Copy from the example:
110
+
111
+ ```bash
112
+ cp examples/mcp/.env.example examples/mcp/.env
113
+ ```
114
+
115
+ ### 3. Add a GraphQL operation
116
+
117
+ **`operations/Cves.graphql`**
118
+
119
+ ```graphql
120
+ # List CVEs with pagination
121
+ query Cves($first: Int!) {
122
+ cves(first: $first) {
123
+ totalCount
124
+ edges {
125
+ node {
126
+ title
127
+ url
128
+ }
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ Rules for operation files:
135
+
136
+ - Exactly **one named operation** per file (query, mutation, or subscription)
137
+ - The operation name in the GraphQL document becomes the MCP tool name (`Cves`)
138
+ - GraphQL docstrings (`"""..."""` or `"..."`) or leading `#` comment lines become the tool description
139
+ - The filename does not have to match the operation name
140
+
141
+ ### 4. Bootstrap the server
142
+
143
+ **`server.py`**
144
+
145
+ ```python
146
+ from pathlib import Path
147
+
148
+ from datalayer_mcp import create_server
149
+
150
+ CONFIG = Path(__file__).resolve().parent / "mcp-config.yaml"
151
+ mcp = create_server(config_path=CONFIG)
152
+
153
+ if __name__ == "__main__":
154
+ mcp.run()
155
+ ```
156
+
157
+ Use `Path(__file__)` so the config path works regardless of shell working directory.
158
+
159
+ ### 5. Run
160
+
161
+ ```bash
162
+ uv run --package datalayer-mcp examples/mcp/server.py
163
+ ```
164
+
165
+ The server starts on **stdio** (standard MCP transport). After the FastMCP banner it waits for an MCP client — that idle state is normal.
166
+
167
+ ---
168
+
169
+ ## Architecture
170
+
171
+ ```
172
+ mcp-config.yaml + .env
173
+ │
174
+ ▼
175
+ create_server() ───► DatalayerExtension(config_path)
176
+ │
177
+ ┌──────────────┴──────────────┐
178
+ ▼ ▼
179
+ FastMCP(lifespan=ext) ext.mount(mcp)
180
+ │ │
181
+ ▼ (on server run) ├── auto-tools (LocalProvider)
182
+ SupergraphClient ├── operation registry
183
+ in lifespan_context └── FileSystemProvider (tools.dir)
184
+ ```
185
+
186
+ ### Auto-generated vs custom tools
187
+
188
+ | Type | Source | Example |
189
+ |------|--------|---------|
190
+ | **Auto** | Each `operations/*.graphql` file | `Cves` — runs the raw GraphQL operation |
191
+ | **Custom** | Python files in `tools.dir` | `cve_count` — calls `Cves` internally, returns a summary |
192
+
193
+ **Auto tool** — registered from `Cves.graphql`:
194
+
195
+ ```python
196
+ # Internally: handler(variables: CvesVariables) → client.execute("Cves", ...)
197
+ # Returns: { "data": { "cves": { ... } } }
198
+ ```
199
+
200
+ **Custom tool** — `tools/custom.py`:
201
+
202
+ ```python
203
+ from datalayer_mcp import get_client
204
+ from fastmcp.tools import tool
205
+
206
+
207
+ @tool
208
+ async def cve_count(first: int = 10) -> dict:
209
+ """Return total CVE count from the Cves operation."""
210
+ client = get_client()
211
+ result = await client.execute(
212
+ operation_name="Cves",
213
+ variables={"first": first},
214
+ )
215
+ return {"total_count": result.data["cves"]["totalCount"]}
216
+ ```
217
+
218
+ Custom tools live in the directory named by `tools.dir`. FastMCP's `FileSystemProvider` discovers `@tool` functions automatically; the files do not import the server.
219
+
220
+ ---
221
+
222
+ ## Configuration reference
223
+
224
+ All paths in `mcp-config.yaml` are resolved **relative to the config file's directory**, not the shell cwd.
225
+
226
+ ### `supergraph`
227
+
228
+ | Field | Type | Default | Description |
229
+ |-------|------|---------|-------------|
230
+ | `url` | URL | — | Supergraph endpoint |
231
+ | `token_env` | string | `SUPERGRAPH_TOKEN` | Env var name for bearer token |
232
+ | `client_name` | string | `rh-datalayer-platform-poc` | Apollo `apollographql-client-name` header |
233
+ | `client_version` | string | `latest` | Apollo client version header |
234
+ | `timeout` | float | `30.0` | HTTP timeout in seconds |
235
+ | `verify_ssl` | bool | `true` | TLS verification (`false` for VPN/internal CAs) |
236
+ | `extra_headers` | object | `{}` | Additional HTTP headers on every request |
237
+
238
+ ### `operations`
239
+
240
+ | Field | Type | Default | Description |
241
+ |-------|------|---------|-------------|
242
+ | `directory` | string | `operations` | Folder containing `*.graphql` files |
243
+
244
+ ### `tools`
245
+
246
+ | Field | Type | Default | Description |
247
+ |-------|------|---------|-------------|
248
+ | `dir` | string \| null | `null` | Directory of custom `@tool` files, relative to the config file |
249
+ | `include_response_headers` | bool | `false` | Include router HTTP headers in auto-tool responses |
250
+
251
+ When `include_response_headers: true`, auto-tools return:
252
+
253
+ ```json
254
+ {
255
+ "data": { },
256
+ "response_headers": { "content-type": "application/json" },
257
+ "extensions": { }
258
+ }
259
+ ```
260
+
261
+ ### Environment variables
262
+
263
+ Loaded from `{config_dir}/.env`, then `{cwd}/.env` as fallback. Existing shell variables are **not** overwritten.
264
+
265
+ | Variable | Required | Description |
266
+ |----------|----------|-------------|
267
+ | `SUPERGRAPH_TOKEN` | Yes (for live calls) | Bearer JWT with `api.graphql` scope |
268
+
269
+ ---
270
+
271
+ ## API reference
272
+
273
+ ### Public exports
274
+
275
+ ```python
276
+ from datalayer_mcp import (
277
+ create_server,
278
+ DatalayerExtension,
279
+ execute_operation,
280
+ get_client,
281
+ SupergraphError,
282
+ SupergraphConnectionError,
283
+ SupergraphHTTPError,
284
+ GraphQLError,
285
+ GraphQLErrorDetail,
286
+ OperationError,
287
+ )
288
+ ```
289
+
290
+ ### `create_server(name="datalayer_mcp", *, config_path, **fastmcp_kwargs)`
291
+
292
+ Loads config, constructs a `DatalayerExtension`, passes it as `lifespan` to a new `FastMCP` instance (forwarding `name` and extra `fastmcp_kwargs`), mounts auto-tools and custom tools from `tools.dir`, and returns the `FastMCP` instance.
293
+
294
+ ```python
295
+ from pathlib import Path
296
+ from datalayer_mcp import create_server
297
+
298
+ mcp = create_server("my-mcp", config_path=Path("mcp-config.yaml").resolve())
299
+ mcp.run()
300
+ ```
301
+
302
+ **Startup validation:**
303
+
304
+ - Operations directory must exist
305
+ - Each `.graphql` file must have valid syntax and exactly one named operation
306
+ - No duplicate operation names across files
307
+ - MCP parser and `datalayer-graphql` loader must agree on operation names
308
+ - Custom `@tool` names in `tools.dir` must not collide with operation names
309
+
310
+ ### `get_client()`
311
+
312
+ Returns the lifespan-managed `SupergraphClient`. Only available from within a tool handler while the MCP server is running — reads from FastMCP's `lifespan_context` via `get_context()`, with no module-level client global.
313
+
314
+ ```python
315
+ from datalayer_mcp import get_client
316
+
317
+ client = get_client()
318
+ result = await client.execute(operation_name="Cves", variables={"first": 10})
319
+ ```
320
+
321
+ Raises `RuntimeError` if called outside an active MCP server session.
322
+
323
+ ### `execute_operation(operation_name, variables=None)`
324
+
325
+ Executes a registered GraphQL operation against the supergraph using the query string stored in the module-level operation registry. Must be called within an active tool handler (where `get_client()` is available).
326
+
327
+ ```python
328
+ from datalayer_mcp import execute_operation
329
+
330
+ result = await execute_operation("Cves", {"first": 10})
331
+ ```
332
+
333
+ Raises `ValueError` if the operation name is not registered, or `RuntimeError` if called outside an active MCP server session.
334
+
335
+ ### `DatalayerExtension(config_path)`
336
+
337
+ A FastMCP `Lifespan`: opens a session-managed `SupergraphClient` on server startup and closes it on shutdown, storing it under `lifespan_context["supergraph_client"]`. `create_server()` wires this up for you; construct it directly for custom `FastMCP` setups:
338
+
339
+ ```python
340
+ from fastmcp import FastMCP
341
+ from datalayer_mcp import DatalayerExtension
342
+
343
+ ext = DatalayerExtension("mcp-config.yaml")
344
+ mcp = FastMCP("my-server", lifespan=ext)
345
+ ext.mount(mcp)
346
+ ```
347
+
348
+ ---
349
+
350
+ ## GraphQL variables → MCP input schema
351
+
352
+ Operation variables are parsed with `graphql-core` and converted to dynamic Pydantic models:
353
+
354
+ | GraphQL type | Python / JSON Schema |
355
+ |--------------|---------------------|
356
+ | `String`, `ID` | `string` |
357
+ | `Int` | `integer` |
358
+ | `Float` | `number` |
359
+ | `Boolean` | `boolean` |
360
+ | `[T]` | `array` |
361
+ | Input object | `object` (untyped in MVP) |
362
+ | Enum | `string` (no enum constraint in MVP) |
363
+
364
+ Non-null (`!`) variables are required. Auto-tools accept a single `variables` object matching the Pydantic model (e.g. `CvesVariables(first: int)`).
365
+
366
+ Apollo Router validates input objects and enums at execution time even when MCP schemas are loose.
367
+
368
+ ---
369
+
370
+ ## Error handling
371
+
372
+ GraphQL and HTTP errors from `datalayer-graphql` are wrapped in `OperationError` for auto-generated tools:
373
+
374
+ ```python
375
+ from datalayer_mcp import OperationError
376
+
377
+ try:
378
+ ...
379
+ except OperationError as exc:
380
+ print(exc.payload)
381
+ # {
382
+ # "error_type": "GraphQLError",
383
+ # "message": "...",
384
+ # "errors": [{"message": "...", "code": "SUBREQUEST_HTTP_ERROR", "service": "..."}],
385
+ # "partial_data": ...
386
+ # }
387
+ ```
388
+
389
+ | Exception | When |
390
+ |-----------|------|
391
+ | `SupergraphConnectionError` | VPN down, network failure |
392
+ | `SupergraphHTTPError` | HTTP 401/403/503 (`status_code` attribute) |
393
+ | `GraphQLError` | GraphQL errors in response (may include partial federation data) |
394
+ | `OperationError` | Wrapper raised by auto-tools |
395
+
396
+ In custom tools, catch `GraphQLError` directly or let errors propagate to the MCP client.
397
+
398
+ ---
399
+
400
+ ## Connect to Cursor
401
+
402
+ Add to Cursor MCP settings (`.cursor/mcp.json` or Settings → MCP):
403
+
404
+ ```json
405
+ {
406
+ "mcpServers": {
407
+ "datalayer": {
408
+ "command": "uv",
409
+ "args": [
410
+ "run",
411
+ "--package",
412
+ "datalayer-mcp",
413
+ "examples/mcp/server.py"
414
+ ],
415
+ "cwd": "/absolute/path/to/datalayer-python-monorepo"
416
+ }
417
+ }
418
+ }
419
+ ```
420
+
421
+ Put `SUPERGRAPH_TOKEN` in `examples/mcp/.env`. Restart Cursor after changing MCP config.
422
+
423
+ Available tools with the example project:
424
+
425
+ - **`Cves`** — auto-generated from `poc/operations/Cves.graphql`
426
+ - **`cve_count`** — custom tool from `poc/tools/custom.py`
427
+
428
+ ---
429
+
430
+ ## Development
431
+
432
+ ### Package layout
433
+
434
+ ```
435
+ packages/datalayer-mcp/
436
+ ├── src/datalayer_mcp/
437
+ │ ├── __init__.py # Public API
438
+ │ ├── config.py # mcp-config.yaml → McpConfig
439
+ │ ├── env.py # .env loading
440
+ │ ├── parse.py # .graphql → ParsedOperation
441
+ │ ├── scan.py # AST collision detection for tools.dir
442
+ │ ├── schema.py # GraphQL variables → Pydantic models
443
+ │ ├── tools.py # Auto-tool registration
444
+ │ ├── server.py # create_server() orchestration
445
+ │ ├── extension.py # DatalayerExtension (FastMCP Lifespan)
446
+ │ ├── runtime.py # get_client() and execute_operation()
447
+ │ └── errors.py # OperationError formatting
448
+ ├── tests/ # Unit tests (no network)
449
+ └── pyproject.toml
450
+
451
+ Runnable POC server: `examples/mcp/` at the monorepo root.
452
+ ```
453
+
454
+ ### Run tests
455
+
456
+ From monorepo root:
457
+
458
+ ```bash
459
+ uv run --package datalayer-mcp pytest packages/datalayer-mcp/tests -v
460
+ ```
461
+
462
+ From the package directory:
463
+
464
+ ```bash
465
+ cd packages/datalayer-mcp
466
+ uv run pytest tests -v
467
+ ```
468
+
469
+ Tests cover env loading, operation parsing, schema mapping, error formatting, duplicate detection, and mocked tool handlers. No VPN or live supergraph required.
470
+
471
+ ### Smoke-test server bootstrap
472
+
473
+ ```bash
474
+ cp examples/mcp/.env.example examples/mcp/.env # set SUPERGRAPH_TOKEN
475
+
476
+ uv run --package datalayer-mcp python -c "
477
+ from pathlib import Path
478
+ from datalayer_mcp import create_server
479
+ create_server(config_path=Path('examples/mcp/mcp-config.yaml').resolve())
480
+ print('create_server OK')
481
+ "
482
+ ```
483
+
484
+ ### Run the example server
485
+
486
+ ```bash
487
+ uv run --package datalayer-mcp examples/mcp/server.py
488
+ ```
489
+
490
+ ### Live supergraph tests
491
+
492
+ Live integration tests live in `datalayer-graphql`:
493
+
494
+ ```bash
495
+ cd packages/datalayer-graphql
496
+ cp .env.example .env
497
+ uv run pytest tests/test_smoke_supergraph.py -v -s
498
+ ```
499
+
500
+ ### Adding a new auto-tool
501
+
502
+ 1. Create `operations/MyOperation.graphql` with one named operation
503
+ 2. Restart the MCP server
504
+ 3. The tool appears automatically as `MyOperation`
505
+
506
+ ### Adding a custom tool
507
+
508
+ 1. Create a `.py` file in the directory named by `tools.dir` in `mcp-config.yaml`
509
+ 2. Use `@tool` (from `fastmcp.tools`) and `get_client()` as shown above
510
+ 3. Restart the MCP server; the tool is discovered automatically
511
+
512
+ ---
513
+
514
+ ## Obtaining a supergraph token
515
+
516
+ Tokens expire after ~15 minutes. Fetch a client-credentials token from Red Hat SSO:
517
+
518
+ ```bash
519
+ curl -X POST \
520
+ "https://sso.stage.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token" \
521
+ -H "Content-Type: application/x-www-form-urlencoded" \
522
+ -d "grant_type=client_credentials" \
523
+ -d "client_id=YOUR_CLIENT_ID" \
524
+ -d "client_secret=YOUR_SECRET" \
525
+ -d "scope=api.graphql"
526
+ ```
527
+
528
+ Copy `access_token` into `.env` as `SUPERGRAPH_TOKEN`.
529
+
530
+ ---
531
+
532
+ ## Relationship to datalayer-graphql
533
+
534
+ | Concern | Package |
535
+ |---------|---------|
536
+ | HTTP client, auth, Apollo headers | `datalayer-graphql` |
537
+ | Operation file loading (registry) | `datalayer-graphql.load_operations()` |
538
+ | MCP tool registration, config, `.env` | `datalayer-mcp` |
539
+ | Variable → JSON Schema for MCP | `datalayer-mcp` |
540
+ | Custom tool integration | `datalayer-mcp` |
541
+
542
+ `datalayer-mcp` does not implement GraphQL HTTP directly — all supergraph communication goes through `SupergraphClient`.
543
+
544
+ ---
545
+
546
+ ## Troubleshooting
547
+
548
+ | Problem | Likely cause | Fix |
549
+ |---------|--------------|-----|
550
+ | `FileNotFoundError: mcp-config.yaml` | Wrong cwd | Use `Path(__file__).parent / "mcp-config.yaml"` |
551
+ | `Missing env var: SUPERGRAPH_TOKEN` | No `.env` or expired token | Copy `.env.example`, refresh token |
552
+ | `FileNotFoundError: tools.dir not found` | `tools.dir` path missing | Create the directory or drop the `tools.dir` key |
553
+ | `ValueError: Custom tool name collides` | `@tool` name matches an operation | Rename the custom tool or the operation |
554
+ | `ValidationError: instructions` | `FastMCP(name, lifespan)` as positional arg | Use `FastMCP(name, lifespan=lifespan)` |
555
+ | `Operation registry mismatch` | Parser disagreement | Check file encoding; both loaders strip whitespace |
556
+ | Server idle after banner | Normal stdio behavior | Connect via Cursor MCP client |
557
+ | Tool call fails | VPN off or bad token | Connect to VPN, refresh `SUPERGRAPH_TOKEN` |
558
+
559
+ ---
560
+
561
+ ## License
562
+
563
+ Internal Red Hat / Datalayer project.