feat(transport): support streamable http server (#152)
* feat(transport): support streamable http server * refactor(transport): add common module for shared transport utilities * perf(tracing): add more log for streamable http session * fix(test): fix port conflict in test with js * fix(transport): fix id generating and session management 1. cancel when deleting 2. use `wrapping` and `saturating` correctly * chore(naming): rename sse server to streamable http server
This commit is contained in:
parent
0847c17fa1
commit
57e14c56e8
3 changed files with 1247 additions and 20 deletions
|
|
@ -1,12 +1,13 @@
|
|||
use rmcp::{
|
||||
ServiceExt,
|
||||
transport::{SseServer, TokioChildProcess},
|
||||
transport::{SseServer, TokioChildProcess, streamable_http_server::axum::StreamableHttpServer},
|
||||
};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
mod common;
|
||||
use common::calculator::Calculator;
|
||||
|
||||
const BIND_ADDRESS: &str = "127.0.0.1:8000";
|
||||
const SSE_BIND_ADDRESS: &str = "127.0.0.1:8000";
|
||||
const STREAMABLE_HTTP_BIND_ADDRESS: &str = "127.0.0.1:8001";
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_with_js_client() -> anyhow::Result<()> {
|
||||
|
|
@ -24,7 +25,7 @@ async fn test_with_js_client() -> anyhow::Result<()> {
|
|||
.wait()
|
||||
.await?;
|
||||
|
||||
let ct = SseServer::serve(BIND_ADDRESS.parse()?)
|
||||
let ct = SseServer::serve(SSE_BIND_ADDRESS.parse()?)
|
||||
.await?
|
||||
.with_service(Calculator::default);
|
||||
|
||||
|
|
@ -66,3 +67,33 @@ async fn test_with_js_server() -> anyhow::Result<()> {
|
|||
client.cancel().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_with_js_streamable_http_client() -> anyhow::Result<()> {
|
||||
let _ = tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "debug".to_string().into()),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.try_init();
|
||||
tokio::process::Command::new("npm")
|
||||
.arg("install")
|
||||
.current_dir("tests/test_with_js")
|
||||
.spawn()?
|
||||
.wait()
|
||||
.await?;
|
||||
|
||||
let ct = StreamableHttpServer::serve(STREAMABLE_HTTP_BIND_ADDRESS.parse()?)
|
||||
.await?
|
||||
.with_service(Calculator::default);
|
||||
|
||||
let exit_status = tokio::process::Command::new("node")
|
||||
.arg("tests/test_with_js/streamable_client.js")
|
||||
.spawn()?
|
||||
.wait()
|
||||
.await?;
|
||||
assert!(exit_status.success());
|
||||
ct.cancel();
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.7.0"
|
||||
"@modelcontextprotocol/sdk": "^1.10",
|
||||
"eventsource-parser": "^3.0.1"
|
||||
},
|
||||
"type": "module",
|
||||
"name": "test_with_ts",
|
||||
|
|
|
|||
28
crates/rmcp/tests/test_with_js/streamable_client.js
Normal file
28
crates/rmcp/tests/test_with_js/streamable_client.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
|
||||
const transport = new StreamableHTTPClientTransport(new URL(`http://127.0.0.1:8001/`));
|
||||
|
||||
const client = new Client(
|
||||
{
|
||||
name: "example-client",
|
||||
version: "1.0.0"
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
prompts: {},
|
||||
resources: {},
|
||||
tools: {}
|
||||
}
|
||||
}
|
||||
);
|
||||
await client.connect(transport);
|
||||
const tools = await client.listTools();
|
||||
console.log(tools);
|
||||
const resources = await client.listResources();
|
||||
console.log(resources);
|
||||
const templates = await client.listResourceTemplates();
|
||||
console.log(templates);
|
||||
const prompts = await client.listPrompts();
|
||||
console.log(prompts);
|
||||
await client.close();
|
||||
Loading…
Reference in a new issue