mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 20:07:16 +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 whole suite -- including test_mcp.py, which could not even import while the dependency was broken -- passes at 100% coverage, 901 tests. Two things the port buys, neither of which existed in 1.x: * Tool annotations. The section banner here already said "state-only; never expose secrets, never mutate"; now the protocol says it too, as read-only, idempotent and closed-world hints, which is what lets a client run these without asking first. The day one of them starts writing, the annotation is what has to change with it. * Structured output. 2.x derives an output schema from the return types and sends structured content alongside the text, so a caller no longer re-parses JSON we already had. 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
feb7c173d5
commit
a0a3fb0eb0
@ -101,7 +101,7 @@ repos:
|
||||
- pytest
|
||||
- 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
|
||||
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
|
||||
@ -20,7 +20,7 @@ screen-locker-status = "screen_locker.status_view:main"
|
||||
# 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"]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
# This repo also holds stronglift_replacement/ (an unrelated Flutter app, not
|
||||
|
||||
@ -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
|
||||
pylint>=3.0.0
|
||||
|
||||
@ -37,7 +37,8 @@ from pathlib import Path
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.server import MCPServer
|
||||
from mcp.types import ToolAnnotations
|
||||
|
||||
from screen_locker._compliance_state import (
|
||||
has_logged_today,
|
||||
@ -62,7 +63,18 @@ logging.basicConfig(
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
mcp = FastMCP("screen-locker")
|
||||
mcp = MCPServer("screen-locker")
|
||||
|
||||
# Every tool here only reads local state: the workout log, the sick history
|
||||
# and the clock. Saying so lets a client run them without asking -- and the
|
||||
# day one of them stops being read-only, this annotation is the thing that
|
||||
# has to change with it. (See the section banner below: "never mutate" was
|
||||
# already the rule; this is the machine-readable half of it.)
|
||||
_READS_ONLY = ToolAnnotations(
|
||||
read_only_hint=True,
|
||||
idempotent_hint=True,
|
||||
open_world_hint=False,
|
||||
)
|
||||
|
||||
# The workout log lives beside the package, same default as
|
||||
# ``_status_data._DEFAULT_LOG_FILE``; declared here to avoid importing a
|
||||
@ -75,7 +87,7 @@ _LOG_FILE = Path(__file__).resolve().parent / "workout_log.json"
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@mcp.tool(title="Workout gate status", annotations=_READS_ONLY)
|
||||
def get_status() -> dict[str, Any]:
|
||||
"""Return the full read-only status snapshot.
|
||||
|
||||
@ -87,7 +99,7 @@ def get_status() -> dict[str, Any]:
|
||||
return asdict(gather_status())
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@mcp.tool(title="One-line status summary", annotations=_READS_ONLY)
|
||||
def get_summary() -> dict[str, Any]:
|
||||
"""Return the one-line i3blocks summary plus the compliance-state word.
|
||||
|
||||
@ -102,7 +114,7 @@ def get_summary() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@mcp.tool(title="Why the screen is locked", annotations=_READS_ONLY)
|
||||
def explain_lock() -> dict[str, Any]:
|
||||
"""Return why the screen is or is not being locked right now.
|
||||
|
||||
@ -114,7 +126,7 @@ def explain_lock() -> dict[str, Any]:
|
||||
return asdict(gather_status().lock_explanation)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@mcp.tool(title="Lock-decision predicates", annotations=_READS_ONLY)
|
||||
def get_flags() -> dict[str, bool]:
|
||||
"""Return the individual boolean lock-decision predicates for today.
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user