teamwork-graph-mcp
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
Two tools. Each takes `{"args": [...]}`: the words that would follow `twg`, command first, global options (`-o json`, `--output-summary inline`, `--select …`) after them.
| 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, … |
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`)
- 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)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues