Skip to content

Commit 7c27a03

Browse files
authored
Merge pull request #48 from syncable-dev/feature/langgraph-integration
Feature/langgraph integration
2 parents 6a7f2fd + e4b91ad commit 7c27a03

12 files changed

Lines changed: 158 additions & 66 deletions

File tree

mcp-python-server-client/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ dependencies = [
1111
"langgraph>=0.4.8",
1212
"mcp[cli]>=1.9.3",
1313
"python-dotenv>=1.1.0",
14+
"rich>=13.0.0",
1415
]

mcp-python-server-client/src/langgraph_stdio_demo.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
load_dotenv()
1515
openai.api_key = os.getenv("OPENAI_API_KEY")
1616

17+
1718
async def main():
1819
client = MultiServerMCPClient({
1920
"syncable_cli": {
2021
# Adjust this path if needed—just needs to point
2122
# at your compiled mcp-stdio binary.
22-
"command": "../rust-mcp-server-syncable-cli/target/release/mcp-stdio",
23+
#"command": "../rust-mcp-server-syncable-cli/target/release/mcp-stdio",
24+
"command": "mcp-stdio",
2325
"args": [], # no extra args
2426
"transport": "stdio", # stdio transport
2527
}

mcp-python-server-client/src/mcp_py_client_rust_server_stdio.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
async def main():
1111
async with stdio_client(
1212
StdioServerParameters(command="../rust-mcp-server-syncable-cli/target/release/mcp-stdio")
13+
#StdioServerParameters(command="mcp-stdio")
1314
) as (read, write):
1415
async with ClientSession(read, write) as session:
1516
await session.initialize()
@@ -24,7 +25,7 @@ async def main():
2425
print("About info result:")
2526
render_utility_result(about_info_result)
2627

27-
code_analyze_result = await session.call_tool("analysis_scan", {"path": "../", "display": "matrix"})
28+
code_analyze_result = await session.call_tool("analysis_scan", {"path": "../", "display": "summary"})
2829
print("Code analysis result:")
2930
render_utility_result(code_analyze_result)
3031

mcp-python-server-client/src/utils.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import json
22
from pprint import pprint
3+
from rich.console import Console
4+
from rich.json import JSON
5+
from rich.panel import Panel
6+
7+
console = Console()
38

49
def render_utility_result(result):
510
"""
@@ -18,12 +23,14 @@ def render_utility_result(result):
1823
try:
1924
# First, try to load as JSON. This handles tool outputs that
2025
# are JSON-encoded strings (e.g., a report string inside a JSON string).
21-
report_string = json.loads(text_content)
22-
print(report_string)
26+
report_data = json.loads(text_content)
27+
# Pretty print JSON with rich syntax highlighting and colors
28+
json_obj = JSON(json.dumps(report_data, ensure_ascii=False))
29+
console.print(Panel(json_obj, title="📋 Tool Response", border_style="blue"))
2330
except json.JSONDecodeError:
2431
# If JSON decoding fails, assume it's a raw, pre-formatted string
2532
# (like the output from the 'about_info' tool with ANSI codes).
26-
print(text_content)
33+
console.print(Panel(text_content, title="📄 Tool Response", border_style="green"))
2734
except (IndexError, AttributeError) as e:
2835
print(f"Error parsing result: {e}")
2936
print("Printing raw result instead:")

mcp-python-server-client/uv.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust-mcp-server-syncable-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ serde = { version = "1.0", features = ["derive"] }
3434
serde_json = "1.0"
3535
tokio = { version = "1", features = ["full"] }
3636
env_logger = "0.11"
37-
syncable-cli = "0.15.0"
37+
syncable-cli = "0.18.0"
3838
axum = { version = "0.8.4", features = ["json"] }
3939
futures = "0.3.31"
4040
bytes = "1.10.1"

rust-mcp-server-syncable-cli/src/handler.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ impl ServerHandler for MyServerHandler {
4040
// Match on the specific tool variant and execute its logic
4141
match tool_call {
4242
ServerTools::AboutInfoTool(tool) => tool.call_tool(),
43-
ServerTools::AnalysisScanTool(tool) => tool.call_tool(),
43+
ServerTools::AnalysisScanTool(tool) => tool.call_tool().await,
4444
ServerTools::SecurityScanTool(tool) => tool.call_tool(),
4545
ServerTools::DependencyScanTool(tool) => tool.call_tool().await,
4646
}
4747
}
48-
}
48+
}

rust-mcp-server-syncable-cli/src/tests/handler_tests.rs

Lines changed: 0 additions & 20 deletions
This file was deleted.

rust-mcp-server-syncable-cli/src/tests/mod.rs

Lines changed: 0 additions & 19 deletions
This file was deleted.

rust-mcp-server-syncable-cli/src/tools.rs

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub struct AnalysisScanTool {
7575
}
7676

7777
impl AnalysisScanTool {
78-
pub fn call_tool(&self) -> Result<CallToolResult, CallToolError> {
78+
pub async fn call_tool(&self) -> Result<CallToolResult, CallToolError> {
7979
let project_path_str = self.path.as_deref().unwrap_or(".");
8080
let display = self.display.clone().unwrap_or("matrix".to_string());
8181

@@ -86,29 +86,50 @@ impl AnalysisScanTool {
8686
_ => None,
8787
};
8888

89-
println!("🔍 Analyzing project: {}", project_path_str);
90-
println!("🔍 Display: {}", display);
89+
// Log to stderr so we don't interfere with MCP stdout JSON messages
90+
eprintln!("🔍 Analyzing project: {}", project_path_str);
91+
eprintln!("🔍 Display: {}", display);
92+
eprintln!("➡️ Calling syncable_cli::handle_analyze...");
93+
94+
let analysis_result = tokio::task::spawn_blocking({
95+
let project_path = Path::new(project_path_str).to_path_buf();
96+
move || {
97+
syncable_cli::handle_analyze(
98+
project_path,
99+
true,
100+
false,
101+
display_format,
102+
None,
103+
None,
104+
)
105+
}
106+
}).await;
91107

92-
let analysis_result = syncable_cli::handle_analyze(
93-
Path::new(project_path_str).to_path_buf(),
94-
false,
95-
false,
96-
display_format,
97-
None,
98-
None,
99-
);
108+
let analysis_result = match analysis_result {
109+
Ok(result) => result,
110+
Err(e) => return Err(CallToolError::new(AnalyzeToolError(format!("Task panicked: {}", e)))),
111+
};
100112
match analysis_result {
101-
Ok(analysis) => {
102-
let json_output = serde_json::to_string_pretty(&analysis).unwrap_or_else(|e| {
103-
format!(
104-
"{{\"error\": \"Failed to serialize analysis result: {}\"}}",
105-
e
106-
)
107-
});
108-
Ok(CallToolResult::text_content(vec![TextContent::new(json_output, None, None)]))
113+
Ok(analysis_json_str) => {
114+
eprintln!("✅ handle_analyze returned ({} bytes)", analysis_json_str.len());
115+
116+
// Validate JSON to ensure it's well-formed
117+
match serde_json::from_str::<serde_json::Value>(&analysis_json_str) {
118+
Ok(_) => {
119+
eprintln!("✅ JSON validation passed");
120+
eprintln!("📤 Sending full response ({} bytes)", analysis_json_str.len());
121+
Ok(CallToolResult::text_content(vec![TextContent::new(analysis_json_str, None, None)]))
122+
}
123+
Err(e) => {
124+
eprintln!("⚠️ JSON validation failed: {}", e);
125+
eprintln!("First 500 chars: {}", &analysis_json_str[..std::cmp::min(500, analysis_json_str.len())]);
126+
return Err(CallToolError::new(AnalyzeToolError(format!("Invalid JSON response: {}", e))));
127+
}
128+
}
109129
}
110130
Err(e) => {
111131
let error_message = format!("Failed to analyze project: {}", e);
132+
eprintln!("❌ handle_analyze error: {}", &error_message);
112133
Err(CallToolError::new(AnalyzeToolError(error_message)))
113134
}
114135
}

0 commit comments

Comments
 (0)