Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
include:
- profile: locked-py311
python-version: "3.11"
core-ref: 302ce2b9cda575f927a3e4d7f13a310cd798a3d3
core-ref: 612bfe7e5290eb5b23f2811baa83b8a28293b36e
sync-args: --frozen
- profile: compatibility-py312
python-version: "3.12"
Expand Down
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ does not rely on projection for security.
opaque, owner-bound, expiring handle and does not change `tools/list` or confer
permission. An expired or mismatched handle returns a structured recovery error.

Collection facades (`*_query`, `*_search`, event/history views, draft indexes,
content packs, NPC conversations, Skills, and exposure search) accept a bounded
filter plus `limit`/`top_k` of 1–100 and an opaque `cursor`. Successful list
results preserve the existing `result` and text fallback while adding `page`
and top-level `next_cursor`. A cursor is bound to its authorized collection and
filter; it is neither identity nor authority, and every continuation is
authorized again. Do not parse cursors. Restart at page one after an invalid or
expired cursor. `content_pack(include_package=true)` is the deliberate exception:
it requests one complete, finite import artifact rather than a catalog page.

## Install and run

Python 3.11 or newer is required.
Expand Down Expand Up @@ -283,10 +293,14 @@ uv run --package sagasmith-dnd-mcp pytest \
```

The repository contains ten independent, deterministic, read-only MCP Builder
evaluations in `packages/mcp/evaluations/read_only.xml`; the test fixture solves
and verifies every answer without a paid model or external service. Write tests
separately cover authorization, idempotency, stale revisions, concurrency,
cancellation, restart recovery, structured errors, media, and transport parity.
evaluations in `packages/mcp/evaluations/read_only.xml`. Each question starts a
fresh traversal of a six-campaign, 30-actor fixture, follows multiple campaign
and roster continuation pages, inspects actor details, resolves the system
catalog, and computes its own answer. The test requires at least 35 public-tool
calls and seven cursor continuations per question and verifies every answer
without a paid model or external service. Write tests separately cover
authorization, idempotency, stale revisions, concurrency, cancellation, restart
recovery, structured errors, media, and transport parity.

## Upgrade and rollback

Expand Down
41 changes: 36 additions & 5 deletions packages/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,34 @@ metadata are evaluated per request. Cross-call state is explicit:

Handles are names, never capabilities. Every use is independently authorized.

### Bounded collection contract

All potentially growing list/search/query surfaces use the same continuation
contract. Supply a text `query`, `limit` (or `top_k` for ranked search) from 1
through 100, and the previous opaque `cursor`. The response retains the
compatible `result` plus concise text and adds:

```json
{
"page": {
"limit": 50,
"returned": 50,
"has_more": true,
"next_cursor": "opaque"
},
"next_cursor": "opaque"
}
```

The cursor is scoped to the authorized collection and normalized filter. It
must not be parsed, stored as a credential, or reused with another campaign,
principal, view, branch, or query. Continuations rerun authorization. On an
invalid/expired cursor, restart without a cursor. Older facade clients may place
the cursor, query, limit, or offset inside `payload`; new clients should use the
top-level fields. A materialized catalog page also reports `total_count` when it
is available without reading unbounded history. `content_pack(include_package=true)`
explicitly retrieves one complete finite import artifact and is not a catalog list.

### Stable catalog and bounded Host projection

The current contract test locks 77 public tools. `tools/list` returns them in
Expand Down Expand Up @@ -324,11 +352,14 @@ uv run --package sagasmith-dnd-mcp pytest \
```

`evaluations/read_only.xml` contains ten independent, complex, deterministic,
read-only MCP Builder questions. `test_read_only_evaluations.py` creates a fixed
fixture, solves every question through read-only public tools, and verifies the
answers without a paid model or external service. Write-side tests separately
cover idempotency, authorization isolation, stale revisions, concurrency,
cancellation, restart recovery, and structured errors.
read-only MCP Builder questions. For every question,
`test_read_only_evaluations.py` independently traverses multiple pages of six
campaigns and 30 nested actors, reads actor detail records, resolves system
metadata, and computes the answer. The test rejects a nominal ten-question file
unless each solver performs at least 35 public-tool calls and seven cursor
continuations. It uses no paid model or external service. Write-side tests
separately cover idempotency, authorization isolation, stale revisions,
concurrency, cancellation, restart recovery, and structured errors.

The opt-in real-provider corpus regression is documented in
[`docs/FULL_AGENT_REGRESSION.md`](docs/FULL_AGENT_REGRESSION.md). It is not part
Expand Down
36 changes: 18 additions & 18 deletions packages/mcp/evaluations/read_only.xml
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<evaluation>
<qa_pair>
<question>Across every active campaign in the deterministic evaluation fixture, inspect each campaign and its actor roster. Which campaign has the largest roster?</question>
<answer>Alpha Expedition</answer>
<question>Traverse the complete campaign catalog through pagination, then page through every campaign roster. Compare NPC counts across campaigns; which campaign has the largest NPC roster?</question>
<answer>Ivory Archive</answer>
</qa_pair>
<qa_pair>
<question>Explore all active campaigns and all of their actors. Considering only player characters, which actor name is alphabetically last?</question>
<answer>Zephyr</answer>
<question>Explore every paginated campaign and roster, separate monsters from PCs and NPCs, and compare monster totals by campaign. Which campaign contains the most monsters?</question>
<answer>Violet March</answer>
</qa_pair>
<qa_pair>
<question>Find the sole actor classified as a monster, then resolve its campaign. What is that campaign's name?</question>
<answer>Beta Citadel</answer>
<question>After paginating across all campaigns and their complete rosters, keep only campaigns with exactly two player characters. Of those, which has the larger total roster?</question>
<answer>Cobalt Labyrinth</answer>
</qa_pair>
<qa_pair>
<question>Enumerate all active campaigns and their complete actor rosters. How many actors are classified as NPCs in total?</question>
<answer>4</answer>
<question>Walk all campaign pages and all nested roster pages. Considering only actors classified as player characters, what is the alphabetically last actor name?</question>
<answer>Zephyr</answer>
</qa_pair>
<qa_pair>
<question>After traversing every active campaign and its actor roster, how many campaign-bound actors exist altogether?</question>
<answer>7</answer>
<question>Discover all campaigns, page through their rosters, and inspect the detailed actor records to find the keeper whose summary mentions the sealed lens. What is that actor's campaign slug?</question>
<answer>amber-observatory</answer>
</qa_pair>
<qa_pair>
<question>Compare role composition across the active campaigns. Which campaign has the same number of player characters and NPCs?</question>
<answer>Beta Citadel</answer>
<question>Locate the campaign whose description says tidal bells guide navigation, then page through its roster and sort only its NPCs. Which NPC name comes first alphabetically?</question>
<answer>Quill</answer>
</qa_pair>
<qa_pair>
<question>Read the detailed records for every actor and locate the summary that mentions a sealed observatory. What is that actor's name?</question>
<answer>Nyx</answer>
<question>Paginate every campaign and roster, exclude every campaign that has any monsters, and compare the remaining NPC-to-PC ratios. Which campaign has the highest ratio?</question>
<answer>Ivory Archive</answer>
</qa_pair>
<qa_pair>
<question>Resolve the roster belonging to Beta Citadel and count the distinct actor classifications represented there. What is the count?</question>
<question>Find the campaign with the largest total roster after traversing all campaign and character pages. How many distinct actor classifications appear in that roster?</question>
<answer>3</answer>
</qa_pair>
<qa_pair>
<question>Resolve each campaign's system identifier through the server's supported-system catalog. What display name is shared by both fixture campaigns?</question>
<question>Sort the fully paginated campaign catalog by name, compare the alphabetically first and last campaigns, and resolve their shared system identifier through the supported-system catalog. What display name does it have?</question>
<answer>Dungeons &amp; Dragons 5e</answer>
</qa_pair>
<qa_pair>
<question>Sort the active campaigns by their human-readable names, select the first, and report its canonical slug.</question>
<answer>alpha-expedition</answer>
<question>Across every campaign and complete paginated roster, find the campaign where the monster count equals the combined PC and NPC count. What is that campaign's canonical slug?</question>
<answer>violet-march</answer>
</qa_pair>
</evaluation>
Loading