Implement MCP automation and approvals.
This commit is contained in:
12
crates/bds-mcp/Cargo.toml
Normal file
12
crates/bds-mcp/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "bds-mcp"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bds-core = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
45
crates/bds-mcp/src/main.rs
Normal file
45
crates/bds-mcp/src/main.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::io::{BufRead as _, Write as _};
|
||||
use std::process::ExitCode;
|
||||
|
||||
use bds_core::engine::mcp::{McpContext, handle_rpc};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
fn main() -> ExitCode {
|
||||
if let Err(error) = run_stdio() {
|
||||
eprintln!("MCP server error: {error}");
|
||||
ExitCode::from(1)
|
||||
} else {
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
fn run_stdio() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let database_path = bds_core::util::application_database_path();
|
||||
if let Some(parent) = database_path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
let context = McpContext::new(database_path);
|
||||
context.prepare()?;
|
||||
let stdin = std::io::stdin();
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
for line in stdin.lock().lines() {
|
||||
let line = line?;
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let response = match serde_json::from_str::<Value>(&line) {
|
||||
Ok(request) => handle_rpc(&context, &request),
|
||||
Err(_) => Some(json!({
|
||||
"jsonrpc":"2.0",
|
||||
"id":null,
|
||||
"error":{"code":-32700,"message":"Parse error"}
|
||||
})),
|
||||
};
|
||||
if let Some(response) = response {
|
||||
serde_json::to_writer(&mut stdout, &response)?;
|
||||
stdout.write_all(b"\n")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
38
crates/bds-mcp/tests/process.rs
Normal file
38
crates/bds-mcp/tests/process.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::io::Write as _;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
#[test]
|
||||
fn stdio_process_initializes_lists_capabilities_and_reports_parse_errors() {
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let mut child = Command::new(env!("CARGO_BIN_EXE_bds-mcp"))
|
||||
.env("HOME", home.path())
|
||||
.env("XDG_DATA_HOME", home.path().join("data"))
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let stdin = child.stdin.as_mut().unwrap();
|
||||
writeln!(stdin, r#"{{"jsonrpc":"2.0","id":1,"method":"initialize","params":{{"protocolVersion":"2025-06-18"}}}}"#).unwrap();
|
||||
writeln!(stdin, r#"{{"jsonrpc":"2.0","id":2,"method":"tools/list"}}"#).unwrap();
|
||||
writeln!(stdin, "not-json").unwrap();
|
||||
drop(child.stdin.take());
|
||||
let output = child.wait_with_output().unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let lines = String::from_utf8(output.stdout).unwrap();
|
||||
let responses = lines
|
||||
.lines()
|
||||
.map(|line| serde_json::from_str::<serde_json::Value>(line).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(responses.len(), 3);
|
||||
assert_eq!(responses[0]["result"]["protocolVersion"], "2025-06-18");
|
||||
assert_eq!(
|
||||
responses[1]["result"]["tools"].as_array().unwrap().len(),
|
||||
12
|
||||
);
|
||||
assert_eq!(responses[2]["error"]["code"], -32700);
|
||||
}
|
||||
Reference in New Issue
Block a user