MCP Server Tutorial β Expose a Collection and an Aggregation
RESTHeart Cloud|
Note
|
The MCP server is available starting from RESTHeart v9.9. |
π§ Configuration
β‘ Setup Guide
To run the examples on this page, you need a RESTHeart instance.
Option 1: Use RESTHeart Cloud (Recommended)
The fastest way to get started is with RESTHeart Cloud. Create a free service in minutes:
-
Sign up at cloud.restheart.com
-
Create a free API service
-
Set up your root user following the Root User Setup guide
-
Use the configuration panel above to set your service URL and credentials
|
Tip
|
All code examples on this page will automatically use your configured RESTHeart Cloud credentials. |
Option 2: Run RESTHeart Locally
If you prefer local development, follow the Setup Guide to install RESTHeart on your machine.
|
Note
|
Local instances run at http://localhost:8080 with default credentials admin:secret
|
This tutorial takes a plain MongoDB collection and makes it discoverable and callable by an AI agent: enable a collection, add an aggregation, connect a real MCP client, and watch it find both and use them β no code written on either side.
See the MCP Server overview for the full feature reference this tutorial pulls from.
What You’ll Learn
-
Opt a collection into MCP with a metadata
PATCH -
Call
list_apis/how_to_calldirectly over JSON-RPC to see exactly what an agent sees -
Connect a real MCP client (Claude Code) and let it discover the resource on its own
-
Add an aggregation with a
$varparameter and expose it the same way
Prerequisites
-
RESTHeart 9.9+ running locally (the MCP server is included by default β see the overview page if you need to build it from source)
-
A tool for making HTTP requests (cURL is used below)
-
Optional, for the last section: Claude Code or any other MCP-capable client
|
Note
|
In all examples below:
The interactive examples on this page can automatically substitute the RESTHeart URL and credentials. |
1. Create and seed a collection
curl -X PUT [RESTHEART-URL]/inventory -u admin:secret
curl -X POST [RESTHEART-URL]/inventory -u admin:secret -H 'Content-Type: application/json' \
-d '{ "item": "notebook", "qty": 50, "status": "A" }'
curl -X POST [RESTHEART-URL]/inventory -u admin:secret -H 'Content-Type: application/json' \
-d '{ "item": "notebook", "qty": 25, "status": "D" }'
curl -X POST [RESTHEART-URL]/inventory -u admin:secret -H 'Content-Type: application/json' \
-d '{ "item": "journal", "qty": 10, "status": "A" }'
2. Opt it into MCP
curl -X PATCH [RESTHEART-URL]/inventory -u admin:secret -H 'Content-Type: application/json' \
-d '{
"mcp": {
"enabled": true,
"description": "Product inventory."
}
}'
That’s the whole opt-in. No restart, no extra plugin β the next list_apis call (once the catalog cache picks it up) shows it.
3. See what an agent sees, over raw JSON-RPC
An MCP client talks to /mcp in JSON-RPC over HTTP. This section does the same calls a real client would, so you can see the exact protocol β skip to section 4 if you just want to plug in a real client.
First, a session handshake β initialize, capturing the Mcp-Session-Id response header:
curl -i -X POST [RESTHEART-URL]/mcp -u admin:secret \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"tutorial","version":"1.0"}}}'
Copy the Mcp-Session-Id header from the response and use it below ([SESSION-ID]):
curl -X POST [RESTHEART-URL]/mcp -u admin:secret \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-H 'Mcp-Session-Id: [SESSION-ID]' -H 'Mcp-Protocol-Version: 2025-03-26' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_apis","arguments":{}}}'
The response is one text/event-stream frame wrapping the actual JSON-RPC result β inventory shows up in resources:
{ "resources": [
{ "uri": "http://localhost:8080/inventory", "kind": "collection", "description": "Product inventory." }
]}
Now ask how to query it for low-quantity items:
curl -X POST [RESTHEART-URL]/mcp -u admin:secret \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-H 'Mcp-Session-Id: [SESSION-ID]' -H 'Mcp-Protocol-Version: 2025-03-26' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"how_to_call","arguments":{"resource":"[RESTHEART-URL]/inventory","action":"query","args":{"filter":{"qty":{"$lt":30}}}}}}'
how_to_call composes the descriptor β it does not execute it:
{ "transport": "http", "method": "GET",
"url": "http://localhost:8080/inventory?filter=%7B%22qty%22%3A%7B%22%24lt%22%3A30%7D%7D",
"headers": { "Authorization": "Bearer <token>" } }
Run that url yourself and you get the real, live-queried documents β the agent makes this exact request directly against RESTHeart, the MCP server was never in the data path.
[[4-connect-a-real-client]] === 4. Connect a real client
Point Claude Code at your running instance:
claude mcp add --transport http restheart [RESTHEART-URL]/mcp \
--header "Authorization: Bearer <your-token>"
Restart the session so it picks up the new server, then ask it directly β no need to name tools or endpoints yourself:
> what APIs do you have access to on restheart?
> query the inventory collection for items with qty under 30
It calls list_apis, finds inventory, calls how_to_call, and executes the composed request β the same three steps you just did by hand.
5. Add an aggregation
Expose a $var-parameterized aggregation the same way β declare status in mcp.params since an agent can’t guess its enum from the pipeline alone:
curl -X PATCH [RESTHEART-URL]/inventory -u admin:secret -H 'Content-Type: application/json' \
-d '{
"aggrs": [{
"uri": "byStatus",
"stages": [
{ "$match": { "status": { "$var": "status" } } },
{ "$group": { "_id": "$item", "total": { "$sum": "$qty" } } }
],
"mcp": {
"enabled": true,
"description": "Total quantity by item, for a given status.",
"params": { "status": { "type": "string", "enum": ["A", "D"] } }
}
}]
}'
Ask your connected client:
> what's the total quantity of each item with status A in inventory?
It discovers byStatus under inventory’s `list_apis context, sees status is one of A/D under the avars param, and calls how_to_call with {"avars": {"status": "A"}} β the same avars bundling the overview explains.
Next steps
-
Full MCP Server reference β change streams, GraphQL apps, configuration
-
McpAwareβ howlist_apis/how_to_callwork under the hood, and how to expose your own plugin’s data the same way -
examples/mcp-mongodbβ a worked example with all four kinds (collection, aggregation, stream, GraphQL app) loaded in one shot -
Vector Search β combine MCP with
$vectorScan/$vectorSearchto let an agent search semantically, not just filter