Skip to main content
Glama
README.md
# teamwork-graph-mcp

**MCP server for Atlassian Teamwork Graph**: Jira, Confluence, Jira Service Management, Assets, Goals, Projects, Teams, Loom, Trello, Rovo search, code search, people and org context, and the links between them, served from Atlassian's `twg` CLI to Claude (claude.ai, Claude Desktop, Claude Code, mobile) and other AI agents over the Model Context Protocol.

One standard-library Python file. It serves streamable HTTP on the loopback interface behind a bearer secret, and runs `twg` for each call against explicit allowlists.

## Features

Three tools. `twg_read` and `twg_write` each take `{"args": [...]}`: the words that would follow `twg`, command first, global options (`-o json`, `--output-summary inline`, `--select …`) after them. `twg_download` takes a Jira attachment id.

| Tool | What it runs |
|---|---|
| `twg_read` | Only commands on the read allowlist (313 commands): get, query, list, search and other non-mutating subcommands |
| `twg_write` | Only commands on the write allowlist (263 commands): create, update, delete, comment, transition, worklog, link/unlink, archive, approve, move, share, … |
| `twg_download` | Downloads one Jira attachment by numeric id and returns it: images as images (large ones scaled down), videos as up to 10 evenly spaced frames, small text files as text, other types are only saved. Files go to one folder created with mode 700 (`TWG_MCP_DOWNLOAD_DIR`, default `~/.cache/twg-mcp/downloads`), are limited to 25 MB, and are deleted after 1 hour. Needs `ffmpeg` and `ffprobe` for scaling and video frames |

Read allowlist, top-level commands: access, admin, artifacts, assets, capabilities, collaborators, commits, confluence, context, csm, deployments, docs, doctor, focus-areas, focus-areas-tree, goals, help, jira, jsm, loom, meetings, notifications, org-tree, people, pr-tree, projects, pull-requests, recently-viewed, resolve, responsibility, rovo, search-code, spaces, subgraph, talent, teams, trello, user, user-search, videos, whoami, work, work-tree, workitem-tree.

Write allowlist, top-level commands: admin, api, assets, confluence, goals, jira, jsm, loom, projects, teams, trello.

A read command sent to `twg_write`, a write command sent to `twg_read`, and anything on neither list are refused. `--help` works on any allowed command through the tool that would run it. The full lists are in the server's `initialize` instructions and in `READ_COMMANDS` / `WRITE_COMMANDS` in [server.py](src/teamwork_graph_mcp/server.py). They were generated from twg 1.3.1's help; anything a later twg adds stays refused until it is classified.

Refused by both tools:

- `bitbucket` / `bb` (use a separate Bitbucket server, such as bitbucket-mcp)
- `login`, `logout`, `auth …`, `admin auth …`, `rovo auth`, `env`, `setup …`, `update`/`upgrade`, `uninstall`, `upkeep …`, `consent`, `skills …`, `cache clear`, `completion`, `doctor --fix`
- `benchmark …`, `feedback`, `visualize`
- commands that move files between the server machine and Atlassian (attachment upload/download, `artifacts file create|update`, `loom video upload`). The only exception is `twg_download`, which can write into its own download folder and nowhere else; uploads stay refused
- every option that names a local file (`--body-file`, `--input-file`, `--variables-file`, `--out`, …). Pass content inline instead (`--body`, `--input-json`, `--variables-json`)
- `api` with an absolute URL, so the credentials never go to another host

## Requirements

- Linux or macOS with Python 3.8 or newer (standard library only)
- Atlassian's Teamwork Graph CLI, `twg`, installed and signed in (follow Atlassian's instructions; check with `twg whoami`). It is an external requirement and is not included here.
- Optional: `cloudflared`, to reach the server from claude.ai and mobile

## Install

```sh
git clone <this repository's URL> teamwork-graph-mcp
cd teamwork-graph-mcp
pipx install .        # or: python3 -m pip install --user .
twg-mcp --check       # prints the allowlist sizes
```

## Configure

All settings are environment variables and all are optional. See [config.example.env](config.example.env).

| Variable | Default | Meaning |
|---|---|---|
| `TWG_MCP_BIND` | `127.0.0.1` | Address the server listens on. Keep it on the loopback interface |
| `TWG_MCP_PORT` | `8772` | Port the server listens on (`--port` overrides it) |
| `TWG_MCP_SECRET_FILE` | `~/.config/twg-mcp/mcp-secret` | File holding the bearer secret (mode 600) |
| `TWG_MCP_PUBLIC_HOSTS` | none | Comma-separated public host names that may reach the server, e.g. a tunnel hostname |
| `TWG_BIN` | `twg` on `TWG_PATH` | The twg binary |
| `TWG_HOME` | your home folder | `HOME` that twg runs with |
| `TWG_PATH` | `$TWG_HOME/.local/bin:/usr/local/bin:/usr/bin:/bin` | `PATH` that twg runs with |
| `TWG_CONFIG_DIR` | twg's default | Passed on to twg when set |
| `TWG_MCP_ACCOUNT_LABEL` | none | Account shown to the model ("signed in as …") |

## Run

```sh
twg-mcp --init-secret     # create the bearer secret (prints its path only)
twg-mcp                   # serve on 127.0.0.1:8772
twg-mcp --show-header     # print "Bearer …" (only when stdout is a terminal)
twg-mcp --rotate-secret   # replace the secret, then restart and update clients
```

As a systemd user service: copy [examples/teamwork-graph-mcp.service](examples/teamwork-graph-mcp.service) to `~/.config/systemd/user/`, set the path to `twg-mcp`, put your settings in `~/.config/twg-mcp/teamwork-graph-mcp.env`, then:

```sh
systemctl --user daemon-reload
systemctl --user enable --now teamwork-graph-mcp
journalctl --user -u teamwork-graph-mcp -f   # one line per request
```

The unit runs with `ProtectSystem=strict`; only `~/.config/twg` and `~/.cache/twg` are writable, because twg refreshes its OAuth token there.

## Connect to Claude

**claude.ai (web, desktop and mobile)**: Settings → Connectors → Add custom connector. URL `https://<your-public-host>/mcp`, and an `Authorization` header with the value from `twg-mcp --show-header`. claude.ai needs a public HTTPS URL; see the tunnel section below.

**Claude Code** on the same machine:

```sh
claude mcp add --transport http twg http://127.0.0.1:8772/mcp \
  --header "Authorization: Bearer $(cat ~/.config/twg-mcp/mcp-secret)"
```

## Optional: expose over Cloudflare Tunnel

```sh
cloudflared tunnel create <TUNNEL-NAME>
cloudflared tunnel route dns <TUNNEL-NAME> <MCP-HOSTNAME>
```

Use [examples/cloudflared.yml](examples/cloudflared.yml) as the tunnel config, set `TWG_MCP_PUBLIC_HOSTS=<MCP-HOSTNAME>`, and restart the service. The server itself still listens only on the loopback interface.

## Security notes

- Every request needs `Authorization: Bearer <secret>`, compared in constant time. A wrong secret gets `401`; more than 10 failures a minute from one client get `429` for 60 seconds.
- Requests whose `Host` is not `127.0.0.1:<port>`, `localhost:<port>` or a name in `TWG_MCP_PUBLIC_HOSTS` get `421`. `/.well-known/*` answers `404` without the secret, so clients do not look for OAuth.
- twg's credentials stay in twg's own config folder; the server never opens them.
- Output is scrubbed of the bearer secret and of anything shaped like a token (JWTs, Atlassian API tokens, `Bearer …`) before it is returned. The log records the tool, the command path and the exit code, never arguments or output.
- twg runs from an argument list (never a shell) with `TWG_AGENT_DEFAULTS=1`, stdin `/dev/null`, a 120-second timeout (the process group is killed) and combined output capped at 200 KB.
- `twg_read` only runs reads, so a client can auto-approve it and ask before `twg_write`.

## Credits

Written by Febin Micheal Antony. Built on Atlassian's Teamwork Graph CLI (`twg`), which is Atlassian's software and is not included here, and the [Model Context Protocol](https://modelcontextprotocol.io/).

## License

[MIT](LICENSE)