Skip to content

MCP resources: decks, due-today, recent (#166) - #186

Merged
philphilphil merged 19 commits into
mainfrom
feat/mcp-resources
Jun 13, 2026
Merged

philphilphil merged 19 commits into
mainfrom
feat/mcp-resources

Conversation

@philphilphil

Copy link
Copy Markdown
Owner

Closes #166.

Summary

Implements MCP resources for the Fasolt MCP server:

  • fasolt://deck/{deckId} — one entry per active deck the user owns
  • fasolt://due-today — singleton, cards due today grouped by deck
  • fasolt://recent — singleton, cards created in the last 7 days, newest first

Clients with @-resource pickers (Claude Code, Claude Desktop) can now attach these into a conversation by deck name. The deck mention syntax in Claude Code is @fasolt:fasolt://deck/<id>, surfaced in the autocomplete by human deck name.

Side effect: the resources/list and resources/templates/list "method not implemented" log lines from codex-mcp-client are gone — those endpoints now have real handlers.

Architecture

Pure handler-based MCP registration via WithListResourcesHandler, WithListResourceTemplatesHandler, WithReadResourceHandler in a new AddFasoltResources() extension. All rendering logic lives in McpResourceService (unit-testable). Auth pattern matches existing tools (McpUserResolver.GetUserId), with a localized try/catch that translates UnauthorizedAccessException into a clean MCP error.

Files:

  • fasolt.Server/Application/Services/McpResourceService.cs — rendering + queries
  • fasolt.Server/Api/McpResources/ResourceUris.cs — URI constants + parser
  • fasolt.Server/Api/McpResources/McpResourceHandlers.cs — handler delegate registration
  • fasolt.Server/Program.cs — .AddFasoltResources() in the AddMcpServer chain + # Resources note in ServerInstructions

Content shape

Each card renders as:

```markdown
Front: essen

Back: to eat

Source: notes/german/verbs.md
```

  • Back: block omitted if back is empty
  • *Source: ...* footer omitted if no source file
  • Cards with SVG: [has SVG image — use get_card for full content] (text-focused, keeps content bounded)

Truncation: 100 cards / 80 KB budget per resource with *Showing X of Y cards.* footer.

Test plan

  • 30 unit tests in fasolt.Tests/McpResourceServiceTests.cs cover deck/due-today/recent rendering, truncation, suspended-card and suspended-deck filtering, source/SVG handling, cross-user isolation, deck grouping with (no deck) bucket, lazy group-header emission on truncation boundaries.
  • 2 integration tests in fasolt.Tests/McpResourceEndpointTests.cs confirm McpResourceService resolves from the real DI container with a seeded user, and /mcp rejects unauthenticated callers.
  • Full suite: 356/356 pass, 0 warnings.

References

  • Design spec: docs/superpowers/specs/2026-05-28-mcp-resources-design.md
  • Implementation plan: docs/superpowers/plans/2026-05-28-mcp-resources.md

🤖 Generated with Claude Code

philphilphil and others added 19 commits May 28, 2026 12:58
Spec for exposing decks, due-today, and recent as MCP resources so
clients with @-pickers (Claude Code, Claude Desktop) can pin them
into conversations. Also resolves resources/list and
resources/templates/list 'method not implemented' log noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 TDD tasks covering McpResourceService rendering, URI helpers,
handler registration in Program.cs, and integration tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lists active decks plus two static resources (due-today, recent) for
the authenticated user. Render methods stubbed; subsequent tasks fill
them in.

Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements RenderDeckAsync with header (name, counts, description),
**Front:** / **Back:** card blocks, source footer, and SVG note.
Empty and missing decks handled.

Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cards due now (or new), suspended cards/decks excluded, grouped by
first alphabetical deck name with a (no deck) bucket at the end.

Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the '## DeckName (N cards)' header was written before the
inner card loop, so if truncation fired on the group's first card the
header appeared with nothing under it. Now the header is written lazily,
right before the first successful card append. Also adds the
"rendered > 0" guard to match AppendCardsWithTruncation so a single
oversized card still renders.

Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Excludes suspended cards. Each block carries Created date and first
non-suspended deck name as metadata.

Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires resources/list, resources/templates/list, resources/read into
the MCP server via AddFasoltResources(). Server now advertises the
resources capability. Eliminates 'method not implemented' log noise
for clients that probe these endpoints.

Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
McpUserResolver throws UnauthorizedAccessException; before this fix it
would propagate as a 500 from the resource handlers (the tool path is
already covered by AddCallToolFilter). Wraps the resolve call in a
small helper that re-throws as McpProtocolException with
McpErrorCode.InvalidRequest so the client gets a clean JSON-RPC error.

Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Confirms McpResourceService resolves from the real DI container and
produces the expected entries for a seeded user, and that the /mcp
endpoint rejects unauthenticated callers.

Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps the assertions in try/finally so the DELETE always runs, avoiding
orphaned rows in the shared test database when an assertion throws.

Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the ReviewService constructor parameter that was scaffolded
in Task 1 but never used; the EF queries in the render methods went
direct to AppDbContext. Test factory updated to match.

Also updates the design spec: the per-card source footer is now
'*Source: <file>*' without the '→ heading' suffix the original draft
implied, since Card.SourceHeading is not part of the data model.

Refs #166

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tool path already does this via AddCallToolFilter in Program.cs;
resource handlers had no equivalent so an unexpected exception (e.g.
DB hiccup, EF projection failure) would surface to the SDK with no Seq
log entry. Matches the tool pattern: info on each call, error on
non-McpException / non-cancellation throws.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resource render queries selected the full FrontSvg/BackSvg columns
(up to ~1MB each) only to null-check them for the "[has SVG image]"
placeholder. Project a HasSvg boolean instead so the payloads stay in
Postgres. Also add a hard pre-render cap to RenderDueTodayAsync
(QueryHardCap, matching RenderRecentAsync) with soonest-due ordering so
a large due backlog no longer materializes in full.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resource summaries/footers read counts from a separate COUNT query
instead of the materialized page, so they stay accurate when the result
set exceeds the 200-row page cap (e.g. "Showing 100 of 250 cards").
Also fix the deck list description to count only non-suspended cards,
matching the rendered deck body.

Refs #166

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@philphilphil
philphilphil merged commit cfe56cd into main Jun 13, 2026
2 checks 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.

MCP Resources: expose decks as readable resources

1 participant