hub_kernel-mcp 0.4.0 → 0.6.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/README.md +21 -4
- data/app/controllers/hub_kernel/mcp/messages_controller.rb +28 -2
- data/config/routes.rb +1 -0
- data/lib/hub_kernel/mcp/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dbd198a0ec695b49e09abc0435889275eb2d8d56197496bad51fcdc7698582d8
|
|
4
|
+
data.tar.gz: 3d2e0d358be8c253b6996977d8687ed6b46509e4d33a16f5c74f76e5ca25a836
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18a08e74dfccbf30a4db3cd6831119ce1cc5ad447a4080d1c47e5a208d1b73504f99ec42ed00b3a60dea04286679fdc6ba8713a5ad9eb5cee403104d252a2e9c
|
|
7
|
+
data.tar.gz: '028002bdb287b6828ce5e73f8130ab2b818d8710deba05335c508121d27619770848026156cb9bf93e721bd15d001faefd3bb1890dd31643ff343f6bfa8a64c0'
|
data/README.md
CHANGED
|
@@ -65,13 +65,30 @@ give, and returns the method's answer as JSON text:
|
|
|
65
65
|
{ "content": [ { "type": "text", "text": "\"soap costs 3\"" } ], "isError": false }
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
A hub's refusal,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
A hub's refusal, a value the method requires and the call left out, a value the method is not
|
|
69
|
+
listed with, and a record the call names that does not exist each come back as a tool error
|
|
70
|
+
carrying the reason, with `isError` set. The last two reasons come from hub_kernel-interface, so
|
|
71
|
+
they read the same in every interface gem, such as `price_of does not take colour` and
|
|
72
|
+
`No item has the id 9`. A tool the caller may not call, a tool that does not exist, and a tool
|
|
73
|
+
naming a hub the host does not serve are all answered with the same JSON-RPC error, code
|
|
74
|
+
-32602, `Unknown tool: <name>`, whatever values the call sends.
|
|
72
75
|
|
|
73
76
|
A caller the host's sign-in refuses is refused before any hub is asked.
|
|
74
77
|
|
|
78
|
+
A `ping` is answered with an empty result. Every other request the endpoint cannot answer gets
|
|
79
|
+
a JSON-RPC error:
|
|
80
|
+
|
|
81
|
+
| Request | Code | Message |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| A body that is not valid JSON | -32700 | `Parse error` |
|
|
84
|
+
| A body that is not one JSON-RPC request, such as a batch | -32600 | `Invalid Request` |
|
|
85
|
+
| An MCP method the endpoint does not support | -32601 | `Method not found: <method>` |
|
|
86
|
+
| An unexpected error inside a hub method | -32603 | `Internal error` |
|
|
87
|
+
|
|
88
|
+
An unexpected error's own message is never sent to the client. It is reported to the host
|
|
89
|
+
app's error reporting through `Rails.error`. A GET to the endpoint's address is answered with
|
|
90
|
+
status 405, since the endpoint offers no sessions or server-sent events.
|
|
91
|
+
|
|
75
92
|
## Installation
|
|
76
93
|
|
|
77
94
|
```ruby
|
|
@@ -2,24 +2,44 @@ module HubKernel
|
|
|
2
2
|
module Mcp
|
|
3
3
|
class MessagesController < HubKernel::Mcp.base_controller.constantize
|
|
4
4
|
UnknownTool = Class.new(StandardError)
|
|
5
|
+
UnknownMethod = Class.new(StandardError)
|
|
5
6
|
|
|
6
7
|
PROTOCOL_VERSIONS = %w[2025-11-25 2025-06-18 2025-03-26 2024-11-05].freeze
|
|
7
8
|
|
|
9
|
+
rescue_from(ActionDispatch::Http::Parameters::ParseError) { render_error(-32700, "Parse error") }
|
|
10
|
+
|
|
8
11
|
def create
|
|
12
|
+
return render_error(-32600, "Invalid Request") unless single_request?
|
|
9
13
|
return head :accepted unless params.key?(:id)
|
|
10
14
|
|
|
11
15
|
render json: { jsonrpc: "2.0", id: params[:id], result: answer }
|
|
12
16
|
rescue UnknownTool, HubKernel::UnexposedMethodError, HubKernel::NotAllowed
|
|
13
|
-
|
|
17
|
+
render_error(-32602, "Unknown tool: #{tool_name}", id: params[:id])
|
|
18
|
+
rescue UnknownMethod
|
|
19
|
+
render_error(-32601, "Method not found: #{params[:method]}", id: params[:id])
|
|
20
|
+
rescue StandardError => error
|
|
21
|
+
Rails.error.report(error, handled: true)
|
|
22
|
+
render_error(-32603, "Internal error", id: params[:id])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def refuse_get
|
|
26
|
+
response.set_header("Allow", "POST")
|
|
27
|
+
head :method_not_allowed
|
|
14
28
|
end
|
|
15
29
|
|
|
16
30
|
private
|
|
17
31
|
|
|
32
|
+
def single_request? = !params.key?(:_json) && params[:jsonrpc] == "2.0" && params[:method].is_a?(String)
|
|
33
|
+
|
|
34
|
+
def render_error(code, message, id: nil) = render(json: { jsonrpc: "2.0", id: id, error: { code: code, message: message } })
|
|
35
|
+
|
|
18
36
|
def answer
|
|
19
37
|
case params[:method]
|
|
20
38
|
when "initialize" then initialized
|
|
39
|
+
when "ping" then {}
|
|
21
40
|
when "tools/list" then { tools: tools }
|
|
22
41
|
when "tools/call" then called
|
|
42
|
+
else raise UnknownMethod
|
|
23
43
|
end
|
|
24
44
|
end
|
|
25
45
|
|
|
@@ -38,12 +58,18 @@ module HubKernel
|
|
|
38
58
|
def called
|
|
39
59
|
served_name, method_name = tool_name.split("__", 2)
|
|
40
60
|
hub = HubKernel::Interface.find(served_name) || raise(UnknownTool)
|
|
61
|
+
raise UnknownTool unless hub.exposed(method_name)
|
|
62
|
+
HubKernel::Interface::CallReasons.refuse_unlisted_values(hub, method_name, values: arguments, person: caller_person, account: caller_account)
|
|
41
63
|
answer = hub.call_exposed(method_name, values: arguments, person: caller_person, account: caller_account)
|
|
42
64
|
{ content: [ { type: "text", text: answer.to_json } ], isError: false }
|
|
43
65
|
rescue HubKernel::Refused, HubKernel::MissingArgumentError => refusal
|
|
44
|
-
|
|
66
|
+
tool_error(refusal.message)
|
|
67
|
+
rescue ActiveRecord::RecordNotFound => missing
|
|
68
|
+
tool_error(HubKernel::Interface::CallReasons.missing_record(missing))
|
|
45
69
|
end
|
|
46
70
|
|
|
71
|
+
def tool_error(reason) = { content: [ { type: "text", text: reason } ], isError: true }
|
|
72
|
+
|
|
47
73
|
def tool_name = params.dig(:params, :name).to_s
|
|
48
74
|
|
|
49
75
|
def arguments = params.dig(:params, :arguments)&.to_unsafe_h.to_h.deep_symbolize_keys
|
data/config/routes.rb
CHANGED