# teem MCP

Endpoint: `https://teem.so/mcp`. Transport: stateless Streamable HTTP.
Authenticate with `Authorization: Bearer teem_pat_...` on every request.
No MCP session ID is required. Browser cookies do not authenticate MCP.

Create an agent in Settings → API & MCP. Copy its access key when it is created;
teem stores the key's digest and cannot show it again. Each agent belongs to one membership
in one organization. Changes to that role take effect on the next request.

## Connect

```sh
claude mcp add --transport http teem https://teem.so/mcp \
  --header "Authorization: Bearer teem_pat_..."
```

Replace the placeholder privately with the agent's access key. Other MCP clients use the same endpoint
and Authorization header. For local development, use your local Rails origin in place of
`https://teem.so`; Settings and Connect show the current origin automatically.

## Connect with OAuth

ChatGPT, Claude and other clients that support OAuth can connect without an access key. Add
`https://teem.so/mcp` as a connector, sign in to teem when asked, pick the organization and approve.
In Claude, open Customize → Connectors, choose Add custom connector and paste the URL. Claude Code
connects with `claude mcp add --transport http teem https://teem.so/mcp`, then `/mcp` to sign in.
Choose how much the app can do:

- `read`: conversations, feedback, the changelog and help articles.
- `draft`: also changelog and help article drafts. Nothing goes public.
- `write`: also replies, statuses, publishing and deleting.

These connections work on MCP only, never the REST API. You'll find each one under Connected apps in
Settings → API & MCP, and you can disconnect it there. Access tokens last an hour, and your app
refreshes them. Your app sees only the tools its access allows; calling any other returns
`insufficient_scope`.

## Tools

| Tool | Operation |
| --- | --- |
| `whoami` | `whoami` |
| `update_profile` | `update_profile` |
| `notification_settings` | `read_notification_settings` |
| `update_notification_settings` | `update_notification_settings` |
| `list_conversations` | `list_conversations` |
| `search` | `search` |
| `get_conversation` | `get_conversation` |
| `reply` | `reply_to_conversation` |
| `resolve` | `set_conversation_status` |
| `delete_conversation` | `delete_conversation` |
| `assign_conversation` | `assign_conversation` |
| `slack_status` | `read_slack_status` |
| `retry_slack_delivery` | `retry_slack_delivery` |
| `list_posts` | `list_posts` |
| `get_post` | `get_post` |
| `create_post` | `create_post` |
| `update_status` | `update_post_status` |
| `update_post` | `update_post` |
| `delete_post` | `delete_post` |
| `list_changelog` | `list_changelog` |
| `create_changelog_entry` | `create_changelog_entry` |
| `publish_changelog_entry` | `publish_changelog_entry` |
| `import_changelog_entry` | `import_changelog_entry` |
| `update_changelog_entry` | `update_changelog_entry` |
| `delete_changelog_entry` | `delete_changelog_entry` |
| `list_help_topics` | `list_help_topics` |
| `create_help_topic` | `create_help_topic` |
| `update_help_topic` | `update_help_topic` |
| `delete_help_topic` | `delete_help_topic` |
| `reorder_help_topics` | `reorder_help_topics` |
| `reorder_help_articles` | `reorder_help_articles` |
| `list_help_articles` | `list_help_articles` |
| `get_help_article` | `get_help_article` |
| `create_help_article` | `create_help_article` |
| `update_help_article` | `update_help_article` |
| `preview_help_article` | `preview_help_article` |
| `publish_help_article` | `publish_help_article` |
| `unpublish_help_article` | `unpublish_help_article` |
| `discard_help_article_changes` | `discard_help_article_changes` |
| `delete_help_article` | `delete_help_article` |
| `update_organization` | `update_organization` |

Ask your agent to call `whoami`. It takes no arguments and returns:

```text
user:         { id, name, email, theme_preference, avatar_url }
organization: { id, slug, name, key, public_key, board_url, installed_at,
                last_seen_at, accent, visitor_theme, logo_url }
role:         owner | member
token:        { id, name, created_at, last_used_at }
billing:      { state, locked, trial_ends_at, widget_off_at, billing_url }
```

Results include structured content and an equivalent JSON text block. The agent's access key
and the signing secret are never included.

`update_profile` accepts any of `name` (1–100 characters after trimming), `theme_preference`
(`system`, `light`, or `dark`), `avatar_base64`, and `remove_avatar` (boolean). Encode a JPG,
PNG or WebP image up to 2 MB and 40 million pixels as standard base64 to upload it; send
`remove_avatar: true` to remove it. Upload and removal cannot be combined. Profile changes apply
across the user's organizations; the visitor theme stays separate. The result includes
`avatar_url`, a same-origin path or null. Sign-in email changes are unavailable.

`update_organization` accepts `name`, `accent`, `visitor_theme`, `logo_base64`, and
`remove_logo` (boolean). Members can update the organization their agent belongs to. Upload a
JPG, PNG or WebP up to 2 MB and 40 million pixels as standard base64, or send
`remove_logo: true`; do not combine upload and removal. The result includes `logo_url`,
a same-origin path or null. Logos fit within 256 × 256 pixels without cropping.

`notification_settings` reads the current member's email choices and the organization's Slack
choices for `new_conversation`, `bug_report`, and `feature_request`.
`update_notification_settings` accepts partial `email` and `slack` objects with those boolean
fields. Any member can change their own email choices; only an owner can change Slack choices.

`list_conversations` accepts `status`, `after` and `limit`. Pass a returned conversation id to
`get_conversation`, `reply`, `resolve` or `assign_conversation`. `reply` takes a body;
`resolve` takes `open` or `resolved`; `assign_conversation` takes a numeric `membership_id` or
null to clear the assignment. All conversation tools are scoped to the agent's organization.
Pass `idempotency_key` to `reply` when you retry; teem writes and mails the reply once per key.
`search` finds conversations or feedback by text. Every conversation and feedback post the tools
return carries a `url` to its page in teem.

`slack_status` returns the connected workspace, selected channel, readiness and last repair
error without returning credentials. `retry_slack_delivery` queues one conversation after an
owner repairs Slack; it is owner-only and remains idempotent. OAuth, channel selection and
disconnect stay out of MCP because an agent never receives browser-bound OAuth state or Slack
credentials; their REST operations remain available where PRD-008 specifies them.

Help Center tools keep editing separate from publication. Use `list_help_articles` with a query
to find an answer, `get_help_article` to inspect editable and published Markdown, then pass the
returned editable `revision` to `update_help_article`. Inspect the updated result and pass its new
revision to `publish_help_article`; neither create nor update publishes implicitly. Set
`version: "published"` when listing to retrieve the exact snapshots customers can see.

REST and MCP share 120 requests per minute per agent. Invalid credentials receive HTTP 401;
rate-limited requests receive HTTP 429 and `Retry-After: 60`. Protocol errors use JSON-RPC;
operation errors use the API error object with `isError: true` in the tool result.

When an organization's trial or subscription has ended, every tool except `whoami` returns the
error code `subscription_required`. Its message says when it ended and where an owner can
subscribe, and `details.billing_url` holds the link. Tell the user; retrying will not help.

Revocation or membership removal ends access on the next request. Create a separate agent
for each organization. The public organization key and signing secret cannot authenticate
this endpoint. See the [API reference](/api.md) for permissions and the REST endpoints.
