pub mod jsonrpc;
pub mod tap;
use std::sync::Arc;
use chrono::{DateTime, Utc};
use serde_json::Value;
use systemprompt_mcp::models::{ExecutionStatus, ToolExecutionRequest, ToolExecutionResult};
use systemprompt_mcp::repository::ToolUsageRepository;
use systemprompt_models::RequestContext;
pub(crate) use jsonrpc::parse_tool_call;
pub(crate) use tap::record;
use jsonrpc::{ToolCallInvocation, ToolCallOutcome};
#[derive(Debug)]
pub struct McpAudit {
repo: Arc<ToolUsageRepository>,
context: RequestContext,
server_name: String,
invocation: ToolCallInvocation,
started_at: DateTime<Utc>,
}
impl McpAudit {
pub fn new(
repo: Arc<ToolUsageRepository>,
context: RequestContext,
server_name: String,
invocation: ToolCallInvocation,
) -> Self {
Self {
repo,
context,
server_name,
invocation,
started_at: Utc::now(),
}
}
const fn request_id(&self) -> &Value {
&self.invocation.id
}
fn finalize(self, outcome: Option<ToolCallOutcome>) {
let (output, error_message) = match outcome {
Some(o) => (o.output, o.error_message),
None => (
None,
Some("external MCP tool call produced no parseable result".to_owned()),
),
};
let request = ToolExecutionRequest {
tool_name: self.invocation.tool_name,
server_name: self.server_name.clone(),
input: self.invocation.arguments,
started_at: self.started_at,
context: self.context,
request_method: Some("mcp".to_owned()),
request_source: Some(self.server_name),
ai_tool_call_id: None,
};
let result = ToolExecutionResult {
status: ExecutionStatus::from_error(error_message.is_some()).to_string(),
error_message,
output,
output_schema: None,
started_at: self.started_at,
completed_at: Utc::now(),
};
let repo = self.repo;
tokio::spawn(async move {
if let Err(e) = repo.log_execution_sync(&request, &result).await {
tracing::warn!(
tool = %request.tool_name,
server = %request.server_name,
error = %e,
"Failed to record external MCP tool execution"
);
}
});
}
}