# MCP server

> Connect Claude Code and other MCP clients to Fishcamp. Endpoint, setup, and every tool with its arguments and an example.

Source: https://docs.fishcamp.space/docs/agents/mcp

New in November 2026

Fishcamp has a built-in [Model Context Protocol](https://modelcontextprotocol.io) server. Connect an AI agent such as
Claude Code and it can read channels, search messages, check audio rooms and transcripts, and post, all inside your
workspace and under the name of an [API key](https://docs.fishcamp.space/docs/agents/api-keys).

* **Endpoint:** `https://api.fishcamp.space/api/mcp`
* **Transport:** Streamable HTTP, stateless: every request stands alone.
* **Authentication:** `Authorization: Bearer cwk_…` on every request.

## Set it up
### 1. Create an API key
An admin creates the key in **Settings** → **API keys**: `read` lets the agent look around, `write` also lets it
post messages and create channels. The MCP server needs `read`: a key with only `write` gets `403`. An
[AI agent's key](https://docs.fishcamp.space/docs/agents) works too. See [API keys](https://docs.fishcamp.space/docs/agents/api-keys).

### 2. Connect Claude Code
```bash
claude mcp add --transport http fishcamp https://api.fishcamp.space/api/mcp --header "Authorization: Bearer cwk_YOUR_KEY"
```

* Add `--scope user` to use Fishcamp in all your projects, not only the current one.
* In Claude Code, run `/mcp` to check that `fishcamp` is connected.
* To disconnect, run `claude mcp remove fishcamp` (and revoke the key if you won't use it again).

### Other MCP clients
Clients that support remote servers take an entry like this one. The file and the top-level key depend on the client:
`.mcp.json` (Claude Code) and `mcp.json` (Cursor) use `mcpServers`; `.vscode/mcp.json` (VS Code) uses `servers`.

```json
{
  "mcpServers": {
    "fishcamp": {
      "type": "http",
      "url": "https://api.fishcamp.space/api/mcp",
      "headers": { "Authorization": "Bearer cwk_YOUR_KEY" }
    }
  }
}
```

For clients that only start local (stdio) servers, such as Claude Desktop, bridge with
[`mcp-remote`](https://www.npmjs.com/package/mcp-remote):

```json
{
  "mcpServers": {
    "fishcamp": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://api.fishcamp.space/api/mcp", "--header", "Authorization:${FISHCAMP_AUTH}"],
      "env": { "FISHCAMP_AUTH": "Bearer cwk_YOUR_KEY" }
    }
  }
}
```

Chat apps whose connectors only sign in with OAuth, such as ChatGPT, can't use API keys.

## Things to ask
```txt
Who's in the audio room in #platform right now?
Read the last 30 messages in #design-crit and list the open questions with who asked them.
Search for "rate limit" in #incidents and summarize what we decided.
Read yesterday's transcript from #planning and draft the follow-up message, but don't post it.
Create a channel called release-2-5 and post the checklist from RELEASE.md there.
```

Messages the agent posts appear under the key's name with an **AI** label.

## Tools
| Tool                                    | What it does                                                            | Scope   |
| --------------------------------------- | ----------------------------------------------------------------------- | ------- |
| [`whoami`](#whoami)                     | Shows the key's name and scopes, the workspace and who the key acts for | `read`  |
| [`list_channels`](#list_channels)       | Lists the channels the key can see                                      | `read`  |
| [`read_messages`](#read_messages)       | Reads a channel's messages                                              | `read`  |
| [`search_messages`](#search_messages)   | Searches messages                                                       | `read`  |
| [`post_message`](#post_message)         | Posts a message                                                         | `write` |
| [`create_channel`](#create_channel)     | Creates a channel                                                       | `write` |
| [`list_members`](#list_members)         | Lists the people in the workspace                                       | `read`  |
| [`get_audio_room`](#get_audio_room)     | Shows who's in a channel's audio room                                   | `read`  |
| [`list_transcripts`](#list_transcripts) | Lists transcripts                                                       | `read`  |
| [`read_transcript`](#read_transcript)   | Reads one transcript                                                    | `read`  |

Where a tool takes a `channel`, pass the channel's ID or its name, for example `design-crit`.

With an [AI agent's key](https://docs.fishcamp.space/docs/agents/api-keys#agent-keys), the tools only see the agent's channels: `list_channels`,
`search_messages` and `list_transcripts` leave the others out, and any other channel answers "not found".
`list_members` still lists everyone in the workspace.

### `whoami`
Returns the workspace the key belongs to, the person it acts for (the admin who created it) and the key's name and
scopes. Call it first to check the connection. No arguments.

```json
{}
```

### `list_channels`
Lists the channels the key can see, with their IDs, names and descriptions. No arguments.

```json
{}
```

### `read_messages`
Reads the latest messages of a channel. To go further back, pass the cursor from the previous result as `before`.

| Argument  | Type    | Required | Description                                          |
| --------- | ------- | -------- | ---------------------------------------------------- |
| `channel` | string  | Yes      | Channel ID or name.                                  |
| `limit`   | integer | No       | How many messages, from 1 to 100.                    |
| `before`  | string  | No       | Cursor from a previous call, to read older messages. |

```json
{ "channel": "design-crit", "limit": 20 }
```

### `search_messages`
Searches message text in the channels the key can see.

| Argument  | Type    | Required | Description                            |
| --------- | ------- | -------- | -------------------------------------- |
| `query`   | string  | Yes      | Words to look for.                     |
| `channel` | string  | No       | Only search this channel (ID or name). |
| `limit`   | integer | No       | Maximum number of results.             |

```json
{ "query": "rate limit", "channel": "incidents", "limit": 10 }
```

### `post_message`
Posts a message to a channel, under the key's name with an **AI** label. Needs `write`.

| Argument  | Type   | Required | Description                          |
| --------- | ------ | -------- | ------------------------------------ |
| `channel` | string | Yes      | Channel ID or name.                  |
| `text`    | string | Yes      | The message, up to 4,000 characters. |

```json
{ "channel": "general", "text": "Release 2.5 is out. Notes: https://example.com/releases/2.5" }
```

### `create_channel`
Creates a channel. Names follow the same rules as in the app: lowercase, hyphens instead of spaces, other
punctuation dropped, up to 60 characters, unique among active channels. Needs `write`.

| Argument      | Type    | Required | Description                                        |
| ------------- | ------- | -------- | -------------------------------------------------- |
| `name`        | string  | Yes      | Channel name.                                      |
| `description` | string  | No       | What the channel is for, up to 250 characters.     |
| `private`     | boolean | No       | `true` creates a private channel. Default: public. |

```json
{ "name": "release-2-5", "description": "Checklist and status for release 2.5", "private": false }
```

### `list_members`
Lists the people in the workspace with their names, emails and roles. No arguments.

```json
{}
```

### `get_audio_room`
Shows whether the channel's audio room is open, since when, who's in it and who's sharing a screen.

| Argument  | Type   | Required | Description         |
| --------- | ------ | -------- | ------------------- |
| `channel` | string | Yes      | Channel ID or name. |

```json
{ "channel": "platform" }
```

### `list_transcripts`
Lists recent [transcripts](https://docs.fishcamp.space/docs/guides/transcription), with when the call happened and what was decided for it.

| Argument  | Type    | Required | Description                                      |
| --------- | ------- | -------- | ------------------------------------------------ |
| `channel` | string  | No       | Only transcripts from this channel (ID or name). |
| `limit`   | integer | No       | Maximum number of transcripts.                   |

```json
{ "channel": "planning", "limit": 5 }
```

### `read_transcript`
Reads one transcript: every line with its speaker, and the routing decision (discard, save to OpenSync or send to
agents).

| Argument | Type   | Required | Description                             |
| -------- | ------ | -------- | --------------------------------------- |
| `id`     | string | Yes      | Transcript ID, from `list_transcripts`. |

```json
{ "id": "3f2d9c1e-5b7a-4c2e-9a0b-6d8e1f4a2c7b" }
```

## Call it without a client
The server speaks JSON-RPC over HTTP, so you can test it with `curl`. List the tools:

```bash
curl -sS https://api.fishcamp.space/api/mcp \
  -H "Authorization: Bearer cwk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```

Call one:

```bash
curl -sS https://api.fishcamp.space/api/mcp \
  -H "Authorization: Bearer cwk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"read_messages","arguments":{"channel":"general","limit":5}}}'
```

The answer is a JSON-RPC message; depending on the server's choice it arrives as plain JSON or as a server-sent event
(a `data: {…}` line).

## Troubleshooting
| Symptom                             | Fix                                                                                                                                                          |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `401` or "unauthorized"             | The key is missing, mistyped or revoked. Check the header is exactly `Authorization: Bearer cwk_…`.                                                          |
| A write tool fails                  | The key only has `read`. Create a key with `write`.                                                                                                          |
| `403` on every request              | The key has no `read` scope. Create a key with `read`.                                                                                                       |
| "not found" for a channel           | Check the name or ID with `list_channels`. Private channels the key can't see look like they don't exist, and an agent's key only sees the agent's channels. |
| Claude Code doesn't list `fishcamp` | Run `claude mcp list`. If you added it with the default scope, it only exists in the project where you ran the command.                                      |
