ask-mcp 0.4.0 → 0.4.2
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/CHANGELOG.md +119 -0
- data/README.md +104 -175
- data/docs/SPEC_COMPLIANCE.md +184 -0
- data/lib/ask/mcp/adapters/tool_server.rb +33 -27
- data/lib/ask/mcp/auth/client_id_metadata_document.rb +74 -0
- data/lib/ask/mcp/auth/oauth.rb +52 -4
- data/lib/ask/mcp/client.rb +237 -23
- data/lib/ask/mcp/native/messages.rb +23 -2
- data/lib/ask/mcp/prompt.rb +9 -3
- data/lib/ask/mcp/resource.rb +9 -3
- data/lib/ask/mcp/server/stdio.rb +194 -4
- data/lib/ask/mcp/server.rb +7 -2
- data/lib/ask/mcp/tool.rb +11 -4
- data/lib/ask/mcp/trace_context.rb +39 -0
- data/lib/ask/mcp/transport/sse.rb +5 -1
- data/lib/ask/mcp/transport/stdio.rb +1 -1
- data/lib/ask/mcp/transport/streamable_http.rb +108 -26
- data/lib/ask/mcp/validator.rb +19 -1
- data/lib/ask/mcp/version.rb +1 -1
- data/lib/ask/mcp/x_mcp_header.rb +95 -0
- data/lib/ask/mcp.rb +15 -0
- metadata +19 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module MCP
|
|
5
|
+
# Validation of `x-mcp-header` annotations in tool inputSchemas
|
|
6
|
+
# (2026-07-28, SEP-2243).
|
|
7
|
+
#
|
|
8
|
+
# Servers MAY designate tool parameters to be mirrored into HTTP headers
|
|
9
|
+
# via an `x-mcp-header` extension property. Clients using the Streamable
|
|
10
|
+
# HTTP transport MUST reject a tool definition whose annotations violate
|
|
11
|
+
# the constraints below (rejection = exclude the tool from tools/list);
|
|
12
|
+
# clients on other transports (stdio, SSE) MAY ignore the annotations.
|
|
13
|
+
module XMcpHeader
|
|
14
|
+
# RFC 9110 field-name token characters (1*tchar).
|
|
15
|
+
TOKEN_RE = /\A[!#$%&'*+\-.^_`|~0-9A-Za-z]+\z/
|
|
16
|
+
# Primitive types that may carry an x-mcp-header annotation.
|
|
17
|
+
PRIMITIVE_TYPES = %w[string integer boolean].freeze
|
|
18
|
+
# JavaScript safe integer range.
|
|
19
|
+
JS_SAFE_MIN = -(2**53) + 1
|
|
20
|
+
JS_SAFE_MAX = 2**53 - 1
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# Returns the reason the tool definition's x-mcp-header annotations are
|
|
25
|
+
# invalid, or nil if the definition is valid (or has no annotations).
|
|
26
|
+
def invalid_reason(schema)
|
|
27
|
+
seen = {}
|
|
28
|
+
find_invalid(schema, seen, tainted: false)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Whether an integer value is representable as a safe header value.
|
|
32
|
+
def safe_integer?(value)
|
|
33
|
+
value.is_a?(Integer) && value.between?(JS_SAFE_MIN, JS_SAFE_MAX)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Depth-first walk. An annotation is valid only if the chain from the
|
|
37
|
+
# schema root to it passes solely through `properties` keys; every other
|
|
38
|
+
# container keyword (items, oneOf/anyOf/allOf/not, if/then/else, $ref,
|
|
39
|
+
# $defs, ...) taints the path, which is exactly the spec's "statically
|
|
40
|
+
# reachable" rule.
|
|
41
|
+
def find_invalid(node, seen, tainted:)
|
|
42
|
+
return nil unless node.is_a?(Hash)
|
|
43
|
+
|
|
44
|
+
annotation = node[:'x-mcp-header'] || node["x-mcp-header"]
|
|
45
|
+
if annotation
|
|
46
|
+
return "x-mcp-header must not appear under non-properties keywords" if tainted
|
|
47
|
+
|
|
48
|
+
reason = annotation_error(annotation, node)
|
|
49
|
+
return reason if reason
|
|
50
|
+
|
|
51
|
+
key = annotation.to_s.downcase
|
|
52
|
+
return "duplicate x-mcp-header #{annotation.inspect} (case-insensitive)" if seen[key]
|
|
53
|
+
|
|
54
|
+
seen[key] = true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
node.each do |key, value|
|
|
58
|
+
if key.to_s == "properties" && value.is_a?(Hash)
|
|
59
|
+
# The allowed step: descending through a `properties` key does not
|
|
60
|
+
# taint the chain.
|
|
61
|
+
value.each_value do |sub|
|
|
62
|
+
reason = find_invalid(sub, seen, tainted: tainted)
|
|
63
|
+
return reason if reason
|
|
64
|
+
end
|
|
65
|
+
elsif value.is_a?(Hash)
|
|
66
|
+
reason = find_invalid(value, seen, tainted: true)
|
|
67
|
+
return reason if reason
|
|
68
|
+
elsif value.is_a?(Array)
|
|
69
|
+
value.each do |item|
|
|
70
|
+
next unless item.is_a?(Hash)
|
|
71
|
+
reason = find_invalid(item, seen, tainted: true)
|
|
72
|
+
return reason if reason
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def annotation_error(header_name, prop)
|
|
81
|
+
name = header_name.to_s
|
|
82
|
+
return "x-mcp-header must not be empty" if name.empty?
|
|
83
|
+
return "x-mcp-header #{name.inspect} contains invalid characters" unless name.match?(TOKEN_RE)
|
|
84
|
+
|
|
85
|
+
type = prop[:type] || prop["type"]
|
|
86
|
+
unless PRIMITIVE_TYPES.include?(type.to_s)
|
|
87
|
+
return "x-mcp-header #{name.inspect} must be on a primitive type (string/integer/boolean), got #{type.inspect}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
private_class_method :find_invalid, :annotation_error
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/ask/mcp.rb
CHANGED
|
@@ -4,12 +4,26 @@ require_relative "mcp/version"
|
|
|
4
4
|
|
|
5
5
|
module Ask
|
|
6
6
|
module MCP
|
|
7
|
+
# The MCP protocol revision this gem implements and advertises.
|
|
8
|
+
# Single source of truth — Client and Server::Stdio both reference this
|
|
9
|
+
# so the advertised version can never drift between the two sides.
|
|
10
|
+
#
|
|
11
|
+
# `PROTOCOL_VERSION` is the legacy default (stateful `initialize`
|
|
12
|
+
# handshake). `SUPPORTED_PROTOCOL_VERSIONS` is what the server advertises
|
|
13
|
+
# via `server/discover`; `LATEST_PROTOCOL_VERSION` is the stateless
|
|
14
|
+
# 2026-07-28 revision that clients and servers negotiate to.
|
|
15
|
+
PROTOCOL_VERSION = "2025-06-18"
|
|
16
|
+
LATEST_PROTOCOL_VERSION = "2026-07-28"
|
|
17
|
+
SUPPORTED_PROTOCOL_VERSIONS = [PROTOCOL_VERSION, "2025-11-25", LATEST_PROTOCOL_VERSION].freeze
|
|
18
|
+
|
|
7
19
|
autoload :Client, "ask/mcp/client"
|
|
8
20
|
autoload :Server, "ask/mcp/server"
|
|
9
21
|
autoload :Tool, "ask/mcp/tool"
|
|
10
22
|
autoload :Resource, "ask/mcp/resource"
|
|
11
23
|
autoload :Prompt, "ask/mcp/prompt"
|
|
12
24
|
autoload :Validator, "ask/mcp/validator"
|
|
25
|
+
autoload :XMcpHeader, "ask/mcp/x_mcp_header"
|
|
26
|
+
autoload :TraceContext, "ask/mcp/trace_context"
|
|
13
27
|
|
|
14
28
|
module Native
|
|
15
29
|
autoload :Messages, "ask/mcp/native/messages"
|
|
@@ -24,6 +38,7 @@ module Ask
|
|
|
24
38
|
module Auth
|
|
25
39
|
autoload :OAuth, "ask/mcp/auth/oauth"
|
|
26
40
|
autoload :Token, "ask/mcp/auth/token"
|
|
41
|
+
autoload :ClientIdMetadataDocument, "ask/mcp/auth/client_id_metadata_document"
|
|
27
42
|
end
|
|
28
43
|
|
|
29
44
|
module Adapters
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '5.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: base64
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: minitest
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -91,11 +105,13 @@ files:
|
|
|
91
105
|
- CHANGELOG.md
|
|
92
106
|
- LICENSE
|
|
93
107
|
- README.md
|
|
108
|
+
- docs/SPEC_COMPLIANCE.md
|
|
94
109
|
- docs/auth-setup.md
|
|
95
110
|
- lib/ask-mcp.rb
|
|
96
111
|
- lib/ask/mcp.rb
|
|
97
112
|
- lib/ask/mcp/adapters/ask_tool.rb
|
|
98
113
|
- lib/ask/mcp/adapters/tool_server.rb
|
|
114
|
+
- lib/ask/mcp/auth/client_id_metadata_document.rb
|
|
99
115
|
- lib/ask/mcp/auth/oauth.rb
|
|
100
116
|
- lib/ask/mcp/auth/token.rb
|
|
101
117
|
- lib/ask/mcp/client.rb
|
|
@@ -105,11 +121,13 @@ files:
|
|
|
105
121
|
- lib/ask/mcp/server.rb
|
|
106
122
|
- lib/ask/mcp/server/stdio.rb
|
|
107
123
|
- lib/ask/mcp/tool.rb
|
|
124
|
+
- lib/ask/mcp/trace_context.rb
|
|
108
125
|
- lib/ask/mcp/transport/sse.rb
|
|
109
126
|
- lib/ask/mcp/transport/stdio.rb
|
|
110
127
|
- lib/ask/mcp/transport/streamable_http.rb
|
|
111
128
|
- lib/ask/mcp/validator.rb
|
|
112
129
|
- lib/ask/mcp/version.rb
|
|
130
|
+
- lib/ask/mcp/x_mcp_header.rb
|
|
113
131
|
homepage: https://github.com/ask-rb/ask-mcp
|
|
114
132
|
licenses:
|
|
115
133
|
- MIT
|