micro_mcp 0.1.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6f37463e6cd3dddd85d8d87ec62b9c8346cd717ef49cf0855f79b78de77c4a4
4
- data.tar.gz: fd2df666bcfe8b4396049ea786aa9f8fb39081917af9f33737eaa1ec7776f23e
3
+ metadata.gz: e552447ebf478c88bda0459eb3141fe6ba5420c95e2d77c40aa38b288ca15316
4
+ data.tar.gz: b595bbd5ea356a94e655db28187d654beff9eaefe17a2ecd89ec195bc12094da
5
5
  SHA512:
6
- metadata.gz: 2adbaa5d4ba0e384d14c33d4a6bf2bfc9b1c84ab403bcee15061733b2b3e0381503aa635050b0aba704e385195eeaa6719cb97a8c65169b42a438d274f21ee0f
7
- data.tar.gz: bbc23b9278cd35e85233133ab98317a2898c9bedccd61c663f45420fd30ef2e8e92d4a04f13c26a9fc4f381e427c01c1bc584f95d5090cf6422f64e65d3b2f65
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.4.2"
1095
+ version = "0.5.0"
1096
1096
  source = "registry+https://github.com/rust-lang/crates.io-index"
1097
- checksum = "375eba9388d9d5ffc8951e5f25296f9739be4e5700375ac7c74ad957a903b92b"
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.6.0"
1108
+ version = "0.7.1"
1109
1109
  source = "registry+https://github.com/rust-lang/crates.io-index"
1110
- checksum = "a794de25669a2d21c5074ec5082f74f5e88863a112339fe90264d9e480b0ee8b"
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.4.7"
1118
+ version = "0.5.0"
1119
1119
  source = "registry+https://github.com/rust-lang/crates.io-index"
1120
- checksum = "83f03dc968444ec56bdcddf17536b4b6860136a0d81d987f2fb909d5aec61ed7"
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.3.6"
1136
+ version = "0.4.0"
1137
1137
  source = "registry+https://github.com/rust-lang/crates.io-index"
1138
- checksum = "94b5a7f8e688f8b15d67f8d94cc0481cd08c9f4e848f4b9e832677dd5fdb063e"
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.10"
1297
+ version = "0.4.11"
1298
1298
  source = "registry+https://github.com/rust-lang/crates.io-index"
1299
- checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
1299
+ checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
1300
1300
 
1301
1301
  [[package]]
1302
1302
  name = "smallvec"
@@ -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.
@@ -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.4.3", default-features = false, features = [
19
- "server", "client", "2025_03_26"
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"
@@ -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())?;
@@ -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, Implementation,
6
- InitializeResult, ListToolsRequest, ListToolsResult, RpcError, ServerCapabilities,
7
- ServerCapabilitiesTools, Tool, ToolInputSchema, LATEST_PROTOCOL_VERSION,
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::{block::Proc, value::ReprValue, Error, Ruby, Value};
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
- #[derive(Clone)]
34
- struct RubyHandler(Proc);
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(text, None)),
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, TextContent,
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MicroMcp
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.5.pre.1"
5
5
  end
data/lib/micro_mcp.rb CHANGED
@@ -1,9 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "micro_mcp/version"
4
- require_relative "micro_mcp/micro_mcp"
4
+ ruby_version = RUBY_VERSION[/\d+\.\d+/].to_s
5
+ begin
6
+ require_relative "micro_mcp/micro_mcp"
7
+ rescue LoadError
8
+ begin
9
+ require_relative "micro_mcp/#{ruby_version}/micro_mcp"
10
+ rescue LoadError
11
+ raise LoadError, "No native extension found for Ruby #{ruby_version}"
12
+ end
13
+ end
5
14
  require_relative "micro_mcp/schema"
6
15
  require_relative "micro_mcp/tool_registry"
16
+ require_relative "micro_mcp/prompt_registry"
7
17
  require_relative "micro_mcp/server"
8
18
  require_relative "micro_mcp/runtime_helpers"
9
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.1
4
+ version: 0.1.5.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erwin Kroon
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.9.91
18
+ version: 0.9.116
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.9.91
25
+ version: 0.9.116
26
26
  description: ''
27
27
  email:
28
28
  - 123574+ekroon@users.noreply.github.com
@@ -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.7
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: []