Tích hợp
MCP agents
Connect an AI agent with an API key. The tool list is filtered to what that key can do, sensitive actions ask first, and every call lands in the audit log.
Cập nhật lần cuối
Who this is for: Owner — requires
integration.mcp.connect. Where: Integrations → MCP Agents
An MCP agent is an AI system you let operate the hotel. Nerve exposes its capabilities as MCP tools at /api/mcp, and an agent connects with an ordinary API key — the same kind of credential a script would use, carrying the same permissions, writing to the same audit log.
That is the whole design, and it is worth saying plainly before the mechanics: an agent is not a new kind of user with a new kind of access. It is a credential inside the model you already have. See Open Hotel Protocols for why the product is built this way.

The whole tool surface, with the permission each one costs. This is the catalog, not what any given agent sees.
Connect an agent#
- Create an API key with
integration.mcp.connectplus the permission behind each tool the agent should reach. - Point the agent at
POST /api/mcp. - Send the key as
Authorization: Bearer nrvk_….
The endpoint speaks single-shot JSON-RPC over POST — one request, one response, no session to hold open.
curl -X POST http://localhost:8080/api/mcp \
-H "Authorization: Bearer nrvk_..." \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Quan trọng
integration.mcp.connectis a separate permission from the tools. A key withcrs.booking.readand nothing else is refused at the door — it never gets as far as being told which tools exist.
The tool list is filtered, not just enforced#
This is the part that matters, and it is stronger than it sounds. A key does not see the tools it cannot use and then get refused. It does not see them.
A key holding integration.mcp.connect and crs.booking.read:
{
"result": {
"tools": [
{
"name": "list_bookings",
"description": "List channel and direct bookings for the API key's hotel."
}
]
}
}The same request, from a key that also holds frontdesk.reservation.read, frontdesk.guest.read, frontdesk.guest.write and rms.analytics.read:
{
"result": {
"tools": [
{ "name": "list_reservations" },
{ "name": "list_guests" },
{ "name": "list_bookings" },
{ "name": "get_kpis" },
{ "name": "create_guest" }
]
}
}One key, one tool. The other, five. Nothing about the agent changed.
Calling a tool that was filtered out does not produce a permission error — it produces unknown tool:
{
"error": { "code": -32601, "message": "unknown tool: get_kpis" }
}That is deliberate. An agent reasons about what it can do from the list it was given, so a filtered surface is the only surface it knows about — and a refusal never tells it what it was not allowed to see.
The practical consequence: scope the key, not the prompt. An instruction not to touch guest data is a request; a key without frontdesk.guest.read is a fact.
A sensitive tool asks before it acts#
Tools that change something are badged sensitive. Calling one does not do the thing:
{
"result": {
"content": [{
"type": "text",
"text": "This is a sensitive action. Re-call with \"confirm\": true in the arguments to proceed."
}],
"isError": true
}
}The agent must call again with "confirm": true in the arguments. Note that this comes back as a result with isError, not as a JSON-RPC error — an agent reads it as something to act on, not as a failure to retry blindly.
This is a deliberate speed bump between "the model decided to" and "the hotel did", and it is per call. It is not a substitute for scoping the key: an agent that holds frontdesk.guest.write and confirms will create the guest.
Every call is in the audit log#
- Open Settings → Audit Log.

Every call, including the refused ones. An agent's tool call sits in the same log as a person's action, and the denials are recorded as carefully as the successes.
Each row carries the key prefix as the actor, the channel (mcp for a tool call, api for the transport call underneath it, app for a human action), the tool or endpoint name, and the outcome.
Two things follow. There is no separate "AI log" to go and find — an agent's activity is in the same place as everything else, which is what makes "who did this" answerable at all. And a denied row is evidence, not noise: it is the record of the permission model refusing something, and a run of them is how you notice an agent trying to do a job it has not been scoped for.
The tools#
| Tool | Permission | Sensitive |
|---|---|---|
list_reservations |
frontdesk.reservation.read |
|
list_guests |
frontdesk.guest.read |
|
list_bookings |
crs.booking.read |
|
get_kpis |
rms.analytics.read |
|
create_guest |
frontdesk.guest.write |
yes |
Every tool is bound to the key's property. There is no argument for choosing a hotel and no way to reach another one, so an agent scoped to one property cannot read across a group.
The current list is always the one on the MCP Agents screen, and the machine-readable version is on Integrations → API Docs, generated from the contract the server is running.
What's next#
- The playground — call a tool from inside the product before wiring an agent up to it.
- API keys — the credential this all hangs off.
- Open Hotel Protocols — why the agent surface and the human surface are the same surface.