.md

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.

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.
  • 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.
  • 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)
  • when the template has 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). 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.

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):

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 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.

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.

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".

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.

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.

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.

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.

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.

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.

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.

Arguments
{ "modelKey": "Article", "options": { "attribute": "category", "count": 10, "sort": "size-desc" } }
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.

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.

Arguments
{ "modelKey": "Article", "options": { "attribute": "productName", "value": "/soccer/", "regexFlags": "i", "max": 5 } }
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.

Average and maximum price of football articles
{
  "modelKey": "Article",
  "options": { "attribute": "price", "types": ["avg", "max"] },
  "query": { "queries": [{ "operator": "eq", "attribute": "category", "value": "Football" }] }
}
Articles per vendor country
{ "modelKey": "Article", "options": { "attribute": "vendor.country", "sort": "size-desc" } }
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.