From 20b39cc8c532fc68229267ae8577cfcdaab3edba Mon Sep 17 00:00:00 2001 From: JR Boos Date: Wed, 4 Mar 2026 16:47:33 -0500 Subject: [PATCH 1/4] Update lightspeed-stack configuration and enhance MCP OAuth handling - Changed the Llama stack URL to localhost for local development. - Added MCP server configuration with authorization headers. - Introduced a new utility function to build request bodies for the Responses API, ensuring proper authorization handling. - Updated response retrieval methods to utilize the new utility function. - Enhanced the MCP OAuth probing function to conditionally include authorization headers and only raise exceptions for 401 responses. - Modified the mock MCP server to handle authorization checks more effectively. --- lightspeed-stack.yaml | 10 ++++-- src/app/endpoints/query.py | 3 +- src/app/endpoints/streaming_query.py | 3 +- src/app/endpoints/tools.py | 4 +-- src/utils/mcp_oauth_probe.py | 32 +++++++++++-------- src/utils/responses.py | 47 ++++++++++++++++++++++------ tests/e2e/features/mcp.feature | 8 +---- tests/e2e/mock_mcp_server/server.py | 12 ++++--- tests/unit/utils/test_responses.py | 1 + 9 files changed, 79 insertions(+), 41 deletions(-) diff --git a/lightspeed-stack.yaml b/lightspeed-stack.yaml index 98b2555a8..d8f7bfe5c 100644 --- a/lightspeed-stack.yaml +++ b/lightspeed-stack.yaml @@ -14,7 +14,7 @@ llama_stack: # Alternative for "as library use" # use_as_library_client: true # library_client_config_path: - url: http://llama-stack:8321 + url: http://localhost:8321 api_key: xyzzy user_data_collection: feedback_enabled: true @@ -35,4 +35,10 @@ authentication: # OKP Solr for supplementary RAG solr: enabled: false - offline: true \ No newline at end of file + offline: true + +mcp_servers: + - name: "ggg-mcp-server" + url: "http://localhost:3001" + authorization_headers: + Authorization: "oauth" \ No newline at end of file diff --git a/src/app/endpoints/query.py b/src/app/endpoints/query.py index cbd06e0e7..0a897131b 100644 --- a/src/app/endpoints/query.py +++ b/src/app/endpoints/query.py @@ -56,6 +56,7 @@ extract_vector_store_ids_from_tools, get_topic_summary, prepare_responses_params, + responses_params_to_request_body, ) from utils.shields import ( append_turn_to_conversation, @@ -301,7 +302,7 @@ async def retrieve_response( # pylint: disable=too-many-locals ) return TurnSummary(llm_response=violation_message) response = await client.responses.create( - **responses_params.model_dump(exclude_none=True) + **responses_params_to_request_body(responses_params), ) response = cast(OpenAIResponseObject, response) diff --git a/src/app/endpoints/streaming_query.py b/src/app/endpoints/streaming_query.py index 81f0c1dcf..818c1162d 100644 --- a/src/app/endpoints/streaming_query.py +++ b/src/app/endpoints/streaming_query.py @@ -73,6 +73,7 @@ from utils.quota import check_tokens_available, get_available_quotas from utils.responses import ( build_mcp_tool_call_from_arguments_done, + responses_params_to_request_body, build_tool_call_summary, build_tool_result_from_mcp_output_item_done, deduplicate_referenced_documents, @@ -303,7 +304,7 @@ async def retrieve_response_generator( ) # Retrieve response stream (may raise exceptions) response = await context.client.responses.create( - **responses_params.model_dump(exclude_none=True) + **responses_params_to_request_body(responses_params), ) # Store pre-RAG documents for later merging turn_summary.pre_rag_documents = doc_ids_from_chunks diff --git a/src/app/endpoints/tools.py b/src/app/endpoints/tools.py index 950facc71..a3df0e1a4 100644 --- a/src/app/endpoints/tools.py +++ b/src/app/endpoints/tools.py @@ -156,9 +156,7 @@ async def tools_endpoint_handler( # pylint: disable=too-many-locals,too-many-st continue except (AuthenticationError, AuthenticationRequiredError) as e: if toolgroup.mcp_endpoint: - await probe_mcp_oauth_and_raise_401( - toolgroup.mcp_endpoint.uri, chain_from=e - ) + await probe_mcp_oauth_and_raise_401(toolgroup.mcp_endpoint.uri) error_response = UnauthorizedResponse(cause=str(e)) raise HTTPException(**error_response.model_dump()) from e except APIConnectionError as e: diff --git a/src/utils/mcp_oauth_probe.py b/src/utils/mcp_oauth_probe.py index 6d893c999..a3f3bd246 100644 --- a/src/utils/mcp_oauth_probe.py +++ b/src/utils/mcp_oauth_probe.py @@ -13,40 +13,46 @@ async def probe_mcp_oauth_and_raise_401( url: str, - chain_from: Optional[BaseException] = None, + authorization: Optional[str] = None, ) -> None: - """Probe MCP endpoint and raise 401 so the client can perform OAuth. + """Probe MCP endpoint and raise 401 only when the server responds with 401. - Performs an async GET to the given URL to obtain a WWW-Authenticate header, - then raises HTTPException with status 401 and that header. If the probe - fails (connection error, timeout), raises 401 without the header. + Performs a GET to the given URL with the optional Authorization header. + If the response status is 401, raises HTTPException with status 401 and + WWW-Authenticate header when present. Otherwise returns without raising. Args: url: MCP server URL to probe. + authorization: Optional Authorization header value (e.g. "Bearer "). chain_from: Exception to chain the HTTPException from when - the probe succeeds (e.g. the original AuthenticationError). + the server returns 401 (e.g. the original AuthenticationError). Returns: - None. Always raises an HTTPException. + None. Raises only when the server responds with 401. Raises: - HTTPException: 401 with WWW-Authenticate when the probe succeeds, or - 401 without the header when the probe fails. + HTTPException: 401 with WWW-Authenticate when the server returns 401. """ cause = f"MCP server at {url} requires OAuth" error_response = UnauthorizedResponse(cause=cause) + headers: Optional[dict[str, str]] = ( + {"Authorization": authorization} if authorization is not None else None + ) try: timeout = aiohttp.ClientTimeout(total=10) async with aiohttp.ClientSession(timeout=timeout) as session: - async with session.get(url) as resp: + async with session.get(url, headers=headers) as resp: + print(resp.status) + if resp.status != 401: + return www_auth = resp.headers.get("WWW-Authenticate") if www_auth is None: logger.warning("No WWW-Authenticate header received from %s", url) - raise HTTPException(**error_response.model_dump()) from chain_from + raise HTTPException(**error_response.model_dump()) raise HTTPException( **error_response.model_dump(), headers={"WWW-Authenticate": www_auth}, - ) from chain_from + ) except (aiohttp.ClientError, TimeoutError) as probe_err: logger.warning("OAuth probe failed for %s: %s", url, probe_err) - raise HTTPException(**error_response.model_dump()) from probe_err + # Only raise on 401; connection/timeout are not 401, so do not raise diff --git a/src/utils/responses.py b/src/utils/responses.py index 524b8d34d..aa8ff697d 100644 --- a/src/utils/responses.py +++ b/src/utils/responses.py @@ -65,6 +65,33 @@ logger = get_logger(__name__) +def responses_params_to_request_body(params: ResponsesApiParams) -> dict[str, Any]: + """Build request body for Responses API from ResponsesApiParams. + + Serializes params and ensures MCP tool authorization is included (the + llama_stack_api marks it Field(exclude=True), so it is omitted by + model_dump() otherwise). + + Parameters: + params: The Responses API parameters. + + Returns: + Dict suitable for client.responses.create(**result). + """ + body = params.model_dump(exclude_none=True) + tools = getattr(params, "tools", None) + if tools is not None: + tools_out: list[dict[str, Any]] = [] + for tool in tools: + tool_dump = tool.model_dump(exclude_none=True) + auth = getattr(tool, "authorization", None) + if auth is not None: + tool_dump["authorization"] = auth + tools_out.append(tool_dump) + body["tools"] = tools_out + return body + + async def get_topic_summary( question: str, client: AsyncLlamaStackClient, model_id: str ) -> str: @@ -391,20 +418,20 @@ def _get_token_value(original: str, header: str) -> Optional[str]: if h_value is not None: headers[name] = h_value + uses_oauth = ( + constants.MCP_AUTH_OAUTH + in mcp_server.resolved_authorization_headers.values() + ) + + if uses_oauth: + await probe_mcp_oauth_and_raise_401( + mcp_server.url, authorization=headers.get("Authorization", None) + ) + # Skip server if auth headers were configured but not all could be resolved if mcp_server.authorization_headers and len(headers) != len( mcp_server.authorization_headers ): - # If OAuth was required and no headers passed, probe endpoint and forward - # 401 with WWW-Authenticate so the client can perform OAuth - uses_oauth = ( - constants.MCP_AUTH_OAUTH - in mcp_server.resolved_authorization_headers.values() - ) - if uses_oauth and ( - mcp_headers is None or not mcp_headers.get(mcp_server.name) - ): - await probe_mcp_oauth_and_raise_401(mcp_server.url) logger.warning( "Skipping MCP server %s: required %d auth headers but only resolved %d", mcp_server.name, diff --git a/tests/e2e/features/mcp.feature b/tests/e2e/features/mcp.feature index ae3da6ab0..14f031ab4 100644 --- a/tests/e2e/features/mcp.feature +++ b/tests/e2e/features/mcp.feature @@ -56,7 +56,6 @@ Feature: MCP tests """ And The headers of the response contains the following header "www-authenticate" - @skip # will be fixed in LCORE-1368 Scenario: Check if tools endpoint succeeds when MCP auth token is passed Given The system is in default state And I set the "MCP-HEADERS" header to @@ -93,14 +92,13 @@ Feature: MCP tests "parameters": [], "provider_id": "", "toolgroup_id": "mcp-oauth", - "server_source": "http://localhost:3001", + "server_source": "http://mock-mcp:3001", "type": "" } ] } """ - @skip # will be fixed in LCORE-1366 Scenario: Check if query endpoint succeeds when MCP auth token is passed Given The system is in default state And I set the "MCP-HEADERS" header to @@ -118,7 +116,6 @@ Feature: MCP tests | hello | And The token metrics should have increased - @skip # will be fixed in LCORE-1366 Scenario: Check if streaming_query endpoint succeeds when MCP auth token is passed Given The system is in default state And I set the "MCP-HEADERS" header to @@ -137,7 +134,6 @@ Feature: MCP tests | hello | And The token metrics should have increased - @skip # will be fixed in LCORE-1368 Scenario: Check if tools endpoint reports error when MCP invalid auth token is passed Given The system is in default state And I set the "MCP-HEADERS" header to @@ -157,7 +153,6 @@ Feature: MCP tests """ And The headers of the response contains the following header "www-authenticate" - @skip # will be fixed in LCORE-1366 Scenario: Check if query endpoint reports error when MCP invalid auth token is passed Given The system is in default state And I set the "MCP-HEADERS" header to @@ -180,7 +175,6 @@ Feature: MCP tests """ And The headers of the response contains the following header "www-authenticate" - @skip # will be fixed in LCORE-1366 Scenario: Check if streaming_query endpoint reports error when MCP invalid auth token is passed Given The system is in default state And I set the "MCP-HEADERS" header to diff --git a/tests/e2e/mock_mcp_server/server.py b/tests/e2e/mock_mcp_server/server.py index 0e3cc72ba..6ad27ef26 100644 --- a/tests/e2e/mock_mcp_server/server.py +++ b/tests/e2e/mock_mcp_server/server.py @@ -45,10 +45,14 @@ def _json_response(self, data: dict) -> None: def do_GET(self) -> None: # pylint: disable=invalid-name """Handle GET requests.""" - if self.path == "/health": - self._json_response({"status": "ok"}) - else: - self._require_oauth() + if self._parse_auth() is None: + if self.path == "/health": + self._json_response({"status": "ok"}) + else: + self._require_oauth() + return + + self._json_response({"status": "ok"}) def do_POST(self) -> None: # pylint: disable=invalid-name """Handle POST requests.""" diff --git a/tests/unit/utils/test_responses.py b/tests/unit/utils/test_responses.py index c0810965d..22665ab3c 100644 --- a/tests/unit/utils/test_responses.py +++ b/tests/unit/utils/test_responses.py @@ -599,6 +599,7 @@ async def test_get_mcp_tools_oauth_no_headers_raises_401_with_www_authenticate( mocker.patch("utils.responses.configuration", mock_config) mock_resp = mocker.Mock() + mock_resp.status = 401 mock_resp.headers = {"WWW-Authenticate": 'Bearer error="invalid_token"'} mock_session = mocker.MagicMock() mock_get_cm = mocker.AsyncMock() From 90337d0b2e5ee456f6ee8417a479b6c5d7530351 Mon Sep 17 00:00:00 2001 From: JR Boos Date: Wed, 4 Mar 2026 17:50:01 -0500 Subject: [PATCH 2/4] Refactor topic summary generation in query handling - Moved topic summary generation logic to prevent cancellation by MCP session cleanup. - Added error handling for topic summary generation in streaming response to ensure completion even on cancellation. - Removed redundant topic summary code from query endpoint handler. - Updated e2e tests for tools endpoint to include new authorization checks and response validation. --- src/app/endpoints/query.py | 20 +-- src/app/endpoints/streaming_query.py | 24 ++- src/utils/mcp_oauth_probe.py | 1 - tests/e2e/features/mcp.feature | 223 +++++++++++++++++---------- 4 files changed, 172 insertions(+), 96 deletions(-) diff --git a/src/app/endpoints/query.py b/src/app/endpoints/query.py index 0a897131b..62c0f46fb 100644 --- a/src/app/endpoints/query.py +++ b/src/app/endpoints/query.py @@ -191,6 +191,17 @@ async def query_endpoint_handler( vector_store_ids = extract_vector_store_ids_from_tools(responses_params.tools) rag_id_mapping = configuration.rag_id_mapping + # Get topic summary for new conversation before main response so it is not + # cancelled by MCP session cleanup (MCPSessionManager.close_all) that runs + # after retrieve_response completes. + if not user_conversation and query_request.generate_topic_summary: + logger.debug("Generating topic summary for new conversation") + topic_summary = await get_topic_summary( + query_request.query, client, responses_params.model + ) + else: + topic_summary = None + # Retrieve response using Responses API turn_summary = await retrieve_response( client, @@ -208,15 +219,6 @@ async def query_endpoint_handler( doc_ids_from_chunks + turn_summary.referenced_documents ) - # Get topic summary for new conversation - if not user_conversation and query_request.generate_topic_summary: - logger.debug("Generating topic summary for new conversation") - topic_summary = await get_topic_summary( - query_request.query, client, responses_params.model - ) - else: - topic_summary = None - logger.info("Consuming tokens") consume_query_tokens( user_id=user_id, diff --git a/src/app/endpoints/streaming_query.py b/src/app/endpoints/streaming_query.py index 818c1162d..936c6b061 100644 --- a/src/app/endpoints/streaming_query.py +++ b/src/app/endpoints/streaming_query.py @@ -432,7 +432,7 @@ async def _on_interrupt() -> None: return guard -async def generate_response( +async def generate_response( # pylint: disable=too-many-statements generator: AsyncIterator[str], context: ResponseGeneratorContext, responses_params: ResponsesApiParams, @@ -507,17 +507,25 @@ async def generate_response( # Post-stream side effects: only run when streaming finished successfully - # Get topic summary for new conversations if needed + # Get topic summary for new conversations if needed. Guard against + # CancelledError from MCP session cleanup (MCPSessionManager.close_all) + # so we still yield stream_end_event and complete the ASGI response. topic_summary = None if not context.query_request.conversation_id: should_generate = context.query_request.generate_topic_summary if should_generate: - logger.debug("Generating topic summary for new conversation") - topic_summary = await get_topic_summary( - context.query_request.query, - context.client, - responses_params.model, - ) + try: + logger.debug("Generating topic summary for new conversation") + topic_summary = await get_topic_summary( + context.query_request.query, + context.client, + responses_params.model, + ) + except asyncio.CancelledError: + logger.debug( + "Topic summary cancelled (e.g. MCP cleanup); completing without it" + ) + topic_summary = None # Consume tokens logger.info("Consuming tokens") diff --git a/src/utils/mcp_oauth_probe.py b/src/utils/mcp_oauth_probe.py index a3f3bd246..6a1b2372d 100644 --- a/src/utils/mcp_oauth_probe.py +++ b/src/utils/mcp_oauth_probe.py @@ -42,7 +42,6 @@ async def probe_mcp_oauth_and_raise_401( timeout = aiohttp.ClientTimeout(total=10) async with aiohttp.ClientSession(timeout=timeout) as session: async with session.get(url, headers=headers) as resp: - print(resp.status) if resp.status != 401: return www_auth = resp.headers.get("WWW-Authenticate") diff --git a/tests/e2e/features/mcp.feature b/tests/e2e/features/mcp.feature index 14f031ab4..0be88e5a6 100644 --- a/tests/e2e/features/mcp.feature +++ b/tests/e2e/features/mcp.feature @@ -56,84 +56,6 @@ Feature: MCP tests """ And The headers of the response contains the following header "www-authenticate" - Scenario: Check if tools endpoint succeeds when MCP auth token is passed - Given The system is in default state - And I set the "MCP-HEADERS" header to - """ - {"mcp-oauth": {"Authorization": "Bearer test-token"}} - """ - When I access REST API endpoint "tools" using HTTP GET method - Then The status code of the response is 200 - And The body of the response is the following - """ - { - "tools": [ - { - "identifier": "", - "description": "Insert documents into memory", - "parameters": [], - "provider_id": "", - "toolgroup_id": "builtin::rag", - "server_source": "builtin", - "type": "" - }, - { - "identifier": "", - "description": "Search for information in a database.", - "parameters": [], - "provider_id": "", - "toolgroup_id": "builtin::rag", - "server_source": "builtin", - "type": "" - }, - { - "identifier": "", - "description": "Mock tool for E2E", - "parameters": [], - "provider_id": "", - "toolgroup_id": "mcp-oauth", - "server_source": "http://mock-mcp:3001", - "type": "" - } - ] - } - """ - - Scenario: Check if query endpoint succeeds when MCP auth token is passed - Given The system is in default state - And I set the "MCP-HEADERS" header to - """ - {"mcp-oauth": {"Authorization": "Bearer test-token"}} - """ - And I capture the current token metrics - When I use "query" to ask question with authorization header - """ - {"query": "Say hello", "model": "{MODEL}", "provider": "{PROVIDER}"} - """ - Then The status code of the response is 200 - And The response should contain following fragments - | Fragments in LLM response | - | hello | - And The token metrics should have increased - - Scenario: Check if streaming_query endpoint succeeds when MCP auth token is passed - Given The system is in default state - And I set the "MCP-HEADERS" header to - """ - {"mcp-oauth": {"Authorization": "Bearer test-token"}} - """ - And I capture the current token metrics - When I use "streaming_query" to ask question with authorization header - """ - {"query": "Say hello", "model": "{MODEL}", "provider": "{PROVIDER}"} - """ - When I wait for the response to be completed - Then The status code of the response is 200 - And The streamed response should contain following fragments - | Fragments in LLM response | - | hello | - And The token metrics should have increased - Scenario: Check if tools endpoint reports error when MCP invalid auth token is passed Given The system is in default state And I set the "MCP-HEADERS" header to @@ -196,3 +118,148 @@ Feature: MCP tests } """ And The headers of the response contains the following header "www-authenticate" + + Scenario: Check if tools endpoint succeeds when MCP auth token is passed + Given The system is in default state + And I set the "MCP-HEADERS" header to + """ + {"mcp-oauth": {"Authorization": "Bearer test-token"}} + """ + When I access REST API endpoint "tools" using HTTP GET method + Then The status code of the response is 200 + And The body of the response is the following + """ + { + "tools":[ + { + "identifier":"insert_into_memory", + "description":"Insert documents into memory", + "parameters":[], + "provider_id":"rag-runtime", + "toolgroup_id":"builtin::rag", + "server_source":"builtin", + "type":"tool_group" + }, + { + "identifier":"knowledge_search", + "description":"Search for information in a database.", + "parameters":[ + { + "name":"query", + "description":"The query to search for. Can be a natural language sentence or keywords.", + "parameter_type":"string", + "required":true, + "default":"None" + } + ], + "provider_id":"rag-runtime", + "toolgroup_id":"builtin::rag", + "server_source":"builtin", + "type":"tool_group" + }, + { + "identifier":"mock_tool_no_auth", + "description":"Mock tool with no authorization", + "parameters":[ + { + "name":"message", + "description":"Test message", + "parameter_type":"string", + "required":false, + "default":"None" + } + ], + "provider_id":"model-context-protocol", + "toolgroup_id":"github-api", + "server_source":"builtin", + "type":"tool_group" + }, + { + "identifier":"mock_tool_no_auth", + "description":"Mock tool with no authorization", + "parameters":[ + { + "name":"message", + "description":"Test message", + "parameter_type":"string", + "required":false, + "default":"None" + } + ], + "provider_id":"model-context-protocol", + "toolgroup_id":"gitlab-api", + "server_source":"builtin", + "type":"tool_group" + }, + { + "identifier":"mock_tool_no_auth", + "description":"Mock tool with no authorization", + "parameters":[ + { + "name":"message", + "description":"Test message", + "parameter_type":"string", + "required":false, + "default":"None" + } + ], + "provider_id":"model-context-protocol", + "toolgroup_id":"public-api", + "server_source":"builtin", + "type":"tool_group" + }, + { + "identifier":"mock_tool_e2e", + "description":"Mock tool for E2E", + "parameters":[ + { + "name":"message", + "description":"Test message", + "parameter_type":"string", + "required":false, + "default":"None" + } + ], + "provider_id":"model-context-protocol", + "toolgroup_id":"mcp-oauth", + "server_source":"http://mock-mcp:3001", + "type":"tool_group" + } + ] + } + """ + + Scenario: Check if query endpoint succeeds when MCP auth token is passed + Given The system is in default state + And I set the "MCP-HEADERS" header to + """ + {"mcp-oauth": {"Authorization": "Bearer test-token"}} + """ + And I capture the current token metrics + When I use "query" to ask question with authorization header + """ + {"query": "Say hello", "model": "{MODEL}", "provider": "{PROVIDER}"} + """ + Then The status code of the response is 200 + And The response should contain following fragments + | Fragments in LLM response | + | Hello | + And The token metrics should have increased + + Scenario: Check if streaming_query endpoint succeeds when MCP auth token is passed + Given The system is in default state + And I set the "MCP-HEADERS" header to + """ + {"mcp-oauth": {"Authorization": "Bearer test-token"}} + """ + And I capture the current token metrics + When I use "streaming_query" to ask question with authorization header + """ + {"query": "Say hello", "model": "{MODEL}", "provider": "{PROVIDER}"} + """ + When I wait for the response to be completed + Then The status code of the response is 200 + And The streamed response should contain following fragments + | Fragments in LLM response | + | Hello | + And The token metrics should have increased From 746497b6c52effd410054c1285c5a9e32900a0a1 Mon Sep 17 00:00:00 2001 From: JR Boos Date: Wed, 4 Mar 2026 18:19:49 -0500 Subject: [PATCH 3/4] fixed tools e2e --- tests/e2e/features/mcp.feature | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/e2e/features/mcp.feature b/tests/e2e/features/mcp.feature index 0be88e5a6..db1d5c2e1 100644 --- a/tests/e2e/features/mcp.feature +++ b/tests/e2e/features/mcp.feature @@ -149,7 +149,7 @@ Feature: MCP tests "description":"The query to search for. Can be a natural language sentence or keywords.", "parameter_type":"string", "required":true, - "default":"None" + "default": None } ], "provider_id":"rag-runtime", @@ -166,7 +166,7 @@ Feature: MCP tests "description":"Test message", "parameter_type":"string", "required":false, - "default":"None" + "default":None } ], "provider_id":"model-context-protocol", @@ -183,7 +183,7 @@ Feature: MCP tests "description":"Test message", "parameter_type":"string", "required":false, - "default":"None" + "default":None } ], "provider_id":"model-context-protocol", @@ -200,7 +200,7 @@ Feature: MCP tests "description":"Test message", "parameter_type":"string", "required":false, - "default":"None" + "default":None } ], "provider_id":"model-context-protocol", @@ -217,7 +217,7 @@ Feature: MCP tests "description":"Test message", "parameter_type":"string", "required":false, - "default":"None" + "default":None } ], "provider_id":"model-context-protocol", From 9bbc2cbbc9fceec47f14d546a6d4156da63d7c2d Mon Sep 17 00:00:00 2001 From: JR Boos Date: Wed, 4 Mar 2026 18:40:43 -0500 Subject: [PATCH 4/4] fixed json format in tools e2e --- tests/e2e/features/mcp.feature | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/e2e/features/mcp.feature b/tests/e2e/features/mcp.feature index db1d5c2e1..6ec471d4b 100644 --- a/tests/e2e/features/mcp.feature +++ b/tests/e2e/features/mcp.feature @@ -149,7 +149,7 @@ Feature: MCP tests "description":"The query to search for. Can be a natural language sentence or keywords.", "parameter_type":"string", "required":true, - "default": None + "default": null } ], "provider_id":"rag-runtime", @@ -166,7 +166,7 @@ Feature: MCP tests "description":"Test message", "parameter_type":"string", "required":false, - "default":None + "default": null } ], "provider_id":"model-context-protocol", @@ -183,7 +183,7 @@ Feature: MCP tests "description":"Test message", "parameter_type":"string", "required":false, - "default":None + "default": null } ], "provider_id":"model-context-protocol", @@ -200,7 +200,7 @@ Feature: MCP tests "description":"Test message", "parameter_type":"string", "required":false, - "default":None + "default": null } ], "provider_id":"model-context-protocol", @@ -217,7 +217,7 @@ Feature: MCP tests "description":"Test message", "parameter_type":"string", "required":false, - "default":None + "default": null } ], "provider_id":"model-context-protocol",