Summary
Bump the mcp (Model Context Protocol Python SDK) dependency from the current >=1.0.0 (installed: 1.28.0) to mcp>=2.0.0,<3.0.0. This is a major version with breaking API changes, but the blast radius in dsagt is small and fully scoped below.
Background
mcp 2.0.0 reworked the low-level Server API (dsagt uses mcp.server.lowlevel.Server, not FastMCP) to support the 2026-07-28 MCP spec. Verified directly against the installed mcp==2.0.0 package (not from training-data memory, which predates this release):
@server.list_tools() / @server.call_tool() decorators are removed. Handlers are now passed as on_list_tools= / on_call_tool= kwargs to the Server() constructor.
on_list_tools(ctx, params) must return types.ListToolsResult(tools=[...]), not a bare list.
on_call_tool(ctx, params: types.CallToolRequestParams) replaces (tool_name: str, arguments: dict) — read params.name / params.arguments.
on_call_tool must return types.CallToolResult(content=[...]), not a bare list[TextContent].
server.request_handlers[SomeRequestClass] (dict keyed by pydantic request classes) is removed. Public replacement: server.get_request_handler("tools/call") / "tools/list" (string-keyed), returning HandlerEntry(params_type, handler).
- Response wrappers no longer have
.root (the RootModel union pattern is gone) — read result.content[0].text directly.
Confirmed unaffected by a real import/construction smoke test against mcp==2.0.0: mcp.server.stdio.stdio_server, mcp.server.lowlevel.NotificationOptions, mcp.server.models.InitializationOptions, server.get_capabilities(notification_options=, experimental_capabilities=), and types.Tool(inputSchema=...) construction (camelCase kwargs still populate via pydantic alias).
Practical implication: registry_tools.py, knowledge_tools.py, memory_tools.py, skill_tools.py never touch the SDK Server object directly — they only build types.Tool(...) and return plain str/dict, which is a dsagt-internal contract. Only build_dispatch_server() in src/dsagt/mcp/server.py (~50 lines) talks to the Server class.
Scope
-
pyproject.toml / uv.lock — bump to mcp>=2.0.0,<3.0.0 (pin the upper bound given the demonstrated history of breaking majors), uv sync.
-
src/dsagt/mcp/server.py — build_dispatch_server() — replace the decorator-based list_tools()/call_tool() with on_list_tools/on_call_tool callables passed to Server(name, on_list_tools=..., on_call_tool=...). Preserve the existing per-tool dispatch logic (span opening, ValueError → error-dict, str-vs-dict formatting) as the body of on_call_tool; only the outer signature (ctx, params in; types.CallToolResult out) and on_list_tools (wrap in types.ListToolsResult) change. _run_stdio, create_dsagt_server, and main() are untouched — they only call server.run(...) / server.get_capabilities(...), both stable across the bump.
-
Test harness — 5 files reach into server.request_handlers[...] / .root, which no longer exist:
tests/mcp_helpers.py (call_tool_sync / call_tool_async, used broadly)
tests/test_dsagt_server.py (_list_tools / _call helpers)
tests/test_kb_search_filters.py, tests/test_knowledge_server.py, tests/test_memory_tools.py (inline copies of the same pattern)
Fix: replace server.request_handlers[types.CallToolRequest] with server.get_request_handler("tools/call").handler (pass ctx=None — dsagt's own handler never touches it), same for "tools/list", and drop the .root unwrap. Verified this pattern end-to-end against real mcp==2.0.0.
tests/mcp_helpers.py's subprocess-based helpers (mcp_call_tool, mcp_initialize, real JSON-RPC over stdio) are unaffected — they exercise the wire protocol, not SDK internals.
Open question: the 3 inline copies (test_kb_search_filters.py, test_knowledge_server.py, test_memory_tools.py) duplicate the same helper logic already in mcp_helpers.py. Since all three need to be touched anyway, consider consolidating them onto the shared mcp_helpers.py versions instead of patching each copy in place.
Verification
uv run black .
uv run ruff check .
uv run --no-sync python -m pytest tests/test_dsagt_server.py tests/test_registry_server.py tests/test_knowledge_server.py tests/test_memory_tools.py tests/test_skill_tools.py tests/test_kb_search_filters.py -q
- Skip the network/subprocess integration suites (
test_integration.py, test_*_integration.py, test_server_startup.py, test_dependency_integration.py) unless something above regresses them.
Summary
Bump the
mcp(Model Context Protocol Python SDK) dependency from the current>=1.0.0(installed: 1.28.0) tomcp>=2.0.0,<3.0.0. This is a major version with breaking API changes, but the blast radius in dsagt is small and fully scoped below.Background
mcp2.0.0 reworked the low-levelServerAPI (dsagt usesmcp.server.lowlevel.Server, notFastMCP) to support the 2026-07-28 MCP spec. Verified directly against the installedmcp==2.0.0package (not from training-data memory, which predates this release):@server.list_tools()/@server.call_tool()decorators are removed. Handlers are now passed ason_list_tools=/on_call_tool=kwargs to theServer()constructor.on_list_tools(ctx, params)must returntypes.ListToolsResult(tools=[...]), not a bare list.on_call_tool(ctx, params: types.CallToolRequestParams)replaces(tool_name: str, arguments: dict)— readparams.name/params.arguments.on_call_toolmust returntypes.CallToolResult(content=[...]), not a barelist[TextContent].server.request_handlers[SomeRequestClass](dict keyed by pydantic request classes) is removed. Public replacement:server.get_request_handler("tools/call")/"tools/list"(string-keyed), returningHandlerEntry(params_type, handler)..root(theRootModelunion pattern is gone) — readresult.content[0].textdirectly.Confirmed unaffected by a real import/construction smoke test against
mcp==2.0.0:mcp.server.stdio.stdio_server,mcp.server.lowlevel.NotificationOptions,mcp.server.models.InitializationOptions,server.get_capabilities(notification_options=, experimental_capabilities=), andtypes.Tool(inputSchema=...)construction (camelCase kwargs still populate via pydantic alias).Practical implication:
registry_tools.py,knowledge_tools.py,memory_tools.py,skill_tools.pynever touch the SDKServerobject directly — they only buildtypes.Tool(...)and return plainstr/dict, which is a dsagt-internal contract. Onlybuild_dispatch_server()insrc/dsagt/mcp/server.py(~50 lines) talks to theServerclass.Scope
pyproject.toml/uv.lock— bump tomcp>=2.0.0,<3.0.0(pin the upper bound given the demonstrated history of breaking majors),uv sync.src/dsagt/mcp/server.py—build_dispatch_server()— replace the decorator-basedlist_tools()/call_tool()withon_list_tools/on_call_toolcallables passed toServer(name, on_list_tools=..., on_call_tool=...). Preserve the existing per-tool dispatch logic (span opening,ValueError→ error-dict, str-vs-dict formatting) as the body ofon_call_tool; only the outer signature (ctx, paramsin;types.CallToolResultout) andon_list_tools(wrap intypes.ListToolsResult) change._run_stdio,create_dsagt_server, andmain()are untouched — they only callserver.run(...)/server.get_capabilities(...), both stable across the bump.Test harness — 5 files reach into
server.request_handlers[...]/.root, which no longer exist:tests/mcp_helpers.py(call_tool_sync/call_tool_async, used broadly)tests/test_dsagt_server.py(_list_tools/_callhelpers)tests/test_kb_search_filters.py,tests/test_knowledge_server.py,tests/test_memory_tools.py(inline copies of the same pattern)Fix: replace
server.request_handlers[types.CallToolRequest]withserver.get_request_handler("tools/call").handler(passctx=None— dsagt's own handler never touches it), same for"tools/list", and drop the.rootunwrap. Verified this pattern end-to-end against realmcp==2.0.0.tests/mcp_helpers.py's subprocess-based helpers (mcp_call_tool,mcp_initialize, real JSON-RPC over stdio) are unaffected — they exercise the wire protocol, not SDK internals.Open question: the 3 inline copies (
test_kb_search_filters.py,test_knowledge_server.py,test_memory_tools.py) duplicate the same helper logic already inmcp_helpers.py. Since all three need to be touched anyway, consider consolidating them onto the sharedmcp_helpers.pyversions instead of patching each copy in place.Verification
uv run black .uv run ruff check .uv run --no-sync python -m pytest tests/test_dsagt_server.py tests/test_registry_server.py tests/test_knowledge_server.py tests/test_memory_tools.py tests/test_skill_tools.py tests/test_kb_search_filters.py -qtest_integration.py,test_*_integration.py,test_server_startup.py,test_dependency_integration.py) unless something above regresses them.