mcp 1.6.1 → 1.8.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 +1 -1
- data/lib/mcp/client/http.rb +105 -18
- data/lib/mcp/client/oauth/flow.rb +276 -23
- data/lib/mcp/client/oauth/in_memory_storage.rb +74 -1
- data/lib/mcp/client/oauth/provider.rb +77 -6
- data/lib/mcp/client/stdio.rb +23 -4
- data/lib/mcp/icon.rb +61 -0
- data/lib/mcp/prompt.rb +1 -1
- data/lib/mcp/resource.rb +2 -2
- data/lib/mcp/resource_template.rb +2 -2
- data/lib/mcp/server/oauth/access_token.rb +89 -0
- data/lib/mcp/server/oauth/authenticator.rb +150 -0
- data/lib/mcp/server/oauth/challenge.rb +114 -0
- data/lib/mcp/server/oauth/errors.rb +58 -0
- data/lib/mcp/server/oauth/introspection_verifier.rb +151 -0
- data/lib/mcp/server/oauth/jwt_verifier.rb +330 -0
- data/lib/mcp/server/oauth/middleware.rb +60 -0
- data/lib/mcp/server/oauth/protected_resource_metadata.rb +138 -0
- data/lib/mcp/server/oauth/protected_resource_metadata_middleware.rb +86 -0
- data/lib/mcp/server/oauth/token_verifier.rb +135 -0
- data/lib/mcp/server/oauth.rb +29 -0
- data/lib/mcp/server/transports/streamable_http_transport.rb +712 -160
- data/lib/mcp/server.rb +63 -31
- data/lib/mcp/server_context.rb +26 -2
- data/lib/mcp/server_session.rb +14 -4
- data/lib/mcp/tool.rb +1 -1
- data/lib/mcp/version.rb +1 -1
- metadata +13 -2
data/lib/mcp/server.rb
CHANGED
|
@@ -13,6 +13,7 @@ require_relative "protocol_deprecations"
|
|
|
13
13
|
require_relative "server_context"
|
|
14
14
|
require_relative "server/capabilities"
|
|
15
15
|
require_relative "server/input_required_result"
|
|
16
|
+
require_relative "server/oauth"
|
|
16
17
|
require_relative "server/pagination"
|
|
17
18
|
require_relative "server/pending_response"
|
|
18
19
|
require_relative "server/request_state_security"
|
|
@@ -181,8 +182,13 @@ module MCP
|
|
|
181
182
|
Methods::RESOURCES_READ,
|
|
182
183
|
].freeze
|
|
183
184
|
|
|
184
|
-
attr_accessor :description, :
|
|
185
|
-
attr_reader :resources, :page_size, :client_capabilities, :ttl_ms, :cache_scope, :request_state_security
|
|
185
|
+
attr_accessor :description, :name, :title, :version, :website_url, :instructions, :tools, :prompts, :resource_templates, :server_context, :configuration, :capabilities, :transport, :logging_message_notification
|
|
186
|
+
attr_reader :icons, :resources, :page_size, :client_capabilities, :ttl_ms, :cache_scope, :request_state_security
|
|
187
|
+
|
|
188
|
+
# Replaces the icons advertised in `serverInfo`; a Hash is converted through `Icon.from`.
|
|
189
|
+
def icons=(value)
|
|
190
|
+
@icons = Icon.from_list(value)
|
|
191
|
+
end
|
|
186
192
|
|
|
187
193
|
def initialize(
|
|
188
194
|
description: nil,
|
|
@@ -207,7 +213,7 @@ module MCP
|
|
|
207
213
|
transport: nil
|
|
208
214
|
)
|
|
209
215
|
@description = description
|
|
210
|
-
@icons = icons
|
|
216
|
+
@icons = Icon.from_list(icons)
|
|
211
217
|
@name = name
|
|
212
218
|
@title = title
|
|
213
219
|
@version = version
|
|
@@ -274,10 +280,13 @@ module MCP
|
|
|
274
280
|
# @param session [ServerSession, nil] Per-connection session. Passed by
|
|
275
281
|
# `ServerSession#handle` for session-scoped notification delivery.
|
|
276
282
|
# When `nil`, progress and logging notifications from tool handlers are silently skipped.
|
|
283
|
+
# @param auth_info [Server::OAuth::AccessToken, nil] The verified access token
|
|
284
|
+
# for the current request, passed by the transport layer and exposed to
|
|
285
|
+
# handlers as `server_context.auth_info`.
|
|
277
286
|
# @return [Hash, nil] The JSON-RPC response, or `nil` for notifications.
|
|
278
|
-
def handle(request, session: nil)
|
|
287
|
+
def handle(request, session: nil, auth_info: nil)
|
|
279
288
|
JsonRpcHandler.handle(request) do |method, request_id|
|
|
280
|
-
handle_request(request, method, session: session, related_request_id: request_id)
|
|
289
|
+
handle_request(request, method, session: session, related_request_id: request_id, auth_info: auth_info)
|
|
281
290
|
end
|
|
282
291
|
end
|
|
283
292
|
|
|
@@ -287,10 +296,12 @@ module MCP
|
|
|
287
296
|
# @param session [ServerSession, nil] Per-connection session. Passed by
|
|
288
297
|
# `ServerSession#handle_json` for session-scoped notification delivery.
|
|
289
298
|
# When `nil`, progress and logging notifications from tool handlers are silently skipped.
|
|
299
|
+
# @param auth_info [Server::OAuth::AccessToken, nil] The verified access token
|
|
300
|
+
# for the current request, as in `handle`.
|
|
290
301
|
# @return [String, nil] The JSON-RPC response as JSON, or `nil` for notifications.
|
|
291
|
-
def handle_json(request, session: nil)
|
|
302
|
+
def handle_json(request, session: nil, auth_info: nil)
|
|
292
303
|
JsonRpcHandler.handle_json(request) do |method, request_id|
|
|
293
|
-
handle_request(request, method, session: session, related_request_id: request_id)
|
|
304
|
+
handle_request(request, method, session: session, related_request_id: request_id, auth_info: auth_info)
|
|
294
305
|
end
|
|
295
306
|
end
|
|
296
307
|
|
|
@@ -595,7 +606,7 @@ module MCP
|
|
|
595
606
|
end
|
|
596
607
|
end
|
|
597
608
|
|
|
598
|
-
def handle_request(request, method, session: nil, related_request_id: nil)
|
|
609
|
+
def handle_request(request, method, session: nil, related_request_id: nil, auth_info: nil)
|
|
599
610
|
# A well-formed notification carries no JSON-RPC id and receives no response.
|
|
600
611
|
# If a client erroneously sends a notification-only method with an id, the message
|
|
601
612
|
# is framed as a request; since notification methods have no request handler,
|
|
@@ -686,25 +697,25 @@ module MCP
|
|
|
686
697
|
when Methods::INITIALIZE
|
|
687
698
|
init(params, session: session)
|
|
688
699
|
when Methods::RESOURCES_READ
|
|
689
|
-
contents = read_resource_contents(params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope)
|
|
700
|
+
contents = read_resource_contents(params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope, auth_info: auth_info)
|
|
690
701
|
|
|
691
702
|
# An SEP-2322 `input_required` result must not be wrapped as `contents` or stamped with SEP-2549 cache hints.
|
|
692
703
|
contents.is_a?(InputRequiredResult) ? contents : build_read_resource_result(contents)
|
|
693
704
|
when Methods::RESOURCES_SUBSCRIBE, Methods::RESOURCES_UNSUBSCRIBE
|
|
694
705
|
validate_resource_subscription_params!(params)
|
|
695
|
-
handler_result = dispatch_optional_context_handler(@handlers[method], params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope)
|
|
706
|
+
handler_result = dispatch_optional_context_handler(@handlers[method], params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope, auth_info: auth_info)
|
|
696
707
|
|
|
697
708
|
subscription_result(handler_result)
|
|
698
709
|
when Methods::TOOLS_CALL
|
|
699
|
-
call_tool(params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope)
|
|
710
|
+
call_tool(params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope, auth_info: auth_info)
|
|
700
711
|
when Methods::PROMPTS_GET
|
|
701
|
-
get_prompt(params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope)
|
|
712
|
+
get_prompt(params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope, auth_info: auth_info)
|
|
702
713
|
when Methods::COMPLETION_COMPLETE
|
|
703
|
-
complete(params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope)
|
|
714
|
+
complete(params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope, auth_info: auth_info)
|
|
704
715
|
when Methods::LOGGING_SET_LEVEL
|
|
705
716
|
configure_logging_level(params, session: session)
|
|
706
717
|
else
|
|
707
|
-
dispatch_optional_context_handler(@handlers[method], params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope)
|
|
718
|
+
dispatch_optional_context_handler(@handlers[method], params, session: session, related_request_id: related_request_id, cancellation: cancellation, envelope: envelope, auth_info: auth_info)
|
|
708
719
|
end
|
|
709
720
|
client = session&.client || @client
|
|
710
721
|
add_instrumentation_data(client: client) if client
|
|
@@ -755,6 +766,15 @@ module MCP
|
|
|
755
766
|
rescue CancelledError => e
|
|
756
767
|
add_instrumentation_data(cancelled: true, cancellation_reason: e.reason)
|
|
757
768
|
next JsonRpcHandler::NO_RESPONSE
|
|
769
|
+
rescue OAuth::InsufficientScopeError => e
|
|
770
|
+
# A handler's `require_scopes!` failure is a client authorization error, not a server fault:
|
|
771
|
+
# surface it as an invalid-request JSON-RPC error whose message names the missing scopes.
|
|
772
|
+
# HTTP-level 403 challenges remain the transport `required_scopes` gate's job.
|
|
773
|
+
report_exception(e, { request: request })
|
|
774
|
+
add_instrumentation_data(error: :insufficient_scope)
|
|
775
|
+
converted = RequestHandlerError.new(e.message, request, error_type: :invalid_request, original_error: e)
|
|
776
|
+
reported_exception = converted
|
|
777
|
+
raise converted
|
|
758
778
|
rescue RequestHandlerError => e
|
|
759
779
|
report_exception(e.original_error || e, { request: request })
|
|
760
780
|
add_instrumentation_data(error: e.error_type)
|
|
@@ -1219,7 +1239,7 @@ module MCP
|
|
|
1219
1239
|
apply_cache_metadata({ tools: page[:items], nextCursor: page[:next_cursor] }.compact)
|
|
1220
1240
|
end
|
|
1221
1241
|
|
|
1222
|
-
def call_tool(request, session: nil, related_request_id: nil, cancellation: nil, envelope: nil)
|
|
1242
|
+
def call_tool(request, session: nil, related_request_id: nil, cancellation: nil, envelope: nil, auth_info: nil)
|
|
1223
1243
|
tool_name = request[:name]
|
|
1224
1244
|
|
|
1225
1245
|
tool = tools[tool_name]
|
|
@@ -1254,12 +1274,13 @@ module MCP
|
|
|
1254
1274
|
response = call_tool_with_args(
|
|
1255
1275
|
tool,
|
|
1256
1276
|
arguments,
|
|
1257
|
-
server_context_with_meta(request),
|
|
1277
|
+
server_context_with_meta(request, auth_info: auth_info),
|
|
1258
1278
|
progress_token: progress_token,
|
|
1259
1279
|
session: session,
|
|
1260
1280
|
related_request_id: related_request_id,
|
|
1261
1281
|
cancellation: cancellation,
|
|
1262
1282
|
envelope: envelope,
|
|
1283
|
+
auth_info: auth_info,
|
|
1263
1284
|
retry_fields: mrtr_retry_fields(request),
|
|
1264
1285
|
)
|
|
1265
1286
|
# An SEP-2322 `input_required` result is not a tool result: output schema
|
|
@@ -1273,9 +1294,11 @@ module MCP
|
|
|
1273
1294
|
result,
|
|
1274
1295
|
content_provided: response.respond_to?(:content_provided?) && response.content_provided?,
|
|
1275
1296
|
)
|
|
1276
|
-
rescue RequestHandlerError, CancelledError
|
|
1297
|
+
rescue RequestHandlerError, CancelledError, OAuth::InsufficientScopeError
|
|
1277
1298
|
# CancelledError is intentionally not wrapped so `handle_request` can turn it into
|
|
1278
|
-
# `JsonRpcHandler::NO_RESPONSE` per the MCP cancellation spec
|
|
1299
|
+
# `JsonRpcHandler::NO_RESPONSE` per the MCP cancellation spec, and
|
|
1300
|
+
# `OAuth::InsufficientScopeError` so `require_scopes!` failures keep their
|
|
1301
|
+
# scope-naming message instead of the generic internal-error shape.
|
|
1279
1302
|
raise
|
|
1280
1303
|
rescue => e
|
|
1281
1304
|
# `e.message` is deliberately not included: it can carry internals (class, method
|
|
@@ -1295,7 +1318,7 @@ module MCP
|
|
|
1295
1318
|
apply_cache_metadata({ prompts: page[:items], nextCursor: page[:next_cursor] }.compact)
|
|
1296
1319
|
end
|
|
1297
1320
|
|
|
1298
|
-
def get_prompt(request, session: nil, related_request_id: nil, cancellation: nil, envelope: nil)
|
|
1321
|
+
def get_prompt(request, session: nil, related_request_id: nil, cancellation: nil, envelope: nil, auth_info: nil)
|
|
1299
1322
|
prompt_name = request[:name]
|
|
1300
1323
|
prompt = @prompts[prompt_name]
|
|
1301
1324
|
unless prompt
|
|
@@ -1323,6 +1346,7 @@ module MCP
|
|
|
1323
1346
|
related_request_id: related_request_id,
|
|
1324
1347
|
cancellation: cancellation,
|
|
1325
1348
|
envelope: envelope,
|
|
1349
|
+
auth_info: auth_info,
|
|
1326
1350
|
)
|
|
1327
1351
|
|
|
1328
1352
|
call_prompt_template_with_args(prompt, prompt_args, server_context)
|
|
@@ -1435,7 +1459,7 @@ module MCP
|
|
|
1435
1459
|
{ ttlMs: @ttl_ms || 0, cacheScope: @cache_scope || "private" }.merge(result)
|
|
1436
1460
|
end
|
|
1437
1461
|
|
|
1438
|
-
def complete(params, session: nil, related_request_id: nil, cancellation: nil, envelope: nil)
|
|
1462
|
+
def complete(params, session: nil, related_request_id: nil, cancellation: nil, envelope: nil, auth_info: nil)
|
|
1439
1463
|
validate_completion_params!(params)
|
|
1440
1464
|
|
|
1441
1465
|
result = dispatch_optional_context_handler(
|
|
@@ -1445,6 +1469,7 @@ module MCP
|
|
|
1445
1469
|
related_request_id: related_request_id,
|
|
1446
1470
|
cancellation: cancellation,
|
|
1447
1471
|
envelope: envelope,
|
|
1472
|
+
auth_info: auth_info,
|
|
1448
1473
|
)
|
|
1449
1474
|
|
|
1450
1475
|
normalize_completion_result(result)
|
|
@@ -1453,7 +1478,7 @@ module MCP
|
|
|
1453
1478
|
# Invokes `resources/read` via the registered handler. If the handler block opts in to `server_context:`,
|
|
1454
1479
|
# pass an `MCP::ServerContext` so the handler can observe cancellation via `server_context.cancelled?` or
|
|
1455
1480
|
# `server_context.raise_if_cancelled!`.
|
|
1456
|
-
def read_resource_contents(request, session: nil, related_request_id: nil, cancellation: nil, envelope: nil)
|
|
1481
|
+
def read_resource_contents(request, session: nil, related_request_id: nil, cancellation: nil, envelope: nil, auth_info: nil)
|
|
1457
1482
|
dispatch_optional_context_handler(
|
|
1458
1483
|
@handlers[Methods::RESOURCES_READ],
|
|
1459
1484
|
request,
|
|
@@ -1461,6 +1486,7 @@ module MCP
|
|
|
1461
1486
|
related_request_id: related_request_id,
|
|
1462
1487
|
cancellation: cancellation,
|
|
1463
1488
|
envelope: envelope,
|
|
1489
|
+
auth_info: auth_info,
|
|
1464
1490
|
)
|
|
1465
1491
|
end
|
|
1466
1492
|
|
|
@@ -1468,7 +1494,7 @@ module MCP
|
|
|
1468
1494
|
# `completion_handler`, `resources_subscribe_handler`, `resources_unsubscribe_handler`, or `define_custom_method`.
|
|
1469
1495
|
# Existing handlers that only accept `params` are called unchanged; handlers that declare a `server_context:`
|
|
1470
1496
|
# keyword receive an `MCP::ServerContext` wrapping the raw server context with cancellation plumbing.
|
|
1471
|
-
def dispatch_optional_context_handler(handler, params, session: nil, related_request_id: nil, cancellation: nil, envelope: nil)
|
|
1497
|
+
def dispatch_optional_context_handler(handler, params, session: nil, related_request_id: nil, cancellation: nil, envelope: nil, auth_info: nil)
|
|
1472
1498
|
return handler.call(params) unless handler_declares_server_context?(handler)
|
|
1473
1499
|
|
|
1474
1500
|
server_context = build_server_context(
|
|
@@ -1477,6 +1503,7 @@ module MCP
|
|
|
1477
1503
|
related_request_id: related_request_id,
|
|
1478
1504
|
cancellation: cancellation,
|
|
1479
1505
|
envelope: envelope,
|
|
1506
|
+
auth_info: auth_info,
|
|
1480
1507
|
)
|
|
1481
1508
|
handler.call(params, server_context: server_context)
|
|
1482
1509
|
end
|
|
@@ -1501,13 +1528,13 @@ module MCP
|
|
|
1501
1528
|
|
|
1502
1529
|
# Builds an `MCP::ServerContext` used to give a handler access to session-scoped helpers
|
|
1503
1530
|
# (progress, cancellation, nested server-to-client requests).
|
|
1504
|
-
def build_server_context(request:, session:, related_request_id:, cancellation:, envelope: nil)
|
|
1531
|
+
def build_server_context(request:, session:, related_request_id:, cancellation:, envelope: nil, auth_info: nil)
|
|
1505
1532
|
meta_source = request.is_a?(Hash) ? request : {}
|
|
1506
1533
|
progress_token = meta_source.dig(:_meta, :progressToken)
|
|
1507
1534
|
progress = Progress.new(notification_target: session, progress_token: progress_token, related_request_id: related_request_id)
|
|
1508
1535
|
retry_fields = mrtr_retry_fields(meta_source)
|
|
1509
1536
|
ServerContext.new(
|
|
1510
|
-
server_context_with_meta(meta_source),
|
|
1537
|
+
server_context_with_meta(meta_source, auth_info: auth_info),
|
|
1511
1538
|
progress: progress,
|
|
1512
1539
|
notification_target: session,
|
|
1513
1540
|
related_request_id: related_request_id,
|
|
@@ -1515,6 +1542,7 @@ module MCP
|
|
|
1515
1542
|
envelope: envelope,
|
|
1516
1543
|
input_responses: retry_fields[:input_responses],
|
|
1517
1544
|
request_state: retry_fields[:request_state],
|
|
1545
|
+
auth_info: auth_info,
|
|
1518
1546
|
)
|
|
1519
1547
|
end
|
|
1520
1548
|
|
|
@@ -1574,7 +1602,7 @@ module MCP
|
|
|
1574
1602
|
end
|
|
1575
1603
|
end
|
|
1576
1604
|
|
|
1577
|
-
def call_tool_with_args(tool, arguments, context, progress_token: nil, session: nil, related_request_id: nil, cancellation: nil, envelope: nil, retry_fields: nil)
|
|
1605
|
+
def call_tool_with_args(tool, arguments, context, progress_token: nil, session: nil, related_request_id: nil, cancellation: nil, envelope: nil, auth_info: nil, retry_fields: nil)
|
|
1578
1606
|
# Transports parse incoming JSON with `symbolize_names: true`, so `arguments` already arrives symbolized
|
|
1579
1607
|
# at every nesting level. This top-level transform only guards callers that hand in string-keyed top-level arguments;
|
|
1580
1608
|
# it does not recurse, and nested object keys remain symbols. Tools therefore receive symbol keys all the way down.
|
|
@@ -1592,6 +1620,7 @@ module MCP
|
|
|
1592
1620
|
envelope: envelope,
|
|
1593
1621
|
input_responses: retry_fields&.fetch(:input_responses, nil),
|
|
1594
1622
|
request_state: retry_fields&.fetch(:request_state, nil),
|
|
1623
|
+
auth_info: auth_info,
|
|
1595
1624
|
)
|
|
1596
1625
|
tool.call(**args, server_context: server_context)
|
|
1597
1626
|
else
|
|
@@ -1609,15 +1638,18 @@ module MCP
|
|
|
1609
1638
|
raw_result.is_a?(InputRequiredResult) ? raw_result : raw_result.to_h
|
|
1610
1639
|
end
|
|
1611
1640
|
|
|
1612
|
-
def server_context_with_meta(request)
|
|
1641
|
+
def server_context_with_meta(request, auth_info: nil)
|
|
1613
1642
|
meta = request[:_meta]
|
|
1614
|
-
if meta &&
|
|
1615
|
-
|
|
1616
|
-
|
|
1643
|
+
return server_context if meta.nil? && auth_info.nil?
|
|
1644
|
+
|
|
1645
|
+
if server_context.is_a?(Hash) || server_context.nil?
|
|
1646
|
+
context = (server_context || {}).dup
|
|
1647
|
+
context[:_meta] = meta if meta
|
|
1648
|
+
context[:auth_info] = auth_info if auth_info
|
|
1617
1649
|
context
|
|
1618
|
-
elsif meta && server_context.nil?
|
|
1619
|
-
{ _meta: meta }
|
|
1620
1650
|
else
|
|
1651
|
+
# A custom (non-Hash) user context is passed through untouched; handlers on that path read
|
|
1652
|
+
# the token via `server_context.auth_info` instead.
|
|
1621
1653
|
server_context
|
|
1622
1654
|
end
|
|
1623
1655
|
end
|
data/lib/mcp/server_context.rb
CHANGED
|
@@ -15,8 +15,11 @@ module MCP
|
|
|
15
15
|
# (the same access model as the envelope readers).
|
|
16
16
|
attr_reader :input_responses, :request_state
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
# The verified `MCP::Server::OAuth::AccessToken` for the current request, or nil when the request was not bearer-authenticated.
|
|
19
|
+
# This reader is the canonical accessor; when the underlying context is a Hash the same value is also reachable as `server_context[:auth_info]`.
|
|
20
|
+
attr_reader :auth_info
|
|
21
|
+
|
|
22
|
+
def initialize(context, progress:, notification_target:, related_request_id: nil, cancellation: nil, envelope: nil, input_responses: nil, request_state: nil, auth_info: nil)
|
|
20
23
|
@context = context
|
|
21
24
|
@progress = progress
|
|
22
25
|
@notification_target = notification_target
|
|
@@ -25,6 +28,7 @@ module MCP
|
|
|
25
28
|
@envelope = envelope
|
|
26
29
|
@input_responses = input_responses
|
|
27
30
|
@request_state = request_state
|
|
31
|
+
@auth_info = auth_info
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
# Reads one entry of {#input_responses} by its `inputRequests` key, tolerating symbol or string keys.
|
|
@@ -89,6 +93,26 @@ module MCP
|
|
|
89
93
|
raise Server::MissingRequiredClientCapabilityError, required
|
|
90
94
|
end
|
|
91
95
|
|
|
96
|
+
# Whether the current request was bearer-authenticated.
|
|
97
|
+
def authenticated?
|
|
98
|
+
!@auth_info.nil?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Guards the current operation on OAuth scopes the token must carry, for authorization decisions finer-grained than
|
|
102
|
+
# the transport-wide `required_scopes`. Raises `Server::OAuth::InsufficientScopeError`, which surfaces as
|
|
103
|
+
# a JSON-RPC invalid-request error naming the missing scopes. HTTP-level 403 step-up challenges remain the job of
|
|
104
|
+
# the transport's `required_scopes` gate, matching how the Python and TypeScript SDKs split endpoint-level challenges
|
|
105
|
+
# from in-handler authorization. The transport's `scope_matcher:` applies here as well: the authenticator attaches it
|
|
106
|
+
# to the token it hands over, so a scope hierarchy is judged the same way at the gate and in handlers.
|
|
107
|
+
def require_scopes!(*scopes)
|
|
108
|
+
raise ArgumentError, "at least one scope is required" if scopes.empty?
|
|
109
|
+
|
|
110
|
+
missing_scopes = scopes.reject { |scope| @auth_info&.scope?(scope) }
|
|
111
|
+
return if missing_scopes.empty?
|
|
112
|
+
|
|
113
|
+
raise Server::OAuth::InsufficientScopeError.new("Token is missing required scopes: #{missing_scopes.join(", ")}", required_scopes: scopes)
|
|
114
|
+
end
|
|
115
|
+
|
|
92
116
|
# Reports progress for the current tool operation.
|
|
93
117
|
# The notification is automatically scoped to the originating session.
|
|
94
118
|
#
|
data/lib/mcp/server_session.rb
CHANGED
|
@@ -120,12 +120,22 @@ module MCP
|
|
|
120
120
|
MCP.configuration.exception_reporter.call(e, { notification: "cancelled", request_id: request_id })
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
# Accepts the request either as a positional Hash or as bare keyword arguments (`session.handle(jsonrpc: "2.0", ...)`),
|
|
124
|
+
# a calling style that predates this method having keyword parameters. `auth_info:` is honored only alongside a positional request:
|
|
125
|
+
# in the bare-keyword style every keyword belongs to the request body, and the body is attacker-authored JSON,
|
|
126
|
+
# so a captured `auth_info` keyword is folded back into the request rather than trusted as a verified credential.
|
|
127
|
+
def handle(request = nil, auth_info: nil, **request_keywords)
|
|
128
|
+
if request.nil?
|
|
129
|
+
request = request_keywords
|
|
130
|
+
request[:auth_info] = auth_info unless auth_info.nil?
|
|
131
|
+
auth_info = nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
@server.handle(request, session: self, auth_info: auth_info)
|
|
125
135
|
end
|
|
126
136
|
|
|
127
|
-
def handle_json(request_json)
|
|
128
|
-
@server.handle_json(request_json, session: self)
|
|
137
|
+
def handle_json(request_json, auth_info: nil)
|
|
138
|
+
@server.handle_json(request_json, session: self, auth_info: auth_info)
|
|
129
139
|
end
|
|
130
140
|
|
|
131
141
|
# Called by `Server#init` during the initialization handshake.
|
data/lib/mcp/tool.rb
CHANGED
data/lib/mcp/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Model Context Protocol
|
|
@@ -85,6 +85,17 @@ files:
|
|
|
85
85
|
- lib/mcp/server.rb
|
|
86
86
|
- lib/mcp/server/capabilities.rb
|
|
87
87
|
- lib/mcp/server/input_required_result.rb
|
|
88
|
+
- lib/mcp/server/oauth.rb
|
|
89
|
+
- lib/mcp/server/oauth/access_token.rb
|
|
90
|
+
- lib/mcp/server/oauth/authenticator.rb
|
|
91
|
+
- lib/mcp/server/oauth/challenge.rb
|
|
92
|
+
- lib/mcp/server/oauth/errors.rb
|
|
93
|
+
- lib/mcp/server/oauth/introspection_verifier.rb
|
|
94
|
+
- lib/mcp/server/oauth/jwt_verifier.rb
|
|
95
|
+
- lib/mcp/server/oauth/middleware.rb
|
|
96
|
+
- lib/mcp/server/oauth/protected_resource_metadata.rb
|
|
97
|
+
- lib/mcp/server/oauth/protected_resource_metadata_middleware.rb
|
|
98
|
+
- lib/mcp/server/oauth/token_verifier.rb
|
|
88
99
|
- lib/mcp/server/pagination.rb
|
|
89
100
|
- lib/mcp/server/pending_response.rb
|
|
90
101
|
- lib/mcp/server/request_state_security.rb
|
|
@@ -108,7 +119,7 @@ licenses:
|
|
|
108
119
|
- Apache-2.0
|
|
109
120
|
metadata:
|
|
110
121
|
allowed_push_host: https://rubygems.org
|
|
111
|
-
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v1.
|
|
122
|
+
changelog_uri: https://github.com/modelcontextprotocol/ruby-sdk/releases/tag/v1.8.0
|
|
112
123
|
homepage_uri: https://ruby.sdk.modelcontextprotocol.io
|
|
113
124
|
source_code_uri: https://github.com/modelcontextprotocol/ruby-sdk
|
|
114
125
|
bug_tracker_uri: https://github.com/modelcontextprotocol/ruby-sdk/issues
|