chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# Run sample
|
||||
|
||||
## Set up virtual environment
|
||||
|
||||
```sh
|
||||
python -m venv venv
|
||||
source ./venv/bin/activate
|
||||
```
|
||||
|
||||
## Install dependencies
|
||||
|
||||
```sh
|
||||
pip install "mcp[cli]"
|
||||
```
|
||||
|
||||
## Run code
|
||||
|
||||
```sh
|
||||
python client.py
|
||||
```
|
||||
|
||||
You should see the text:
|
||||
|
||||
```text
|
||||
Available tools: ['add']
|
||||
Result of add tool: meta=None content=[TextContent(type='text', text='8.0', annotations=None, meta=None)] structuredContent=None isError=False
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from pydantic import AnyUrl
|
||||
|
||||
from mcp import ClientSession, StdioServerParameters, types
|
||||
from mcp.client.stdio import stdio_client
|
||||
from mcp.shared.context import RequestContext
|
||||
|
||||
# Create server parameters for stdio connection
|
||||
server_params = StdioServerParameters(
|
||||
command="python", # Using uv to run the server
|
||||
args=["server.py"] # We're already in snippets dir
|
||||
|
||||
)
|
||||
|
||||
async def run():
|
||||
async with stdio_client(server_params) as (read, write):
|
||||
async with ClientSession(read, write) as session:
|
||||
# Initialize the connection
|
||||
await session.initialize()
|
||||
|
||||
tools = await session.list_tools()
|
||||
print(f"Available tools: {[t.name for t in tools.tools]}")
|
||||
|
||||
result = await session.call_tool("add", { "a": 5, "b": 3 })
|
||||
print(f"Result of add tool: {result}")
|
||||
|
||||
def main():
|
||||
"""Entry point for the client script."""
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,83 @@
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any
|
||||
|
||||
import mcp.server.stdio
|
||||
import mcp.types as types
|
||||
from mcp.server.lowlevel import NotificationOptions, Server
|
||||
from mcp.server.models import InitializationOptions
|
||||
|
||||
from tools import tools
|
||||
|
||||
server = Server("example-server")
|
||||
|
||||
def convert_to_json(model_cls: type) -> dict:
|
||||
schema = model_cls.schema()
|
||||
properties = {}
|
||||
required = schema.get("required", [])
|
||||
for prop, details in schema.get("properties", {}).items():
|
||||
properties[prop] = {"type": details.get("type", "string")}
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
"required": required
|
||||
}
|
||||
|
||||
@server.list_tools()
|
||||
async def handle_list_tools() -> list[types.Tool]:
|
||||
"""List available tools."""
|
||||
vm_tools = []
|
||||
for tool in tools.values():
|
||||
print(f"Registered tool: {tool['name']}")
|
||||
vm_tools.append(
|
||||
types.Tool(
|
||||
name=tool["name"],
|
||||
description=tool["description"],
|
||||
inputSchema=convert_to_json(tool["input_schema"]),
|
||||
)
|
||||
)
|
||||
return vm_tools
|
||||
|
||||
@server.call_tool()
|
||||
async def handle_call_tool(
|
||||
name: str, arguments: dict[str, str] | None
|
||||
) -> list[types.TextContent]:
|
||||
|
||||
# tools is a dictionary with tool names as keys
|
||||
if name not in tools:
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
|
||||
tool = tools[name]
|
||||
|
||||
result = "default"
|
||||
try:
|
||||
result = await tool["handler"](arguments)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error calling tool {name}: {str(e)}")
|
||||
|
||||
return [
|
||||
types.TextContent(type="text", text=str(result))
|
||||
]
|
||||
|
||||
async def run():
|
||||
"""Run the server with lifespan management."""
|
||||
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
||||
await server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
InitializationOptions(
|
||||
server_name="example-server",
|
||||
server_version="0.1.0",
|
||||
capabilities=server.get_capabilities(
|
||||
notification_options=NotificationOptions(),
|
||||
experimental_capabilities={},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
print("Starting server...")
|
||||
asyncio.run(run())
|
||||
@@ -0,0 +1,5 @@
|
||||
from .add import add
|
||||
|
||||
tools = {
|
||||
add["name"] : add
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
from .schema import AddInputModel
|
||||
|
||||
async def add_handler(args) -> float:
|
||||
try:
|
||||
# Validate input using Pydantic model
|
||||
input_model = AddInputModel(**args)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Invalid input: {str(e)}")
|
||||
|
||||
# TODO: add Pydantic, so we can create an AddInputModel and validate args
|
||||
|
||||
"""Handler function for the add tool."""
|
||||
return float(input_model.a) + float(input_model.b)
|
||||
|
||||
add = {
|
||||
"name": "add",
|
||||
"description": "Adds two numbers",
|
||||
"input_schema": AddInputModel,
|
||||
"handler": add_handler
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
class AddInputModel(BaseModel):
|
||||
a: float
|
||||
b: float
|
||||
Reference in New Issue
Block a user