From b145bada38673ebda857805e195db6170a8e4074 Mon Sep 17 00:00:00 2001 From: Taehoon Moon Date: Thu, 12 Jun 2025 13:34:17 +0900 Subject: [PATCH 1/3] Add MCP server with note tools --- Cargo.lock | 235 ++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 4 +- README.md | 10 ++ mcp/Cargo.toml | 18 ++++ mcp/src/handler.rs | 47 +++++++++ mcp/src/main.rs | 73 ++++++++++++++ mcp/src/tools.rs | 104 ++++++++++++++++++++ 7 files changed, 474 insertions(+), 17 deletions(-) create mode 100644 mcp/Cargo.toml create mode 100644 mcp/src/handler.rs create mode 100644 mcp/src/main.rs create mode 100644 mcp/src/tools.rs diff --git a/Cargo.lock b/Cargo.lock index 4eb54aa6..89b69f30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,7 +198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", - "axum-core", + "axum-core 0.4.5", "bytes", "futures-util", "http", @@ -207,7 +207,41 @@ dependencies = [ "hyper", "hyper-util", "itoa", - "matchit", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +dependencies = [ + "axum-core 0.5.2", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit 0.8.4", "memchr", "mime", "percent-encoding", @@ -246,6 +280,42 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-server" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495c05f60d6df0093e8fb6e74aa5846a0ad06abaf96d76166283720bf740f8ab" +dependencies = [ + "bytes", + "fs-err", + "http", + "http-body", + "hyper", + "hyper-util", + "tokio", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -1041,6 +1111,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d7be93788013f265201256d58f04936a8079ad5dc898743aa20525f503b683" +dependencies = [ + "autocfg", + "tokio", +] + [[package]] name = "funty" version = "2.0.0" @@ -1212,7 +1292,7 @@ version = "0.6.3" dependencies = [ "async-recursion", "async-trait", - "axum", + "axum 0.7.9", "gluesql", "gluesql-mongo-storage", "reqwest", @@ -1786,9 +1866,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libm" @@ -1860,6 +1940,26 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "mcp" +version = "0.6.3" +dependencies = [ + "async-trait", + "clap", + "color-eyre", + "glues-core", + "rust-mcp-sdk", + "serde", + "serde_json", + "tokio", +] + [[package]] name = "md-5" version = "0.10.6" @@ -2581,11 +2681,13 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls 0.26.2", + "tokio-util", "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots 0.26.11", "windows-registry", @@ -2657,6 +2759,71 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rust-mcp-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512fe25018087e27b9abbe1806fc83e4741c7b8d0dc5f7fa8c4f0f3b389ef658" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.102", +] + +[[package]] +name = "rust-mcp-schema" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9966340f5104a8d22b6c2db8901f8626a0f737820a385db3ffbb29b1f6ae0f" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "rust-mcp-sdk" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edbc126903df8ba1cb99a56b18b95875b34a24f81d05575ebb5eddc4c3293322" +dependencies = [ + "async-trait", + "axum 0.8.4", + "axum-server", + "futures", + "hyper", + "rust-mcp-macros", + "rust-mcp-schema", + "rust-mcp-transport", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tracing", + "uuid", +] + +[[package]] +name = "rust-mcp-transport" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1195b57bfe37111460b44e1624a6552c7378749195709d104d28fc5b1ef59f9c" +dependencies = [ + "async-trait", + "bytes", + "futures", + "reqwest", + "rust-mcp-schema", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "rust_decimal" version = "1.35.0" @@ -2890,9 +3057,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -2908,9 +3075,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -2919,12 +3086,13 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "indexmap 2.2.6", "itoa", + "memchr", "ryu", "serde", ] @@ -3424,14 +3592,15 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2 0.5.7", @@ -3441,9 +3610,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -3480,6 +3649,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -3556,9 +3736,21 @@ checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.102", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -3881,6 +4073,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.77" diff --git a/Cargo.toml b/Cargo.toml index afcf56f4..beb8c6e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "2" -members = ["tui", "core"] -default-members = ["tui", "core"] +members = ["tui", "core", "mcp"] +default-members = ["tui", "core", "mcp"] [workspace.package] authors = ["Taehoon Moon "] diff --git a/README.md b/README.md index f78e4e4d..e7be5609 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,16 @@ Here is our plan for Glues and the features we aim to implement. Below is a list * **More Vim Keybindings:** Integrate Vim keybindings for users who prefer Vim-like shortcuts. * **Additional Storage Backends:** Support more storage options like Redis and object storage for greater flexibility. +## MCP Server + +An experimental MCP server is bundled in the workspace for integrations that rely on HTTP requests. You can run it with: + +```bash +cargo run -p mcp -- --host 127.0.0.1 --port 8080 +``` + +The server currently keeps data in memory and will reset once stopped. + ## License This project is licensed under the Apache License, Version 2.0 - see the [LICENSE](https://github.com/gluesql/glues/blob/main/LICENSE) file for details. diff --git a/mcp/Cargo.toml b/mcp/Cargo.toml new file mode 100644 index 00000000..db6daca0 --- /dev/null +++ b/mcp/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mcp" +authors.workspace = true +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Minimal MCP server for Glues" + +[dependencies] +glues-core.workspace = true +tokio = { version = "1.41.0", features = ["macros", "rt-multi-thread", "signal"] } +serde_json = "1.0" +clap = { version = "4.5.4", features = ["derive"] } +color-eyre = "0.6.3" +serde = { version = "1.0", features = ["derive"] } +rust-mcp-sdk = { version = "0.4.2", default-features = false, features = ["server", "macros", "hyper-server", "2025_03_26"] } +async-trait = "0.1" diff --git a/mcp/src/handler.rs b/mcp/src/handler.rs new file mode 100644 index 00000000..5b5263d5 --- /dev/null +++ b/mcp/src/handler.rs @@ -0,0 +1,47 @@ +use async_trait::async_trait; +use glues_core::{db::Db, proxy::ProxyServer}; +use rust_mcp_sdk::{ + McpServer, + mcp_server::ServerHandler, + schema::{ + CallToolRequest, CallToolResult, ListToolsRequest, ListToolsResult, RpcError, + schema_utils::CallToolError, + }, +}; +use std::sync::Arc; +use tokio::sync::Mutex; + +use crate::tools::NoteTools; + +pub struct GluesHandler { + pub server: Arc>>, +} + +#[async_trait] +impl ServerHandler for GluesHandler { + async fn handle_list_tools_request( + &self, + _request: ListToolsRequest, + _runtime: &dyn McpServer, + ) -> std::result::Result { + Ok(ListToolsResult { + meta: None, + next_cursor: None, + tools: NoteTools::tools(), + }) + } + + async fn handle_call_tool_request( + &self, + request: CallToolRequest, + _runtime: &dyn McpServer, + ) -> std::result::Result { + let tool = NoteTools::try_from(request.params).map_err(CallToolError::new)?; + let server = self.server.clone(); + match tool { + NoteTools::ListNotes(tool) => tool.call_tool(server).await, + NoteTools::GetNote(tool) => tool.call_tool(server).await, + NoteTools::AddNote(tool) => tool.call_tool(server).await, + } + } +} diff --git a/mcp/src/main.rs b/mcp/src/main.rs new file mode 100644 index 00000000..9084fa91 --- /dev/null +++ b/mcp/src/main.rs @@ -0,0 +1,73 @@ +mod handler; +mod tools; + +use std::sync::{Arc, mpsc::channel}; +use std::time::Duration; + +use clap::Parser; +use tokio::sync::Mutex; + +use glues_core::{db::Db, proxy::ProxyServer}; +use rust_mcp_sdk::{ + error::SdkResult, + mcp_server::{HyperServerOptions, hyper_server}, + schema::{ + Implementation, InitializeResult, LATEST_PROTOCOL_VERSION, ServerCapabilities, + ServerCapabilitiesTools, + }, +}; + +use handler::GluesHandler; + +#[derive(Parser)] +#[command(author, version, about)] +struct Cli { + #[arg(long, default_value = "127.0.0.1")] + host: String, + #[arg(long, default_value_t = 8080)] + port: u16, +} + +#[tokio::main] +async fn main() -> SdkResult<()> { + color_eyre::install().ok(); + let cli = Cli::parse(); + + let (tx, _rx) = channel(); + let db = Db::memory(tx) + .await + .map_err(|e| rust_mcp_sdk::error::McpSdkError::AnyError(Box::new(e)))?; + let server = Arc::new(Mutex::new(ProxyServer::new(db))); + + let server_details = InitializeResult { + server_info: Implementation { + name: "Glues MCP".to_string(), + version: env!("CARGO_PKG_VERSION").to_string(), + }, + capabilities: ServerCapabilities { + tools: Some(ServerCapabilitiesTools { list_changed: None }), + ..Default::default() + }, + meta: None, + instructions: Some("use tools to manage notes".to_string()), + protocol_version: LATEST_PROTOCOL_VERSION.to_string(), + }; + + let handler = GluesHandler { + server: server.clone(), + }; + + let mcp_server = hyper_server::create_server( + server_details, + handler, + HyperServerOptions { + host: cli.host, + port: cli.port, + ping_interval: Duration::from_secs(5), + ..Default::default() + }, + ); + + mcp_server.start().await?; + Ok(()) +} diff --git a/mcp/src/tools.rs b/mcp/src/tools.rs new file mode 100644 index 00000000..82fb1a03 --- /dev/null +++ b/mcp/src/tools.rs @@ -0,0 +1,104 @@ +use glues_core::{db::Db, proxy::ProxyServer}; +use rust_mcp_sdk::{ + macros::{JsonSchema, mcp_tool}, + schema::{CallToolResult, schema_utils::CallToolError}, +}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tokio::sync::Mutex; + +#[mcp_tool(name = "list_notes", description = "List notes in a directory")] +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct ListNotes { + pub directory_id: String, +} + +impl ListNotes { + pub async fn call_tool( + &self, + server: Arc>>, + ) -> Result { + let dir = self.directory_id.clone(); + let result = tokio::task::spawn_blocking(move || { + let handle = tokio::runtime::Handle::current(); + handle.block_on(async { + let mut srv = server.blocking_lock(); + srv.db.fetch_notes(dir).await + }) + }) + .await + .unwrap(); + match result { + Ok(notes) => Ok(CallToolResult::text_content( + serde_json::to_string(¬es).unwrap(), + None, + )), + Err(e) => Err(CallToolError::new(e)), + } + } +} + +#[mcp_tool(name = "get_note", description = "Fetch note content")] +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct GetNote { + pub note_id: String, +} + +impl GetNote { + pub async fn call_tool( + &self, + server: Arc>>, + ) -> Result { + let note_id = self.note_id.clone(); + let result = tokio::task::spawn_blocking(move || { + let handle = tokio::runtime::Handle::current(); + handle.block_on(async { + let mut srv = server.blocking_lock(); + srv.db.fetch_note_content(note_id).await + }) + }) + .await + .unwrap(); + match result { + Ok(content) => Ok(CallToolResult::text_content(content, None)), + Err(e) => Err(CallToolError::new(e)), + } + } +} + +#[mcp_tool(name = "add_note", description = "Create a new note")] +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct AddNote { + pub directory_id: String, + pub name: String, +} + +impl AddNote { + pub async fn call_tool( + &self, + server: Arc>>, + ) -> Result { + let dir = self.directory_id.clone(); + let name = self.name.clone(); + let result = tokio::task::spawn_blocking(move || { + let handle = tokio::runtime::Handle::current(); + handle.block_on(async { + let mut srv = server.blocking_lock(); + srv.db.add_note(dir, name).await + }) + }) + .await + .unwrap(); + match result { + Ok(note) => Ok(CallToolResult::text_content( + serde_json::to_string(¬e).unwrap(), + None, + )), + Err(e) => Err(CallToolError::new(e)), + } + } +} + +use rust_mcp_sdk::tool_box; + +tool_box!(NoteTools, [ListNotes, GetNote, AddNote]); From 5792bc7e3de135d92e2496b1bc299285433e21f8 Mon Sep 17 00:00:00 2001 From: Taehoon Moon Date: Thu, 12 Jun 2025 14:50:34 +0900 Subject: [PATCH 2/3] Add root ID tool and tests --- README.md | 7 +++++ mcp/src/handler.rs | 1 + mcp/src/tests.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++++ mcp/src/tools.rs | 21 +++++++++++-- 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 mcp/src/tests.rs diff --git a/README.md b/README.md index e7be5609..05444381 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,13 @@ cargo run -p mcp -- --host 127.0.0.1 --port 8080 The server currently keeps data in memory and will reset once stopped. +Available MCP tools: + +- `root_id` – get the root directory identifier +- `list_notes` – list notes in a directory +- `get_note` – fetch note content +- `add_note` – create a new note + ## License This project is licensed under the Apache License, Version 2.0 - see the [LICENSE](https://github.com/gluesql/glues/blob/main/LICENSE) file for details. diff --git a/mcp/src/handler.rs b/mcp/src/handler.rs index 5b5263d5..345c6464 100644 --- a/mcp/src/handler.rs +++ b/mcp/src/handler.rs @@ -39,6 +39,7 @@ impl ServerHandler for GluesHandler { let tool = NoteTools::try_from(request.params).map_err(CallToolError::new)?; let server = self.server.clone(); match tool { + NoteTools::RootId(tool) => tool.call_tool(server).await, NoteTools::ListNotes(tool) => tool.call_tool(server).await, NoteTools::GetNote(tool) => tool.call_tool(server).await, NoteTools::AddNote(tool) => tool.call_tool(server).await, diff --git a/mcp/src/tests.rs b/mcp/src/tests.rs new file mode 100644 index 00000000..0642390a --- /dev/null +++ b/mcp/src/tests.rs @@ -0,0 +1,73 @@ +use glues_core::{data::Note, db::Db, proxy::ProxyServer}; +use crate::tools::{AddNote, GetNote, ListNotes, RootId}; +use std::sync::{Arc, mpsc::channel}; +use tokio::sync::Mutex; + +#[tokio::test] +async fn root_and_note_tools_flow() { + let (tx, _rx) = channel(); + let db = Db::memory(tx).await.unwrap(); + let server = Arc::new(Mutex::new(ProxyServer::new(db))); + + // get root id + let root_result = RootId.call_tool(server.clone()).await.unwrap(); + let root_id = root_result + .content + .first() + .unwrap() + .as_text_content() + .unwrap() + .text + .clone(); + + // add note + let add = AddNote { + directory_id: root_id.clone(), + name: "Test".into(), + }; + let add_result = add.call_tool(server.clone()).await.unwrap(); + let note: Note = serde_json::from_str( + &add_result + .content + .first() + .unwrap() + .as_text_content() + .unwrap() + .text, + ) + .unwrap(); + assert_eq!(note.name, "Test"); + + // list notes + let list = ListNotes { + directory_id: root_id.clone(), + }; + let list_result = list.call_tool(server.clone()).await.unwrap(); + let notes: Vec = serde_json::from_str( + &list_result + .content + .first() + .unwrap() + .as_text_content() + .unwrap() + .text, + ) + .unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].id, note.id); + + // get note content + let get = GetNote { + note_id: note.id.clone(), + }; + let get_result = get.call_tool(server.clone()).await.unwrap(); + let content = get_result + .content + .first() + .unwrap() + .as_text_content() + .unwrap() + .text + .clone(); + assert!(content.is_empty()); +} diff --git a/mcp/src/tools.rs b/mcp/src/tools.rs index 82fb1a03..4093616d 100644 --- a/mcp/src/tools.rs +++ b/mcp/src/tools.rs @@ -1,4 +1,7 @@ -use glues_core::{db::Db, proxy::ProxyServer}; +use glues_core::{ + db::{CoreBackend, Db}, + proxy::ProxyServer, +}; use rust_mcp_sdk::{ macros::{JsonSchema, mcp_tool}, schema::{CallToolResult, schema_utils::CallToolError}, @@ -7,6 +10,20 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::Mutex; +#[mcp_tool(name = "root_id", description = "Get root directory id")] +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct RootId {} + +impl RootId { + pub async fn call_tool( + &self, + server: Arc>>, + ) -> Result { + let srv = server.lock().await; + Ok(CallToolResult::text_content(srv.db.root_id(), None)) + } +} + #[mcp_tool(name = "list_notes", description = "List notes in a directory")] #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct ListNotes { @@ -101,4 +118,4 @@ impl AddNote { use rust_mcp_sdk::tool_box; -tool_box!(NoteTools, [ListNotes, GetNote, AddNote]); +tool_box!(NoteTools, [RootId, ListNotes, GetNote, AddNote]); From 8ad700109cf6dd2b20fa74b021c3563016179536 Mon Sep 17 00:00:00 2001 From: Taehoon Moon Date: Thu, 12 Jun 2025 14:50:39 +0900 Subject: [PATCH 3/3] Move MCP integration test --- mcp/src/lib.rs | 3 +++ mcp/src/tools.rs | 21 ++++++++++++--------- mcp/{src/tests.rs => tests/note_tools.rs} | 6 +++--- 3 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 mcp/src/lib.rs rename mcp/{src/tests.rs => tests/note_tools.rs} (90%) diff --git a/mcp/src/lib.rs b/mcp/src/lib.rs new file mode 100644 index 00000000..dced7d2e --- /dev/null +++ b/mcp/src/lib.rs @@ -0,0 +1,3 @@ +pub mod handler; +pub mod tools; + diff --git a/mcp/src/tools.rs b/mcp/src/tools.rs index 4093616d..4ee031c3 100644 --- a/mcp/src/tools.rs +++ b/mcp/src/tools.rs @@ -36,10 +36,11 @@ impl ListNotes { server: Arc>>, ) -> Result { let dir = self.directory_id.clone(); + let server = server.clone(); let result = tokio::task::spawn_blocking(move || { - let handle = tokio::runtime::Handle::current(); - handle.block_on(async { - let mut srv = server.blocking_lock(); + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + let mut srv = server.lock().await; srv.db.fetch_notes(dir).await }) }) @@ -67,10 +68,11 @@ impl GetNote { server: Arc>>, ) -> Result { let note_id = self.note_id.clone(); + let server = server.clone(); let result = tokio::task::spawn_blocking(move || { - let handle = tokio::runtime::Handle::current(); - handle.block_on(async { - let mut srv = server.blocking_lock(); + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + let mut srv = server.lock().await; srv.db.fetch_note_content(note_id).await }) }) @@ -97,10 +99,11 @@ impl AddNote { ) -> Result { let dir = self.directory_id.clone(); let name = self.name.clone(); + let server = server.clone(); let result = tokio::task::spawn_blocking(move || { - let handle = tokio::runtime::Handle::current(); - handle.block_on(async { - let mut srv = server.blocking_lock(); + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + let mut srv = server.lock().await; srv.db.add_note(dir, name).await }) }) diff --git a/mcp/src/tests.rs b/mcp/tests/note_tools.rs similarity index 90% rename from mcp/src/tests.rs rename to mcp/tests/note_tools.rs index 0642390a..dc62367a 100644 --- a/mcp/src/tests.rs +++ b/mcp/tests/note_tools.rs @@ -1,16 +1,16 @@ use glues_core::{data::Note, db::Db, proxy::ProxyServer}; -use crate::tools::{AddNote, GetNote, ListNotes, RootId}; +use mcp::tools::{AddNote, GetNote, ListNotes, RootId}; use std::sync::{Arc, mpsc::channel}; use tokio::sync::Mutex; -#[tokio::test] +#[tokio::test(flavor = "current_thread")] async fn root_and_note_tools_flow() { let (tx, _rx) = channel(); let db = Db::memory(tx).await.unwrap(); let server = Arc::new(Mutex::new(ProxyServer::new(db))); // get root id - let root_result = RootId.call_tool(server.clone()).await.unwrap(); + let root_result = RootId {}.call_tool(server.clone()).await.unwrap(); let root_id = root_result .content .first()