Skip to main content

git_paw/mcp/tools/
git.rs

1//! Git-context tools: `get_branches`, `get_recent_commits`, `get_diff`.
2//!
3//! Thin wrappers over read-only git invocations against the resolved
4//! repository root. These always work (the repo always exists), so there is no
5//! degradation path.
6
7use rmcp::handler::server::wrapper::{Json, Parameters};
8use rmcp::{schemars, tool, tool_router};
9use serde::{Deserialize, Serialize};
10
11use crate::mcp::query;
12use crate::mcp::query::git::Diff;
13use crate::mcp::server::GitPawMcpServer;
14
15/// Parameters for [`GitPawMcpServer::get_recent_commits`].
16#[derive(Debug, Deserialize, schemars::JsonSchema)]
17pub struct GetRecentCommitsParams {
18    /// Branch to read commits from.
19    pub branch: String,
20    /// Maximum number of commits to return (default 20).
21    #[serde(default = "default_limit")]
22    #[schemars(description = "Maximum number of commits to return (default 20)")]
23    pub limit: usize,
24}
25
26fn default_limit() -> usize {
27    20
28}
29
30/// Parameters for [`GitPawMcpServer::get_diff`].
31#[derive(Debug, Deserialize, schemars::JsonSchema)]
32pub struct GetDiffParams {
33    /// Branch to diff.
34    pub branch: String,
35    /// Base to diff against (defaults to the repository's default branch).
36    #[serde(default)]
37    pub base: Option<String>,
38}
39
40/// Response for `get_branches`.
41#[derive(Serialize, schemars::JsonSchema)]
42pub struct BranchesResponse {
43    /// Local branches.
44    pub branches: Vec<query::git::Branch>,
45}
46
47/// Response for `get_recent_commits`.
48#[derive(Serialize, schemars::JsonSchema)]
49pub struct CommitsResponse {
50    /// Commits, newest first.
51    pub commits: Vec<query::git::Commit>,
52}
53
54#[tool_router(router = git_router, vis = "pub(crate)")]
55impl GitPawMcpServer {
56    /// `get_branches` — local branches with head SHA + flags.
57    #[tool(
58        description = "List local branches, each with name, head commit SHA, whether it is the \
59                       currently checked-out branch, and whether it is checked out in a linked \
60                       (git-paw managed) worktree."
61    )]
62    pub(crate) fn get_branches(&self) -> Json<BranchesResponse> {
63        Json(BranchesResponse {
64            branches: query::git::branches(&self.ctx.root),
65        })
66    }
67
68    /// `get_recent_commits` — last N commits on a branch.
69    #[tool(
70        description = "Return up to `limit` (default 20) recent commits on `branch`, newest first, \
71                       each with sha, author, ISO timestamp, and subject."
72    )]
73    pub(crate) fn get_recent_commits(
74        &self,
75        Parameters(p): Parameters<GetRecentCommitsParams>,
76    ) -> Json<CommitsResponse> {
77        Json(CommitsResponse {
78            commits: query::git::recent_commits(&self.ctx.root, &p.branch, p.limit),
79        })
80    }
81
82    /// `get_diff` — diff of a branch against its base.
83    #[tool(
84        description = "Return the diff of `branch` against `base` (default: the repo's default \
85                       branch) with a files-changed / insertions / deletions summary."
86    )]
87    pub(crate) fn get_diff(&self, Parameters(p): Parameters<GetDiffParams>) -> Json<Diff> {
88        Json(query::git::diff(
89            &self.ctx.root,
90            &p.branch,
91            p.base.as_deref(),
92        ))
93    }
94}