Skip to content

Commit 14502a3

Browse files
authored
Merge pull request #24 from syncable-dev/feature/langgraph-stdio-sse
Feature/langgraph stdio sse
2 parents 8a8e333 + 8fcd83a commit 14502a3

7 files changed

Lines changed: 324 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import asyncio
2+
import os
3+
from dotenv import load_dotenv
4+
from langchain_mcp_adapters.client import MultiServerMCPClient
5+
from langgraph.prebuilt import create_react_agent
6+
import openai
7+
8+
# remember to start the sse server first
9+
load_dotenv()
10+
openai.api_key = os.getenv("OPENAI_API_KEY")
11+
12+
async def main():
13+
# Connect to HTTP-based MCP server using SSE
14+
client = MultiServerMCPClient({
15+
"demo": {
16+
"url": "http://127.0.0.1:8000/sse",
17+
"transport": "streamable_http"
18+
}
19+
})
20+
21+
# Get available tools from server
22+
tools = await client.get_tools()
23+
print(f"Fetched {len(tools)} tools from MCP server.")
24+
for tool in tools:
25+
print(f"- {tool.name}")
26+
27+
# Create an agent using GPT-4o and those tools
28+
agent = create_react_agent("openai:gpt-4o", tools)
29+
30+
# Prompts to trigger each tool
31+
prompts = [
32+
("about_info", "Call the 'about_info' tool."),
33+
("analysis_scan", "Call the 'analysis_scan' tool on path '../' with display 'matrix'."),
34+
("security_scan", "Call the 'security_scan' tool on path '../'."),
35+
("dependency_scan", "Call the 'dependency_scan' tool on path '../'.")
36+
]
37+
38+
for tool_name, prompt in prompts:
39+
print(f"\nInvoking agent to call '{tool_name}'...")
40+
response = await agent.ainvoke({
41+
"messages": [
42+
{"role": "user", "content": prompt}
43+
]
44+
})
45+
print(f"Result for '{tool_name}':")
46+
print(response)
47+
48+
asyncio.run(main())
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# src/langgraph_stdio_demo.py
2+
3+
import asyncio
4+
import os
5+
from dotenv import load_dotenv
6+
7+
from langchain_mcp_adapters.client import MultiServerMCPClient
8+
from langgraph.prebuilt import create_react_agent
9+
import openai
10+
11+
load_dotenv()
12+
openai.api_key = os.getenv("OPENAI_API_KEY")
13+
14+
async def main():
15+
client = MultiServerMCPClient({
16+
"syncable_cli": {
17+
# Adjust this path if needed—just needs to point
18+
# at your compiled mcp-stdio binary.
19+
"command": "../rust-mcp-server-syncable-cli/target/release/mcp-stdio",
20+
"args": [], # no extra args
21+
"transport": "stdio", # stdio transport
22+
}
23+
})
24+
25+
tools = await client.get_tools()
26+
print(f"Fetched {len(tools)} tools:")
27+
for t in tools:
28+
print(f" • {t.name}")
29+
30+
agent = create_react_agent("openai:gpt-4o", tools)
31+
32+
tests = [
33+
("about_info", "Call the 'about_info' tool."),
34+
("analysis_scan", "Call 'analysis_scan' on path '../' with display 'matrix'."),
35+
("security_scan", "Call 'security_scan' on path '../'."),
36+
("dependency_scan","Call 'dependency_scan' on path '../'."),
37+
]
38+
39+
for name, prompt in tests:
40+
print(f"\n--- {name}{prompt}")
41+
resp = await agent.ainvoke({
42+
"messages": [{"role": "user", "content": prompt}]
43+
})
44+
print(resp)
45+
46+
if __name__ == "__main__":
47+
asyncio.run(main())
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import asyncio
2+
import json
3+
from mcp.client.session import ClientSession
4+
from mcp.client.sse import sse_client
5+
from .utils import render_utility_result
6+
7+
# to start the server, run:
8+
# cargo run --release --bin mcp-sse
9+
10+
async def main():
11+
# The URL where the Rust SSE server is listening.
12+
server_url = "http://127.0.0.1:8000/sse"
13+
14+
async with sse_client(server_url) as (read, write):
15+
async with ClientSession(read, write) as session:
16+
await session.initialize()
17+
18+
# List available tools
19+
tools = await session.list_tools()
20+
print("Tools:")
21+
render_utility_result(tools)
22+
23+
# Call the 'about info' tool
24+
about_info_result = await session.call_tool("about_info", {})
25+
print("About info result:")
26+
render_utility_result(about_info_result)
27+
28+
code_analyze_result = await session.call_tool("analysis_scan", {"path": "../", "display": "matrix"})
29+
print("Code analysis result:")
30+
render_utility_result(code_analyze_result)
31+
32+
# Call the 'security scan' tool
33+
security_scan_result = await session.call_tool("security_scan", {"path": "../"})
34+
print("Security scan result:")
35+
render_utility_result(security_scan_result)
36+
37+
# Call the 'dependency scan' tool
38+
dependency_scan_result = await session.call_tool("dependency_scan", {"path": "../"})
39+
print("Dependency scan result:")
40+
render_utility_result(dependency_scan_result)
41+
42+
43+
if __name__ == "__main__":
44+
asyncio.run(main())
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import asyncio
2+
import json
3+
from mcp.client.session import ClientSession
4+
from mcp.client.stdio import StdioServerParameters, stdio_client
5+
from .utils import render_utility_result
6+
7+
# to start the server, run:
8+
# from cpr-rust-server folder cargo run --release
9+
10+
async def main():
11+
async with stdio_client(
12+
StdioServerParameters(command="mcp-stdio")
13+
) as (read, write):
14+
async with ClientSession(read, write) as session:
15+
await session.initialize()
16+
17+
# List available tools
18+
tools = await session.list_tools()
19+
print("Tools:")
20+
render_utility_result(tools)
21+
22+
# Call the 'about info' tool
23+
about_info_result = await session.call_tool("about_info", {})
24+
print("About info result:")
25+
render_utility_result(about_info_result)
26+
27+
code_analyze_result = await session.call_tool("analysis_scan", {"path": "../", "display": "matrix"})
28+
print("Code analysis result:")
29+
render_utility_result(code_analyze_result)
30+
31+
# Call the 'security scan' tool
32+
security_scan_result = await session.call_tool("security_scan", {"path": "../"})
33+
print("Security scan result:")
34+
render_utility_result(security_scan_result)
35+
36+
# Call the 'dependency scan' tool
37+
dependency_scan_result = await session.call_tool("dependency_scan", {"path": "../"})
38+
print("Dependency scan result:")
39+
render_utility_result(dependency_scan_result)
40+
41+
42+
asyncio.run(main())

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ serde_json = "1.0"
3535
tokio = { version = "1", features = ["full"] }
3636
env_logger = "0.11"
3737
syncable-cli = "0.11.1"
38+
axum = { version = "0.8.4", features = ["json"] }
39+
futures = "0.3.31"
40+
bytes = "1.10.1"
3841

3942
[[bin]]
4043
name = "mcp-stdio"

rust-mcp-server-syncable-cli/README.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,116 @@ We are planning to add **first-class support for the [LangGraph](https://github.
262262

263263
---
264264

265+
## Python LangGraph Agent Integration
266+
267+
You can use the [LangGraph](https://github.com/langchain-ai/langgraph) framework to connect to this MCP server in both stdio and SSE modes. Below are example Python scripts for each mode.
268+
269+
### Using Stdio Mode
270+
271+
This example launches the `mcp-stdio` binary and connects via stdio:
272+
273+
```python
274+
import asyncio
275+
import os
276+
from dotenv import load_dotenv
277+
from langchain_mcp_adapters.client import MultiServerMCPClient
278+
from langgraph.prebuilt import create_react_agent
279+
import openai
280+
281+
load_dotenv()
282+
openai.api_key = os.getenv("OPENAI_API_KEY")
283+
284+
async def main():
285+
client = MultiServerMCPClient({
286+
"syncable_cli": {
287+
"command": "mcp-stdio", # Ensure mcp-stdio is in your PATH
288+
"args": [],
289+
"transport": "stdio",
290+
}
291+
})
292+
293+
tools = await client.get_tools()
294+
print(f"Fetched {len(tools)} tools:")
295+
for t in tools:
296+
print(f"{t.name}")
297+
298+
agent = create_react_agent("openai:gpt-4o", tools)
299+
300+
tests = [
301+
("about_info", "Call the 'about_info' tool."),
302+
("analysis_scan", "Call 'analysis_scan' on path '../' with display 'matrix'."),
303+
("security_scan", "Call 'security_scan' on path '../'."),
304+
("dependency_scan","Call 'dependency_scan' on path '../'."),
305+
]
306+
307+
for name, prompt in tests:
308+
print(f"\n--- {name}{prompt}")
309+
resp = await agent.ainvoke({
310+
"messages": [{"role": "user", "content": prompt}]
311+
})
312+
print(resp)
313+
314+
if __name__ == "__main__":
315+
asyncio.run(main())
316+
```
317+
318+
### Using SSE Mode
319+
320+
This example connects to a running `mcp-sse` server via HTTP/SSE:
321+
322+
```python
323+
import asyncio
324+
import os
325+
from dotenv import load_dotenv
326+
from langchain_mcp_adapters.client import MultiServerMCPClient
327+
from langgraph.prebuilt import create_react_agent
328+
import openai
329+
330+
load_dotenv()
331+
openai.api_key = os.getenv("OPENAI_API_KEY")
332+
333+
async def main():
334+
client = MultiServerMCPClient({
335+
"demo": {
336+
"url": "http://127.0.0.1:8000/sse",
337+
"transport": "sse",
338+
}
339+
})
340+
341+
tools = await client.get_tools()
342+
print(f"Fetched {len(tools)} tools from MCP server:")
343+
for t in tools:
344+
print(f"{t.name}")
345+
346+
agent = create_react_agent("openai:gpt-4o", tools)
347+
348+
prompts = [
349+
("about_info", "Call the 'about_info' tool."),
350+
("analysis_scan", "Call the 'analysis_scan' tool on path '../' with display 'matrix'."),
351+
("security_scan", "Call the 'security_scan' tool on path '../'."),
352+
("dependency_scan","Call the 'dependency_scan' tool on path '../'."),
353+
]
354+
355+
for name, prompt in prompts:
356+
print(f"\n--- Invoking {name} ---")
357+
resp = await agent.ainvoke({
358+
"messages": [{"role": "user", "content": prompt}]
359+
})
360+
print(resp)
361+
362+
if __name__ == "__main__":
363+
asyncio.run(main())
364+
```
365+
366+
**Requirements:**
367+
- Install the required Python packages:
368+
```bash
369+
pip install langgraph openai python-dotenv langchain_mcp_adapters
370+
```
371+
- For stdio mode, ensure `mcp-stdio` is in your `PATH`.
372+
- For SSE mode, start the server with `mcp-sse` and connect to the correct URL.
373+
374+
---
265375

266376
## License
267377

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// src/schema/schema_utils.rs
2+
3+
use std::fmt;
4+
5+
/// An error returned when calling a tool fails.
6+
pub struct CallToolError(Box<dyn std::error::Error + Send + Sync + 'static>);
7+
8+
impl CallToolError {
9+
/// Wrap any `Error + Send + Sync + 'static` into a `CallToolError`.
10+
pub fn new<E>(e: E) -> Self
11+
where
12+
E: std::error::Error + Send + Sync + 'static,
13+
{
14+
CallToolError(Box::new(e))
15+
}
16+
}
17+
18+
impl fmt::Debug for CallToolError {
19+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20+
fmt::Debug::fmt(&*self.0, f)
21+
}
22+
}
23+
24+
impl fmt::Display for CallToolError {
25+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26+
fmt::Display::fmt(&*self.0, f)
27+
}
28+
}
29+
30+
impl std::error::Error for CallToolError {}

0 commit comments

Comments
 (0)