MCP server for tangled
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: improve context efficiency of result types

- rename `issue_id` → `id` for consistency across all types
- exclude redundant `repo` field from serialized output
(still available internally for URL computation)
- add test verifying repo exclusion from serialization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

zzstoatzz 3dcee1bc d610b0aa

+25 -17
+3 -3
src/tangled_mcp/server.py
··· 104 104 # create_issue doesn't need knot (uses atproto putRecord, not XRPC) 105 105 response = _tangled.create_issue(repo_id, title, body, labels) 106 106 107 - return CreateIssueResult(repo=repo, issue_id=response["issueId"]) 107 + return CreateIssueResult(repo=repo, id=response["issueId"]) 108 108 109 109 110 110 @tangled_mcp.tool ··· 143 143 # update_issue doesn't need knot (uses atproto putRecord, not XRPC) 144 144 _tangled.update_issue(repo_id, issue_id, title, body, labels) 145 145 146 - return UpdateIssueResult(repo=repo, issue_id=issue_id) 146 + return UpdateIssueResult(repo=repo, id=issue_id) 147 147 148 148 149 149 @tangled_mcp.tool ··· 172 172 # delete_issue doesn't need knot (uses atproto deleteRecord, not XRPC) 173 173 _tangled.delete_issue(repo_id, issue_id) 174 174 175 - return DeleteIssueResult(issue_id=issue_id) 175 + return DeleteIssueResult(id=issue_id) 176 176 177 177 178 178 @tangled_mcp.tool
+8 -8
src/tangled_mcp/types/_issues.py
··· 18 18 19 19 uri: str 20 20 cid: str 21 - issue_id: int = Field(alias="issueId") 21 + id: int = Field(alias="issueId") 22 22 title: str 23 23 body: str | None = None 24 24 created_at: str = Field(alias="createdAt") ··· 28 28 class CreateIssueResult(BaseModel): 29 29 """result of creating an issue""" 30 30 31 - repo: RepoIdentifier 32 - issue_id: int 31 + repo: RepoIdentifier = Field(exclude=True) 32 + id: int 33 33 34 34 @computed_field 35 35 @property 36 36 def url(self) -> str: 37 37 """construct clickable tangled.org URL""" 38 - return _tangled_issue_url(self.repo, self.issue_id) 38 + return _tangled_issue_url(self.repo, self.id) 39 39 40 40 41 41 class UpdateIssueResult(BaseModel): 42 42 """result of updating an issue""" 43 43 44 - repo: RepoIdentifier 45 - issue_id: int 44 + repo: RepoIdentifier = Field(exclude=True) 45 + id: int 46 46 47 47 @computed_field 48 48 @property 49 49 def url(self) -> str: 50 50 """construct clickable tangled.org URL""" 51 - return _tangled_issue_url(self.repo, self.issue_id) 51 + return _tangled_issue_url(self.repo, self.id) 52 52 53 53 54 54 class DeleteIssueResult(BaseModel): 55 55 """result of deleting an issue""" 56 56 57 - issue_id: int 57 + id: int 58 58 59 59 60 60 class ListIssuesResult(BaseModel):
+14 -6
tests/test_types.py
··· 15 15 16 16 def test_strips_at_prefix(self): 17 17 """@ prefix is stripped during validation""" 18 - result = CreateIssueResult(repo="@owner/repo", issue_id=1) 18 + result = CreateIssueResult(repo="@owner/repo", id=1) 19 19 assert result.repo == "owner/repo" 20 20 21 21 def test_accepts_without_at_prefix(self): 22 22 """repo identifier without @ works""" 23 - result = CreateIssueResult(repo="owner/repo", issue_id=1) 23 + result = CreateIssueResult(repo="owner/repo", id=1) 24 24 assert result.repo == "owner/repo" 25 25 26 26 def test_rejects_invalid_format(self): 27 27 """repo identifier without slash is rejected""" 28 28 with pytest.raises(ValidationError, match="invalid repo format"): 29 - CreateIssueResult(repo="invalid", issue_id=1) 29 + CreateIssueResult(repo="invalid", id=1) 30 30 31 31 32 32 class TestIssueResultURLs: ··· 34 34 35 35 def test_create_issue_url(self): 36 36 """create result generates correct tangled.org URL""" 37 - result = CreateIssueResult(repo="owner/repo", issue_id=42) 37 + result = CreateIssueResult(repo="owner/repo", id=42) 38 38 assert result.url == "https://tangled.org/@owner/repo/issues/42" 39 39 40 40 def test_update_issue_url(self): 41 41 """update result generates correct tangled.org URL""" 42 - result = UpdateIssueResult(repo="owner/repo", issue_id=42) 42 + result = UpdateIssueResult(repo="owner/repo", id=42) 43 43 assert result.url == "https://tangled.org/@owner/repo/issues/42" 44 44 45 45 def test_url_handles_at_prefix_input(self): 46 46 """URL is correct even when input has @ prefix""" 47 - result = CreateIssueResult(repo="@owner/repo", issue_id=42) 47 + result = CreateIssueResult(repo="@owner/repo", id=42) 48 48 assert result.url == "https://tangled.org/@owner/repo/issues/42" 49 + 50 + def test_repo_excluded_from_serialization(self): 51 + """repo field is excluded from JSON output""" 52 + result = CreateIssueResult(repo="owner/repo", id=42) 53 + data = result.model_dump() 54 + assert "repo" not in data 55 + assert data["id"] == 42 56 + assert "url" in data 49 57 50 58 51 59 class TestListBranchesFromAPIResponse: