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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 363a48c785a072c9ba3c744969cb93de55d2514c0e68008873a9bb7312fd8fb0
4
- data.tar.gz: c3f27a9e3a246c1742cd101415b286f7096dfeed8e52bb618e604b7e5127060b
3
+ metadata.gz: dbd198a0ec695b49e09abc0435889275eb2d8d56197496bad51fcdc7698582d8
4
+ data.tar.gz: 3d2e0d358be8c253b6996977d8687ed6b46509e4d33a16f5c74f76e5ca25a836
5
5
  SHA512:
6
- metadata.gz: 58097086aeed03bd87cb0d10ae8b554b2963823300f1d70f85f8efd7ddcaf5d195eaf2e397edf5cad03fc98966fa754fe0eb489251bc3e3c552178d50d5cc292
7
- data.tar.gz: 581abee68889b126bb28291ad58329fab0b7d5c48e7bef6be0860a766105fde7afe633d5ca72e715defe35721268f7572dc4e35421b3050c6d2be752cbf6c2f4
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, or a value the method requires and the call left out, comes back as a tool
69
- error carrying the reason, with `isError` set. A tool the caller may not call, a tool that does
70
- not exist, and a tool naming a hub the host does not serve are all answered with the same
71
- JSON-RPC error, code -32602, `Unknown tool: <name>`.
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
- render json: { jsonrpc: "2.0", id: params[:id], error: { code: -32602, message: "Unknown tool: #{tool_name}" } }
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
- { content: [ { type: "text", text: refusal.message } ], isError: true }
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
@@ -1,3 +1,4 @@
1
1
  HubKernel::Mcp::Engine.routes.draw do
2
2
  post "/", to: "messages#create"
3
+ get "/", to: "messages#refuse_get"
3
4
  end
@@ -1,5 +1,5 @@
1
1
  module HubKernel
2
2
  module Mcp
3
- VERSION = "0.4.0"
3
+ VERSION = "0.6.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hub_kernel-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tylercschneider