MCP servers
Each Taktoria app has a Model Context Protocol server in every workspace, so an AI assistant such as Claude, ChatGPT or Cursor can work in it. An assistant connects with OAuth, as a person: it gets their seats, sees what they can see and changes only what they could change, in the one workspace whose server address it was given. Every change it makes is in the workspace’s audit log under their name, with the assistant named beside it.
This is different from an API key, which speaks for the whole workspace. Use a key for automation; use MCP for an assistant working on somebody’s behalf.
Server URLs
Every workspace has its own server for each app, with the workspace’s slug
(the acme in https://www.taktoria.com/app/acme) in the address:
For the acme workspace:
People find their workspaces’ addresses, with a button to copy each, under Account → Connected apps in Taktoria, and workspace admins under Settings → MCP connections. Because the address names the workspace, the sign-in step knows it before anybody signs in: see Authorization.
Each is a Streamable HTTP server, stateless: every request is a POST
carrying a bearer token. It speaks MCP 2026-07-28 to a client that asks for
it, and the 2025 revisions (2025-11-25 and 2025-06-18) to any other.
They answer on Taktoria’s own domain only. A workspace whose app has a domain
of its own (dory.acme.com, say) still connects assistants at
https://www.taktoria.com/mcp/<workspace>/<app>: the app’s domain serves its
pages, not its MCP server. The addresses tools return do use it: a page of an
app with a domain of its own, or a Dory join link, is on that domain, and
anything else (another app, settings) on Taktoria’s.
Connecting
Claude
In Claude’s settings, under connectors, add a custom connector with the app’s server URL for your workspace, then connect it. Claude opens Taktoria to sign in and choose what it may do. Add one connector per app. On Team and Enterprise plans an owner of the Claude organization may have to add it first.
ChatGPT
In ChatGPT’s settings, add a custom connector (developer mode may need to be on), paste the server URL, choose OAuth, and create it.
Cursor
In ~/.cursor/mcp.json, or .cursor/mcp.json in a project:
Cursor then shows the server as needing sign-in.
Claude Code
Then run /mcp in Claude Code and authenticate the server.
Authorization
Clients discover everything themselves, as the MCP specification describes:
- A request without a token gets
401andWWW-Authenticate: Bearer resource_metadata="https://www.taktoria.com/.well-known/oauth-protected-resource/mcp/<workspace>/<app>". - That protected resource metadata (RFC 9728) gives the server’s address as
its
resourceand names the authorization server,https://www.taktoria.com/api/auth, whose metadata (RFC 8414) is athttps://www.taktoria.com/.well-known/oauth-authorization-server/api/auth. - The client identifies itself in one of two ways. On protocol 2026-07-28 it
may use a Client ID Metadata Document: its
client_idis the HTTPS address of a JSON document describing it, which Taktoria fetches and checks (the authorization server’s metadata saysclient_id_metadata_document_supported: true). Such a client is shown on the consent page and in the connection lists by that document’s host. Or it registers itself (RFC 7591,POST /api/auth/oauth2/register), as Claude, ChatGPT and Cursor still do. It then sends the person through the authorization code flow with PKCE (S256only), naming the server as itsresource(RFC 8707). A request that names no server, or servers of two workspaces, cannot be granted. - The person signs in the way the server’s workspace signs people in: with Google straight away if it requires Google, through single sign-on if it has it, and otherwise on the workspace’s own sign-in page. Somebody who is already signed in but is not a member is told so, and can sign in as somebody else.
- On the consent page, which shows the workspace (there is nothing to pick), they choose No access, Read only or Read and write for the app. Somebody who is a Viewer of the app is offered only Read only. They can narrow what was asked for, never widen it.
- The client exchanges the code at
POST /api/auth/oauth2/tokenfor an access token (an hour) and, if it asked foroffline_access, a refresh token (30 days, rotated on every use). Tokens are revoked atPOST /api/auth/oauth2/revoke.
A token is bound to the server it was issued for, in its workspace: one for
/mcp/acme/launchcal is refused at /mcp/acme/okrhub and at
/mcp/globex/launchcal. If a workspace’s slug changes, so do its addresses,
and assistants connect again at the new ones.
Scopes
<app> is launchcal, okrhub, dory, golinks or memegen. A server
lists its write tools only to a token with <app>:write, and refuses them to
any other. A Viewer of the app gets a read-only connection whatever it was
granted: its scopes are cut to <app>:read, so no write tool or skill is
listed, and one called anyway is refused (viewer_only). Nothing is stored on
the token, so a Viewer made a Member writes again with the same connection.
See App roles. Each tool carries MCP’s readOnlyHint, destructiveHint and
idempotentHint annotations.
Underneath, <app>:read stands for every read scope of that app’s API
resources, plus members:read and teams:read (who is in the workspace, and
its teams, which every app’s pickers show), and <app>:write adds the app’s
own write scopes: launchcal:write, for
instance, is launches:write, steps:write, insights:write,
artifacts:write and templates:write. They are a ceiling on top of the
person’s own permissions, never a grant of more. No MCP scope stands for
mcpConnections: a connection can never see or revoke connections, nor turn
them off (see Managing connections).
Tools
Every server has:
whoami: the person, their role, the workspace, and this connection’s access to the app.search: everything in the app the person can see that matches some words, grouped by kind with totals, each match with the address of its page.find_people: members of the workspace whose name or email address matches some words, A to Z, with each one’s user id, email address, picture, role, the teams they are on (directly, or through a team inside one), andhasSeat, whether they hold a seat on this server’s app. On LaunchCal’s server, also the functional teams each is on. Paged withcursor, up to 50 at a time (limit, 20 unless asked).find_teams: the workspace’s organizational teams whose name matches, A to Z, with each one’s id, slug, how many people are on it (through the teams inside it too, and directly), and the teams it sits in and holds. On LaunchCal’s server, the first page also has the matching functional teams. Paged asfind_peopleis.
Tools that name people or teams (a go link’s editors, a Dory event’s hosts, a
launch’s owners, stakeholders and reviewers, an objective’s owner) take ids;
find_people and find_teams are how an assistant gets them from a name.
Both find only current members and the workspace’s own teams, the same people
and teams the app’s own pickers offer. Words match as search’s do: every
word somewhere in the name (or the email address), case and accents ignored.
Each app’s read scope is enough to use them, since every member sees these
in the app; underneath, <app>:read carries the API’s members:read and
teams:read.
More tools arrive app by app.
- Go Links: every Go Links tool.
- LaunchCal: every LaunchCal tool.
- OKR Hub: every OKR Hub tool.
- Dory: every Dory tool.
- Memegen: every Memegen tool.
Skills
Each server also has skills: short guides, in the
Agent Skills SKILL.md format, that teach an
assistant how to do something in the app with its tools. Every server starts
with find-anything, on searching it well. A skill that changes things is
offered only to a connection allowed to write in that app.
A skill’s address is skill://<app>/<name>/SKILL.md, and any files it refers
to sit beside it, such as
skill://okrhub/draft-quarterly-okrs/references/writing-good-key-results.md.
Clients reach skills in whichever way they support:
- The Skills extension (SEP-2640,
io.modelcontextprotocol/skills), on protocol 2026-07-28. The server declares it in its capabilities and answersskills/listandskills/get. Each skill comes with a manifest listing every file’s URI,sha256:digest and size, and the files are read withresources/read. The extension is new; check your client’s documentation for whether it uses it. - Resources. Every
SKILL.mdis listed inresources/listand read withresources/read, for clients that let the model read a server’s resources. - Prompts. Every skill is also a prompt of the same name, with its arguments. Some clients, such as Claude Code, offer a server’s prompts as slash commands.
- Instructions. The server’s
instructionsname the skills a connection may use, with their addresses, and ask the model to read the one that fits before multi-step work.
Errors
A problem with the connection itself is answered before MCP begins, with an
HTTP status and a JSON-RPC error whose data.code says which:
Rate limits
Each connection (one person, one client, one workspace) may make 600 reads
and 120 writes a minute, and every connection and API key in a workspace
together 3,000 and 600. A tool call that changes something is a write;
listing, reading and skills are reads. A JSON-RPC batch counts each call in
it. search, export_questions, get_meme_image, preview_meme,
search_template_images, bulk_score, bulk_create_objectives,
link_ticketing_project, sync_ticketing_project, export_artifact and
list_docs_destinations also have a budget of their own, shared with the
REST API (see Rate limits).
Every answer carries the IETF RateLimit-Policy and RateLimit headers.
Over a limit, the request is refused before any of it runs, with HTTP 429,
Retry-After in seconds, and a JSON-RPC error:
Nothing in a refused request was done, so it is safe to send again once
Retry-After has passed. Requests sent while refused are counted too.
Inside a working connection, a tool that cannot do what was asked returns a
tool error (isError: true) with a plain English explanation, such as a
write tool called on a read-only connection or by a Viewer of the app
(viewer_only), or something the person may not see or change.
Every request is checked afresh, so revoking a connection (the person, in Account → Connected apps, or a workspace admin, in Settings → MCP connections), removing the person, or turning MCP connections off takes effect at once.
Managing connections
What Settings → MCP connections and Account → Connected apps do is in the
REST API too, with an API key holding the mcpConnections scopes, or a
signed-in session:
A key, like a workspace admin, sees and revokes every connection in the workspace and
turns the switch. Anybody else’s session sees and revokes only their own, and
is refused the switch (403 admin_required). No token is ever returned, and
revoking and the switch are in the audit log as they are from the settings
pages (mcp_connection.deleted, organization.updated).
These are the platform’s own scopes, never an app’s: no MCP scope stands for them, so an assistant connected through MCP cannot list, revoke or turn off connections, its own included.
Slack’s commands follow the same rule as MCP where a workspace requires
Google: a person’s Slack account acts for them only once they have connected
it signed in with Google, unless the workspace’s admins allow Slack commands
without it. Those connections are managed the same way, at
GET /slack-connections and
DELETE /slack-connections/{id}
with the slackConnections scopes, which no MCP scope stands for either.