mcp 0.1.0 → 0.2.0
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/.cursor/rules/release-changelogs.mdc +11 -24
- data/.github/workflows/release.yml +25 -0
- data/.rubocop.yml +2 -3
- data/CHANGELOG.md +31 -0
- data/Gemfile +12 -5
- data/README.md +120 -25
- data/examples/README.md +197 -0
- data/examples/http_client.rb +184 -0
- data/examples/http_server.rb +168 -0
- data/examples/stdio_server.rb +2 -3
- data/examples/streamable_http_client.rb +205 -0
- data/examples/streamable_http_server.rb +172 -0
- data/lib/mcp/configuration.rb +16 -2
- data/lib/mcp/methods.rb +55 -33
- data/lib/mcp/prompt.rb +2 -2
- data/lib/mcp/resource.rb +1 -1
- data/lib/mcp/server/capabilities.rb +96 -0
- data/lib/mcp/server/transports/stdio_transport.rb +57 -0
- data/lib/mcp/server/transports/streamable_http_transport.rb +289 -0
- data/lib/mcp/server.rb +80 -45
- data/lib/mcp/tool/input_schema.rb +44 -1
- data/lib/mcp/tool.rb +1 -1
- data/lib/mcp/transport.rb +16 -4
- data/lib/mcp/transports/stdio.rb +8 -28
- data/lib/mcp/version.rb +1 -1
- data/lib/mcp.rb +16 -12
- data/mcp.gemspec +1 -2
- metadata +17 -24
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
4
|
+
require "mcp"
|
5
|
+
require "mcp/server/transports/streamable_http_transport"
|
6
|
+
require "rack"
|
7
|
+
require "rackup"
|
8
|
+
require "json"
|
9
|
+
require "logger"
|
10
|
+
|
11
|
+
# Create a logger for SSE-specific logging
|
12
|
+
sse_logger = Logger.new($stdout)
|
13
|
+
sse_logger.formatter = proc do |severity, datetime, _progname, msg|
|
14
|
+
"[SSE] #{severity} #{datetime.strftime("%H:%M:%S.%L")} - #{msg}\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Tool that returns a response that will be sent via SSE if a stream is active
|
18
|
+
class NotificationTool < MCP::Tool
|
19
|
+
tool_name "notification_tool"
|
20
|
+
description "Returns a notification message that will be sent via SSE if stream is active"
|
21
|
+
input_schema(
|
22
|
+
properties: {
|
23
|
+
message: { type: "string", description: "Message to send via SSE" },
|
24
|
+
delay: { type: "number", description: "Delay in seconds before returning (optional)" },
|
25
|
+
},
|
26
|
+
required: ["message"],
|
27
|
+
)
|
28
|
+
|
29
|
+
class << self
|
30
|
+
attr_accessor :logger
|
31
|
+
|
32
|
+
def call(message:, delay: 0)
|
33
|
+
sleep(delay) if delay > 0
|
34
|
+
|
35
|
+
logger&.info("Returning notification message: #{message}")
|
36
|
+
|
37
|
+
MCP::Tool::Response.new([{
|
38
|
+
type: "text",
|
39
|
+
text: "Notification: #{message} (timestamp: #{Time.now.iso8601})",
|
40
|
+
}])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Create the server
|
46
|
+
server = MCP::Server.new(
|
47
|
+
name: "sse_test_server",
|
48
|
+
tools: [NotificationTool],
|
49
|
+
prompts: [],
|
50
|
+
resources: [],
|
51
|
+
)
|
52
|
+
|
53
|
+
# Set logger for tools
|
54
|
+
NotificationTool.logger = sse_logger
|
55
|
+
|
56
|
+
# Add a simple echo tool for basic testing
|
57
|
+
server.define_tool(
|
58
|
+
name: "echo",
|
59
|
+
description: "Simple echo tool",
|
60
|
+
input_schema: { properties: { message: { type: "string" } }, required: ["message"] },
|
61
|
+
) do |message:|
|
62
|
+
MCP::Tool::Response.new([{ type: "text", text: "Echo: #{message}" }])
|
63
|
+
end
|
64
|
+
|
65
|
+
# Create the Streamable HTTP transport
|
66
|
+
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
|
67
|
+
server.transport = transport
|
68
|
+
|
69
|
+
# Create a logger for MCP request/response logging
|
70
|
+
mcp_logger = Logger.new($stdout)
|
71
|
+
mcp_logger.formatter = proc do |_severity, _datetime, _progname, msg|
|
72
|
+
"[MCP] #{msg}\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Create the Rack application
|
76
|
+
app = proc do |env|
|
77
|
+
request = Rack::Request.new(env)
|
78
|
+
|
79
|
+
# Log request details
|
80
|
+
if request.post?
|
81
|
+
body = request.body.read
|
82
|
+
request.body.rewind
|
83
|
+
begin
|
84
|
+
parsed_body = JSON.parse(body)
|
85
|
+
mcp_logger.info("Request: #{parsed_body["method"]} (id: #{parsed_body["id"]})")
|
86
|
+
|
87
|
+
# Log SSE-specific setup
|
88
|
+
if parsed_body["method"] == "initialize"
|
89
|
+
sse_logger.info("New client initializing session")
|
90
|
+
end
|
91
|
+
rescue JSON::ParserError
|
92
|
+
mcp_logger.warn("Invalid JSON in request")
|
93
|
+
end
|
94
|
+
elsif request.get?
|
95
|
+
session_id = request.env["HTTP_MCP_SESSION_ID"] ||
|
96
|
+
Rack::Utils.parse_query(request.env["QUERY_STRING"])["sessionId"]
|
97
|
+
sse_logger.info("SSE connection request for session: #{session_id}")
|
98
|
+
end
|
99
|
+
|
100
|
+
# Handle the request
|
101
|
+
response = transport.handle_request(request)
|
102
|
+
|
103
|
+
# Log response details
|
104
|
+
status, headers, body = response
|
105
|
+
if body.is_a?(Array) && !body.empty? && request.post?
|
106
|
+
begin
|
107
|
+
parsed_response = JSON.parse(body.first)
|
108
|
+
if parsed_response["error"]
|
109
|
+
mcp_logger.error("Response error: #{parsed_response["error"]["message"]}")
|
110
|
+
elsif parsed_response["accepted"]
|
111
|
+
# Response was sent via SSE
|
112
|
+
sse_logger.info("Response sent via SSE stream")
|
113
|
+
else
|
114
|
+
mcp_logger.info("Response: success (id: #{parsed_response["id"]})")
|
115
|
+
|
116
|
+
# Log session ID for initialization
|
117
|
+
if headers["Mcp-Session-Id"]
|
118
|
+
sse_logger.info("Session created: #{headers["Mcp-Session-Id"]}")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
rescue JSON::ParserError
|
122
|
+
mcp_logger.warn("Invalid JSON in response")
|
123
|
+
end
|
124
|
+
elsif request.get? && status == 200
|
125
|
+
sse_logger.info("SSE stream established")
|
126
|
+
end
|
127
|
+
|
128
|
+
response
|
129
|
+
end
|
130
|
+
|
131
|
+
# Build the Rack application with middleware
|
132
|
+
rack_app = Rack::Builder.new do
|
133
|
+
use(Rack::CommonLogger, Logger.new($stdout))
|
134
|
+
use(Rack::ShowExceptions)
|
135
|
+
run(app)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Print usage instructions
|
139
|
+
puts "=== MCP Streaming HTTP Test Server ==="
|
140
|
+
puts ""
|
141
|
+
puts "Starting server on http://localhost:9393"
|
142
|
+
puts ""
|
143
|
+
puts "Available Tools:"
|
144
|
+
puts "1. NotificationTool - Returns messages that are sent via SSE when stream is active"
|
145
|
+
puts "2. echo - Simple echo tool"
|
146
|
+
puts ""
|
147
|
+
puts "Testing SSE:"
|
148
|
+
puts ""
|
149
|
+
puts "1. Initialize session:"
|
150
|
+
puts " curl -i http://localhost:9393 \\"
|
151
|
+
puts ' --json \'{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"sse-test","version":"1.0"}}}\''
|
152
|
+
puts ""
|
153
|
+
puts "2. Connect SSE stream (use the session ID from step 1):"
|
154
|
+
puts ' curl -i -N -H "Mcp-Session-Id: YOUR_SESSION_ID" http://localhost:9393'
|
155
|
+
puts ""
|
156
|
+
puts "3. In another terminal, test tools (responses will be sent via SSE if stream is active):"
|
157
|
+
puts ""
|
158
|
+
puts " Echo tool:"
|
159
|
+
puts ' curl -i http://localhost:9393 -H "Mcp-Session-Id: YOUR_SESSION_ID" \\'
|
160
|
+
puts ' --json \'{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"echo","arguments":{"message":"Hello SSE!"}}}\''
|
161
|
+
puts ""
|
162
|
+
puts " Notification tool (with 2 second delay):"
|
163
|
+
puts ' curl -i http://localhost:9393 -H "Mcp-Session-Id: YOUR_SESSION_ID" \\'
|
164
|
+
puts ' --json \'{"jsonrpc":"2.0","method":"tools/call","id":3,"params":{"name":"notification_tool","arguments":{"message":"Hello SSE!", "delay": 2}}}\''
|
165
|
+
puts ""
|
166
|
+
puts "Note: When an SSE stream is active, tool responses will appear in the SSE stream and the POST request will return {\"accepted\": true}"
|
167
|
+
puts ""
|
168
|
+
puts "Press Ctrl+C to stop the server"
|
169
|
+
puts ""
|
170
|
+
|
171
|
+
# Start the server
|
172
|
+
Rackup::Handler.get("puma").run(rack_app, Port: 9393, Host: "localhost")
|
data/lib/mcp/configuration.rb
CHANGED
@@ -4,12 +4,18 @@ module MCP
|
|
4
4
|
class Configuration
|
5
5
|
DEFAULT_PROTOCOL_VERSION = "2024-11-05"
|
6
6
|
|
7
|
-
attr_writer :exception_reporter, :instrumentation_callback, :protocol_version
|
7
|
+
attr_writer :exception_reporter, :instrumentation_callback, :protocol_version, :validate_tool_call_arguments
|
8
8
|
|
9
|
-
def initialize(exception_reporter: nil, instrumentation_callback: nil, protocol_version: nil
|
9
|
+
def initialize(exception_reporter: nil, instrumentation_callback: nil, protocol_version: nil,
|
10
|
+
validate_tool_call_arguments: true)
|
10
11
|
@exception_reporter = exception_reporter
|
11
12
|
@instrumentation_callback = instrumentation_callback
|
12
13
|
@protocol_version = protocol_version
|
14
|
+
unless validate_tool_call_arguments.is_a?(TrueClass) || validate_tool_call_arguments.is_a?(FalseClass)
|
15
|
+
raise ArgumentError, "validate_tool_call_arguments must be a boolean"
|
16
|
+
end
|
17
|
+
|
18
|
+
@validate_tool_call_arguments = validate_tool_call_arguments
|
13
19
|
end
|
14
20
|
|
15
21
|
def protocol_version
|
@@ -36,6 +42,12 @@ module MCP
|
|
36
42
|
!@instrumentation_callback.nil?
|
37
43
|
end
|
38
44
|
|
45
|
+
attr_reader :validate_tool_call_arguments
|
46
|
+
|
47
|
+
def validate_tool_call_arguments?
|
48
|
+
!!@validate_tool_call_arguments
|
49
|
+
end
|
50
|
+
|
39
51
|
def merge(other)
|
40
52
|
return self if other.nil?
|
41
53
|
|
@@ -54,11 +66,13 @@ module MCP
|
|
54
66
|
else
|
55
67
|
@protocol_version
|
56
68
|
end
|
69
|
+
validate_tool_call_arguments = other.validate_tool_call_arguments
|
57
70
|
|
58
71
|
Configuration.new(
|
59
72
|
exception_reporter:,
|
60
73
|
instrumentation_callback:,
|
61
74
|
protocol_version:,
|
75
|
+
validate_tool_call_arguments:,
|
62
76
|
)
|
63
77
|
end
|
64
78
|
|
data/lib/mcp/methods.rb
CHANGED
@@ -19,8 +19,20 @@ module MCP
|
|
19
19
|
TOOLS_CALL = "tools/call"
|
20
20
|
TOOLS_LIST = "tools/list"
|
21
21
|
|
22
|
+
ROOTS_LIST = "roots/list"
|
22
23
|
SAMPLING_CREATE_MESSAGE = "sampling/createMessage"
|
23
24
|
|
25
|
+
# Notification methods
|
26
|
+
NOTIFICATIONS_INITIALIZED = "notifications/initialized"
|
27
|
+
NOTIFICATIONS_TOOLS_LIST_CHANGED = "notifications/tools/list_changed"
|
28
|
+
NOTIFICATIONS_PROMPTS_LIST_CHANGED = "notifications/prompts/list_changed"
|
29
|
+
NOTIFICATIONS_RESOURCES_LIST_CHANGED = "notifications/resources/list_changed"
|
30
|
+
NOTIFICATIONS_RESOURCES_UPDATED = "notifications/resources/updated"
|
31
|
+
NOTIFICATIONS_ROOTS_LIST_CHANGED = "notifications/roots/list_changed"
|
32
|
+
NOTIFICATIONS_MESSAGE = "notifications/message"
|
33
|
+
NOTIFICATIONS_PROGRESS = "notifications/progress"
|
34
|
+
NOTIFICATIONS_CANCELLED = "notifications/cancelled"
|
35
|
+
|
24
36
|
class MissingRequiredCapabilityError < StandardError
|
25
37
|
attr_reader :method
|
26
38
|
attr_reader :capability
|
@@ -32,41 +44,51 @@ module MCP
|
|
32
44
|
end
|
33
45
|
end
|
34
46
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
47
|
+
class << self
|
48
|
+
def ensure_capability!(method, capabilities)
|
49
|
+
case method
|
50
|
+
when PROMPTS_GET, PROMPTS_LIST
|
51
|
+
require_capability!(method, capabilities, :prompts)
|
52
|
+
when NOTIFICATIONS_PROMPTS_LIST_CHANGED
|
53
|
+
require_capability!(method, capabilities, :prompts)
|
54
|
+
require_capability!(method, capabilities, :prompts, :listChanged)
|
55
|
+
when RESOURCES_LIST, RESOURCES_TEMPLATES_LIST, RESOURCES_READ
|
56
|
+
require_capability!(method, capabilities, :resources)
|
57
|
+
when NOTIFICATIONS_RESOURCES_LIST_CHANGED
|
58
|
+
require_capability!(method, capabilities, :resources)
|
59
|
+
require_capability!(method, capabilities, :resources, :listChanged)
|
60
|
+
when RESOURCES_SUBSCRIBE, RESOURCES_UNSUBSCRIBE, NOTIFICATIONS_RESOURCES_UPDATED
|
61
|
+
require_capability!(method, capabilities, :resources)
|
62
|
+
require_capability!(method, capabilities, :resources, :subscribe)
|
63
|
+
when TOOLS_CALL, TOOLS_LIST
|
64
|
+
require_capability!(method, capabilities, :tools)
|
65
|
+
when NOTIFICATIONS_TOOLS_LIST_CHANGED
|
66
|
+
require_capability!(method, capabilities, :tools)
|
67
|
+
require_capability!(method, capabilities, :tools, :listChanged)
|
68
|
+
when LOGGING_SET_LEVEL, NOTIFICATIONS_MESSAGE
|
69
|
+
require_capability!(method, capabilities, :logging)
|
70
|
+
when COMPLETION_COMPLETE
|
71
|
+
require_capability!(method, capabilities, :completions)
|
72
|
+
when ROOTS_LIST
|
73
|
+
require_capability!(method, capabilities, :roots)
|
74
|
+
when NOTIFICATIONS_ROOTS_LIST_CHANGED
|
75
|
+
require_capability!(method, capabilities, :roots)
|
76
|
+
require_capability!(method, capabilities, :roots, :listChanged)
|
77
|
+
when SAMPLING_CREATE_MESSAGE
|
78
|
+
require_capability!(method, capabilities, :sampling)
|
79
|
+
when INITIALIZE, PING, NOTIFICATIONS_INITIALIZED, NOTIFICATIONS_PROGRESS, NOTIFICATIONS_CANCELLED
|
80
|
+
# No specific capability required for initialize, ping, progress or cancelled
|
46
81
|
end
|
82
|
+
end
|
47
83
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
unless capabilities[:sampling]
|
57
|
-
raise MissingRequiredCapabilityError.new(method, :sampling)
|
58
|
-
end
|
59
|
-
when COMPLETION_COMPLETE
|
60
|
-
unless capabilities[:completions]
|
61
|
-
raise MissingRequiredCapabilityError.new(method, :completions)
|
62
|
-
end
|
63
|
-
when LOGGING_SET_LEVEL
|
64
|
-
# Logging is unsupported by the Server
|
65
|
-
unless capabilities[:logging]
|
66
|
-
raise MissingRequiredCapabilityError.new(method, :logging)
|
67
|
-
end
|
68
|
-
when INITIALIZE, PING
|
69
|
-
# No specific capability required for initialize or ping
|
84
|
+
private
|
85
|
+
|
86
|
+
def require_capability!(method, capabilities, *keys)
|
87
|
+
name = keys.join(".") # :resources, :subscribe -> "resources.subscribe"
|
88
|
+
has_capability = capabilities.dig(*keys)
|
89
|
+
return if has_capability
|
90
|
+
|
91
|
+
raise MissingRequiredCapabilityError.new(method, name)
|
70
92
|
end
|
71
93
|
end
|
72
94
|
end
|
data/lib/mcp/prompt.rb
CHANGED
@@ -9,7 +9,7 @@ module MCP
|
|
9
9
|
attr_reader :description_value
|
10
10
|
attr_reader :arguments_value
|
11
11
|
|
12
|
-
def template(args, server_context:)
|
12
|
+
def template(args, server_context: nil)
|
13
13
|
raise NotImplementedError, "Subclasses must implement template"
|
14
14
|
end
|
15
15
|
|
@@ -57,7 +57,7 @@ module MCP
|
|
57
57
|
prompt_name name
|
58
58
|
description description
|
59
59
|
arguments arguments
|
60
|
-
define_singleton_method(:template) do |args, server_context
|
60
|
+
define_singleton_method(:template) do |args, server_context: nil|
|
61
61
|
instance_exec(args, server_context:, &block)
|
62
62
|
end
|
63
63
|
end
|
data/lib/mcp/resource.rb
CHANGED
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MCP
|
4
|
+
class Server
|
5
|
+
class Capabilities
|
6
|
+
def initialize(capabilities_hash = nil)
|
7
|
+
@completions = nil
|
8
|
+
@experimental = nil
|
9
|
+
@logging = nil
|
10
|
+
@prompts = nil
|
11
|
+
@resources = nil
|
12
|
+
@tools = nil
|
13
|
+
|
14
|
+
if capabilities_hash
|
15
|
+
support_completions if capabilities_hash.key?(:completions)
|
16
|
+
support_experimental(capabilities_hash[:experimental]) if capabilities_hash.key?(:experimental)
|
17
|
+
support_logging if capabilities_hash.key?(:logging)
|
18
|
+
|
19
|
+
if capabilities_hash.key?(:prompts)
|
20
|
+
support_prompts
|
21
|
+
prompts_config = capabilities_hash[:prompts] || {}
|
22
|
+
support_prompts_list_changed if prompts_config[:listChanged]
|
23
|
+
end
|
24
|
+
|
25
|
+
if capabilities_hash.key?(:resources)
|
26
|
+
support_resources
|
27
|
+
resources_config = capabilities_hash[:resources] || {}
|
28
|
+
support_resources_list_changed if resources_config[:listChanged]
|
29
|
+
support_resources_subscribe if resources_config[:subscribe]
|
30
|
+
end
|
31
|
+
|
32
|
+
if capabilities_hash.key?(:tools)
|
33
|
+
support_tools
|
34
|
+
tools_config = capabilities_hash[:tools] || {}
|
35
|
+
support_tools_list_changed if tools_config[:listChanged]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def support_completions
|
41
|
+
@completions ||= {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def support_experimental(config = {})
|
45
|
+
@experimental = config || {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def support_logging
|
49
|
+
@logging ||= {}
|
50
|
+
end
|
51
|
+
|
52
|
+
def support_prompts
|
53
|
+
@prompts ||= {}
|
54
|
+
end
|
55
|
+
|
56
|
+
def support_prompts_list_changed
|
57
|
+
support_prompts
|
58
|
+
@prompts[:listChanged] = true
|
59
|
+
end
|
60
|
+
|
61
|
+
def support_resources
|
62
|
+
@resources ||= {}
|
63
|
+
end
|
64
|
+
|
65
|
+
def support_resources_list_changed
|
66
|
+
support_resources
|
67
|
+
@resources[:listChanged] = true
|
68
|
+
end
|
69
|
+
|
70
|
+
def support_resources_subscribe
|
71
|
+
support_resources
|
72
|
+
@resources[:subscribe] = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def support_tools
|
76
|
+
@tools ||= {}
|
77
|
+
end
|
78
|
+
|
79
|
+
def support_tools_list_changed
|
80
|
+
support_tools
|
81
|
+
@tools[:listChanged] = true
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_h
|
85
|
+
{
|
86
|
+
completions: @completions,
|
87
|
+
experimental: @experimental,
|
88
|
+
logging: @logging,
|
89
|
+
prompts: @prompts,
|
90
|
+
resources: @resources,
|
91
|
+
tools: @tools,
|
92
|
+
}.compact
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../transport"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module MCP
|
7
|
+
class Server
|
8
|
+
module Transports
|
9
|
+
class StdioTransport < Transport
|
10
|
+
STATUS_INTERRUPTED = Signal.list["INT"] + 128
|
11
|
+
|
12
|
+
def initialize(server)
|
13
|
+
@server = server
|
14
|
+
@open = false
|
15
|
+
$stdin.set_encoding("UTF-8")
|
16
|
+
$stdout.set_encoding("UTF-8")
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def open
|
21
|
+
@open = true
|
22
|
+
while @open && (line = $stdin.gets)
|
23
|
+
handle_json_request(line.strip)
|
24
|
+
end
|
25
|
+
rescue Interrupt
|
26
|
+
warn("\nExiting...")
|
27
|
+
|
28
|
+
exit(STATUS_INTERRUPTED)
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
@open = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def send_response(message)
|
36
|
+
json_message = message.is_a?(String) ? message : JSON.generate(message)
|
37
|
+
$stdout.puts(json_message)
|
38
|
+
$stdout.flush
|
39
|
+
end
|
40
|
+
|
41
|
+
def send_notification(method, params = nil)
|
42
|
+
notification = {
|
43
|
+
jsonrpc: "2.0",
|
44
|
+
method: method,
|
45
|
+
}
|
46
|
+
notification[:params] = params if params
|
47
|
+
|
48
|
+
send_response(notification)
|
49
|
+
true
|
50
|
+
rescue => e
|
51
|
+
MCP.configuration.exception_reporter.call(e, { error: "Failed to send notification" })
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|