MCP
Reference article: Part of the API reference. Nimbus has two MCP servers. One uses an API key and can work across the organization. The other belongs to an App install and stays in one room.
An MCP client such as Cursor or Claude connects to Nimbus over HTTP and receives a catalog of tools, each with a description and a JSON Schema, so the model knows how to call them. You do not paste this page into the client. The server sends the catalog on initialize and tools/list.
Note: Partially available. The Nimbus MCP server is on integration at
https://mcp.gonimbus.ai. Staging and production do not serve/v1/mcpor/v1/apps/mcpyet.tools/liston the host you connect to is the catalog that host actually has.
Which server
| Nimbus MCP | App MCP | |
|---|---|---|
| Use it when | An agent should act as a person: tasks, rooms, the workstream agent, loops, runs, the wiki. | An agent should act as one App, in one room. |
| Endpoint | POST https://mcp.gonimbus.ai or POST /v1/mcp on the API origin |
POST /v1/apps/mcp |
| Credential | API key, Authorization: Bearer nmb_sk_… or nmb_rk_… |
Install token, Authorization: Bearer nmb_it_… |
| Acts as | The person who created the key | The App, in that install's room |
| Catalog | tools/list on that host |
Four message tools, below |
There is no OAuth login for either server. A client that can only complete an OAuth handshake, and cannot send a bearer header, cannot connect. OAuth you see elsewhere in Nimbus is for connecting someone else's server, such as Linear, to Nimbus. It does not sign Cursor in.
Transport
Both servers speak MCP over streamable HTTP, and both are stateless:
- Send JSON-RPC 2.0 requests as the body of a
POST. You can send one request, or a batch as a JSON array. - Responses are plain JSON (
Content-Type: application/json), never a server-sent event stream. - A body containing only notifications gets
202with no body. GETandDELETEreturn405: there's no server-to-client stream and no session to end.
Supported protocol versions: 2025-06-18, 2025-03-26, and 2024-11-05. If initialize asks for another version, the server answers with 2025-06-18.
Supported methods: initialize, ping, tools/list, tools/call, and notifications.
initialize returns a short instructions string: how tasks are named, that Frames render as cards, and which tool starts an agent turn. tools/list returns every tool's name, description, and inputSchema. Field descriptions inside the schema are the argument docs. A tool that fails returns isError: true and a JSON error the model can read, including insufficient_scope when the key lacks that tool's scope.
Nimbus MCP
POST https://mcp.gonimbus.ai
Authorization: Bearer nmb_sk_…
The same handler answers POST /mcp on that host, and POST /v1/mcp on the API origin for the lane (https://api-integration.gonimbus.ai/v1/mcp on integration). Create the key on the Developers page. A secret key (nmb_sk_…) can call every tool. A restricted key (nmb_rk_…) needs the scope named on the tool. Write includes read. Posts and tasks appear under the key's name, as the person who created the key.
{
"mcpServers": {
"nimbus": {
"type": "http",
"url": "https://mcp.gonimbus.ai",
"headers": {
"Authorization": "Bearer nmb_sk_…"
}
}
}
}
Keep the key out of files you commit. The server's serverInfo.version is the public API date (2026-09-25), not a count of tools. Trust tools/list.
Tasks
Tasks use identifiers like ENG-42. Statuses are triage, backlog, todo, in_progress, in_review, done, and canceled. Scope: tasks:read or tasks:write.
| Tool | What it does |
|---|---|
whoami |
Who the key acts as, and which organization. Requires tasks:read. |
task_projects_list, task_projects_create, task_projects_update |
Projects. The key (ENG) prefixes every identifier and cannot be changed later. The default project is Tasks (TASK); list always includes it. |
task_labels_list |
Labels already in use. Reuse those spellings. |
tasks_summary |
Open tasks assigned to the caller, and how many of those are overdue. For every task in the workspace, use tasks_search. |
tasks_search, tasks_get |
Find tasks (the whole workspace, unless assignees is set), or read one with its parent, subtasks, comments and history. History is status, assignees, priority, due date, labels, parent and archive, not title or description edits. |
tasks_create, tasks_update, tasks_update_many |
Create or change tasks. Omit project to use Tasks (TASK). parent is another task's identifier. description is markdown, including LaTeX. Pass source_key to make a repeat create update the same task. Anyone who can see a task can update it; only the creator can archive or restore it. Title and description edits are saved and left off the history. A history change returns an event id. |
tasks_comment, tasks_undo |
Comment, or reverse one event. Only the creator can undo. Undoing creation archives the task. A second undo of the same event is refused. |
task_suggestions_accept, task_suggestions_dismiss |
Turn a Sync's follow-ups on a message into tasks, or dismiss them. |
tasks_from_todo |
Save a to-do card's items as tasks in that room. |
task_import_sources_list, task_import_projects_list, task_imports_start, task_imports_list |
Import from Linear, Jira, GitHub, Asana, ClickUp, Trello, or monday.com. Connecting the tracker still happens in the Nimbus app. |
A task can sit on the workspace, or in one room or one workstream, not both.
Rooms
Scope: messages:read or messages:write.
| Tool | What it does |
|---|---|
rooms_list |
Public rooms, and private rooms the key's creator belongs to. |
messages_post |
Post Markdown, a Frame, or both. reply_to posts in a thread. |
messages_read |
Latest messages, or one message and its thread. Includes task suggestions and to-do cards. |
messages_update |
Replace the text of a message this key posted. |
files_post |
Attach a file (base64 or text). Files are scanned. The default ceiling is 25 MB. |
Agent, loops, and runs
These tools call the same operations as the agent, loops, and runs REST endpoints.
| Tool | Scope | What it does |
|---|---|---|
agent_ask |
agent:write |
Ask the workstream agent. Returns immediately, usually still queued. Spends NTUs the same way a question in the app does. |
agent_get |
agent:read |
Poll until status is completed (output_text is the answer) or failed. Pass conversation back to agent_ask to continue. |
agent_cancel |
agent:write |
Stop a response that is still running. |
loops_list, loops_get, loop_executions_list |
loops:read |
Shared loops and their executions. Private loops are hidden. |
loops_run |
loops:write |
Run a loop now. Does not create or edit a loop. The execution's run id is what the run tools take. |
runs_list, runs_get |
runs:read |
Runs this key can see. |
runs_report |
runs:read |
The report, once the run has written one. |
Wiki, decisions, connections, and workstreams
| Tool | Scope | What it does |
|---|---|---|
documents_search |
documents:read |
Search the Company Wiki by meaning. Returns passages, not whole files. |
decisions_list |
decisions:read |
Decisions already recorded. It does not approve or reject. |
connections_list, connections_get |
connections:read |
Which apps are connected and whether they work. Never returns credentials, and does not call the app. |
workstreams_list, workstreams_get |
workstreams:read |
Workstreams this key can see. |
workstreams_create, workstreams_update |
workstreams:write |
Create a workstream, rename it, or archive it. |
workstreams_delete |
workstreams:write |
Move a workstream to the trash. Prefer archive when you only want to hide it. |
The Nimbus MCP server does not call a connector, approve a human-in-the-loop checkpoint, start a Sync, author a loop, or read billing.
App MCP
Every App install comes with its own MCP server, so an agent can act in the App's room: post messages and Frames, update its own messages, and read the conversation. It acts as the App, in that one room, and nowhere else.
POST https://api.gonimbus.ai/v1/apps/mcp
Authorization: Bearer nmb_it_…
Use the install's mcp_url and its MCP token (nmb_it_…), both from the install response. The token says which install, App, and room you are, so every install shares the same URL. Transport is the same as the Nimbus MCP server, above.
Connecting an App
Most MCP clients that support remote HTTP servers with custom headers can connect with a configuration like this:
{
"mcpServers": {
"nimbus": {
"type": "http",
"url": "https://api.gonimbus.ai/v1/apps/mcp",
"headers": {
"Authorization": "Bearer nmb_it_7Kp2Rt9Vx4Lb8Nc1Wz6Hd3Qm5Fs0Yj2Ea7Gu4Ci9"
}
}
}
}
The exact keys vary by client, so check your client's documentation for how it names the server type, URL, and headers. Keep the token out of files you commit; most clients can read it from an environment variable.
Calling the server directly
You don't need an MCP library to use it. Initialize, then call a tool:
::: code-group
MCP_URL='https://api.gonimbus.ai/v1/apps/mcp'
TOKEN='nmb_it_7Kp2Rt9Vx4Lb8Nc1Wz6Hd3Qm5Fs0Yj2Ea7Gu4Ci9'
curl "$MCP_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"deploy-bot","version":"1.0.0"}}}'
curl "$MCP_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"messages_post","arguments":{"text":"Deploy **v2.14** finished on production."}}}'
let nextId = 1
const rpc = async (method: string, params: object = {}) => {
const response = await fetch('https://api.gonimbus.ai/v1/apps/mcp', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NIMBUS_MCP_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ jsonrpc: '2.0', id: nextId++, method, params }),
})
if (!response.ok) throw new Error(`Nimbus MCP ${response.status}: ${await response.text()}`)
const reply = await response.json()
if (reply.error) throw new Error(`JSON-RPC ${reply.error.code}: ${reply.error.message}`)
return reply.result
}
await rpc('initialize', { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'deploy-bot', version: '1.0.0' } })
const result = await rpc('tools/call', {
name: 'messages_post',
arguments: { text: 'Deploy **v2.14** finished on production.' },
})
if (result.isError) throw new Error(result.content[0].text)
const message = result.structuredContent
import itertools
import os
import requests
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['NIMBUS_MCP_TOKEN']}"
ids = itertools.count(1)
def rpc(method, params=None):
response = session.post(
"https://api.gonimbus.ai/v1/apps/mcp",
json={"jsonrpc": "2.0", "id": next(ids), "method": method, "params": params or {}},
)
response.raise_for_status()
reply = response.json()
if "error" in reply:
raise RuntimeError(f"JSON-RPC {reply['error']['code']}: {reply['error']['message']}")
return reply["result"]
rpc("initialize", {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "deploy-bot", "version": "1.0.0"}})
result = rpc("tools/call", {"name": "messages_post", "arguments": {"text": "Deploy **v2.14** finished on production."}})
if result.get("isError"):
raise RuntimeError(result["content"][0]["text"])
message = result["structuredContent"]
:::
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{ "type": "text", "text": "{\"id\":\"2c7e9a41-5b3d-4f8e-a6c1-9d0b4e2f7a58\",\"object\":\"message\",…}" }],
"structuredContent": {
"id": "2c7e9a41-5b3d-4f8e-a6c1-9d0b4e2f7a58",
"object": "message",
"author": { "type": "app", "id": "app_5Hc2Lq9Rv4Tx7Nb1Mz8Kw3Pd", "name": "Deploy Bot" },
"text": "Deploy **v2.14** finished on production.",
"frame": null
}
}
}
A successful tool call returns its output twice: as JSON text in content, for clients that only read text, and as structuredContent. The structuredContent above is shortened.
App tools
Tool names use underscores, not dots, because some common MCP clients reject dots in tool names. tools/list returns each tool's full JSON Schema. These four tools are the App server only. The Nimbus MCP server's messages_update edits a message the API key posted, not a message an App posted.
messages_post
Posts text, a Frame, or both to the room, as the App. Returns the created Message object, with its frame.
| Argument | Type | Description |
|---|---|---|
text |
string, optional | Markdown, 1–16,000 characters. |
frame |
object, optional | A Frame. Send text, frame, or both. |
reply_to |
string, optional | The ID of a message in this room, to post in its thread. |
messages_update
Replaces the text or Frame of a message this App posted, in this room. A field you leave out keeps its current value. Returns the updated message.
| Argument | Type | Description |
|---|---|---|
message_id |
string | The message to update. |
text |
string, optional | New Markdown text, 1–16,000 characters. |
frame |
object, optional | A new Frame. Send text, frame, or both. |
Updating a message the App didn't post fails with message_not_owned. A message that isn't in this room fails with resource_missing.
messages_read
Reads the room's latest messages, or one message and its thread.
| Argument | Type | Description |
|---|---|---|
message_id |
string, optional | Read this message and its replies, instead of the latest messages. |
limit |
integer, optional | How many messages to return, 1–50. Default 20. |
Without message_id, it returns { "messages": [ … ] }, newest first. With it, it returns { "message": { … }, "replies": [ … ] }. Every message includes its frame.
surfaces_info
Says which App this is, which organization it's in, and which room it's installed in. Takes no arguments.
{
"object": "app_install",
"install": "ins_8Wd3Kp6Rm1Vt4Lx9Qb2Nh7Cz",
"app": { "id": "app_5Hc2Lq9Rv4Tx7Nb1Mz8Kw3Pd", "name": "Deploy Bot" },
"organization": { "id": "acme", "name": "Acme Corp" },
"surface": { "kind": "workstream", "id": "3f6c2a1e-8b4d-4e2f-9a7c-5d1b0e6f2a94", "workspace": "default" }
}
Errors
Errors come back in three ways, depending on where they happen:
| Where | How it comes back | Examples |
|---|---|---|
| Authentication and HTTP | An HTTP error status with the usual error body. | 401 missing_install_token, invalid_install_token, install_revoked; 429 rate_limited; 400 invalid_json. |
| Protocol | A JSON-RPC error object, with HTTP 200. |
-32600 invalid request, -32601 unknown method, -32602 unknown tool, -32603 internal error. |
| Tool | A normal tool result with "isError": true, whose text is the error as JSON, so the model can read it and correct itself. |
frame_invalid, parameter_invalid, message_not_owned, resource_missing. |
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"type\":\"permission_error\",\"code\":\"message_not_owned\",\"message\":\"An App can only update messages it posted.\",\"param\":\"message_id\",\"doc_url\":\"https://docs.gonimbus.ai/api-reference/errors#message_not_owned\"}"
}
],
"isError": true
}
}
Each install has its own rate limit allowance, shared by its MCP calls and its incoming webhook, and reported in the usual RateLimit-* headers.
FAQ
Which URL do I give Cursor?
For an agent that should keep tasks and ask the workstream agent, use https://mcp.gonimbus.ai with an API key. For an agent that should post as an App, use the install's mcp_url with that install's nmb_it_… token. They are not interchangeable.
Does the client need this page to know the tools?
No. initialize and tools/list are the catalog. This page is the map of what those tools are for. If a tool named here is missing from tools/list, that host has not been deployed with it yet.
Can the App MCP server reach other rooms?
No. The install token belongs to one room. To act in another room, install the App there and use that install's token. The Nimbus MCP server can reach the rooms and workstreams its key is allowed to see.
Can I send several requests at once?
Yes. Send a JSON array of JSON-RPC requests, and the response is an array of their responses. Notifications in the batch get no response.
Can an MCP client ask the Nimbus agent?
The App token cannot. It only posts, updates its own messages, and reads. The Nimbus MCP server can, with agent_ask and an API key that has agent:write. You can also call the agent endpoints over REST.
What happens when I rotate the credentials?
The old token or key stops working immediately. App calls fail with 401 invalid_install_token. API-key calls fail with 401 invalid_api_key or api_key_revoked. Update the header in the client's configuration.
Is there a session to keep alive?
No. Each request stands alone, so you can call tools/call without keeping a connection open. Calling initialize first is still good practice, and most clients do it for you. agent_ask returns before the answer is ready. Poll agent_get. That is a new request, not a held connection.