micro_mcp 0.1.0 → 0.1.4

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.
data/README.md CHANGED
@@ -1,28 +1,59 @@
1
1
  # MicroMcp
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ MicroMcp is a tiny framework for building [MCP](https://github.com/openai/AIAPI-Protocol) servers in Ruby. It ships with a Rust extension that handles the low level protocol while your Ruby code focuses on registering tools.
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/micro_mcp`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ The gem is available on [RubyGems](https://rubygems.org/gems/micro_mcp).
6
6
 
7
7
  ## Installation
8
8
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
9
+ Add the gem to your application's Gemfile:
10
10
 
11
- Install the gem and add to the application's Gemfile by executing:
11
+ ```ruby
12
+ gem "micro_mcp"
13
+ ```
14
+
15
+ Then execute:
12
16
 
13
17
  ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
18
+ bundle install
15
19
  ```
16
20
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
21
+ Or install it directly with RubyGems:
18
22
 
19
23
  ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
24
+ gem install micro_mcp
21
25
  ```
22
26
 
23
27
  ## Usage
24
28
 
25
- TODO: Write usage instructions here
29
+ Define one or more tools and start the server:
30
+
31
+ ```ruby
32
+ require "micro_mcp"
33
+
34
+ MicroMcp::ToolRegistry.register_tool(name: "say_hello") do
35
+ "Hello World!"
36
+ end
37
+
38
+ # Tools can also accept arguments defined using JSON Schema.
39
+ # The arguments hash is provided as the first block parameter.
40
+ MicroMcp::ToolRegistry.register_tool(
41
+ name: "add_numbers",
42
+ description: "Adds two integers",
43
+ arguments: {
44
+ "type" => "object",
45
+ "properties" => {
46
+ "a" => {"type" => "integer"},
47
+ "b" => {"type" => "integer"}
48
+ },
49
+ "required" => ["a", "b"]
50
+ }
51
+ ) do |args, _runtime|
52
+ (args["a"] + args["b"]).to_s
53
+ end
54
+
55
+ MicroMcp.start_server
56
+ ```
26
57
 
27
58
  ## Development
28
59
 
@@ -0,0 +1,133 @@
1
+ # MicroMCP Ergonomic Improvements
2
+
3
+ ## Problem
4
+
5
+ The original MCP tool creation was error-prone due to several issues:
6
+
7
+ 1. **Missing return value handling** - Easy to forget `result["content"]["text"]`
8
+ 2. **Cryptic error messages** - "no implicit conversion of Hash into String"
9
+ 3. **Symbol vs String key confusion** - JSON requires string keys
10
+ 4. **Repetitive boilerplate** - Same patterns repeated in every tool
11
+ 5. **No validation** - Hard to debug parameter issues
12
+
13
+ ## Solution
14
+
15
+ We've added several layers of improvements to make tool creation more ergonomic and less error-prone:
16
+
17
+ ### 1. Helper Methods (`runtime_helpers.rb`)
18
+
19
+ ```ruby
20
+ # Simple question-answering
21
+ runtime.ask_assistant("What is the capital of France?")
22
+
23
+ # Multi-message conversations
24
+ runtime.chat_with_assistant([
25
+ "Let's discuss Ruby",
26
+ {"role" => "assistant", "content" => {"type" => "text", "text" => "I'd love to help!"}},
27
+ "What are the key features?"
28
+ ])
29
+
30
+ # Safe wrapper with validation
31
+ runtime.safe_create_message(params)
32
+ ```
33
+
34
+ ### 2. Enhanced Tool Registration (`tool_registry.rb`)
35
+
36
+ ```ruby
37
+ # One-liner for simple Q&A tools
38
+ TR.register_qa_tool(
39
+ name: "ask",
40
+ description: "Ask a question"
41
+ )
42
+
43
+ # Enhanced registration with error handling
44
+ TR.register_assistant_tool(name: "custom", description: "Custom tool") do |args, runtime|
45
+ runtime.ask_assistant(args["question"])
46
+ end
47
+ ```
48
+
49
+ ### 3. Validation System (`validation_helpers.rb`)
50
+
51
+ - Pre-flight validation of `create_message` parameters
52
+ - Clear error messages for common mistakes
53
+ - Automatic symbol-to-string key conversion
54
+ - Structural validation of message format
55
+
56
+ ## Response Format Handling
57
+
58
+ The system now properly handles the MCP response format:
59
+
60
+ ```json
61
+ {
62
+ "role": "assistant",
63
+ "content": {
64
+ "type": "text",
65
+ "text": "The actual response text"
66
+ },
67
+ "model": "o4-mini",
68
+ "stopReason": "endTurn"
69
+ }
70
+ ```
71
+
72
+ Helper methods automatically extract `result["content"]["text"]` with proper error handling.
73
+
74
+ ## Before vs After
75
+
76
+ ### Before (Error-prone)
77
+ ```ruby
78
+ TR.register_tool(
79
+ name: "ask",
80
+ arguments: S.object(question: S.string.required)
81
+ ) do |args, runtime|
82
+ result = runtime.create_message({
83
+ "messages" => [
84
+ {"role" => "user", "content" => {"type" => "text", "text" => args["question"]}}
85
+ ],
86
+ "modelPreferences" => {
87
+ "hints" => [{"name" => "o4-mini"}],
88
+ "intelligencePriority" => 0.8,
89
+ "speedPriority" => 0.5
90
+ },
91
+ "systemPrompt" => "You are a helpful assistant.",
92
+ "maxTokens" => 100
93
+ })
94
+ result["content"]["text"] # Easy to forget!
95
+ end
96
+ ```
97
+
98
+ ### After (Simple)
99
+ ```ruby
100
+ # One-liner approach
101
+ TR.register_qa_tool(name: "ask", description: "Ask a question")
102
+
103
+ # Or with customization
104
+ TR.register_assistant_tool(name: "ask", description: "Ask a question") do |args, runtime|
105
+ runtime.ask_assistant(args["question"])
106
+ end
107
+ ```
108
+
109
+ ## Error Handling
110
+
111
+ The new system provides:
112
+
113
+ 1. **Pre-validation** - Catches errors before sending to MCP
114
+ 2. **Clear error messages** - Explains what went wrong
115
+ 3. **Automatic error recovery** - Handles common formatting issues
116
+ 4. **Debug mode** - Set `ENV['MCP_DEBUG']` for detailed logging
117
+
118
+ ## Backward Compatibility
119
+
120
+ All existing tools continue to work unchanged. The improvements are additive.
121
+
122
+ ## Usage Examples
123
+
124
+ See `test/support/ergonomic_test.rb` and `test/support/ergonomic_examples.rb` for comprehensive examples.
125
+
126
+ ## Key Benefits
127
+
128
+ 1. ✅ **Reduced boilerplate** - Simple tools are one-liners
129
+ 2. ✅ **Better error messages** - Clear validation feedback
130
+ 3. ✅ **Automatic result extraction** - No more forgetting return values
131
+ 4. ✅ **Symbol key safety** - Automatic string conversion
132
+ 5. ✅ **Validation** - Catch issues before they cause problems
133
+ 6. ✅ **Backward compatible** - Existing code keeps working
@@ -0,0 +1,66 @@
1
+ # Rust Development Agent Instructions
2
+
3
+ This directory contains Rust code that interfaces with Ruby via FFI. These instructions help agents navigate Rust source code efficiently.
4
+
5
+ ## Environment Setup
6
+
7
+ The following environment variables should be exported for Rust source navigation:
8
+
9
+ ```bash
10
+ # Standard library source (requires `rustup component add rust-src`)
11
+ export RUST_STD_SRC="$(rustc --print sysroot)/lib/rustlib/src/rust/library"
12
+
13
+ # Crates.io tarball cache (downloaded by `cargo fetch`)
14
+ export RUST_CRATE_SRC="${CARGO_HOME:-$HOME/.cargo}/registry/src"
15
+
16
+ # Git checkouts for dependencies using `git = "..."`
17
+ export RUST_GIT_SRC="${CARGO_HOME:-$HOME/.cargo}/git/checkouts"
18
+ ```
19
+
20
+ ## Helper Commands
21
+
22
+ ### Locating Standard Library Items
23
+
24
+ ```bash
25
+ rg "pub .* <TypeOrFnName>" "$RUST_STD_SRC"
26
+ ```
27
+
28
+ ### Finding Versioned Crates
29
+
30
+ ```bash
31
+ # Example: finding serde 1.0.200
32
+ rg --files "$RUST_CRATE_SRC" | grep '/serde-1\.0\.200/' | head -n1
33
+ # Then search inside that path
34
+ ```
35
+
36
+ ### Locating Git Dependencies
37
+
38
+ ```bash
39
+ # Example: finding a crate directory containing 'mycrate'
40
+ find "$RUST_GIT_SRC" -maxdepth 3 -type d -name '*mycrate*' | head
41
+ ```
42
+
43
+ ### Quick Jump to Crate Root
44
+
45
+ ```bash
46
+ cargo metadata --format-version 1 --no-deps \
47
+ | jq -r '.packages[] | select(.name=="tokio") | .manifest_path' \
48
+ | xargs dirname
49
+ ```
50
+
51
+ ## Agent Rules
52
+
53
+ 1. **Source Code Lookup Order**: Always search in this order:
54
+ - `$RUST_STD_SRC` (standard library)
55
+ - `$RUST_CRATE_SRC` (crates.io dependencies)
56
+ - `$RUST_GIT_SRC` (git dependencies)
57
+
58
+ 2. **Exact Signatures**: Copy the **exact** function/type signature you find in the source. If no match is found, report:
59
+ - Which symbol is missing
60
+ - Which directory was searched
61
+ - Stop and ask for clarification
62
+
63
+ 3. **FFI Considerations**: This project uses Ruby FFI, so pay attention to:
64
+ - Memory management between Rust and Ruby
65
+ - Proper error handling across language boundaries
66
+ - Thread safety considerations
@@ -15,7 +15,9 @@ 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 = "0.4.3"
18
+ rust-mcp-sdk = { version = "0.4.3", default-features = false, features = [
19
+ "server", "client", "2025_03_26"
20
+ ]}
19
21
  serde = "1.0.219"
20
22
  serde_json = "1.0.140"
21
23
  tokio = "1.45.1"
@@ -1,12 +1,28 @@
1
- mod utils;
2
1
  mod server;
2
+ mod utils;
3
3
 
4
- use magnus::{function, prelude::*, Error, Ruby};
4
+ use magnus::{function, method, prelude::*, Error, Ruby};
5
5
 
6
6
  #[magnus::init]
7
7
  fn init(ruby: &Ruby) -> Result<(), Error> {
8
- let module = ruby.define_module("MicroMcpNative")?;
9
- module.define_singleton_method("start_server", function!(server::start_server, 0))?;
10
- module.define_singleton_method("register_tool", function!(server::register_tool, 3))?;
8
+ let native = ruby.define_module("MicroMcpNative")?;
9
+ native.define_singleton_method("start_server", function!(server::start_server, 0))?;
10
+ native.define_singleton_method("shutdown_server", function!(server::shutdown_server, 0))?;
11
+ native.define_singleton_method("register_tool", function!(server::register_tool, 4))?;
12
+
13
+ let parent = ruby.define_module("MicroMcp")?;
14
+ let class = parent.define_class("Runtime", ruby.class_object())?;
15
+ class.define_method(
16
+ "is_initialized",
17
+ method!(server::RubyMcpServer::is_initialized, 0),
18
+ )?;
19
+ class.define_method(
20
+ "client_supports_sampling",
21
+ method!(server::RubyMcpServer::client_supports_sampling, 0),
22
+ )?;
23
+ class.define_method(
24
+ "create_message",
25
+ method!(server::RubyMcpServer::create_message, 1),
26
+ )?;
11
27
  Ok(())
12
28
  }