60e0ffc959
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
162 lines
5.2 KiB
Python
162 lines
5.2 KiB
Python
"""Example: Client using regex search to discover and call tools.
|
|
|
|
Regex search lets clients find tools by matching patterns against tool names
|
|
and descriptions. Precise when you know what you're looking for.
|
|
|
|
Run with:
|
|
uv run python examples/search/client_regex.py
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
from typing import Any
|
|
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.table import Table
|
|
|
|
from fastmcp.client import Client
|
|
|
|
console = Console()
|
|
|
|
|
|
def _get_result(result) -> Any:
|
|
"""Extract the value from a CallToolResult (structured or text)."""
|
|
if result.structured_content is not None:
|
|
data = result.structured_content
|
|
if isinstance(data, dict) and set(data) == {"result"}:
|
|
return data["result"]
|
|
return data
|
|
return result.content[0].text
|
|
|
|
|
|
def _format_params(tool: dict) -> str:
|
|
"""Format inputSchema properties as a compact signature."""
|
|
schema = tool.get("inputSchema", {})
|
|
props = schema.get("properties", {})
|
|
if not props:
|
|
return "()"
|
|
parts = []
|
|
for name, info in props.items():
|
|
typ = info.get("type", "")
|
|
parts.append(f"{name}: {typ}" if typ else name)
|
|
return f"({', '.join(parts)})"
|
|
|
|
|
|
def _tool_table(
|
|
tools: list[dict], *, ranked: bool = False, show_params: bool = False
|
|
) -> Table:
|
|
table = Table(show_header=True, show_edge=False, pad_edge=False, expand=True)
|
|
if ranked:
|
|
table.add_column("#", style="dim", width=3, justify="right")
|
|
table.add_column("Tool", style="cyan", no_wrap=True)
|
|
if show_params:
|
|
table.add_column("Parameters", style="dim", no_wrap=True)
|
|
table.add_column("Description", style="dim")
|
|
for i, tool in enumerate(tools, 1):
|
|
row = [tool["name"]]
|
|
if show_params:
|
|
row.append(_format_params(tool))
|
|
row.append(tool.get("description", ""))
|
|
if ranked:
|
|
row.insert(0, str(i))
|
|
table.add_row(*row)
|
|
return table
|
|
|
|
|
|
async def main():
|
|
async with Client("examples/search/server_regex.py") as client:
|
|
console.print()
|
|
console.rule("[bold]Regex Search Transform[/bold]")
|
|
console.print()
|
|
|
|
# Step 1: list_tools shows only synthetic tools
|
|
console.print(
|
|
"The server has 6 tools. RegexSearchTransform replaces them with "
|
|
"just [bold]search_tools[/bold] and [bold]call_tool[/bold]:"
|
|
)
|
|
console.print()
|
|
tools = await client.list_tools()
|
|
visible = [{"name": t.name, "description": t.description} for t in tools]
|
|
console.print(
|
|
Panel(
|
|
_tool_table(visible),
|
|
title="[bold]list_tools()[/bold]",
|
|
title_align="left",
|
|
border_style="blue",
|
|
)
|
|
)
|
|
console.print()
|
|
|
|
# Step 2: regex patterns discover tools
|
|
console.print(
|
|
"The LLM uses [bold]search_tools[/bold] with regex patterns "
|
|
"to find tools by name:"
|
|
)
|
|
console.print()
|
|
result = await client.call_tool(
|
|
"search_tools", {"pattern": "add|multiply|fibonacci"}
|
|
)
|
|
found = _get_result(result)
|
|
if isinstance(found, str):
|
|
found = json.loads(found)
|
|
console.print(
|
|
Panel(
|
|
_tool_table(found, show_params=True),
|
|
title='[bold]search_tools[/bold] [dim]pattern="add|multiply|fibonacci"[/dim]',
|
|
title_align="left",
|
|
border_style="green",
|
|
)
|
|
)
|
|
console.print()
|
|
|
|
result = await client.call_tool("search_tools", {"pattern": "text|string|word"})
|
|
found = _get_result(result)
|
|
if isinstance(found, str):
|
|
found = json.loads(found)
|
|
console.print(
|
|
Panel(
|
|
_tool_table(found, show_params=True),
|
|
title='[bold]search_tools[/bold] [dim]pattern="text|string|word"[/dim]',
|
|
title_align="left",
|
|
border_style="green",
|
|
)
|
|
)
|
|
console.print()
|
|
|
|
# Step 3: call discovered tools
|
|
console.print(
|
|
"Then the LLM calls discovered tools through [bold]call_tool[/bold]:"
|
|
)
|
|
console.print()
|
|
result = await client.call_tool(
|
|
"call_tool", {"name": "add", "arguments": {"a": 17, "b": 25}}
|
|
)
|
|
console.print(
|
|
Panel(
|
|
f'call_tool(name="add", arguments={{"a": 17, "b": 25}})\n→ [bold green]{_get_result(result)}[/bold green]',
|
|
title="[bold]call_tool()[/bold]",
|
|
title_align="left",
|
|
border_style="magenta",
|
|
)
|
|
)
|
|
console.print()
|
|
|
|
result = await client.call_tool(
|
|
"call_tool",
|
|
{"name": "reverse_string", "arguments": {"text": "hello world"}},
|
|
)
|
|
console.print(
|
|
Panel(
|
|
f'call_tool(name="reverse_string", arguments={{"text": "hello world"}})\n→ [bold green]{_get_result(result)}[/bold green]',
|
|
title="[bold]call_tool()[/bold]",
|
|
title_align="left",
|
|
border_style="magenta",
|
|
)
|
|
)
|
|
console.print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|