Compare commits

...
6 changed files with 26 additions and 8 deletions

No files matched your search

Binary file not shown.
Binary file not shown.
+20 -7
View File
@@ -389,8 +389,9 @@ async def tea_label(repo: Repo, number: Number, add: Labels | None = None,
@mcp.tool(description="Read a pull request: its branches, whether it can merge, the checks on its head, and its diff "
"(first 200 KB).", annotations=READ)
async def tea_pull_get(repo: Repo, number: Number, diff: bool = True) -> dict[str, Any]:
"(first `diff_kb` KB, default 40; up to 200).", annotations=READ)
async def tea_pull_get(repo: Repo, number: Number, diff: bool = True,
diff_kb: Annotated[int, Field(ge=1, le=200)] = 40) -> dict[str, Any]:
rp = repo_path(repo)
p = await gitea("GET", f"{rp}/pulls/{number}")
head, base = p.get("head") or {}, p.get("base") or {}
@@ -402,7 +403,7 @@ async def tea_pull_get(repo: Repo, number: Number, diff: bool = True) -> dict[st
"statuses": [{k: s.get(k) for k in ("context", "status", "description", "target_url")}
for s in checks.get("statuses") or []]}}
if diff:
out["diff"], out["diff_truncated"] = await gitea_text(f"{rp}/pulls/{number}.diff", cap=MAX_BYTES)
out["diff"], out["diff_truncated"] = await gitea_text(f"{rp}/pulls/{number}.diff", cap=diff_kb * 1000)
return out
@@ -537,9 +538,12 @@ async def tea_tree(repo: Repo, path: Annotated[str, Field(pattern=r"^[^\x00-\x1f
"entries": [{k: e.get(k) for k in ("name", "type", "size")} for e in rows[:500]]}
@mcp.tool(description="Read a text file from a repository (up to 200 KB). Larger and binary files report only their "
"size and sha.", annotations=READ)
async def tea_file_get(repo: Repo, path: FilePath, ref: Ref = "") -> dict[str, Any]:
@mcp.tool(description="Read a text file from a repository, a window of lines at a time: `start` is the first line "
"(1-based) and `lines` how many (default 400). The reply says the file's total line count and "
"whether more follows, so read further only when you need it.", annotations=READ)
async def tea_file_get(repo: Repo, path: FilePath, ref: Ref = "",
start: Annotated[int, Field(ge=1)] = 1,
lines: Annotated[int, Field(ge=1, le=2000)] = 400) -> dict[str, Any]:
p = file_path(path)
x = await gitea("GET", contents(repo_path(repo), p), params={"ref": ref} if ref else None)
if not isinstance(x, dict) or x.get("type") != "file":
@@ -548,9 +552,18 @@ async def tea_file_get(repo: Repo, path: FilePath, ref: Ref = "") -> dict[str, A
if (x.get("size") or 0) > MAX_BYTES:
return {**meta, "too_large": True}
try:
return {**meta, "content": base64.b64decode(x.get("content") or "").decode("utf-8")}
text = base64.b64decode(x.get("content") or "").decode("utf-8")
except (binascii.Error, ValueError):
return {**meta, "binary": True}
return {**meta, **line_window(text, start, lines)}
def line_window(text: str, start: int, lines: int) -> dict[str, Any]:
"""A window of a text: chat clients resend every tool result each turn, so whole files get expensive fast."""
rows = text.splitlines(keepends=True)
part = rows[start - 1:start - 1 + lines]
end = start - 1 + len(part)
return {"content": "".join(part), "start": start, "end": end, "total_lines": len(rows), "more": end < len(rows)}
@mcp.tool(description="Create or update one text file on a branch, as a commit by you. Branch protection applies.",
Binary file not shown.
Binary file not shown.
+6 -1
View File
@@ -272,7 +272,7 @@ class TeaMCP(unittest.IsolatedAsyncioTestCase):
pr = await self.call("tea_pull_get", repo="o/r", number=2)
self.assertEqual((pr["head"], pr["base"], pr["head_sha"], pr["is_pull"]), ("feature", "trunk", "abcdef123456", True))
self.assertEqual((pr["checks"]["state"], pr["checks"]["statuses"][0]["context"]), ("success", "ci / test"))
self.assertEqual((len(pr["diff"]), pr["diff_truncated"]), (server.MAX_BYTES, True))
self.assertEqual((len(pr["diff"]), pr["diff_truncated"]), (40_000, True))
self.assertNotIn("diff", await self.call("tea_pull_get", repo="o/r", number=2, diff=False))
review = await self.call("tea_pull_review", repo="o/r", number=2, event="APPROVED")
self.assertEqual(review["state"], "APPROVED")
@@ -295,6 +295,11 @@ class TeaMCP(unittest.IsolatedAsyncioTestCase):
self.assertIn(b"/api/v1/repos/o/r/contents/docs/a%20b.md", self.gitea.raw_paths)
self.gitea.files["big.txt"] = b"x" * (server.MAX_BYTES + 1)
self.assertTrue((await self.call("tea_file_get", repo="o/r", path="big.txt"))["too_large"])
self.assertEqual({k: text[k] for k in ("start", "end", "total_lines", "more")},
{"start": 1, "end": 1, "total_lines": 1, "more": False})
from tea_mcp.server import line_window
w = line_window("".join(f"{i}\n" for i in range(1, 11)), 4, 3)
self.assertEqual((w["content"], w["end"], w["more"]), ("4\n5\n6\n", 6, True))
self.assertIn("not a file", await self.refused("tea_file_get", repo="o/r", path="docs"))
self.assertIn("plain repository path", await self.refused("tea_file_get", repo="o/r", path="docs/../x"))