TaskFrame MCP Server
by DozZzeR
README.md
# TaskFrame MCP Server
HTTP MCP server for [TaskFrame](https://taskframe.app) — lets AI agents work with projects, tasks, comments and docs via the TaskFrame external API.
Each request is stateless. The Bearer token from the client is forwarded directly to TaskFrame — no credentials are stored on the server.
---
## For humans
### Requirements
- Node.js 22+
- TaskFrame API key (`tf_live_...`) — create one in TaskFrame → Settings → API Keys
- `TASKFRAME_BASE_URL` — your TaskFrame instance URL
### Run locally
```bash
cp .env.example .env
# edit .env: set TASKFRAME_BASE_URL
npm install
npm run dev
```
### Run with Docker
```bash
docker build -t taskframe-mcp .
docker run -e TASKFRAME_BASE_URL=https://your-taskframe-domain.com -p 3000:3000 taskframe-mcp
```
### Environment variables
| Variable | Required | Description |
|---|---|---|
| `TASKFRAME_BASE_URL` | yes | Base URL of your TaskFrame instance |
| `PORT` | no | HTTP port, default `3000` |
| `ALLOWED_HOSTS` | no | Comma-separated Host header allowlist; enables DNS-rebinding protection when set (e.g. `mcp.example.com`) |
### Health check
```bash
curl http://localhost:3000/health
# → {"ok":true}
```
---
## Security & deployment
> [!IMPORTANT]
> **Always deploy behind TLS.** The API key is sent as a plaintext `Authorization: Bearer` header on every request. Terminate HTTPS at a reverse proxy (nginx, Caddy, Traefik, cloud LB) and point clients at the `https://` URL. Never expose the raw `http://` port to a network you don't fully control.
Built-in protections:
- **Stateless** — no token, workspace id, or session is stored between requests. Each call is authenticated solely by its own Bearer header.
- **ID validation** — all UUID/key arguments are schema-validated and URL-encoded, so a tool argument can never alter the upstream request path (no path traversal / SSRF into other TaskFrame endpoints).
- **Upstream timeout** — requests to TaskFrame abort after 15s, returning `504` instead of hanging.
- **Body limit** — JSON bodies over 1 MB are rejected with `413`.
- **Scoped by the key** — the server never grants more than the API key's own permissions; it cannot escalate access.
Recommended for production: run the reverse proxy with rate limiting, set `ALLOWED_HOSTS` to your public hostname, and keep the container non-root (the `node` base image already runs as a non-root `node` user when configured).
---
## Connect to Claude Code
Add to your `~/.claude/settings.json` (global) or `.claude/settings.json` (project):
```json
{
"mcpServers": {
"taskframe": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer tf_live_..."
}
}
}
}
```
Each user sets their own API key in the `Authorization` header. The server is shared — the key is not.
---
## For agents
The server exposes a resource `taskframe://guide` that Claude Code loads automatically into context. If your client does not load resources automatically, call `get_guide` first.
### How auth works
Pass your TaskFrame API key as `Authorization: Bearer <key>` in the HTTP request to this MCP server. The server resolves your `workspaceId` automatically via `/me` — **never pass workspaceId as a tool argument**.
### Recommended flow
```
list_projects
→ pick projectId
get_project_meta(projectId)
→ columns[].id → use as statusId
→ sprints[].id → use as sprintId
→ members[].id → use as assigneeId
list_tasks(projectId) — read existing tasks
create_task(projectId, …) — create new task
update_task(taskId, …) — update fields
```
### Key shortcuts
- Use `get_task_by_key` with keys like `APP-43` when you have them — it's faster than `get_task` by UUID
- `description` (tasks) and `content` (docs/comments) accept plain text — the server wraps it in TipTap JSON automatically
- `sprintId`: omit → active sprint, `null` → backlog, UUID → specific sprint
### Tools
#### Context
| Tool | Description |
|---|---|
| `get_guide` | Get usage instructions (fallback if resource not loaded) |
| `get_me` | Get workspace id, user, and effective permissions |
#### Projects
| Tool | Arguments | Description |
|---|---|---|
| `list_projects` | `status?`, `clientId?` | List projects |
| `get_project_meta` | `projectId` | Columns, sprints, labels, members |
#### Tasks
| Tool | Arguments | Description |
|---|---|---|
| `list_tasks` | `projectId` | All tasks in a project |
| `get_task` | `taskId` | Get task by UUID |
| `get_task_by_key` | `taskKey` | Get task by key, e.g. `APP-43` |
| `create_task` | `projectId`, `name`, ...fields | Create task |
| `update_task` | `taskId`, ...fields | Update task (partial) |
| `delete_task` | `taskId` | Delete task permanently |
`create_task` and `update_task` accept: `description`, `type`, `priority`, `statusId`, `assigneeId`, `parentTaskId`, `estimateMinutes`, `storyPoints`, `dueDate`, `startDate`, `sprintId`, `labelIds`.
#### Comments
| Tool | Arguments | Description |
|---|---|---|
| `list_comments` | `taskId` | List comments on a task |
| `create_comment` | `taskId`, `content`, `parentCommentId?` | Add comment |
| `update_comment` | `commentId`, `content` | Edit own comment |
| `delete_comment` | `commentId` | Delete comment |
#### Docs
| Tool | Arguments | Description |
|---|---|---|
| `list_docs` | `projectId` | Flat list of pages; use `parentPageId` to build tree |
| `get_doc` | `projectId`, `pageId` | Get page with full content |
| `create_doc` | `projectId`, `title`, ...fields | Create page |
| `update_doc` | `projectId`, `pageId`, ...fields | Update page; use `isArchived: true` to archive |
| `delete_doc` | `projectId`, `pageId` | Delete page and all children permanently |
### Permissions
The server uses whatever permissions your API key has. If a tool returns a 403 error, the key needs the corresponding permission added in TaskFrame settings.
| Operation | Required permission |
|---|---|
| Read projects | `projects:read` |
| Read tasks and comments | `tasks:read` |
| Create / update / delete tasks | `tasks:create`, `tasks:update`, `tasks:delete` |
| Create / edit / delete comments | `comments:create`, `comments:update_own`, `comments:delete_own` |
| Read docs | `wiki:read` |
| Create / update / delete docs | `wiki:create`, `wiki:update`, `wiki:delete` |
| Columns in project meta | `board:read` |
| Sprints in project meta | `sprints:read` |
| Labels in project meta | `tags:read` |
| Members in project meta | `members:read` |
### Error format
All tools return errors as text content with `isError: true`. The message includes the HTTP status and TaskFrame error description:
```
Error: 403: API key does not allow this operation
Error: 404: Task not found
```
---
## Development
```bash
npm run dev # watch mode with tsx
npm run build # compile to dist/
npm start # run compiled output
```
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues