fix: index_create updates tracked project for search_text

index_create and index_update now update the MCP server's tracked
project path so subsequent calls like search_text use the same
project. search_text also gains an optional 'project' parameter
to explicitly override the default. Closes #9.
This commit is contained in:
endergebnis committed 2026-09-19 15:23:07 +02:00
1 parent 4b55a41ece
commit 2cd801558d
1 file changed
+31 -16
+31 -16
View File
@@ -1,7 +1,7 @@
//! Minimal MCP server: JSON-RPC over stdio, dispatching to the same ops as the CLI.
use std::io::{BufRead, Write};
use std::path::Path;
use std::path::{Path, PathBuf};
use anyhow::Result;
use serde_json::{Value, json};
@@ -10,6 +10,7 @@ const PROTOCOL_VERSION: &str = "2024-11-05";
pub fn serve(project: &Path) -> Result<()> {
eprintln!("rustytokens MCP server listening on stdio");
let mut project = project.to_path_buf();
let stdin = std::io::stdin();
let stdout = std::io::stdout();
for line in stdin.lock().lines() {
@@ -39,7 +40,7 @@ pub fn serve(project: &Path) -> Result<()> {
continue;
}
};
if let Some(resp) = handle(&msg, project) {
if let Some(resp) = handle(&msg, &mut project) {
let mut out = stdout.lock();
serde_json::to_writer(&mut out, &resp)?;
out.write_all(b"\n")?;
@@ -49,7 +50,7 @@ pub fn serve(project: &Path) -> Result<()> {
Ok(())
}
fn handle(msg: &Value, project: &Path) -> Option<Value> {
fn handle(msg: &Value, project: &mut PathBuf) -> Option<Value> {
let id = msg.get("id").cloned();
// Notifications have no id -> no response.
@@ -101,7 +102,7 @@ fn handle(msg: &Value, project: &Path) -> Option<Value> {
}
}
fn call_tool(name: &str, args: &Value, project: &Path) -> Value {
fn call_tool(name: &str, args: &Value, project: &mut PathBuf) -> Value {
let result: Result<Value> = dispatch(name, args, project);
match result {
Ok(v) => json!({
@@ -114,7 +115,7 @@ fn call_tool(name: &str, args: &Value, project: &Path) -> Value {
}
}
fn dispatch(name: &str, args: &Value, project: &Path) -> Result<Value> {
fn dispatch(name: &str, args: &Value, project: &mut PathBuf) -> Result<Value> {
use crate::ops;
fn req_str<'a>(args: &'a Value, key: &str, tool: &str) -> Result<&'a str> {
@@ -128,8 +129,16 @@ fn dispatch(name: &str, args: &Value, project: &Path) -> Result<Value> {
let excludes = crate::config::ExcludeSet::new(&exclude_list(args));
match name {
"index_create" => ops::index_create(Path::new(req_str(args, "path", name)?)),
"index_update" => ops::index_update(Path::new(req_str(args, "path", name)?)),
"index_create" => {
let path = PathBuf::from(req_str(args, "path", name)?);
project.clone_from(&path);
ops::index_create(&path)
}
"index_update" => {
let path = PathBuf::from(req_str(args, "path", name)?);
project.clone_from(&path);
ops::index_update(&path)
}
"index_file" => ops::index_file(project, req_str(args, "path", name)?),
"search_symbols" => ops::search_symbols(
project,
@@ -143,15 +152,20 @@ fn dispatch(name: &str, args: &Value, project: &Path) -> Result<Value> {
.unwrap_or(200),
&excludes,
),
"search_text" => ops::search_text(
project,
req_str(args, "query", name)?,
args.get("limit")
.and_then(|v| v.as_u64())
.map(|n| n as usize)
.unwrap_or(100),
&excludes,
),
"search_text" => {
let search_project = opt_str("project")
.map(PathBuf::from)
.unwrap_or_else(|| project.clone());
ops::search_text(
&search_project,
req_str(args, "query", name)?,
args.get("limit")
.and_then(|v| v.as_u64())
.map(|n| n as usize)
.unwrap_or(100),
&excludes,
)
}
"inspect_symbol" => {
if let Some(id) = args.get("id").and_then(|v| v.as_i64()) {
ops::inspect_symbol(project, &id.to_string())
@@ -272,6 +286,7 @@ fn tools() -> Value {
"type": "object",
"properties": {
"query": { "type": "string", "description": "Text or phrase to search for" },
"project": { "type": "string", "description": "Project directory to search (defaults to the last indexed project)" },
"limit": { "type": "integer", "description": "Maximum matching files (default 100)" },
"exclude": {
"type": "array",