From eb5e54b116c16a6b25a2f70a79adf3bfea3dd33d Mon Sep 17 00:00:00 2001 From: Szymon Kurcab Date: Sun, 15 Feb 2026 00:01:37 +0100 Subject: [PATCH] completion context: implement for MCP 2025-11-25 --- lib/mcp_client/client.rb | 6 +- lib/mcp_client/server_http.rb | 7 +- lib/mcp_client/server_sse.rb | 7 +- lib/mcp_client/server_stdio.rb | 7 +- lib/mcp_client/server_streamable_http.rb | 7 +- spec/lib/mcp_client/completion_spec.rb | 92 +++++++++++++++++++++++- 6 files changed, 114 insertions(+), 12 deletions(-) diff --git a/lib/mcp_client/client.rb b/lib/mcp_client/client.rb index 2a604c7..5310937 100644 --- a/lib/mcp_client/client.rb +++ b/lib/mcp_client/client.rb @@ -486,13 +486,15 @@ def send_notification(method, params: {}, server: nil) # Request completion suggestions from a server (MCP 2025-06-18) # @param ref [Hash] reference object (e.g., { 'type' => 'ref/prompt', 'name' => 'prompt_name' }) # @param argument [Hash] the argument being completed (e.g., { 'name' => 'arg_name', 'value' => 'partial' }) + # @param context [Hash, nil] optional context for the completion (MCP 2025-11-25), + # e.g., { 'arguments' => { 'arg1' => 'value1' } } for previously-resolved arguments # @param server [Integer, String, Symbol, MCPClient::ServerBase, nil] server selector # @return [Hash] completion result with 'values', optional 'total', and 'hasMore' fields # @raise [MCPClient::Errors::ServerNotFound] if no server is available # @raise [MCPClient::Errors::ServerError] if server returns an error - def complete(ref:, argument:, server: nil) + def complete(ref:, argument:, context: nil, server: nil) srv = select_server(server) - srv.complete(ref: ref, argument: argument) + srv.complete(ref: ref, argument: argument, context: context) end # Set the logging level on all connected servers (MCP 2025-06-18) diff --git a/lib/mcp_client/server_http.rb b/lib/mcp_client/server_http.rb index 75dfe72..e633063 100644 --- a/lib/mcp_client/server_http.rb +++ b/lib/mcp_client/server_http.rb @@ -325,10 +325,13 @@ def read_resource(uri) # Request completion suggestions from the server (MCP 2025-06-18) # @param ref [Hash] reference to complete (prompt or resource) # @param argument [Hash] the argument being completed (e.g., { 'name' => 'arg_name', 'value' => 'partial' }) + # @param context [Hash, nil] optional context for the completion (MCP 2025-11-25) # @return [Hash] completion result with 'values', optional 'total', and 'hasMore' fields # @raise [MCPClient::Errors::ServerError] if server returns an error - def complete(ref:, argument:) - result = rpc_request('completion/complete', { ref: ref, argument: argument }) + def complete(ref:, argument:, context: nil) + params = { ref: ref, argument: argument } + params[:context] = context if context + result = rpc_request('completion/complete', params) result['completion'] || { 'values' => [] } rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError raise diff --git a/lib/mcp_client/server_sse.rb b/lib/mcp_client/server_sse.rb index 2cef201..7b08ee5 100644 --- a/lib/mcp_client/server_sse.rb +++ b/lib/mcp_client/server_sse.rb @@ -330,10 +330,13 @@ def call_tool(tool_name, parameters) # Request completion suggestions from the server (MCP 2025-06-18) # @param ref [Hash] reference object (e.g., { 'type' => 'ref/prompt', 'name' => 'prompt_name' }) # @param argument [Hash] the argument being completed (e.g., { 'name' => 'arg_name', 'value' => 'partial' }) + # @param context [Hash, nil] optional context for the completion (MCP 2025-11-25) # @return [Hash] completion result with 'values', optional 'total', and 'hasMore' fields # @raise [MCPClient::Errors::ServerError] if server returns an error - def complete(ref:, argument:) - result = rpc_request('completion/complete', { ref: ref, argument: argument }) + def complete(ref:, argument:, context: nil) + params = { ref: ref, argument: argument } + params[:context] = context if context + result = rpc_request('completion/complete', params) result['completion'] || { 'values' => [] } rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError raise diff --git a/lib/mcp_client/server_stdio.rb b/lib/mcp_client/server_stdio.rb index 286cf3b..ae43c44 100644 --- a/lib/mcp_client/server_stdio.rb +++ b/lib/mcp_client/server_stdio.rb @@ -345,16 +345,19 @@ def call_tool(tool_name, parameters) # Request completion suggestions from the server (MCP 2025-06-18) # @param ref [Hash] reference object (e.g., { 'type' => 'ref/prompt', 'name' => 'prompt_name' }) # @param argument [Hash] the argument being completed (e.g., { 'name' => 'arg_name', 'value' => 'partial' }) + # @param context [Hash, nil] optional context for the completion (MCP 2025-11-25) # @return [Hash] completion result with 'values', optional 'total', and 'hasMore' fields # @raise [MCPClient::Errors::ServerError] if server returns an error - def complete(ref:, argument:) + def complete(ref:, argument:, context: nil) ensure_initialized req_id = next_id + params = { 'ref' => ref, 'argument' => argument } + params['context'] = context if context req = { 'jsonrpc' => '2.0', 'id' => req_id, 'method' => 'completion/complete', - 'params' => { 'ref' => ref, 'argument' => argument } + 'params' => params } send_request(req) res = wait_response(req_id) diff --git a/lib/mcp_client/server_streamable_http.rb b/lib/mcp_client/server_streamable_http.rb index 4950ce2..a041376 100644 --- a/lib/mcp_client/server_streamable_http.rb +++ b/lib/mcp_client/server_streamable_http.rb @@ -222,10 +222,13 @@ def call_tool_streaming(tool_name, parameters) # Request completion suggestions from the server (MCP 2025-06-18) # @param ref [Hash] reference object (e.g., { 'type' => 'ref/prompt', 'name' => 'prompt_name' }) # @param argument [Hash] the argument being completed (e.g., { 'name' => 'arg_name', 'value' => 'partial' }) + # @param context [Hash, nil] optional context for the completion (MCP 2025-11-25) # @return [Hash] completion result with 'values', optional 'total', and 'hasMore' fields # @raise [MCPClient::Errors::ServerError] if server returns an error - def complete(ref:, argument:) - result = rpc_request('completion/complete', { ref: ref, argument: argument }) + def complete(ref:, argument:, context: nil) + params = { ref: ref, argument: argument } + params[:context] = context if context + result = rpc_request('completion/complete', params) result['completion'] || { 'values' => [] } rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError raise diff --git a/spec/lib/mcp_client/completion_spec.rb b/spec/lib/mcp_client/completion_spec.rb index 292f8a0..7bb8bb9 100644 --- a/spec/lib/mcp_client/completion_spec.rb +++ b/spec/lib/mcp_client/completion_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Completion (MCP 2025-06-18)' do +RSpec.describe 'Completion (MCP 2025-06-18 / 2025-11-25 context)' do let(:mock_server) { instance_double(MCPClient::ServerStdio, name: 'stdio-server') } before do @@ -37,7 +37,7 @@ result = client.complete(ref: ref, argument: argument) - expect(mock_server).to have_received(:complete).with(ref: ref, argument: argument) + expect(mock_server).to have_received(:complete).with(ref: ref, argument: argument, context: nil) expect(result['values']).to eq(%w[python pytho]) end @@ -49,6 +49,15 @@ expect(mock_server).to have_received(:complete) end + it 'passes context to the server when provided' do + context = { 'arguments' => { 'language' => 'python' } } + allow(mock_server).to receive(:complete).and_return(completion_result) + + client.complete(ref: ref, argument: argument, context: context) + + expect(mock_server).to have_received(:complete).with(ref: ref, argument: argument, context: context) + end + context 'when no server is available' do let(:empty_client) { described_class.new(mcp_server_configs: []) } @@ -113,6 +122,28 @@ server.complete(ref: ref, argument: argument) end.to raise_error(MCPClient::Errors::ServerError) end + + it 'includes context in params when provided' do + context = { 'arguments' => { 'language' => 'python' } } + server.complete(ref: ref, argument: argument, context: context) + + expect(server).to have_received(:send_request).with( + hash_including( + 'method' => 'completion/complete', + 'params' => { 'ref' => ref, 'argument' => argument, 'context' => context } + ) + ) + end + + it 'omits context from params when not provided' do + server.complete(ref: ref, argument: argument) + + expect(server).to have_received(:send_request).with( + hash_including( + 'params' => { 'ref' => ref, 'argument' => argument } + ) + ) + end end end @@ -153,6 +184,25 @@ expect(result['values']).to eq([]) end + + it 'includes context in rpc_request when provided' do + context = { 'arguments' => { 'path' => '/home' } } + server.complete(ref: ref, argument: argument, context: context) + + expect(server).to have_received(:rpc_request).with( + 'completion/complete', + { ref: ref, argument: argument, context: context } + ) + end + + it 'omits context from rpc_request when not provided' do + server.complete(ref: ref, argument: argument) + + expect(server).to have_received(:rpc_request).with( + 'completion/complete', + { ref: ref, argument: argument } + ) + end end end @@ -192,6 +242,25 @@ expect(result['values']).to eq([]) end + + it 'includes context in rpc_request when provided' do + context = { 'arguments' => { 'style' => 'casual' } } + server.complete(ref: ref, argument: argument, context: context) + + expect(server).to have_received(:rpc_request).with( + 'completion/complete', + { ref: ref, argument: argument, context: context } + ) + end + + it 'omits context from rpc_request when not provided' do + server.complete(ref: ref, argument: argument) + + expect(server).to have_received(:rpc_request).with( + 'completion/complete', + { ref: ref, argument: argument } + ) + end end end @@ -231,6 +300,25 @@ expect(result['values']).to eq([]) end + + it 'includes context in rpc_request when provided' do + context = { 'arguments' => { 'language' => 'english' } } + server.complete(ref: ref, argument: argument, context: context) + + expect(server).to have_received(:rpc_request).with( + 'completion/complete', + { ref: ref, argument: argument, context: context } + ) + end + + it 'omits context from rpc_request when not provided' do + server.complete(ref: ref, argument: argument) + + expect(server).to have_received(:rpc_request).with( + 'completion/complete', + { ref: ref, argument: argument } + ) + end end end end