Skip to content

feat(mcp): #36 add prompts assertions + per-type content + dual-mode drill-ins#37

Merged
othercodes merged 2 commits into
masterfrom
feat/36-mcp-prompts-assertions
May 20, 2026
Merged

feat(mcp): #36 add prompts assertions + per-type content + dual-mode drill-ins#37
othercodes merged 2 commits into
masterfrom
feat/36-mcp-prompts-assertions

Conversation

@othercodes
Copy link
Copy Markdown
Owner

Summary

Closes #36. Adds prompts/list and prompts/get assertions to the MCP module and refactors content drill-in to follow the library-wide dual-mode scoping pattern.

  • Per-type AssertableContent — discriminator splits into 5 typed subclasses (AssertableTextContent, AssertableImageContent, AssertableAudioContent, AssertableResourceLinkContent, AssertableResourceContent) with @overload narrowing on is_<type>(cb=None).
  • Dual-mode content() on AssertableToolCall (callback now optional) + is_<type>(idx, cb=None) typed shortcuts as siblings.
  • AssertablePromptList + AssertablePromptDef for catalog assertions mirroring the tools surface.
  • AssertablePromptGet + AssertablePromptMessage for prompts/get, with dual-mode first_message / message / last_message and per-type is_<type>(cb=None) shortcuts inside messages.
  • is_prompts_list_changed_notification() on AssertableMCP.

The full design discussion, pattern audit, and decision log lives in #36.

Backward compatibility

  • AssertableToolCall.content(idx, callback) callback was required, now optional. All existing call sites still work (they pass callbacks).
  • AssertableContent.is_<type>() previously returned Self; now returns the typed subclass. Existing chains like c.is_text().text_equals(...) keep working because text_equals now lives on AssertableTextContent (returned by the new is_text()), not on AssertableContent.

Test plan

  • 1118 tests pass (uv run pytest)
  • 100% coverage enforced and met
  • uv run mypy --strict src/pyssertive/protocols/mcp/ clean
  • uv run ruff check . and uv run ruff format --check . clean
  • Smell-test goal achieved: the two raw _envelope.result accesses cited in feat(mcp): add prompts/list and prompts/get assertions #36 can be rewritten using only the public API

… content + dual-mode drill-ins

- Refactor AssertableContent into discriminator + 5 per-type subclasses
  (AssertableTextContent, AssertableImageContent, AssertableAudioContent,
  AssertableResourceLinkContent, AssertableResourceContent) with @overload
  type narrowing on is_<type>(cb=None)
- Migrate AssertableToolCall.content() to dual-mode (callback optional)
  and add typed is_text / is_image / is_audio / is_resource_link / is_resource(idx)
  shortcuts that scope into the typed subclass
- Add AssertablePromptList + AssertablePromptDef for prompts/list assertions
  (with_count, contains_prompt, does_not_contain_prompt, every_prompt,
  has_more_pages, documented, accepts, accepts_optional, does_not_accept)
- Add AssertablePromptGet + AssertablePromptMessage for prompts/get
  assertions (with_description, with_message_count, first_message,
  message, last_message, every_message, is_rejected_with_invalid_params,
  is_from_user, is_from_assistant, has_text_content, content, is_<type>)
- Add is_prompts_list_changed_notification() helper on AssertableMCP
- Apply dual-mode scoping (cb=None) across content() and message drill-ins
  for consistency with json() / html() / arch.module() patterns

Closes #36
@othercodes othercodes force-pushed the feat/36-mcp-prompts-assertions branch 2 times, most recently from 1c33db2 to cd000db Compare May 19, 2026 08:46
@othercodes othercodes self-assigned this May 19, 2026
@othercodes othercodes force-pushed the feat/36-mcp-prompts-assertions branch 2 times, most recently from 409ab67 to de1a53a Compare May 19, 2026 09:07
…tool shortcuts

BREAKING CHANGE: AssertableTextContent, AssertableImageContent,
AssertableAudioContent, AssertableResourceLinkContent, and
AssertableResourceContent have been removed from the public API.
All type-aware assertions live on AssertableContent now, with
implicit type-checks per method and auto-dispatch across compatible
types (e.g. with_uri works on both resource_link and embedded
resource; with_text works on both text and resource embedded text).

The typed shortcut methods on AssertableToolCall
(is_text/is_image/is_audio/is_resource_link/is_resource at index)
have been removed. Chain via .content(idx) instead.

The text-shortcut methods on AssertablePromptMessage
(has_text, with_text, with_text_containing) and the typed
is_<type> shortcuts have been removed. Chain via .content()
instead.

Behavior changes:
- AssertableContent.is_not_empty() now covers image and audio block
  types (was text/resource only). A resource block with blob but
  no text field now passes is_not_empty - semantically "has payload,
  even if binary".
- named() now accepts embedded resource blocks too, not just
  resource_link, per MCP spec 2025-11-25 which permits a name field
  inside the resource object.

Migration:
  .tool().is_text(0).with_text("hi")  -> .tool().content(0).with_text("hi")
  .first_message().has_text()         -> .first_message().content().is_not_empty()
  .first_message().with_text("hi")    -> .first_message().content().with_text("hi")
  .first_message().is_image()...      -> .first_message().content().with_mime_type(...)

Co-changes in this PR:
- AssertablePromptGet gains succeeds() for symmetry with ToolCall.
- AssertableContent constructor takes label: str (was index: int)
  so error messages carry caller context (Tool 'X' content[N] vs
  Message[N].content).
- AssertableContent gained a class-level docstring with the
  method/type compatibility matrix.
- AssertablePromptMessage._content_block distinguishes "missing
  content field" from "non-dict content".
- AssertableToolCall._content_at resolves negative indices to
  positive in error labels for consistency with PromptMessage.
- Parametrized tests use ids= for readable failure messages.

Closes #36
@othercodes othercodes force-pushed the feat/36-mcp-prompts-assertions branch from de1a53a to 0c4be12 Compare May 20, 2026 00:12
@sonarqubecloud
Copy link
Copy Markdown

@othercodes othercodes merged commit 62b673a into master May 20, 2026
33 checks passed
@othercodes othercodes deleted the feat/36-mcp-prompts-assertions branch May 20, 2026 00:20
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.

feat(mcp): add prompts/list and prompts/get assertions

1 participant