diff --git a/examples/apps/qr_server/qr_server.py b/examples/apps/qr_server/qr_server.py index 04639fdb9..28ea8d4d1 100644 --- a/examples/apps/qr_server/qr_server.py +++ b/examples/apps/qr_server/qr_server.py @@ -23,7 +23,7 @@ import base64 import io import qrcode # type: ignore[import-untyped] -from mcp import types +from mcp_types import ImageContent from fastmcp import FastMCP from fastmcp.apps import AppConfig, ResourceCSP @@ -153,7 +153,7 @@ def generate_qr( img.save(buffer, format="PNG") b64 = base64.b64encode(buffer.getvalue()).decode() return ToolResult( - content=[types.ImageContent(type="image", data=b64, mimeType="image/png")] + content=[ImageContent(type="image", data=b64, mime_type="image/png")] ) diff --git a/examples/apps/quiz/quiz_server.py b/examples/apps/quiz/quiz_server.py index 7de6f08d7..f4eacedc3 100644 --- a/examples/apps/quiz/quiz_server.py +++ b/examples/apps/quiz/quiz_server.py @@ -28,12 +28,20 @@ from prefab_ui.components import ( Text, ) from prefab_ui.rx import ERROR, RESULT, Rx +from typing_extensions import TypedDict from fastmcp import FastMCP, FastMCPApp app = FastMCPApp("Quiz") -DEFAULT_QUESTIONS = [ + +class Question(TypedDict): + question: str + options: list[str] + correct: int + + +DEFAULT_QUESTIONS: list[Question] = [ { "question": "What is the capital of Australia?", "options": ["Sydney", "Melbourne", "Canberra", "Perth"], @@ -102,7 +110,7 @@ def submit_answer( @app.ui() def take_quiz( topic: str = "General Knowledge", - questions: list[dict] | None = None, + questions: list[Question] | None = None, ) -> PrefabApp: """Launch a quiz UI. diff --git a/examples/apps/sales_dashboard/sales_dashboard_server.py b/examples/apps/sales_dashboard/sales_dashboard_server.py index fe38c64bc..04f3781a1 100644 --- a/examples/apps/sales_dashboard/sales_dashboard_server.py +++ b/examples/apps/sales_dashboard/sales_dashboard_server.py @@ -1,3 +1,5 @@ +from typing import TypedDict + from prefab_ui.components import ( Card, CardContent, @@ -17,7 +19,15 @@ from fastmcp import FastMCP mcp = FastMCP("Sales Dashboard") -MONTHLY_REVENUE = [ + +class MonthlyRevenue(TypedDict): + month: str + new_business: int + expansion: int + renewal: int + + +MONTHLY_REVENUE: list[MonthlyRevenue] = [ {"month": "Jul", "new_business": 182_000, "expansion": 74_000, "renewal": 210_000}, {"month": "Aug", "new_business": 195_000, "expansion": 81_000, "renewal": 215_000}, {"month": "Sep", "new_business": 224_000, "expansion": 93_000, "renewal": 208_000}, diff --git a/examples/auth/aws_oauth/server.py b/examples/auth/aws_oauth/server.py index 261164391..a854c790c 100644 --- a/examples/auth/aws_oauth/server.py +++ b/examples/auth/aws_oauth/server.py @@ -48,6 +48,8 @@ def echo(message: str) -> str: async def get_access_token_claims() -> dict: """Get the authenticated user's access token claims.""" token = get_access_token() + if token is None: + return {"error": "Not authenticated"} return { "sub": token.claims.get("sub"), "username": token.claims.get("username"), diff --git a/examples/auth/huggingface_oauth/server.py b/examples/auth/huggingface_oauth/server.py index 9745eb88a..5a819aa88 100644 --- a/examples/auth/huggingface_oauth/server.py +++ b/examples/auth/huggingface_oauth/server.py @@ -2,6 +2,7 @@ import os from fastmcp import FastMCP from fastmcp.server.auth.providers.huggingface import HuggingFaceProvider +from fastmcp.server.dependencies import get_access_token auth_provider = HuggingFaceProvider( # Your Hugging Face OAuth app client ID @@ -21,9 +22,9 @@ mcp = FastMCP(name="Hugging Face Secured App", auth=auth_provider) @mcp.tool async def get_user_info() -> dict: """Returns information about the authenticated Hugging Face user.""" - from fastmcp.server.dependencies import get_access_token - token = get_access_token() + if token is None: + return {"error": "Not authenticated"} return { "subject": token.claims.get("sub"), "username": token.claims.get("preferred_username"), diff --git a/examples/auth/keycloak_oauth/server.py b/examples/auth/keycloak_oauth/server.py index 7b4653103..5bf50b9b1 100644 --- a/examples/auth/keycloak_oauth/server.py +++ b/examples/auth/keycloak_oauth/server.py @@ -33,6 +33,8 @@ def echo(message: str) -> str: async def get_access_token_claims() -> dict: """Get the authenticated user's access token claims.""" token = get_access_token() + if token is None: + return {"error": "Not authenticated"} return { "sub": token.claims.get("sub"), "scope": token.claims.get("scope"), diff --git a/examples/custom_tool_serializer_decorator.py b/examples/custom_tool_serializer_decorator.py index 7075b7238..80c77b074 100644 --- a/examples/custom_tool_serializer_decorator.py +++ b/examples/custom_tool_serializer_decorator.py @@ -12,8 +12,9 @@ from typing import Any import yaml -from fastmcp import FastMCP -from fastmcp.tools.tool import ToolResult +from fastmcp import Client, FastMCP +from fastmcp.tools import ToolResult +from fastmcp.types import TextContent def with_serializer(serializer: Callable[[Any], str]): @@ -55,18 +56,19 @@ def get_json_data() -> dict: async def example_usage(): - # YAML serialized tool - yaml_result = await server._call_tool_mcp("get_example_data", {}) - print("YAML Tool Result:") - print(yaml_result) - print() + async with Client(server) as client: + # YAML serialized tool + yaml_result = await client.call_tool("get_example_data", {}) + print("YAML Tool Result:") + if yaml_result.content and isinstance(yaml_result.content[0], TextContent): + print(yaml_result.content[0].text) + print() - # Default JSON serialized tool - json_result = await server._call_tool_mcp("get_json_data", {}) - print("JSON Tool Result:") - print(json_result) + # Default JSON serialized tool + json_result = await client.call_tool("get_json_data", {}) + print("JSON Tool Result:") + print(json_result.data) if __name__ == "__main__": asyncio.run(example_usage()) - server.run() diff --git a/examples/providers/sqlite/server.py b/examples/providers/sqlite/server.py index dd9f19ff6..694a2906e 100644 --- a/examples/providers/sqlite/server.py +++ b/examples/providers/sqlite/server.py @@ -23,7 +23,7 @@ from rich import print from fastmcp import Client, FastMCP from fastmcp.server.providers import Provider -from fastmcp.tools.tool import Tool, ToolResult +from fastmcp.tools import Tool, ToolResult DB_PATH = Path(__file__).parent / "tools.db" diff --git a/examples/skills/client.py b/examples/skills/client.py index a376fe235..d5005ac23 100644 --- a/examples/skills/client.py +++ b/examples/skills/client.py @@ -41,7 +41,7 @@ async def main(): print("=== Resource Templates ===") templates = await client.list_resource_templates() for t in templates: - print(f" {t.uriTemplate}") + print(f" {t.uri_template}") print() # Read a skill's main file diff --git a/examples/task_elicitation.py b/examples/task_elicitation.py index 2c7852e03..8709eb21f 100644 --- a/examples/task_elicitation.py +++ b/examples/task_elicitation.py @@ -51,6 +51,7 @@ async def plan_dinner(ctx: Context) -> str: return "Dinner cancelled!" prefs = result.data + assert isinstance(prefs, DinnerPrefs) await ctx.report_progress(1, 2, "Planning your menu...") await asyncio.sleep(1) await ctx.report_progress(2, 2, "Done!") diff --git a/examples/text_me.py b/examples/text_me.py index 26a0350ab..7f5e4c33c 100644 --- a/examples/text_me.py +++ b/examples/text_me.py @@ -28,9 +28,7 @@ from fastmcp import FastMCP class SurgeSettings(BaseSettings): - model_config: SettingsConfigDict = SettingsConfigDict( - env_prefix="SURGE_", env_file=".env" - ) + model_config = SettingsConfigDict(env_prefix="SURGE_", env_file=".env") api_key: str account_id: str diff --git a/examples/tool_result_echo.py b/examples/tool_result_echo.py index bd1185f29..54ed151de 100644 --- a/examples/tool_result_echo.py +++ b/examples/tool_result_echo.py @@ -10,7 +10,7 @@ import time from dataclasses import dataclass from fastmcp import FastMCP -from fastmcp.tools.tool import ToolResult +from fastmcp.tools import ToolResult mcp = FastMCP("Echo Server") diff --git a/pyproject.toml b/pyproject.toml index f8cacf956..91ed0fc19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,14 +136,32 @@ python_functions = ["test_*"] addopts = ["--inline-snapshot=disable"] [tool.ty.src] -include = ["fastmcp_slim", "fastmcp_remote", "tests"] -exclude = ["**/node_modules", "**/__pycache__", ".venv", ".git", "dist"] +include = ["fastmcp_slim", "fastmcp_remote", "tests", "examples"] +exclude = [ + "**/node_modules", + "**/__pycache__", + ".venv", + ".git", + "dist", + # Example subtrees excluded from the ty gate. Each either pins its own + # fastmcp (resolved against a different install) or requires a third-party + # dependency not installed in this tree. + "examples/testing_demo", # own uv.lock, targets fastmcp v1 on purpose + "examples/atproto_mcp", # needs atproto (+ its own package) + "examples/smart_home", # needs phue + "examples/apps/qr_server", # needs qrcode + "examples/providers/sqlite", # needs aiosqlite + "examples/memory.py", # needs asyncpg, numpy, pydantic_ai, pgvector + "examples/get_file.py", # needs aiohttp +] [tool.ty.environment] python-version = "3.10" [tool.ty.analysis] -replace-imports-with-any = ["prefab_ui.**"] +# prefab_ui is the apps SDK; pyautogui/PIL are optional runtime deps used only +# inside example tool bodies (screenshot demos) and are not installed here. +replace-imports-with-any = ["prefab_ui.**", "pyautogui", "PIL", "PIL.**"] [tool.ty.rules] division-by-zero = "warn"