micro_mcp 0.1.4 → 0.1.5.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/Cargo.lock +10 -10
- data/ext/micro_mcp/AGENTS.md +31 -0
- data/ext/micro_mcp/Cargo.toml +2 -2
- data/ext/micro_mcp/src/lib.rs +1 -0
- data/ext/micro_mcp/src/server.rs +244 -12
- data/lib/micro_mcp/prompt_registry.rb +11 -0
- data/lib/micro_mcp/version.rb +1 -1
- data/lib/micro_mcp.rb +2 -1
- data/scripts/build_platforms.rb +44 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e552447ebf478c88bda0459eb3141fe6ba5420c95e2d77c40aa38b288ca15316
|
|
4
|
+
data.tar.gz: b595bbd5ea356a94e655db28187d654beff9eaefe17a2ecd89ec195bc12094da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 90b0849f3d2a230a9549469361b8cd28eaacedda35bc56dca909db7c56b8ef76bb524a95d438cbf44171e3802b1012b86430027b043310fa843b4cf7bc127f1a
|
|
7
|
+
data.tar.gz: 3ba54b142bdfc3001b4fade24baf4b6e446041bf9b5d7009cae52d1085c434ae60f3e4b20ce38ac72066d8864c0342787d5a3fac741ba9e3d1c159f5db57b40c
|
data/CHANGELOG.md
CHANGED
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
- Argument support for tools
|
|
7
7
|
- Exposed `client_supports_sampling` on runtime
|
|
8
8
|
- Exposed `create_message` on runtime
|
|
9
|
+
- Prompt registry for registering prompts with arguments and runtime access
|
|
9
10
|
|
|
10
11
|
### Changed
|
|
11
12
|
- Gem renamed from `mcp_lite` to `micro_mcp`
|
|
12
13
|
- Tool handling uses a dynamic registry
|
|
13
14
|
- Improved server error handling
|
|
15
|
+
- Updated dependency `rust-mcp-sdk` to 0.5.0
|
|
16
|
+
- Rooted stored Ruby `Proc` objects to avoid GC issues
|
|
14
17
|
|
|
15
18
|
## [0.1.0] - 2025-06-17
|
|
16
19
|
|
data/Cargo.lock
CHANGED
|
@@ -1092,9 +1092,9 @@ dependencies = [
|
|
|
1092
1092
|
|
|
1093
1093
|
[[package]]
|
|
1094
1094
|
name = "rust-mcp-macros"
|
|
1095
|
-
version = "0.
|
|
1095
|
+
version = "0.5.0"
|
|
1096
1096
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1097
|
-
checksum = "
|
|
1097
|
+
checksum = "5a495ee12964fa392044792db4e9da78c94756e186a5712e5d7e625c143419ff"
|
|
1098
1098
|
dependencies = [
|
|
1099
1099
|
"proc-macro2",
|
|
1100
1100
|
"quote",
|
|
@@ -1105,9 +1105,9 @@ dependencies = [
|
|
|
1105
1105
|
|
|
1106
1106
|
[[package]]
|
|
1107
1107
|
name = "rust-mcp-schema"
|
|
1108
|
-
version = "0.
|
|
1108
|
+
version = "0.7.1"
|
|
1109
1109
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1110
|
-
checksum = "
|
|
1110
|
+
checksum = "fec1ff3507a619b9945f60a94dac448541aef8c9803aa6192c30f4a932cb1499"
|
|
1111
1111
|
dependencies = [
|
|
1112
1112
|
"serde",
|
|
1113
1113
|
"serde_json",
|
|
@@ -1115,9 +1115,9 @@ dependencies = [
|
|
|
1115
1115
|
|
|
1116
1116
|
[[package]]
|
|
1117
1117
|
name = "rust-mcp-sdk"
|
|
1118
|
-
version = "0.
|
|
1118
|
+
version = "0.5.0"
|
|
1119
1119
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1120
|
-
checksum = "
|
|
1120
|
+
checksum = "1fb7d9a1fdcf02efee5cd21a568f31c819b7f2d767c42b37cb79aab302ac5b35"
|
|
1121
1121
|
dependencies = [
|
|
1122
1122
|
"async-trait",
|
|
1123
1123
|
"futures",
|
|
@@ -1133,9 +1133,9 @@ dependencies = [
|
|
|
1133
1133
|
|
|
1134
1134
|
[[package]]
|
|
1135
1135
|
name = "rust-mcp-transport"
|
|
1136
|
-
version = "0.
|
|
1136
|
+
version = "0.4.0"
|
|
1137
1137
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1138
|
-
checksum = "
|
|
1138
|
+
checksum = "d7f5468b8a00e800c3d708321216439ee1ad433592d725df93668bff75436348"
|
|
1139
1139
|
dependencies = [
|
|
1140
1140
|
"async-trait",
|
|
1141
1141
|
"bytes",
|
|
@@ -1294,9 +1294,9 @@ dependencies = [
|
|
|
1294
1294
|
|
|
1295
1295
|
[[package]]
|
|
1296
1296
|
name = "slab"
|
|
1297
|
-
version = "0.4.
|
|
1297
|
+
version = "0.4.11"
|
|
1298
1298
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1299
|
-
checksum = "
|
|
1299
|
+
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
|
1300
1300
|
|
|
1301
1301
|
[[package]]
|
|
1302
1302
|
name = "smallvec"
|
data/ext/micro_mcp/AGENTS.md
CHANGED
|
@@ -64,3 +64,34 @@ cargo metadata --format-version 1 --no-deps \
|
|
|
64
64
|
- Memory management between Rust and Ruby
|
|
65
65
|
- Proper error handling across language boundaries
|
|
66
66
|
- Thread safety considerations
|
|
67
|
+
|
|
68
|
+
## Ruby & Rust GC Rules
|
|
69
|
+
|
|
70
|
+
- Ruby's GC can't see values stored in ordinary Rust memory. Anything kept in a
|
|
71
|
+
`Vec`, `HashMap`, `OnceCell`, or static variable must be rooted or pinned.
|
|
72
|
+
- Use `value::BoxValue<T>` to pin individual Ruby `Value`s. `BoxValue::new` will
|
|
73
|
+
`rb_gc_register_address` the object so it stays valid.
|
|
74
|
+
- Expose Rust structs to Ruby with `#[magnus::wrap]`/`#[derive(TypedData)]` and
|
|
75
|
+
`Obj<T>`. `Obj<T>` is just a typed handle, so wrap it again in `BoxValue` (or
|
|
76
|
+
register it) if you keep it on the Rust side.
|
|
77
|
+
- For containers of many Ruby values, wrap the container itself in a `TypedData`
|
|
78
|
+
struct and implement a `mark` method to mark each entry. Root that single
|
|
79
|
+
wrapper instead of each entry.
|
|
80
|
+
- When using globals, prefer a wrapper similar to:
|
|
81
|
+
|
|
82
|
+
```rust
|
|
83
|
+
static REG: OnceCell<Obj<Registry>> = OnceCell::new();
|
|
84
|
+
let reg = REG.get_or_init(|| {
|
|
85
|
+
let obj = Obj::wrap(Registry::new());
|
|
86
|
+
magnus::gc::register_mark_object(obj); // root the wrapper
|
|
87
|
+
obj
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Testing Checklist
|
|
92
|
+
|
|
93
|
+
1. Call `GC.start` and `GC.compact` after each Ruby call in tests to stress the
|
|
94
|
+
garbage collector.
|
|
95
|
+
2. Run `cargo miri` (or valgrind) to detect use-after-free in unsafe code.
|
|
96
|
+
3. Remember `Obj<T>` is not `Send`/`Sync`; `BoxValue<T>` is. Convert or guard
|
|
97
|
+
before crossing threads.
|
data/ext/micro_mcp/Cargo.toml
CHANGED
|
@@ -15,8 +15,8 @@ magnus = { version = "0.7", features = ["rb-sys"] }
|
|
|
15
15
|
rb-sys = { version = "*", default-features = false, features = [
|
|
16
16
|
"stable-api-compiled-fallback",
|
|
17
17
|
] }
|
|
18
|
-
rust-mcp-sdk = { version = "0.
|
|
19
|
-
"server", "client", "
|
|
18
|
+
rust-mcp-sdk = { version = "0.5.0", default-features = false, features = [
|
|
19
|
+
"server", "client", "2025_06_18"
|
|
20
20
|
]}
|
|
21
21
|
serde = "1.0.219"
|
|
22
22
|
serde_json = "1.0.140"
|
data/ext/micro_mcp/src/lib.rs
CHANGED
|
@@ -9,6 +9,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
9
9
|
native.define_singleton_method("start_server", function!(server::start_server, 0))?;
|
|
10
10
|
native.define_singleton_method("shutdown_server", function!(server::shutdown_server, 0))?;
|
|
11
11
|
native.define_singleton_method("register_tool", function!(server::register_tool, 4))?;
|
|
12
|
+
native.define_singleton_method("register_prompt", function!(server::register_prompt, 4))?;
|
|
12
13
|
|
|
13
14
|
let parent = ruby.define_module("MicroMcp")?;
|
|
14
15
|
let class = parent.define_class("Runtime", ruby.class_object())?;
|
data/ext/micro_mcp/src/server.rs
CHANGED
|
@@ -2,9 +2,11 @@ use async_trait::async_trait;
|
|
|
2
2
|
use rust_mcp_sdk::{
|
|
3
3
|
mcp_server::{server_runtime, ServerHandler, ServerRuntime},
|
|
4
4
|
schema::{
|
|
5
|
-
schema_utils::CallToolError, CallToolRequest, CallToolResult,
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
schema_utils::CallToolError, CallToolRequest, CallToolResult, GetPromptRequest,
|
|
6
|
+
GetPromptResult, Implementation, InitializeResult, ListPromptsRequest, ListPromptsResult,
|
|
7
|
+
ListToolsRequest, ListToolsResult, Prompt, PromptArgument, PromptMessage, RpcError,
|
|
8
|
+
ServerCapabilities, ServerCapabilitiesPrompts, ServerCapabilitiesTools, TextContent, Tool,
|
|
9
|
+
ToolInputSchema, LATEST_PROTOCOL_VERSION,
|
|
8
10
|
},
|
|
9
11
|
McpServer, StdioTransport, TransportOptions,
|
|
10
12
|
};
|
|
@@ -14,7 +16,11 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
14
16
|
use std::sync::{Arc, Mutex, OnceLock};
|
|
15
17
|
use tokio::runtime::Runtime;
|
|
16
18
|
|
|
17
|
-
use magnus::{
|
|
19
|
+
use magnus::{
|
|
20
|
+
block::Proc,
|
|
21
|
+
value::{BoxValue, ReprValue},
|
|
22
|
+
Error, Ruby, Value,
|
|
23
|
+
};
|
|
18
24
|
use magnus::{typed_data::DataTypeFunctions, TypedData};
|
|
19
25
|
use std::cell::RefCell;
|
|
20
26
|
use std::rc::Rc;
|
|
@@ -30,8 +36,13 @@ fn shutdown_flag() -> &'static Arc<AtomicBool> {
|
|
|
30
36
|
|
|
31
37
|
type ToolHandler = RubyHandler;
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
struct RubyHandler(BoxValue<Proc>);
|
|
40
|
+
|
|
41
|
+
impl Clone for RubyHandler {
|
|
42
|
+
fn clone(&self) -> Self {
|
|
43
|
+
RubyHandler(BoxValue::new(*self.0.as_ref()))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
35
46
|
|
|
36
47
|
// SAFETY: We only call the stored Proc while holding the GVL.
|
|
37
48
|
unsafe impl Send for RubyHandler {}
|
|
@@ -45,10 +56,22 @@ struct ToolEntry {
|
|
|
45
56
|
|
|
46
57
|
static TOOLS: OnceLock<Mutex<HashMap<String, ToolEntry>>> = OnceLock::new();
|
|
47
58
|
|
|
59
|
+
#[derive(Clone)]
|
|
60
|
+
struct PromptEntry {
|
|
61
|
+
prompt: Prompt,
|
|
62
|
+
handler: RubyHandler,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static PROMPTS: OnceLock<Mutex<HashMap<String, PromptEntry>>> = OnceLock::new();
|
|
66
|
+
|
|
48
67
|
fn tools() -> &'static Mutex<HashMap<String, ToolEntry>> {
|
|
49
68
|
TOOLS.get_or_init(|| Mutex::new(HashMap::new()))
|
|
50
69
|
}
|
|
51
70
|
|
|
71
|
+
fn prompts() -> &'static Mutex<HashMap<String, PromptEntry>> {
|
|
72
|
+
PROMPTS.get_or_init(|| Mutex::new(HashMap::new()))
|
|
73
|
+
}
|
|
74
|
+
|
|
52
75
|
fn ruby_value_to_json_value(ruby: &Ruby, val: Value) -> Result<JsonValue, Error> {
|
|
53
76
|
let json_str: String = magnus::eval!(ruby, "require 'json'; JSON.generate(obj)", obj = val)?;
|
|
54
77
|
serde_json::from_str(&json_str)
|
|
@@ -96,6 +119,36 @@ fn parse_tool_input_schema(json: JsonValue) -> ToolInputSchema {
|
|
|
96
119
|
}
|
|
97
120
|
}
|
|
98
121
|
|
|
122
|
+
fn parse_prompt_arguments(json: JsonValue) -> Vec<PromptArgument> {
|
|
123
|
+
match json {
|
|
124
|
+
JsonValue::Array(items) => items
|
|
125
|
+
.into_iter()
|
|
126
|
+
.filter_map(|item| match item {
|
|
127
|
+
JsonValue::Object(map) => {
|
|
128
|
+
let name = map.get("name")?.as_str()?.to_string();
|
|
129
|
+
let description = map
|
|
130
|
+
.get("description")
|
|
131
|
+
.and_then(|v| v.as_str())
|
|
132
|
+
.map(|s| s.to_string());
|
|
133
|
+
let required = map.get("required").and_then(|v| v.as_bool());
|
|
134
|
+
let title = map
|
|
135
|
+
.get("title")
|
|
136
|
+
.and_then(|v| v.as_str())
|
|
137
|
+
.map(|s| s.to_string());
|
|
138
|
+
Some(PromptArgument {
|
|
139
|
+
description,
|
|
140
|
+
name,
|
|
141
|
+
required,
|
|
142
|
+
title,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
_ => None,
|
|
146
|
+
})
|
|
147
|
+
.collect(),
|
|
148
|
+
_ => Vec::new(),
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
99
152
|
#[derive(Clone, TypedData)]
|
|
100
153
|
#[magnus(class = "MicroMcp::Runtime", free_immediately, unsafe_generics)]
|
|
101
154
|
pub struct RubyMcpServer<'a> {
|
|
@@ -184,10 +237,13 @@ pub fn register_tool(
|
|
|
184
237
|
annotations: None,
|
|
185
238
|
description,
|
|
186
239
|
input_schema: schema,
|
|
240
|
+
meta: None,
|
|
187
241
|
name: name.clone(),
|
|
242
|
+
output_schema: None,
|
|
243
|
+
title: None,
|
|
188
244
|
};
|
|
189
245
|
|
|
190
|
-
let handler_fn = RubyHandler(handler);
|
|
246
|
+
let handler_fn = RubyHandler(BoxValue::new(handler));
|
|
191
247
|
|
|
192
248
|
let mut map = tools()
|
|
193
249
|
.lock()
|
|
@@ -202,10 +258,121 @@ pub fn register_tool(
|
|
|
202
258
|
Ok(())
|
|
203
259
|
}
|
|
204
260
|
|
|
261
|
+
pub fn register_prompt(
|
|
262
|
+
ruby: &Ruby,
|
|
263
|
+
name: String,
|
|
264
|
+
description: Option<String>,
|
|
265
|
+
arguments: Option<Value>,
|
|
266
|
+
handler: Proc,
|
|
267
|
+
) -> Result<(), Error> {
|
|
268
|
+
let parsed_args = match arguments {
|
|
269
|
+
Some(val) => {
|
|
270
|
+
let json = ruby_value_to_json_value(ruby, val)?;
|
|
271
|
+
parse_prompt_arguments(json)
|
|
272
|
+
}
|
|
273
|
+
None => Vec::new(),
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
let prompt = Prompt {
|
|
277
|
+
arguments: parsed_args,
|
|
278
|
+
description,
|
|
279
|
+
meta: None,
|
|
280
|
+
name: name.clone(),
|
|
281
|
+
title: None,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
let entry = PromptEntry {
|
|
285
|
+
prompt,
|
|
286
|
+
handler: RubyHandler(BoxValue::new(handler)),
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
let mut map = prompts()
|
|
290
|
+
.lock()
|
|
291
|
+
.map_err(|_| Error::new(ruby.exception_runtime_error(), "prompts mutex poisoned"))?;
|
|
292
|
+
map.insert(name, entry);
|
|
293
|
+
Ok(())
|
|
294
|
+
}
|
|
295
|
+
|
|
205
296
|
pub struct MyServerHandler;
|
|
206
297
|
|
|
207
298
|
#[async_trait]
|
|
208
299
|
impl ServerHandler for MyServerHandler {
|
|
300
|
+
async fn handle_list_prompts_request(
|
|
301
|
+
&self,
|
|
302
|
+
_request: ListPromptsRequest,
|
|
303
|
+
_runtime: &dyn McpServer,
|
|
304
|
+
) -> Result<ListPromptsResult, RpcError> {
|
|
305
|
+
let prompts = {
|
|
306
|
+
let map = prompts().lock().map_err(|_| {
|
|
307
|
+
RpcError::internal_error().with_message("prompts mutex poisoned".to_string())
|
|
308
|
+
})?;
|
|
309
|
+
map.values().map(|p| p.prompt.clone()).collect()
|
|
310
|
+
};
|
|
311
|
+
Ok(ListPromptsResult {
|
|
312
|
+
prompts,
|
|
313
|
+
meta: None,
|
|
314
|
+
next_cursor: None,
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async fn handle_get_prompt_request(
|
|
319
|
+
&self,
|
|
320
|
+
request: GetPromptRequest,
|
|
321
|
+
runtime: &dyn McpServer,
|
|
322
|
+
) -> Result<GetPromptResult, RpcError> {
|
|
323
|
+
let map = prompts().lock().map_err(|_| {
|
|
324
|
+
RpcError::internal_error().with_message("prompts mutex poisoned".to_string())
|
|
325
|
+
})?;
|
|
326
|
+
match map.get(&request.params.name) {
|
|
327
|
+
Some(entry) => {
|
|
328
|
+
let proc = *entry.handler.0.as_ref();
|
|
329
|
+
let wrapper = RubyMcpServer::new(runtime);
|
|
330
|
+
let args_value = if let Some(map) = &request.params.arguments {
|
|
331
|
+
let json = JsonValue::Object(
|
|
332
|
+
map.iter()
|
|
333
|
+
.map(|(k, v)| (k.clone(), JsonValue::String(v.clone())))
|
|
334
|
+
.collect(),
|
|
335
|
+
);
|
|
336
|
+
Some(
|
|
337
|
+
crate::utils::with_gvl(|| {
|
|
338
|
+
let ruby = Ruby::get().unwrap();
|
|
339
|
+
json_value_to_ruby_value(&ruby, &json)
|
|
340
|
+
})
|
|
341
|
+
.map_err(|e: Error| {
|
|
342
|
+
RpcError::internal_error().with_message(e.to_string())
|
|
343
|
+
})?,
|
|
344
|
+
)
|
|
345
|
+
} else {
|
|
346
|
+
None
|
|
347
|
+
};
|
|
348
|
+
let msg_value: Result<Value, Error> = crate::utils::with_gvl(|| {
|
|
349
|
+
let ruby = Ruby::get().unwrap();
|
|
350
|
+
let args = args_value.unwrap_or_else(|| ruby.qnil().as_value());
|
|
351
|
+
proc.call::<_, Value>((args, wrapper.clone()))
|
|
352
|
+
});
|
|
353
|
+
wrapper.invalidate();
|
|
354
|
+
let json = msg_value
|
|
355
|
+
.map_err(|e| RpcError::internal_error().with_message(e.to_string()))
|
|
356
|
+
.and_then(|val| {
|
|
357
|
+
crate::utils::with_gvl(|| {
|
|
358
|
+
let ruby = Ruby::get().unwrap();
|
|
359
|
+
ruby_value_to_json_value(&ruby, val)
|
|
360
|
+
})
|
|
361
|
+
.map_err(|e: Error| RpcError::internal_error().with_message(e.to_string()))
|
|
362
|
+
})?;
|
|
363
|
+
|
|
364
|
+
let msgs: Vec<PromptMessage> = serde_json::from_value(json)
|
|
365
|
+
.map_err(|e| RpcError::internal_error().with_message(e.to_string()))?;
|
|
366
|
+
|
|
367
|
+
Ok(GetPromptResult {
|
|
368
|
+
description: entry.prompt.description.clone(),
|
|
369
|
+
messages: msgs,
|
|
370
|
+
meta: None,
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
None => Err(RpcError::invalid_params().with_message("Unknown prompt".to_string())),
|
|
374
|
+
}
|
|
375
|
+
}
|
|
209
376
|
async fn handle_list_tools_request(
|
|
210
377
|
&self,
|
|
211
378
|
_request: ListToolsRequest,
|
|
@@ -234,7 +401,7 @@ impl ServerHandler for MyServerHandler {
|
|
|
234
401
|
.map_err(|_| CallToolError::new(std::io::Error::other("tools mutex poisoned")))?;
|
|
235
402
|
match map.get(request.tool_name()) {
|
|
236
403
|
Some(entry) => {
|
|
237
|
-
let proc = entry.handler.0;
|
|
404
|
+
let proc = *entry.handler.0.as_ref();
|
|
238
405
|
let wrapper = RubyMcpServer::new(runtime);
|
|
239
406
|
let args_value = if let Some(map) = &request.params.arguments {
|
|
240
407
|
let json = JsonValue::Object(map.clone());
|
|
@@ -257,7 +424,9 @@ impl ServerHandler for MyServerHandler {
|
|
|
257
424
|
});
|
|
258
425
|
wrapper.invalidate();
|
|
259
426
|
match text_result {
|
|
260
|
-
Ok(text) => Ok(CallToolResult::text_content(
|
|
427
|
+
Ok(text) => Ok(CallToolResult::text_content(vec![TextContent::new(
|
|
428
|
+
text, None, None,
|
|
429
|
+
)])),
|
|
261
430
|
Err(e) => Err(CallToolError::new(std::io::Error::other(e.to_string()))),
|
|
262
431
|
}
|
|
263
432
|
}
|
|
@@ -277,10 +446,12 @@ pub fn start_server() -> String {
|
|
|
277
446
|
let server_details = InitializeResult {
|
|
278
447
|
server_info: Implementation {
|
|
279
448
|
name: "Hello World MCP Server".to_string(),
|
|
449
|
+
title: None,
|
|
280
450
|
version: "0.1.0".to_string(),
|
|
281
451
|
},
|
|
282
452
|
capabilities: ServerCapabilities {
|
|
283
453
|
tools: Some(ServerCapabilitiesTools { list_changed: None }),
|
|
454
|
+
prompts: Some(ServerCapabilitiesPrompts { list_changed: None }),
|
|
284
455
|
..Default::default()
|
|
285
456
|
},
|
|
286
457
|
meta: None,
|
|
@@ -347,8 +518,8 @@ mod tests {
|
|
|
347
518
|
mcp_client::client_runtime,
|
|
348
519
|
schema::{
|
|
349
520
|
CallToolRequestParams, ClientCapabilities, CreateMessageRequest, CreateMessageResult,
|
|
350
|
-
Implementation, InitializeRequestParams, Role, RpcError,
|
|
351
|
-
LATEST_PROTOCOL_VERSION,
|
|
521
|
+
GetPromptRequestParams, Implementation, InitializeRequestParams, Role, RpcError,
|
|
522
|
+
TextContent, LATEST_PROTOCOL_VERSION,
|
|
352
523
|
},
|
|
353
524
|
McpClient, StdioTransport, TransportOptions,
|
|
354
525
|
};
|
|
@@ -363,7 +534,7 @@ mod tests {
|
|
|
363
534
|
_runtime: &dyn McpClient,
|
|
364
535
|
) -> std::result::Result<CreateMessageResult, RpcError> {
|
|
365
536
|
Ok(CreateMessageResult {
|
|
366
|
-
content: TextContent::new("hello".to_string(), None).into(),
|
|
537
|
+
content: TextContent::new("hello".to_string(), None, None).into(),
|
|
367
538
|
meta: None,
|
|
368
539
|
model: "test-model".to_string(),
|
|
369
540
|
role: Role::Assistant,
|
|
@@ -392,6 +563,7 @@ mod tests {
|
|
|
392
563
|
capabilities: ClientCapabilities::default(),
|
|
393
564
|
client_info: Implementation {
|
|
394
565
|
name: "test-client".into(),
|
|
566
|
+
title: None,
|
|
395
567
|
version: "0.1.0".into(),
|
|
396
568
|
},
|
|
397
569
|
protocol_version: LATEST_PROTOCOL_VERSION.into(),
|
|
@@ -434,6 +606,7 @@ mod tests {
|
|
|
434
606
|
capabilities: ClientCapabilities::default(),
|
|
435
607
|
client_info: Implementation {
|
|
436
608
|
name: "test-client".into(),
|
|
609
|
+
title: None,
|
|
437
610
|
version: "0.1.0".into(),
|
|
438
611
|
},
|
|
439
612
|
protocol_version: LATEST_PROTOCOL_VERSION.into(),
|
|
@@ -490,6 +663,7 @@ mod tests {
|
|
|
490
663
|
capabilities: ClientCapabilities::default(),
|
|
491
664
|
client_info: Implementation {
|
|
492
665
|
name: "test-client".into(),
|
|
666
|
+
title: None,
|
|
493
667
|
version: "0.1.0".into(),
|
|
494
668
|
},
|
|
495
669
|
protocol_version: LATEST_PROTOCOL_VERSION.into(),
|
|
@@ -544,6 +718,7 @@ mod tests {
|
|
|
544
718
|
capabilities: ClientCapabilities::default(),
|
|
545
719
|
client_info: Implementation {
|
|
546
720
|
name: "test-client".into(),
|
|
721
|
+
title: None,
|
|
547
722
|
version: "0.1.0".into(),
|
|
548
723
|
},
|
|
549
724
|
protocol_version: LATEST_PROTOCOL_VERSION.into(),
|
|
@@ -583,6 +758,7 @@ mod tests {
|
|
|
583
758
|
capabilities: ClientCapabilities::default(),
|
|
584
759
|
client_info: Implementation {
|
|
585
760
|
name: "test-client".into(),
|
|
761
|
+
title: None,
|
|
586
762
|
version: "0.1.0".into(),
|
|
587
763
|
},
|
|
588
764
|
protocol_version: LATEST_PROTOCOL_VERSION.into(),
|
|
@@ -605,4 +781,60 @@ mod tests {
|
|
|
605
781
|
|
|
606
782
|
Ok(())
|
|
607
783
|
}
|
|
784
|
+
|
|
785
|
+
#[tokio::test]
|
|
786
|
+
async fn prompts_feature_works() -> SdkResult<()> {
|
|
787
|
+
let transport = StdioTransport::create_with_server_launch(
|
|
788
|
+
"ruby",
|
|
789
|
+
vec![
|
|
790
|
+
"-I".into(),
|
|
791
|
+
"../../lib".into(),
|
|
792
|
+
"../../bin/mcp".into(),
|
|
793
|
+
"../../test/support/prompt_example.rb".into(),
|
|
794
|
+
],
|
|
795
|
+
None,
|
|
796
|
+
TransportOptions::default(),
|
|
797
|
+
)?;
|
|
798
|
+
|
|
799
|
+
let client_details = InitializeRequestParams {
|
|
800
|
+
capabilities: ClientCapabilities::default(),
|
|
801
|
+
client_info: Implementation {
|
|
802
|
+
name: "test-client".into(),
|
|
803
|
+
title: None,
|
|
804
|
+
version: "0.1.0".into(),
|
|
805
|
+
},
|
|
806
|
+
protocol_version: LATEST_PROTOCOL_VERSION.into(),
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
let client = client_runtime::create_client(client_details, transport, TestClientHandler);
|
|
810
|
+
|
|
811
|
+
client.clone().start().await?;
|
|
812
|
+
|
|
813
|
+
// list prompts
|
|
814
|
+
let result = client.list_prompts(None).await?;
|
|
815
|
+
assert_eq!(result.prompts.len(), 1);
|
|
816
|
+
assert_eq!(result.prompts[0].name, "greeting");
|
|
817
|
+
|
|
818
|
+
// get prompt
|
|
819
|
+
let get_res = client
|
|
820
|
+
.get_prompt(GetPromptRequestParams {
|
|
821
|
+
name: "greeting".into(),
|
|
822
|
+
arguments: Some(
|
|
823
|
+
[("name".to_string(), "Codex".to_string())]
|
|
824
|
+
.into_iter()
|
|
825
|
+
.collect(),
|
|
826
|
+
),
|
|
827
|
+
})
|
|
828
|
+
.await?;
|
|
829
|
+
assert_eq!(get_res.messages.len(), 2);
|
|
830
|
+
let text = get_res.messages[0]
|
|
831
|
+
.content
|
|
832
|
+
.as_text_content()
|
|
833
|
+
.unwrap()
|
|
834
|
+
.text
|
|
835
|
+
.clone();
|
|
836
|
+
assert_eq!(text, "Hello Codex");
|
|
837
|
+
|
|
838
|
+
Ok(())
|
|
839
|
+
}
|
|
608
840
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MicroMcp
|
|
4
|
+
module PromptRegistry
|
|
5
|
+
def self.register_prompt(name:, description: nil, arguments: nil, &block)
|
|
6
|
+
raise ArgumentError, "block required" unless block
|
|
7
|
+
|
|
8
|
+
MicroMcpNative.register_prompt(name, description, arguments, block)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
data/lib/micro_mcp/version.rb
CHANGED
data/lib/micro_mcp.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "micro_mcp/version"
|
|
4
|
-
ruby_version =
|
|
4
|
+
ruby_version = RUBY_VERSION[/\d+\.\d+/].to_s
|
|
5
5
|
begin
|
|
6
6
|
require_relative "micro_mcp/micro_mcp"
|
|
7
7
|
rescue LoadError
|
|
@@ -13,6 +13,7 @@ rescue LoadError
|
|
|
13
13
|
end
|
|
14
14
|
require_relative "micro_mcp/schema"
|
|
15
15
|
require_relative "micro_mcp/tool_registry"
|
|
16
|
+
require_relative "micro_mcp/prompt_registry"
|
|
16
17
|
require_relative "micro_mcp/server"
|
|
17
18
|
require_relative "micro_mcp/runtime_helpers"
|
|
18
19
|
require_relative "micro_mcp/validation_helpers"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Script to build MicroMcp gem for multiple platforms using rb-sys-dock
|
|
5
|
+
# This script calls rb-sys-dock for each platform with the specified Ruby versions
|
|
6
|
+
|
|
7
|
+
PLATFORMS = %w[arm64-darwin x86_64-darwin x86_64-linux].freeze
|
|
8
|
+
RUBY_VERSIONS = "3.2,3.3,3.4"
|
|
9
|
+
|
|
10
|
+
def run_command(command)
|
|
11
|
+
puts "Running: #{command}"
|
|
12
|
+
success = system(command)
|
|
13
|
+
unless success
|
|
14
|
+
puts "ERROR: Command failed: #{command}"
|
|
15
|
+
exit 1
|
|
16
|
+
end
|
|
17
|
+
puts "✓ Command completed successfully\n"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def main
|
|
21
|
+
puts "Building MicroMcp gem for multiple platforms..."
|
|
22
|
+
puts "Platforms: #{PLATFORMS.join(", ")}"
|
|
23
|
+
puts "Ruby versions: #{RUBY_VERSIONS}"
|
|
24
|
+
puts
|
|
25
|
+
|
|
26
|
+
run_command("bundle exec rake build")
|
|
27
|
+
|
|
28
|
+
PLATFORMS.each do |platform|
|
|
29
|
+
puts "=" * 60
|
|
30
|
+
puts "Building for platform: #{platform}"
|
|
31
|
+
puts "=" * 60
|
|
32
|
+
|
|
33
|
+
command = "bundle exec rb-sys-dock --platform #{platform} --ruby-versions #{RUBY_VERSIONS} --build"
|
|
34
|
+
run_command(command)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
puts "=" * 60
|
|
38
|
+
puts "All platforms built successfully! 🎉"
|
|
39
|
+
puts "=" * 60
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if __FILE__ == $PROGRAM_NAME
|
|
43
|
+
main
|
|
44
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: micro_mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5.pre.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Erwin Kroon
|
|
@@ -50,12 +50,14 @@ files:
|
|
|
50
50
|
- ext/micro_mcp/src/server.rs
|
|
51
51
|
- ext/micro_mcp/src/utils.rs
|
|
52
52
|
- lib/micro_mcp.rb
|
|
53
|
+
- lib/micro_mcp/prompt_registry.rb
|
|
53
54
|
- lib/micro_mcp/runtime_helpers.rb
|
|
54
55
|
- lib/micro_mcp/schema.rb
|
|
55
56
|
- lib/micro_mcp/server.rb
|
|
56
57
|
- lib/micro_mcp/tool_registry.rb
|
|
57
58
|
- lib/micro_mcp/validation_helpers.rb
|
|
58
59
|
- lib/micro_mcp/version.rb
|
|
60
|
+
- scripts/build_platforms.rb
|
|
59
61
|
homepage: https://github.com/ekroon/micro_mcp
|
|
60
62
|
licenses:
|
|
61
63
|
- MIT
|
|
@@ -78,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
78
80
|
- !ruby/object:Gem::Version
|
|
79
81
|
version: 3.3.11
|
|
80
82
|
requirements: []
|
|
81
|
-
rubygems_version: 3.6.
|
|
83
|
+
rubygems_version: 3.6.9
|
|
82
84
|
specification_version: 4
|
|
83
85
|
summary: Simple gem for quick MCP server creation
|
|
84
86
|
test_files: []
|