Skip to content

feat: MCP capability negotiation + prompts support#5

Merged
kevinwatt merged 8 commits into
mainfrom
mcp-capabilities-and-prompts
Apr 22, 2026
Merged

feat: MCP capability negotiation + prompts support#5
kevinwatt merged 8 commits into
mainfrom
mcp-capabilities-and-prompts

Conversation

@kevinwatt

Copy link
Copy Markdown
Contributor

Summary

  • Fix Dive#348 — negotiate MCP capabilities per spec: tools/list / prompts/list are only called when the server advertises the matching capability. A defensive _safe_list helper swallows -32601 Method not found so a misbehaving server cannot break startup. Same gating applied to the local-HTTP init path.
  • Add Dive#349 — first-class MCP prompts support: cached prompts/list, prompts/get round-trip, and auto-refresh on notifications/prompts/list_changed (notifications deduped via a tracked task so bursts don't stack up sessions).

New HTTP endpoints (the existing /api/tools response also carries prompts):

GET  /api/tools/{server}/prompts          # ?refresh=true bypasses cache
POST /api/tools/{server}/prompts/get      # body: {"name": "...", "arguments": {...}}

Backwards compatibility

Wire format changes are purely additive:

  • McpTool / McpServerInfo gain prompts: [...] = [] (Pydantic v2 default extra="ignore").
  • New routes are additive; literal routes (/initialized, /login/oauth/*, /elicitation/respond) are registered before the new path-param route, so FastAPI matches them first.
  • No public method on McpServer / ToolManager / DiveMcpHost was renamed or removed.

Behavior change: servers that previously failed init because they advertised tools but returned -32601 on tools/list (or were prompts-only) now come up as RUNNING with the correct partial inventory. Old front-ends see this as "more servers work now" rather than a regression.

Docs:

  • README.md rewritten with badges, Mermaid architecture + lifecycle diagrams, config walk-through, capability table.
  • CLAUDE.md added.

Test plan

  • pytest tests/test_capabilities_and_prompts.py — 12/12 pass: tools-only, prompts-only, tools+prompts, no-capabilities, METHOD_NOT_FOUND fallback (both live server and unit), prompt round-trip, list-changed refresh, cache-no-refetch on empty, get_prompt without capability raises ValueError, notification dedup.
  • pytest tests/test_tools.py tests/httpd/routers/test_tools.py — no regressions in existing suites (53 pass; 3 pre-existing environmental failures unrelated to these changes: test_tool_proxy, test_stream_logs_*).
  • Dive frontend session picks up #349 UI (separate PR in main Dive repo).

alex-funmula and others added 8 commits February 25, 2026 14:17
Mainly pillow, langchain-core and cryptography to solve security issue
Fix Dive#348: only call tools/list, prompts/list, etc. when the server
advertises the matching capability in its initialize response. Add a
defensive _safe_list helper that swallows -32601 Method not found so a
misbehaving server cannot break startup. Apply the same gating in
local_http_server's init path.

Add Dive#349: implement prompts/list and prompts/get on McpServer with
a cached list refreshed on notifications/prompts/list_changed.
Notifications are deduped via a tracked _refresh_prompts_task so bursts
do not stack up sessions. Expose prompts via McpServerInfo.prompts and
two new HTTP routes:

  GET  /api/tools/{server}/prompts
  POST /api/tools/{server}/prompts/get

Tests cover tools-only, prompts-only, tools+prompts, and capabilities-
less servers, plus prompts/get round-trip, METHOD_NOT_FOUND fallback,
empty-cache reuse, and notification dedup.
README: full rewrite with badges, Mermaid architecture and lifecycle
diagrams, Quick Start for HTTP / CLI / SDK, configuration walk-through,
MCP capability and HTTP API tables.

CLAUDE.md: project role and backwards-compat discipline, common
commands, ContextProtocol-based lifecycle, McpServer transport
dispatch and capability gating, persistence layout, testing notes.
- add docstrings to build_server / main in capability_server.py
- wrap build_server signature to fit 88-col line length
- mark unused `name` arg in _call_tool with noqa ARG001
- reformat patch.object call in test for line length
- README: corrected DiveMcpHost import path (dive_mcp_host.host.host,
  not dive_mcp_host.host — the latter's __init__.py is empty, so the
  quick-start snippet as written would ImportError).
- Wrap new prompts HTTP endpoints in the repo-standard DataResult[T]
  envelope to match skills.py and the rest of the API surface, so
  frontends always see {success, message, data}. Drops the bespoke
  GetPromptResponse model in favour of DataResult[GetPromptResult].
- Apply `ruff format` to the files missed in the earlier style pass
  (mcp_server.py, tests/mcp_servers/capability_server.py,
  tests/test_capabilities_and_prompts.py).
- mcp_server.py:1109 was assigning to `self._init_result`, a typo — the
  rest of the class reads `self._initialize_result`. Local-HTTP
  spawn-and-connect servers therefore always reported
  initialize_result=None, and my _supports_prompts() helper couldn't
  tell whether they advertised prompts. Fixed the attribute name.

- test_stream_logs_notfound_wait / test_stream_logs_name_with_slash
  were pinned to responses[-3] == STREAMING_ERROR. Adding prompts/list
  during init made the fastmcp-based echo server emit one more
  "Processing request of type ListPromptsRequest" stderr line, shifting
  the index to [-4]. Rewrote the assertions to check the invariant
  (one or more STREAMING_ERROR followed by STDERR+INIT events, ending
  in STATUS_CHANGE+RUNNING) without hard-coding event counts.

- Added a regression test that asserts local-HTTP servers now
  populate initialize_result.
@kevinwatt kevinwatt merged commit 406c90a into main Apr 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants