Skip to content

MCP server

Orbit ships a Model Context Protocol server, so an AI assistant can read your board and do work in it. Claude, Cursor, VS Code and anything else that speaks MCP can connect.

The endpoint is your Orbit deployment plus /mcp:

https://orbit.example.com/mcp

There is nothing extra to run. The MCP server is part of the app.

How access works

OAuth only. There are no API keys, and there will not be.

An API key is a secret that lives forever, gets pasted into config files, and carries whatever permissions its creator had. When an agent runs unattended, that is the wrong shape. Orbit instead makes you sign in, pick a workspace, and approve the scopes, and the resulting grant can be revoked from the same place.

The flow when a client connects:

  1. The client discovers the OAuth server at /.well-known/oauth-authorization-server.
  2. It registers itself dynamically. No manual client setup.
  3. You are sent to /oauth/authorize, where you pick the workspace and re-verify a passkey.
  4. You approve the scopes.
  5. The client gets an access token bound to you, that client, and the workspace you chose.

PKCE throughout. The grant is a row in the database, so revoking it takes effect immediately.

An agent never has more permission than you do. Every tool runs against the same policy that governs your account, so an agent authorised by a guest can read and comment, and nothing else.

Scopes

ScopeGrants
orbit.readRead issues, projects, sprints, docs and members
orbit.writeCreate and update issues, comments, projects and sprints

Tools are registered according to the scopes the token actually carries. With only orbit.read, the write tools are not merely refused, they are never offered, so the assistant cannot attempt something it will not be allowed to finish. A token carrying neither scope is refused with a 403 before any tool is registered.

Give an agent orbit.read alone unless you specifically want it changing things.

Workspace instructions

Workspace administrators can maintain up to 4,000 characters of guidance for connected agents in Settings, Workspace, General. Use it for conventions such as naming, routing, labels and estimates. It is shared workspace context, not a prompt template and not a permission boundary.

For a connection granted orbit.read, the server includes the current workspace instructions in the MCP instructions field during initialization, so a newly connected client receives them with its other Orbit context. A client that stays connected while the text changes can refresh deliberately with the read-only get_workspace_instructions tool.

Reading the tool requires orbit.read and membership in the selected workspace. Any workspace member with that scope can read the text. Only administrators can edit it, through the existing org:manage permission. Rules written in this field are advisory. Policy checks and OAuth scopes remain the authorization boundary for every action.

The first version stores one set of instructions per workspace. Team-specific overrides are not supported yet.

Connect a client

Claude Code

bash
claude mcp add --transport http orbit https://orbit.example.com/mcp

Then run /mcp inside Claude Code and follow the sign-in.

Claude Desktop

Settings, Connectors, Add custom connector, then paste https://orbit.example.com/mcp. A browser window opens for you to authorise it.

Cursor

In .cursor/mcp.json:

json
{
  "mcpServers": {
    "orbit": {
      "url": "https://orbit.example.com/mcp"
    }
  }
}

VS Code

In .vscode/mcp.json:

json
{
  "servers": {
    "orbit": {
      "type": "http",
      "url": "https://orbit.example.com/mcp"
    }
  }
}

Running locally

Point at http://localhost:3000/mcp instead. Everything else is the same.

What the tools do

Seventy odd tools across seven groups. Read tools need orbit.read, write tools need orbit.write.

Most tools take names rather than ids. A team is "ENG" or "Engineering", an assignee or reviewer is a name, handle, email or the literal "me", and a project is a name or a slug. Assistants are much better at names than at UUIDs, and Orbit resolves them.

Identity and workspace

ToolScopeDoes
get_mereadWho the token belongs to, and their role
get_workspace_instructionsreadCurrent workspace guidance for connected agents
list_teamsreadTeams in the workspace
list_usersreadMembers
list_statesreadWorkflow states on a team
list_labelsreadLabels
list_membersreadMembers with their roles
invite_memberwriteInvite someone
list_notificationsreadYour inbox: mentions, assignments, replies and state changes. Each row resolves its issue, or its doc for a doc mention
mark_notification_readwriteMark your own notifications read

Issues

ToolScopeDoes
get_issuereadOne issue by identifier
list_issue_commentsreadThe comment thread on an issue, oldest first
search_issuesreadSearch and filter, including work assigned to or reviewed by a participant
list_my_issuesreadAssigned to the caller or awaiting their review
copy_branch_namereadThe git branch name for an issue
create_issuewriteCreate one with assignee and reviewers, returns ENG-42. Name a label by id when two share a name
update_issuewriteTitle, description, state, priority, assignee, reviewers, labels, estimate
move_issuewriteMove between states or teams. A team move drops the labels the new team cannot use
add_commentwriteComment
set_relationwriteBlocks, blocked by, relates to, duplicates
archive_issue, unarchive_issue, delete_issuewrite
edit_comment, delete_commentwrite
list_issue_attachmentsreadEvery file attached to an issue or to a comment on it
list_attachmentsreadFiles on one issue, comment, doc or project
read_attachmentreadThe contents of an attached file
attach_filewriteUpload a file and attach it to an issue, a comment, a doc or a project

Files

An assistant can both send and read files. attach_file uploads up to 4MB inline as base64 and returns a url and ready made markdown, so a generated report or a chart can be posted straight onto the issue it belongs to. list_issue_attachments shows what is attached to an issue and to every comment on it, and get_issue reports the same list, so an assistant can tell a file is there without being told. list_attachments does the same for one doc or project. read_attachment returns the contents: text-like types come back as text, anything else base64, and a large file is truncated with truncated: true rather than silently cut.

Reading and writing cover the same four parents, so a file an assistant can post is a file it can open again. Reads are governed by the thing the file hangs off, through that type's own check: an issue file by the issue, a comment file by its issue, a doc file by the doc, a project file by the project. If you cannot read the parent, you cannot list or read its files.

Projects and milestones

ToolScopeDoes
list_projectsreadProjects
project_progressreadCompletion against scope
list_project_milestones, list_milestonesreadMilestones
create_project, update_projectwrite
archive_project, delete_projectwrite
create_milestone, update_milestone, delete_milestonewrite
reorder_milestoneswriteThe whole order of a project milestones

Sprints and cycles

ToolScopeDoes
list_cyclesreadSprints on a team
active_cyclereadThe one running now
cycle_progressreadBurndown and completion
create_cycle, update_cyclewrite
complete_cyclewriteClose it and handle carryover
move_to_cyclewriteMove issues in
delete_sprintwrite

Docs

ToolScopeDoes
list_docs, get_docreadDocs
list_doc_collectionsreadCollections
list_doc_commentsreadComments on a doc
create_doc, update_doc, archive_docwritecreate_doc takes kind: markdown or html
comment_on_doc, edit_doc_comment, delete_doc_commentwrite
create_doc_collectionwrite

Views

ToolScopeDoes
list_viewsreadSaved views, each with the filter state it stores
create_view, update_view, delete_viewwrite

create_view takes the same state the app stores. Conditions live under filter.filter.children, each one shaped like {"kind":"condition","property":"priority","operator":"in","values":["1"]}. A key the state does not define is rejected rather than dropped, so a filter written in some other shape fails loudly instead of saving a view that filters nothing. Call list_views first and copy a shape that already works.

Teams, labels and workflow states

ToolScopeDoes
create_team, update_teamwrite
add_team_member, remove_team_memberwrite
remove_memberwriteRemove from the workspace
create_label, update_label, delete_labelwritePass team to pin a label to one team, null to widen it back. Name a label by id when two share a name
create_state, update_statewriteA status carries a category the product reads, so changing it re-dates the issues in it
delete_statewriteRefused while issues sit in it unless moveTo names the status they go to
reorder_stateswriteThe whole board order, first column first

Things worth asking for

Once connected, these all work:

  • "What am I working on this sprint?"
  • "File a bug on Engineering: passkey sign-in fails on Safari when no credential is registered. High priority, assign it to me and ask Rhea to review."
  • "What is blocking the Realtime Sync Engine project?"
  • "Summarise what the team finished last sprint and what carried over."
  • "Read ENG-42 and write the migration it describes."
  • "Everything in Design labelled Bug with no assignee, and who should take each one."
  • "Summarise what each person closed yesterday."

The last one is the shape that pays for itself. An agent with orbit.read can prepare the update nobody wants to compile by hand.

Managing access

Grants live under Settings, Integrations, MCP, where you can see which clients are connected, which workspace and scopes each got, and revoke any of them. Revocation is immediate.

When upgrading from a build that issued unbound MCP credentials, existing clients must reconnect once. Orbit intentionally refuses those older raw credentials, so operators should notify users before deploying the upgrade.

When it does not work

"401 Unauthorized" or a WWW-Authenticate challenge. The token expired or was revoked. Reconnect, and the client will re-run the OAuth flow.

"403" and no tools at all. The token carries neither orbit.read nor orbit.write. Reauthorise and approve at least one scope.

Write tools are missing. The token only has orbit.read. That is working as intended. Reauthorise with orbit.write if you want it.

The client cannot discover the server. Check NEXT_PUBLIC_APP_URL matches the origin you are actually serving from, since discovery documents are built from it. Confirm with:

bash
curl https://orbit.example.com/.well-known/oauth-authorization-server

A tool says it cannot find a team or a person. Names are resolved, but they have to be unambiguous. Use the team key, or the exact name.

For contributors

Tools live in packages/mcp-server/src/tools/, grouped by area. Each is defined with defineTool, declares readOnly, and validates its input with Zod.

Adding one means adding it to the right group file, and adding a test in packages/mcp-server/tests/. A tool that mutates must set readOnly: false, or it will be handed to read-only tokens.

See CONTRIBUTING.md.

Released under the Apache 2.0 License.