Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:39:59 +08:00

138 lines
4.2 KiB
Python

# /// script
# dependencies = ["aiosqlite", "fastmcp"]
# ///
"""
MCP server with database-configured tools.
Tools are loaded from tools.db on each request, so you can add/modify/disable
tools in the database without restarting the server.
Run with: uv run fastmcp run examples/providers/sqlite/server.py
"""
from __future__ import annotations
import asyncio
import json
from collections.abc import Sequence
from pathlib import Path
from typing import Any
import aiosqlite
from rich import print
from fastmcp import Client, FastMCP
from fastmcp.server.providers import Provider
from fastmcp.tools.tool import Tool, ToolResult
DB_PATH = Path(__file__).parent / "tools.db"
class ConfigurableTool(Tool):
"""A tool that performs a configured arithmetic operation.
This demonstrates the pattern: Tool subclass = schema + execution in one place.
"""
operation: str # "add", "multiply", "subtract", "divide"
default_value: float = 0
async def run(self, arguments: dict[str, Any]) -> ToolResult:
a = arguments.get("a", self.default_value)
b = arguments.get("b", self.default_value)
if self.operation == "add":
result = a + b
elif self.operation == "multiply":
result = a * b
elif self.operation == "subtract":
result = a - b
elif self.operation == "divide":
if b == 0:
return ToolResult(
structured_content={
"error": "Division by zero",
"operation": self.operation,
}
)
result = a / b
else:
result = a + b
return ToolResult(
structured_content={"result": result, "operation": self.operation}
)
class SQLiteToolProvider(Provider):
"""Queries SQLite for tool configurations.
Called on every list_tools/get_tool request, so database changes
are reflected immediately without server restart.
"""
def __init__(self, db_path: str):
super().__init__()
self.db_path = db_path
async def list_tools(self) -> Sequence[Tool]:
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
async with db.execute("SELECT * FROM tools WHERE enabled = 1") as cursor:
rows = await cursor.fetchall()
return [self._make_tool(row) for row in rows]
async def get_tool(self, name: str) -> Tool | None:
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
async with db.execute(
"SELECT * FROM tools WHERE name = ? AND enabled = 1", (name,)
) as cursor:
row = await cursor.fetchone()
return self._make_tool(row) if row else None
def _make_tool(self, row: aiosqlite.Row) -> ConfigurableTool:
return ConfigurableTool(
name=row["name"],
description=row["description"],
parameters=json.loads(row["parameters_schema"]),
operation=row["operation"],
default_value=row["default_value"] or 0,
)
provider = SQLiteToolProvider(db_path=str(DB_PATH))
mcp = FastMCP("DynamicToolsServer", providers=[provider])
@mcp.tool
def server_info() -> dict[str, str]:
"""Get information about this server (static tool)."""
return {
"name": "DynamicToolsServer",
"description": "A server with database-configured tools",
"database": str(DB_PATH),
}
async def main():
async with Client(mcp) as client:
tools = await client.list_tools()
print(f"[bold]Available tools ({len(tools)}):[/bold]")
for tool in tools:
print(f" • {tool.name}: {tool.description}")
print()
print("[bold]Calling add_numbers(10, 5):[/bold]")
result = await client.call_tool("add_numbers", {"a": 10, "b": 5})
print(f" Result: {result.structured_content}")
print()
print("[bold]Calling multiply_numbers(7, 6):[/bold]")
result = await client.call_tool("multiply_numbers", {"a": 7, "b": 6})
print(f" Result: {result.structured_content}")
if __name__ == "__main__":
asyncio.run(main())