use rmcp::handler::server::wrapper::{Json, Parameters};
use rmcp::{ErrorData, schemars, tool, tool_router};
use serde::{Deserialize, Serialize};
use crate::config::GovernanceConfig;
use crate::error::PawError;
use crate::mcp::query;
use crate::mcp::server::GitPawMcpServer;
#[allow(clippy::needless_pass_by_value)]
fn to_err(e: PawError) -> ErrorData {
ErrorData::internal_error(e.to_string(), None)
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct GetAdrParams {
pub query: String,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct CheckDodParams {
pub branch: String,
}
#[derive(Serialize, schemars::JsonSchema)]
pub struct DocResponse {
pub content: Option<String>,
}
#[derive(Serialize, schemars::JsonSchema)]
pub struct AdrsResponse {
pub adrs: Vec<query::governance::Adr>,
}
#[derive(Serialize, schemars::JsonSchema)]
pub struct AdrResponse {
pub adr: Option<query::governance::AdrDetail>,
}
#[derive(Serialize, schemars::JsonSchema)]
pub struct CheckDodResponse {
pub branch: String,
pub items: Option<Vec<query::governance::DodItem>>,
}
impl GitPawMcpServer {
fn governance(&self) -> Result<GovernanceConfig, ErrorData> {
query::governance::load(&self.ctx.root).map_err(to_err)
}
}
#[tool_router(router = governance_router, vis = "pub(crate)")]
impl GitPawMcpServer {
#[tool(
description = "List Architecture Decision Records under the configured [governance].adr \
directory. Each carries id, title, path, and status. Empty when unset."
)]
pub(crate) fn get_adrs(&self) -> Result<Json<AdrsResponse>, ErrorData> {
let gov = self.governance()?;
Ok(Json(AdrsResponse {
adrs: query::governance::adrs(&self.ctx.root, &gov),
}))
}
#[tool(
description = "Return a single ADR matching the query (over id/title/body) with its full \
Markdown content, or { \"adr\": null } when none matches."
)]
pub(crate) fn get_adr(
&self,
Parameters(p): Parameters<GetAdrParams>,
) -> Result<Json<AdrResponse>, ErrorData> {
let gov = self.governance()?;
Ok(Json(AdrResponse {
adr: query::governance::adr(&self.ctx.root, &gov, &p.query),
}))
}
#[tool(
description = "Return the configured [governance].test_strategy document content, or null \
when unset. Errors only if the configured file is unreadable."
)]
pub(crate) fn get_test_strategy(&self) -> Result<Json<DocResponse>, ErrorData> {
let gov = self.governance()?;
let content = query::governance::single_doc(&self.ctx.root, gov.test_strategy.as_deref())
.map_err(to_err)?;
Ok(Json(DocResponse { content }))
}
#[tool(
description = "Return the configured [governance].security checklist content, or null when \
unset. Errors only if the configured file is unreadable."
)]
pub(crate) fn get_security_checklist(&self) -> Result<Json<DocResponse>, ErrorData> {
let gov = self.governance()?;
let content = query::governance::single_doc(&self.ctx.root, gov.security.as_deref())
.map_err(to_err)?;
Ok(Json(DocResponse { content }))
}
#[tool(
description = "Return the configured [governance].dod (Definition of Done) document \
content, or null when unset. Errors only if the file is unreadable."
)]
pub(crate) fn get_dod(&self) -> Result<Json<DocResponse>, ErrorData> {
let gov = self.governance()?;
let content =
query::governance::single_doc(&self.ctx.root, gov.dod.as_deref()).map_err(to_err)?;
Ok(Json(DocResponse { content }))
}
#[tool(
description = "Parse the configured Definition-of-Done checklist and return each item with \
its completion state as written in the file (no LLM judgment). \
{ \"items\": null } when no DoD is configured."
)]
pub(crate) fn check_dod(
&self,
Parameters(p): Parameters<CheckDodParams>,
) -> Result<Json<CheckDodResponse>, ErrorData> {
let gov = self.governance()?;
let items = query::governance::check_dod(&self.ctx.root, &gov).map_err(to_err)?;
Ok(Json(CheckDodResponse {
branch: p.branch,
items,
}))
}
#[tool(
description = "Return the configured [governance].constitution document content (e.g. Spec \
Kit's constitution.md), or null when unset. Errors only if unreadable."
)]
pub(crate) fn get_constitution(&self) -> Result<Json<DocResponse>, ErrorData> {
let gov = self.governance()?;
let content = query::governance::single_doc(&self.ctx.root, gov.constitution.as_deref())
.map_err(to_err)?;
Ok(Json(DocResponse { content }))
}
}