# MCP for agents

An MCP-enabled REST API (RAPI) instance describes itself to the agent that connects: the server's instructions name the instance and the conventions, the `models` tool lists what there is to query, and a per-instance **guide** spells out the attributes, reference paths and worked examples. This page is what that agent sees and how it should use it. To turn MCP on and connect a client, see [MCP endpoint](/console/rapi/mcp).

## Before an agent can connect

MCP is served only where it is turned on, and the agent's client has to send what the instance's data routes require:

- **Turned on.** MCP is part of a paid plan that dcupl turns on for your project; ask your dcupl contact. The deployed API template must set `"mcp": { "enabled": true }`; otherwise `POST …/instance/{instanceKey}/mcp` answers `404`. See [Turning it on](/console/rapi/mcp#turning-it-on).
- **API key.** A template with an `api-key` auth entry needs that key as the `api-key` header on every request; without an auth entry the instance is open to anyone with the URL.
- **Version guard.** With `vGuardEnabled: true` every request also needs the current `v`, and most clients report the missing header only as a failed connection. Turn the guard off for instances agents connect to. See [Authentication and the version guard](/console/rapi/mcp#authentication-and-the-version-guard).
- **Ready.** An instance that is not `ready` answers JSON-RPC `-32002` until it is.

Everything below is read-only: no tool, resource or endpoint script can write to the instance.

## What the agent sees

### The instructions

On connect (`initialize`) the server sends its `instructions`, which clients that support them put into the agent's context before the first call. They are generated per instance and kept short:

- an opening sentence that names the instance by its template's name and description (or its instance key when the template has no name) and the dcupl SDK version it runs
- the recommended flow: `models`, then the guide, then `facet` or `suggest`, then `query`; `aggregate`, `facet` or `pivot` for counts
- the cross-tool conventions, one sentence each: string keys, references and dotted paths, `find` and `suggest` matching, `isTruthy` with `false`, paging with `count`, errors that name the field, and checking an empty result with `facet` or `suggest` (see [Conventions](#conventions))
- when the template has [scripted endpoints](/console/rapi/scripted-endpoints), how many there are and that the agent should prefer one that fits the question

The exact text comes from the runner and follows it when it changes; read it from the `initialize` answer rather than from a copy (see [Reading it yourself](#reading-it-yourself)). The same name appears as the server's `serverInfo.title` (`<template name> · dcupl`) and in its `serverInfo.description`.

### The tools

| Tool         | What the agent uses it for                                                                                                                              |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `models`     | The first call: the instance (`key`, `name`, `description`) and its models with item counts. Every other data tool takes one of these `modelKey` values |
| `guide`      | The instance guide as markdown — the same text as the guide resource, for clients that do not surface resources                                         |
| `query`      | Items of a model matching conditions, with `projection`, `sort` and paging (`count`, `start`)                                                           |
| `facet`      | Distinct values of an attribute with their item counts: breakdowns, and the values that really exist to filter on                                       |
| `suggest`    | Complete a term to the stored values of an attribute, to find the exact value to filter on                                                              |
| `aggregate`  | `min`, `max`, `sum`, `count`, `avg`, `distinct` or `group` over an attribute, optionally filtered                                                       |
| `pivot`      | Row and column attributes with aggregations per cell, for two-dimensional breakdowns                                                                    |
| `metadata`   | A model's total and filtered item counts                                                                                                                |
| `endpoint_*` | One tool per scripted endpoint of the template; it runs the application's own script and returns its JSON                                               |

Each tool's input schema in `tools/list` carries short descriptions per field, the same contract the REST routes validate against, so an agent can build its arguments from the schema alone. `aggregate`, `facet`, `pivot`, `metadata` and `suggest` take an optional `query` with the `query` tool's arguments to filter first. How endpoint tools are named and called: [One tool per scripted endpoint](/console/rapi/mcp#one-tool-per-scripted-endpoint).

### The guide resource

Every MCP-enabled instance lists one resource:

```
dcupl://instance/{instanceKey}/guide
```

Its title is "How to query this instance", its type `text/markdown`. It is generated from the instance when it is read, so it always matches the deployed data, and costs nothing until an agent asks for it. It holds:

- **Models**: every model with its item count and number of attributes (up to 50 models; the guide names the next ten of the rest, and `models` lists them all)
- **Attributes**: per model, each attribute's type and notes — `filter: true`, references (target model, single- or multi-valued, "comes back as `{ key }`") and derived attributes (up to 60 per model; `metadata` shows the rest)
- **Reference paths**: the dotted paths each reference opens, such as `vendor.country`
- **Scripted endpoints**: each `endpoint_*` tool with its route, path parameters, input and the description from the template
- **Worked examples**: one `query`, `facet`, `aggregate`, `pivot` and `suggest` call on the instance's own most telling model
- **Conventions**: the rules below in long form, with examples

A client that supports resources shows it next to the tools; for every other client the `guide` tool returns the same markdown. When the instance cannot build it, reading the resource fails with a JSON-RPC error that carries the reason, the same text the tools answer with.

### Reading it yourself

Any MCP client shows the same as the agent. With `curl` (add the `api-key` header where the template has one):

```bash
URL='https://runner.example.com/instance/your-project-id-orders-api/mcp'
H=(-H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -H 'api-key: your-secure-api-key')

# the instructions: result.instructions
curl -s -X POST "$URL" "${H[@]}" -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

# the guide resource
curl -s -X POST "$URL" "${H[@]}" -d '{"jsonrpc":"2.0","id":2,"method":"resources/list"}'
curl -s -X POST "$URL" "${H[@]}" -d '{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"dcupl://instance/your-project-id-orders-api/guide"}}'
```

The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) CLI reads the guide with `--method resources/read --uri dcupl://instance/your-project-id-orders-api/guide`.

## Conventions

The instructions state most of these rules in one sentence each; the guide states all of them in long form, and the tools' field descriptions repeat them where they apply. The examples use a model `Article` with a `productName`, a `category`, a `price` and a reference `vendor` to a model `Vendor`.

### Matching is case-sensitive

In a `find` condition a plain string matches the **whole value, exactly and case-sensitively**. `"football"` does not find `"Football"`, and `"ball"` does not find `"Basketball"`. `eq` compares the whole value too.

```json title="Finds only the exact value"
{ "modelKey": "Article", "queries": [{ "operator": "find", "attribute": "category", "value": "Football" }] }
```

### Regex is `/…/`, flags go into `regexFlags`

A `find` value written as `"/…/"` is a JavaScript regex matched anywhere in the value; anchor it with `^` and `$`. It is case-sensitive unless `options.regexFlags` is `"i"`. Flags are never read from the value: `"/ball/i"` is a plain string, not a regex.

```json title="productName contains “ball” in any case"
{
  "modelKey": "Article",
  "queries": [{ "operator": "find", "attribute": "productName", "value": "/ball/", "options": { "regexFlags": "i" } }]
}
```

### `suggest` completes a prefix

In `suggest` a plain value is a **case-insensitive prefix**: `"bask"` completes to `"Basketball"`, `"ball"` does not. A `"/…/"` value is a regex, case-sensitive unless `options.regexFlags` is `"i"`.

```json title="Stored values containing “ball”, any case"
{ "modelKey": "Article", "options": { "attribute": "productName", "value": "/ball/", "regexFlags": "i", "max": 5 } }
```

### Item keys are strings

Every item has a string `key`, also when it looks like a number. Filter with `"42"`, not `42`; path parameters of endpoint tools are strings as well.

```json title="One item by key"
{ "modelKey": "Article", "queries": [{ "operator": "eq", "attribute": "key", "value": "42" }] }
```

### References come back as `{ key }`, filter through them with a dotted path

A reference attribute returns the key of the referenced item, `{ "key": "v1" }`, not the item itself. A nested projection inlines the target's attributes; a dotted path filters, facets, aggregates or pivots on them. Keep paths to 2–3 levels; the guide lists the paths each reference opens.

```json title="Articles of Austrian vendors, with the vendor's name inlined"
{
  "modelKey": "Article",
  "queries": [{ "operator": "eq", "attribute": "vendor.country", "value": "Austria" }],
  "projection": { "productName": true, "vendor": { "name": true } }
}
```

### Paging uses `count` and `start`

`query` returns at most `count` items (default 100, max 1000) from the zero-based offset `start`; `start: 20, count: 10` returns items 21–30. There is no `limit` or `page`: an unknown top-level key is a tool error (`isError`) that names the key, points a paging alias to the real key and lists the valid keys, for example `unknown option limit (use count); valid keys: modelKey, …`, so the agent calls again with `count`. Over REST the same request still runs with the key ignored (without `count` it returns every matching item) and the runner logs a warning; a runner started with `REST_VALIDATION=strict` answers `400` instead. For totals use `aggregate` or `metadata` instead of paging through a model.

```json title="The second page of ten"
{ "modelKey": "Article", "count": 10, "start": 10 }
```

### Sorting is upper case

`sort` takes the attributes and one direction per attribute, `ASC` or `DESC` in upper case.

```json title="Most expensive first"
{ "modelKey": "Article", "sort": { "attributes": ["price"], "order": ["DESC"] }, "count": 5 }
```

### Empty values: `isTruthy` with `false`

`isTruthy` with value `false` finds null, missing, empty strings and `0`; `true` finds the rest. There is no separate null or empty operator; for a list without entries use `size` with `0`.

```json title="Articles without a description"
{ "modelKey": "Article", "queries": [{ "operator": "isTruthy", "attribute": "description", "value": false }] }
```

### Errors name the field

A wrong input is answered with a tool error (`isError`) that names the field and what it expected. The agent fixes that field and calls again instead of guessing another shape:

```
MCP error -32602: Input validation error: Invalid arguments for tool query: Invalid option: expected one of "ASC"|"DESC" at sort.order[0]
```

An unknown operator lists the valid ones, including the custom operators registered on the instance:

```
unknown operator "contains"; expected one of "eq"|"in"|"find"|"gt"|"gte"|"lt"|"lte"|"typeof"|"isTruthy"|"size" at queries.0.operator
```

### An empty result is a question, not an answer

An empty result usually means a wrong value, case or matching mode, not missing data. Before reporting "none", the agent checks the value with `facet` or `suggest` — see step 5 below.

## The first five calls

An agent asked "What are the most expensive football articles?" on an instance it has never seen:

**1. `models`** — what is there, and how big.

```json title="Answer (shortened)"
{
  "instance": { "key": "your-project-id-shop-api", "name": "Shop API" },
  "models": [
    { "key": "Article", "count": 100 },
    { "key": "Vendor", "count": 99 }
  ]
}
```

**2. `guide`** (or the resource) — the attributes of `Article`, that `vendor` is a reference to `Vendor`, the path `vendor.country`, and a worked example per tool. No arguments.

**3. `facet`** — the values `category` really holds, before filtering on one.

```json title="Arguments"
{ "modelKey": "Article", "options": { "attribute": "category", "count": 10, "sort": "size-desc" } }
```

```json title="Answer (shortened)"
[
  { "key": "Fitness", "value": "Fitness", "size": 13 },
  { "key": "Football", "value": "Football", "size": 9 }
]
```

**4. `query` with `sort`** — the items, with the value spelled as the facet showed it, the most expensive first and only the attributes the answer needs.

```json title="Arguments"
{
  "modelKey": "Article",
  "queries": [{ "operator": "eq", "attribute": "category", "value": "Football" }],
  "sort": { "attributes": ["price"], "order": ["DESC"] },
  "projection": { "productName": true, "price": true },
  "count": 5
}
```

**5. `suggest` with `regexFlags`** — when a term from the question is not a stored value (the user said "soccer"), resolve it before filtering, instead of reporting an empty result.

```json title="Arguments"
{ "modelKey": "Article", "options": { "attribute": "productName", "value": "/soccer/", "regexFlags": "i", "max": 5 } }
```

```json title="Answer"
[{ "value": "Soccer Ball!", "keys": ["2"] }]
```

### Counts and breakdowns

For "how many", "total", "average" or "per vendor" the agent does not read items into the conversation: `aggregate`, `facet` and `pivot` answer from the whole model in one call.

#### aggregate

```json title="Average and maximum price of football articles"
{
  "modelKey": "Article",
  "options": { "attribute": "price", "types": ["avg", "max"] },
  "query": { "queries": [{ "operator": "eq", "attribute": "category", "value": "Football" }] }
}
```

#### facet

```json title="Articles per vendor country"
{ "modelKey": "Article", "options": { "attribute": "vendor.country", "sort": "size-desc" } }
```

#### pivot

```json title="Sum of prices per category and vendor country"
{
  "modelKey": "Article",
  "options": {
    "rows": [{ "attribute": "category" }],
    "columns": [{ "attribute": "vendor.country" }],
    "values": [{ "attribute": "price", "types": ["sum"] }]
  }
}
```

---

When the template has a scripted endpoint that answers the question, the agent should prefer its `endpoint_*` tool: it returns what the application's own script computes.

## Monitoring

The guide shows up in the instance's invocation log like the tools: `mcp:guide` for the tool, `mcp:resource:guide` for a resource read. See [Monitoring](/console/rapi/mcp#monitoring).

## Next steps

- [MCP endpoint](/console/rapi/mcp) — turn MCP on, connect a client, the answers and how to test them
- [Scripted endpoints](/console/rapi/scripted-endpoints) — answers computed by your script, served as `endpoint_*` tools
- [REST APIs](/console/rapi) — the same queries over HTTP
- [Build with agents](/agentic) — dcupl for AI agents beyond a single instance
