MCP server for tangled

add MCP client installation instructions and improve auth error messages

- add usage section with client setup for claude code, cursor, codex cli, and generic clients
- improve authentication error messages in _client.py with actionable guidance
- simplify justfile: remove opinionated push/release commands
- document release process in docs/publishing.md
- bump server.json to 0.0.9

Changed files
+105 -15
docs
src
tangled_mcp
+52
README.md
··· 28 28 29 29 ## usage 30 30 31 + ### using with MCP clients 32 + 33 + #### claude code 34 + 35 + ```bash 36 + # basic setup 37 + claude mcp add tangled -- uvx tangled-mcp 38 + 39 + # with credentials 40 + claude mcp add tangled \ 41 + -e TANGLED_HANDLE=your.handle \ 42 + -e TANGLED_PASSWORD=your-app-password \ 43 + -- uvx tangled-mcp 44 + ``` 45 + 46 + #### cursor 47 + 48 + add to your cursor settings (`~/.cursor/mcp.json` or `.cursor/mcp.json`): 49 + 50 + ```json 51 + { 52 + "mcpServers": { 53 + "tangled": { 54 + "command": "uvx", 55 + "args": ["tangled-mcp"], 56 + "env": { 57 + "TANGLED_HANDLE": "your.handle", 58 + "TANGLED_PASSWORD": "your-app-password" 59 + } 60 + } 61 + } 62 + } 63 + ``` 64 + 65 + #### codex cli 66 + 67 + ```bash 68 + codex mcp add tangled \ 69 + --env TANGLED_HANDLE=your.handle \ 70 + --env TANGLED_PASSWORD=your-app-password \ 71 + -- uvx tangled-mcp 72 + ``` 73 + 74 + #### other clients 75 + 76 + for clients that support MCP server configuration, use: 77 + - **command**: `uvx` 78 + - **args**: `["tangled-mcp"]` 79 + - **environment variables**: `TANGLED_HANDLE`, `TANGLED_PASSWORD`, and optionally `TANGLED_PDS_URL` 80 + 81 + ### development usage 82 + 31 83 ```bash 32 84 uv run tangled-mcp 33 85 ```
+37
docs/publishing.md
··· 47 47 5. authenticates using GitHub OIDC 48 48 6. publishes to MCP registry 49 49 50 + ## cutting a release 51 + 52 + to cut a new release: 53 + 54 + 1. **update server.json version** (both fields must match the version you're releasing) 55 + ```json 56 + { 57 + "version": "0.0.9", 58 + "packages": [{ 59 + "version": "0.0.9" 60 + }] 61 + } 62 + ``` 63 + 64 + 2. **run pre-commit checks** 65 + ```bash 66 + just check 67 + ``` 68 + 69 + 3. **commit and push your changes** 70 + ```bash 71 + git add . 72 + git commit -m "your commit message" 73 + git push origin main 74 + ``` 75 + 76 + 4. **create and push the version tag** 77 + ```bash 78 + git tag v0.0.9 79 + git push origin v0.0.9 80 + ``` 81 + 82 + 5. **verify the release** 83 + - workflow: https://github.com/zzstoatzz/tangled-mcp/actions/workflows/publish-mcp.yml 84 + - pypi: https://pypi.org/project/tangled-mcp/ 85 + - mcp registry: `https://registry.modelcontextprotocol.io/v0/servers/io.github.zzstoatzz%2Ftangled-mcp/versions/X.Y.Z` 86 + 50 87 ## key learnings 51 88 52 89 ### mcp-publisher installation
-6
justfile
··· 10 10 # run pre-commit checks 11 11 check: 12 12 uv run pre-commit run --all-files 13 - 14 - # push to both tangled and github 15 - push message: 16 - git add . 17 - git commit -m "{{message}}" 18 - git push origin main
+2 -2
server.json
··· 3 3 "name": "io.github.zzstoatzz/tangled-mcp", 4 4 "title": "Tangled MCP", 5 5 "description": "MCP server for Tangled git platform. Manage repositories, branches, and issues on tangled.org.", 6 - "version": "0.0.8", 6 + "version": "0.0.9", 7 7 "packages": [ 8 8 { 9 9 "registryType": "pypi", 10 10 "identifier": "tangled-mcp", 11 - "version": "0.0.8", 11 + "version": "0.0.9", 12 12 "transport": { 13 13 "type": "stdio" 14 14 }
+12 -1
src/tangled_mcp/_tangled/_client.py
··· 74 74 75 75 Returns: 76 76 authenticated client connected to user's PDS 77 + 78 + Raises: 79 + RuntimeError: if authentication fails (check handle/password) 77 80 """ 78 81 if settings.tangled_pds_url: 79 82 client = Client(base_url=settings.tangled_pds_url) 80 83 else: 81 84 client = Client() # auto-discover from handle 82 85 83 - client.login(settings.tangled_handle, settings.tangled_password) 86 + try: 87 + client.login(settings.tangled_handle, settings.tangled_password) 88 + except Exception as e: 89 + raise RuntimeError( 90 + f"failed to authenticate with handle '{settings.tangled_handle}'. " 91 + f"verify TANGLED_HANDLE and TANGLED_PASSWORD are correct. " 92 + f"error: {e}" 93 + ) from e 94 + 84 95 return client 85 96 86 97
+2 -6
src/tangled_mcp/_tangled/_issues.py
··· 51 51 ) is not None and name == repo_name: 52 52 repo_at_uri = record.uri 53 53 # get repo's subscribed labels 54 - if ( 55 - subscribed_labels := getattr(record.value, "labels", None) 56 - ) is not None: 54 + if (subscribed_labels := getattr(record.value, "labels", None)) is not None: 57 55 repo_labels = subscribed_labels 58 56 break 59 57 ··· 161 159 for record in records.records: 162 160 if (name := getattr(record.value, "name", None)) and name == repo_name: 163 161 repo_at_uri = record.uri 164 - if ( 165 - subscribed_labels := getattr(record.value, "labels", None) 166 - ) is not None: 162 + if (subscribed_labels := getattr(record.value, "labels", None)) is not None: 167 163 repo_labels = subscribed_labels 168 164 break 169 165