Exercise the SEP-2575 stateless probes and SEP-2243 resource/prompt headers in the conformance fixtures (#3442)
This commit is contained in:
parent
979208c115
commit
10dc173637
3 changed files with 49 additions and 6 deletions
23
.github/actions/conformance/client.py
vendored
23
.github/actions/conformance/client.py
vendored
|
|
@ -20,7 +20,7 @@ Scenarios:
|
|||
json-schema-ref-no-deref - Connect, list tools (no $ref deref)
|
||||
json-schema-2020-12-preservation - List tools, echo the focal inputSchema back verbatim
|
||||
request-metadata - Connect with all callbacks; client stamps _meta
|
||||
http-standard-headers - Connect, call a tool (Mcp-* headers checked)
|
||||
http-standard-headers - Tool, resource and prompt round-trips (Mcp-* headers checked)
|
||||
http-invalid-tool-headers - List tools, call every surfaced tool (x-mcp-header filter)
|
||||
http-custom-headers - Replay the harness's toolCalls (x-mcp-header -> Mcp-Param-*)
|
||||
elicitation-sep1034-client-defaults - Elicitation with default accept callback
|
||||
|
|
@ -311,11 +311,24 @@ async def run_request_metadata(server_url: str) -> None:
|
|||
|
||||
@register("http-standard-headers")
|
||||
async def run_http_standard_headers(server_url: str) -> None:
|
||||
"""Connect on the modern path so Mcp-Method / Mcp-Name / MCP-Protocol-Version are sent (SEP-2243)."""
|
||||
"""Touch tools, resources and prompts on the modern path so each standard header is checked (SEP-2243).
|
||||
|
||||
The scenario inspects Mcp-Method on each request and Mcp-Name on tools/call,
|
||||
resources/read and prompts/get, and reports methods the client never sent as
|
||||
SKIPPED rather than failed, so exercise one of each. initialize and
|
||||
notifications/initialized stay SKIPPED: the modern path discovers via
|
||||
server/discover and never sends them.
|
||||
"""
|
||||
async with Client(server_url, mode=client_mode()) as client:
|
||||
await client.list_tools()
|
||||
result = await client.call_tool("add_numbers", {"a": 5, "b": 3})
|
||||
logger.debug(f"add_numbers result: {result}")
|
||||
tools = await client.list_tools()
|
||||
if tools.tools:
|
||||
await client.call_tool(tools.tools[0].name, _stub_required_args(tools.tools[0].input_schema))
|
||||
resources = await client.list_resources()
|
||||
if resources.resources:
|
||||
await client.read_resource(resources.resources[0].uri)
|
||||
prompts = await client.list_prompts()
|
||||
if prompts.prompts:
|
||||
await client.get_prompt(prompts.prompts[0].name)
|
||||
|
||||
|
||||
def _stub_required_args(input_schema: dict[str, Any]) -> dict[str, Any]:
|
||||
|
|
|
|||
113
examples/clients/simple-chatbot/README.md
Normal file
113
examples/clients/simple-chatbot/README.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# MCP Simple Chatbot
|
||||
|
||||
This example demonstrates how to integrate the Model Context Protocol (MCP) into a simple CLI chatbot. The implementation showcases MCP's flexibility by supporting multiple tools through MCP servers and is compatible with any LLM provider that follows OpenAI API standards.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.10
|
||||
- `python-dotenv`
|
||||
- `requests`
|
||||
- `mcp`
|
||||
- `uvicorn`
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Install the dependencies:**
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **Set up environment variables:**
|
||||
|
||||
Create a `.env` file in the root directory and add your API key:
|
||||
|
||||
```plaintext
|
||||
LLM_API_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
**Note:** The current implementation is configured to use the Groq API endpoint (`https://api.groq.com/openai/v1/chat/completions`) with the `llama-3.2-90b-vision-preview` model. If you plan to use a different LLM provider, you'll need to modify the `LLMClient` class in `main.py` to use the appropriate endpoint URL and model parameters.
|
||||
|
||||
3. **Configure servers:**
|
||||
|
||||
The `servers_config.json` follows the same structure as Claude Desktop, allowing for easy integration of multiple servers.
|
||||
Here's an example:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"sqlite": {
|
||||
"command": "uvx",
|
||||
"args": ["mcp-server-sqlite", "--db-path", "./test.db"]
|
||||
},
|
||||
"puppeteer": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-puppeteer"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Environment variables are supported as well. Pass them as you would with the Claude Desktop App.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"server_name": {
|
||||
"command": "uvx",
|
||||
"args": ["mcp-server-name", "--additional-args"],
|
||||
"env": {
|
||||
"API_KEY": "your_api_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. **Run the client:**
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
2. **Interact with the assistant:**
|
||||
|
||||
The assistant will automatically detect available tools and can respond to queries based on the tools provided by the configured servers.
|
||||
|
||||
3. **Exit the session:**
|
||||
|
||||
Type `quit` or `exit` to end the session.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Tool Discovery**: Tools are automatically discovered from configured servers.
|
||||
- **System Prompt**: Tools are dynamically included in the system prompt, allowing the LLM to understand available capabilities.
|
||||
- **Server Integration**: Supports any MCP-compatible server, tested with various server implementations including Uvicorn and Node.js.
|
||||
|
||||
### Class Structure
|
||||
|
||||
- **Configuration**: Manages environment variables and server configurations
|
||||
- **Server**: Handles MCP server initialization, tool discovery, and execution
|
||||
- **Tool**: Represents individual tools with their properties and formatting
|
||||
- **LLMClient**: Manages communication with the LLM provider
|
||||
- **ChatSession**: Orchestrates the interaction between user, LLM, and tools
|
||||
|
||||
### Logic Flow
|
||||
|
||||
1. **Tool Integration**:
|
||||
- Tools are dynamically discovered from MCP servers
|
||||
- Tool descriptions are automatically included in system prompt
|
||||
- Tool execution is handled through standardized MCP protocol
|
||||
|
||||
2. **Runtime Flow**:
|
||||
- User input is received
|
||||
- Input is sent to LLM with context of available tools
|
||||
- LLM response is parsed:
|
||||
- If it's a tool call → execute tool and return result
|
||||
- If it's a direct response → return to user
|
||||
- Tool results are sent back to LLM for interpretation
|
||||
- Final response is presented to user
|
||||
|
|
@ -12,7 +12,7 @@ from typing import Annotated, Any
|
|||
|
||||
import click
|
||||
from mcp.server import ServerRequestContext
|
||||
from mcp.server.mcpserver import Context, MCPServer, RequestStateSecurity
|
||||
from mcp.server.mcpserver import Context, Elicit, ElicitationResult, MCPServer, RequestStateSecurity, Resolve
|
||||
from mcp.server.mcpserver.exceptions import ToolError
|
||||
from mcp.server.mcpserver.prompts.base import Prompt, UserMessage
|
||||
from mcp.server.streamable_http import EventCallback, EventMessage, EventStore
|
||||
|
|
@ -350,6 +350,9 @@ def test_x_mcp_header(
|
|||
return f"region={region}"
|
||||
|
||||
|
||||
# SEP-2575 server-stateless diagnostics (the conformance scenario probes these tools by name)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def test_missing_capability(ctx: Context) -> str:
|
||||
"""Tests that a handler-raised MISSING_REQUIRED_CLIENT_CAPABILITY surfaces as a top-level JSON-RPC error.
|
||||
|
|
@ -370,6 +373,33 @@ async def test_missing_capability(ctx: Context) -> str:
|
|||
return "Client declared sampling capability; proceeding."
|
||||
|
||||
|
||||
def _ask_stream_probe() -> Elicit[UserResponse]:
|
||||
return Elicit("The stateless streaming probe asks for a word", UserResponse)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def test_streaming_elicitation(
|
||||
answer: Annotated[ElicitationResult[UserResponse], Resolve(_ask_stream_probe)],
|
||||
) -> str:
|
||||
"""A tool that needs elicitation, asked through a resolver (SEP-2575 / SEP-2322).
|
||||
|
||||
On 2026-07-28 the question is returned as an InputRequiredResult rather than sent
|
||||
on the response stream; on earlier versions it is a mid-call elicitation request.
|
||||
"""
|
||||
return f"elicitation {answer.action}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def test_logging_tool(ctx: Context) -> str:
|
||||
"""Logs once on the request-scoped channel (SEP-2575).
|
||||
|
||||
On 2026-07-28 the message is delivered only when the request's `_meta` sets
|
||||
`io.modelcontextprotocol/logLevel`.
|
||||
"""
|
||||
await ctx.info("test_logging_tool ran") # pyright: ignore[reportDeprecated]
|
||||
return "logged through the request-scoped, logLevel-gated channel"
|
||||
|
||||
|
||||
# SEP-2322 InputRequiredResult fixtures (multi-round-trip / ephemeral workflow)
|
||||
|
||||
NAME_SCHEMA = {"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}
|
||||
|
|
|
|||
Loading…
Reference in a new issue