use std::collections::HashMap;
use std::sync::{Arc, Mutex as StdMutex};
use crate::server::McpServer;
use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;
use pyo3::types::{PyModule, PyString};
#[pyclass(name = "McpServer", module = "newt_agent._newt_agent.mcp")]
pub struct PyMcpServer {
handlers: Arc<StdMutex<HashMap<String, Py<PyAny>>>>,
}
#[pymethods]
impl PyMcpServer {
#[new]
fn new() -> Self {
Self {
handlers: Arc::new(StdMutex::new(HashMap::new())),
}
}
fn register(&self, method: &str, callback: Py<PyAny>) -> PyResult<()> {
let mut guard = self
.handlers
.lock()
.map_err(|e| PyRuntimeError::new_err(format!("lock poisoned: {e}")))?;
guard.insert(method.to_string(), callback);
Ok(())
}
fn handle(&self, py: Python<'_>, method: &str, params_json: &str) -> PyResult<Option<String>> {
let callback = {
let guard = self
.handlers
.lock()
.map_err(|e| PyRuntimeError::new_err(format!("lock poisoned: {e}")))?;
match guard.get(method) {
Some(cb) => cb.clone_ref(py),
None => return Ok(None),
}
};
let args = (PyString::new(py, params_json),);
let result = callback.call1(py, args)?;
let json_mod = py.import("json")?;
let dumps = json_mod.getattr("dumps")?;
let encoded = dumps.call1((result,))?;
Ok(Some(encoded.extract::<String>()?))
}
fn registered_methods(&self) -> PyResult<Vec<String>> {
let guard = self
.handlers
.lock()
.map_err(|e| PyRuntimeError::new_err(format!("lock poisoned: {e}")))?;
Ok(guard.keys().cloned().collect())
}
fn __repr__(&self) -> PyResult<String> {
let guard = self
.handlers
.lock()
.map_err(|e| PyRuntimeError::new_err(format!("lock poisoned: {e}")))?;
Ok(format!("McpServer(methods={})", guard.len()))
}
}
impl Default for PyMcpServer {
fn default() -> Self {
Self::new()
}
}
#[pyfunction]
fn default_tool_definitions(py: Python<'_>) -> PyResult<String> {
let _ = py;
let _ = McpServer::new; Ok(r#"[
{"name":"code_read","description":"Read a file's contents"},
{"name":"code_edit","description":"Apply a unified diff patch to a file"},
{"name":"code_search","description":"Search files for a regex pattern"},
{"name":"goal_run","description":"Run a tier-routed inference turn"}
]"#
.to_string())
}
pub fn register(py: Python<'_>, parent: &Bound<'_, PyModule>) -> PyResult<()> {
let m = PyModule::new(py, "mcp")?;
m.add_class::<PyMcpServer>()?;
m.add_function(wrap_pyfunction!(default_tool_definitions, &m)?)?;
parent.add_submodule(&m)?;
Ok(())
}