mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 19:27:14 +01:00
Port the MCP server to mcp 2.x, and say what each tool is allowed to do
mcp 2.0.0 removed mcp.server.fastmcp; MCPServer is the same object under a new name, so @mcp.tool() and mcp.run() are untouched and every tool keeps its name, signature and returned shape. Verified against a clean 2.0.0 venv: all four tools list, and the suite passes at 100% coverage, 609 tests. Two things the port buys, neither of which existed in 1.x: * Tool annotations, which matter more here than in the sibling repos because this server has a tool that writes. The three readers are marked read-only, idempotent and closed-world; log_meal is marked not-read-only but explicitly NOT destructive -- it appends and never rewrites or deletes -- not idempotent, since a second confirmed call logs a second meal, and open-world, because it reaches Open Food Facts when macros are not given. A client can now tell the difference without reading the docstring. * Structured output: 2.x derives an output schema from the return types and sends structured content alongside the text. No code change -- it falls out of the type hints that were already there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsXnTHyuuCUZsqetTaajcF
This commit is contained in:
parent
a737843503
commit
58e6731705
@ -103,7 +103,7 @@ repos:
|
||||
- requests
|
||||
- gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.4.0#subdirectory=gatelock
|
||||
- crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.1.0#subdirectory=crdt-sync
|
||||
- mcp>=1.9,<2
|
||||
- mcp>=2,<3
|
||||
- pydantic
|
||||
# Test suites intentionally use patterns (protected-access, magic-value
|
||||
# comparisons) that don't belong in the source-code 10/10 gate.
|
||||
|
||||
@ -37,7 +37,8 @@ import logging
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.server import MCPServer
|
||||
from mcp.types import ToolAnnotations
|
||||
from pydantic import BaseModel
|
||||
|
||||
from diet_guard._budget import BudgetError
|
||||
@ -65,7 +66,26 @@ logging.basicConfig(
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
mcp = FastMCP("diet-guard")
|
||||
mcp = MCPServer("diet-guard")
|
||||
|
||||
# Machine-readable versions of what the docstrings already say, so a client
|
||||
# can decide what to run unattended. The day a "read" tool starts writing,
|
||||
# these annotations are what has to change with it.
|
||||
_READS_ONLY = ToolAnnotations(
|
||||
read_only_hint=True,
|
||||
idempotent_hint=True,
|
||||
open_world_hint=False,
|
||||
)
|
||||
# log_meal is the one tool that writes. It appends and never rewrites or
|
||||
# deletes, so it is not destructive; it is not idempotent either, because a
|
||||
# second confirmed call logs a second meal. It reaches Open Food Facts when
|
||||
# macros are not supplied, which is exactly what open_world means.
|
||||
_APPENDS = ToolAnnotations(
|
||||
read_only_hint=False,
|
||||
destructive_hint=False,
|
||||
idempotent_hint=False,
|
||||
open_world_hint=True,
|
||||
)
|
||||
|
||||
|
||||
def _entry_view(entry: dict[str, object]) -> dict[str, Any]:
|
||||
@ -98,7 +118,7 @@ def _entry_view(entry: dict[str, object]) -> dict[str, Any]:
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@mcp.tool(title="Today's intake status", annotations=_READS_ONLY)
|
||||
def get_status() -> dict[str, Any]:
|
||||
"""Return today's intake status without ever revealing the daily budget.
|
||||
|
||||
@ -126,7 +146,7 @@ def get_status() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@mcp.tool(title="Meals logged today", annotations=_READS_ONLY)
|
||||
def list_today() -> dict[str, Any]:
|
||||
"""List today's logged meals (valid entries only), newest last.
|
||||
|
||||
@ -141,7 +161,7 @@ def list_today() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@mcp.tool(title="Meal slots for the day", annotations=_READS_ONLY)
|
||||
def get_slots() -> dict[str, Any]:
|
||||
"""Return the day's fixed meal slots and which one is current.
|
||||
|
||||
@ -177,7 +197,7 @@ class Macros(BaseModel):
|
||||
fat: float = 0.0
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@mcp.tool(title="Log a meal (gated write)", annotations=_APPENDS)
|
||||
def log_meal(
|
||||
description: str,
|
||||
grams: float | None = None,
|
||||
|
||||
@ -15,7 +15,7 @@ dependencies = [
|
||||
# so the systemd/CLI system-python path stays dependency-light and never has
|
||||
# to import the MCP SDK.
|
||||
[project.optional-dependencies]
|
||||
mcp = ["mcp>=1.9,<2"]
|
||||
mcp = ["mcp>=2,<3"]
|
||||
|
||||
# Without this, setuptools' flat-layout auto-discovery treats the Flutter
|
||||
# app/ directory as a second top-level package and refuses to build.
|
||||
|
||||
@ -5,7 +5,7 @@ codespell>=2.2.0
|
||||
coverage>=7.4.0
|
||||
crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.1.0#subdirectory=crdt-sync
|
||||
gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.4.0#subdirectory=gatelock
|
||||
mcp>=1.9,<2
|
||||
mcp>=2,<3
|
||||
mypy>=1.8.0
|
||||
pre-commit>=3.6.0
|
||||
pydantic>=2
|
||||
|
||||
Loading…
Reference in New Issue
Block a user