Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ generate:
make generate-go

generate-go:
rm -rf router/gen && buf generate --path proto/wg/cosmo/node --path proto/wg/cosmo/common --path proto/wg/cosmo/graphqlmetrics --template buf.router.go.gen.yaml
rm -rf router/gen && buf generate --path proto/wg/cosmo/node --path proto/wg/cosmo/common --path proto/wg/cosmo/graphqlmetrics --path proto/yoko/v1 --template buf.router.go.gen.yaml
rm -rf graphqlmetrics/gen && buf generate --path proto/wg/cosmo/graphqlmetrics --path proto/wg/cosmo/common --template buf.graphqlmetrics.go.gen.yaml
rm -rf connect-go/wg && buf generate --path proto/wg/cosmo/platform --path proto/wg/cosmo/notifications --path proto/wg/cosmo/common --path proto/wg/cosmo/node --template buf.connect-go.go.gen.yaml

Expand Down
6 changes: 6 additions & 0 deletions buf.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Generated by buf. DO NOT EDIT.
version: v2
deps:
- name: buf.build/bufbuild/protovalidate
commit: 435963d1631043e694e56e6bcc3c79c3
digest: b5:f4ea07ad2dd94bd7243562f9908b9fb104feef8076040c89d9f7c1dedc074de4d4ce2b997686ef4400f3eccb765a7cfc20ed4acdd70b9a3699351245c61dba97
4 changes: 4 additions & 0 deletions buf.router.go.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ managed:
disable:
- file_option: go_package
module: buf.build/googleapis/googleapis
# Keep protovalidate on its published Go module. Without this the build
# fails on a missing gen/buf/validate package.
- file_option: go_package
module: buf.build/bufbuild/protovalidate
override:
- file_option: go_package_prefix
value: github.com/wundergraph/cosmo/router/gen/proto
Expand Down
2 changes: 2 additions & 0 deletions buf.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
version: v2
deps:
- buf.build/bufbuild/protovalidate
modules:
- path: proto
lint:
Expand Down
11 changes: 11 additions & 0 deletions docs-website/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@
"router/mcp/quickstart",
"router/mcp/tools",
"router/mcp/configuration",
{
"group": "Schema Discovery",
"icon": "compass",
"pages": [
"router/mcp/schema-discovery/overview",
"router/mcp/schema-discovery/quickstart",
"router/mcp/schema-discovery/guides",
"router/mcp/schema-discovery/tools",
"router/mcp/schema-discovery/configuration"
]
},
{
"group": "OAuth 2.1",
"icon": "shield-check",
Expand Down
3 changes: 3 additions & 0 deletions docs-website/router/mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ The Cosmo MCP Server builds on top of the concept of persisted operations (also
<Card title="AI Empowerment" icon="robot">
Empower AI assistants to work with your application's data through a standardized interface
</Card>
<Card title="Schema Discovery" icon="compass" href="/router/mcp/schema-discovery/overview">
Let a model search a large schema and generate a valid operation from a prompt, without the schema in its context
</Card>
</CardGroup>

## Get Started
Expand Down
4 changes: 4 additions & 0 deletions docs-website/router/mcp/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ storage_providers:
| `expose_schema` | Enables the `get_schema` built-in tool, exposing the full GraphQL schema to MCP clients. | `false` |
| `omit_tool_name_prefix` | When enabled, MCP tool names omit the `execute_operation_` prefix. For example, `GetUser` becomes `get_user` instead of `execute_operation_get_user`. See [Tools - Omitting the Tool Name Prefix](/router/mcp/tools#omitting-the-tool-name-prefix). | `false` |

| `schema_discovery.enabled` | Enables the `search_schema`, `get_symbols` and `generate_query` built-in tools. They let a model search a large schema and generate a valid operation, without the schema in its context. See [Schema Discovery](/router/mcp/schema-discovery/configuration). | `false` |

For OAuth-specific configuration, see [OAuth 2.1 Authorization](/router/mcp/oauth/overview).

For schema discovery configuration, see [Schema Discovery - Configuration](/router/mcp/schema-discovery/configuration).

## Environment Variables

All MCP options can also be set via environment variables:
Expand Down
112 changes: 112 additions & 0 deletions docs-website/router/mcp/schema-discovery/configuration.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
title: 'Configuration'
description: 'Every configuration key and environment variable for MCP schema discovery.'
icon: 'sliders'
---

Schema discovery is configured under `mcp.schema_discovery`.

```yaml router.config.yaml
mcp:
enabled: true
expose_schema: false
schema_discovery:
enabled: true
url: 'https://discovery.example.com'
token: 'your-token'
request_timeout: 90s
index_poll_interval: 2s
index_timeout: 10m
```

## Keys

| Key | Type | Default | Environment variable |
| --- | --- | --- | --- |
| `enabled` | boolean | `false` | `MCP_SCHEMA_DISCOVERY_ENABLED` |
| `url` | string | | `MCP_SCHEMA_DISCOVERY_URL` |
| `token` | string | | `MCP_SCHEMA_DISCOVERY_TOKEN` |
| `request_timeout` | duration | `90s` | `MCP_SCHEMA_DISCOVERY_REQUEST_TIMEOUT` |
| `index_poll_interval` | duration | `2s` | `MCP_SCHEMA_DISCOVERY_INDEX_POLL_INTERVAL` |
| `index_timeout` | duration | `10m` | `MCP_SCHEMA_DISCOVERY_INDEX_TIMEOUT` |

### enabled

Turns schema discovery on. The router then indexes the client schema and registers the `search_schema`, `get_symbols` and `generate_query` tools.

### url

The base URL of the schema discovery service. Include the scheme.

The service speaks Connect over HTTP/1.1.

<Warning>
The router does not start when `enabled` is `true` and `url` is empty. This stops a server whose tools always fail.
</Warning>

### token

The bearer token for the service. The router sends it as `Authorization: Bearer <token>`.

An empty token sends no `Authorization` header. Use an empty token when your service runs with authentication off.

The router never writes the token to a log or to an error message.

### request_timeout

The timeout for one call to the service.

Query generation takes 10 to 30 seconds. A value below `60s` is too low.

This value also raises the write timeout of the MCP HTTP server, so a slow generation still reaches the caller.

### index_poll_interval

The wait between two index status reads while the router waits for a build.

### index_timeout

The router stops waiting for an index that does not become ready within this time.

A schema of 16,000 lines indexes in about 24 seconds. The default of `10m` only trips on a real fault.

## Interaction with other keys

### expose_schema

Set `expose_schema` to `false`.

`get_schema` returns the full schema. That is the context cost that schema discovery removes. The two settings do not fail together, but they work against each other.

### enable_arbitrary_operations

Set this to `true` when you want the agent to run the operation that `generate_query` returns.

Set it to `false` in production. Use the curated path instead: generate the operation in a development router, review it, publish it as a persisted operation, then deploy that. See [Guides](/router/mcp/schema-discovery/guides).

## Startup behaviour

The router does not wait for the index. It serves GraphQL from the first moment.

Read the log to follow the build.

```
INFO MCP schema discovery enabled url=https://discovery.example.com authenticated=true
INFO schema index is building index_id=sha256:6926769e...
INFO schema index is ready index_id=sha256:6926769e... symbol_count=318
```

The router reindexes after every schema change. An unchanged schema costs nothing, because the router compares the hash locally and makes no network call.

During a rebuild the previous index keeps serving. The router adopts the new index only when it is ready.

## Failure behaviour

| Condition | Result |
| --- | --- |
| The service is unreachable at startup | The router starts. The tools report that the index is not ready. |
| The build fails | The router logs the reason. The tools report that the index is not ready. |
| The build exceeds `index_timeout` | The router stops waiting and logs the reason. |
| The service drops an unused index | The router rebuilds it at the same address on the next tool call. |

A fault in the discovery service never stops the router from serving GraphQL.
157 changes: 157 additions & 0 deletions docs-website/router/mcp/schema-discovery/guides.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
title: 'Guides'
description: 'Solve one task at a time: find duplicate work, curate an operation into a tool, or use an operation in a BFF.'
icon: 'list-check'
---

Each guide solves one problem. Read only the guide that you need.

## Find out if a capability already exists

Use this guide before you build a new field, a new resolver, or a new subgraph.

A large organisation runs many teams. Two teams add the same capability under different names. Schema discovery finds the first one before you build the second.

### Step 1 - Search by intent

Describe the capability in your own words. Do not guess field names.

```json
{ "query": "customer billing address", "kinds": ["field"], "limit": 5 }
```

The search matches meaning, not text. It finds `Customer.invoiceAddress` and `Account.billingAddr` even though neither name contains your words. A text search over the schema finds neither.

### Step 2 - Ask for the operation you were about to build

```json
{ "prompt": "get the billing address and payment status for a customer" }
```

Read the result. It gives you a decision.

| Result | Meaning | What you do |
| --- | --- | --- |
| One or more `queries` | The capability exists today. | Use the operation. Do not build it. |
| Empty `queries` and one `unsatisfied` reason | The schema cannot answer this. | Build the capability. |

### Step 3 - Read the reason

The `unsatisfied` reason names what is missing.

```json
{
"unsatisfied": [
"The indexed schema exposes products, employees, and locations, but no invoice entity, billing address, or payment status."
]
}
```

Collect these reasons across your teams. They tell you what consumers want and your graph does not have.

### What this guide does not tell you

The index holds the composed schema. It cannot show a subgraph that nobody published yet. Another team can be halfway through the same work.

Check your schema registry as well, before you commit to a build.

## Turn a generated operation into a tool

Use this guide to give an agent a curated tool instead of an open prompt.

The router generates an operation. The router never publishes it. You review the operation first, then publish it yourself. Your production router then exposes it as its own MCP tool.

### Step 1 - Generate in a development router

Run schema discovery in a development router. Send the prompt.

```json
{ "prompt": "list active employees with their department and current mood" }
```

### Step 2 - Review the document

Read the `document` field. Check three things:

- The operation reads only the fields that you intend to expose.
- The operation is a `query` when you expect no side effect.
- The variables carry the filters that you want the caller to control.

Give the operation a clear name. The name becomes the tool name.

### Step 3 - Save the operation

Write the document to your MCP operations directory.

```graphql operations/ListActiveEmployees.graphql
query ListActiveEmployees($limit: Int) {
employees(limit: $limit) {
id
details {
forename
surname
}
currentMood
}
}
```

Add a description above the operation. The description becomes the tool description, so write it for the agent.

### Step 4 - Deploy to production

Deploy the operation to your production router. Turn schema discovery off there, and turn arbitrary operations off.

```yaml production.config.yaml
mcp:
enabled: true
enable_arbitrary_operations: false
expose_schema: false
schema_discovery:
enabled: false
```

Your production router now exposes one typed tool. It runs no arbitrary GraphQL, and it sends no schema to an external service.

This is the curated path. Discovery happens in development. Production runs only what you reviewed.

## Use a generated operation in a BFF

Use this guide to put an operation into an application.

### Step 1 - Take both fields

A generated operation gives you two things:

- `document` is the operation text.
- `variablesSchema` is a JSON Schema for the variables.

### Step 2 - Send the document and the variables

```javascript
const response = await fetch('https://router.example.com/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: document,
variables: { limit: 10 },
}),
});
```

The operation is parameterized. Your prompt selected the shape. You supply the values at run time, so one operation serves many requests.

### Step 3 - Use the variables schema to type the inputs

The variables schema carries the descriptions and the allowed values from your GraphQL schema. Use it in two ways:

- Generate types for your application.
- Register the operation as a tool for a language model. The tool name comes from `operationName`, the description from `description`, and the input schema from `variablesSchema`.

A model then fills the variables correctly. It cannot invent a value for an enum, because the schema lists only the allowed names.

### Step 4 - Generate one time

Generation takes 10 to 30 seconds and uses a language model. Do not call it in your request path.

Generate the operation one time. Store the document. Ship it with your application.
Loading
Loading