Skip to content

feat: expose companion skill as MCP resources - #7

Merged
MattJColes merged 1 commit into
mainfrom
feat/expose-companion-skill
Jul 17, 2026
Merged

feat: expose companion skill as MCP resources#7
MattJColes merged 1 commit into
mainfrom
feat/expose-companion-skill

Conversation

@MattJColes

Copy link
Copy Markdown
Owner

Summary

Verification

  • python -m pytest -q -p no:cacheprovider — 81 passed
  • python scripts/spec_drift_gate.py --check-anchors — 20 anchors resolve exactly once
  • python scripts/spec_drift_gate.py --gate --base origin/main — no drift
  • built wheel contains acme_mcp/skills/handle-downloads/SKILL.md

Proposed living-spec patch

The repo requires a human to apply spec prose changes. The anchor sidecars are fixed in this PR; this is the prose diff to apply before marking it ready:

diff --git a/openspec/specs/composition/spec.md b/openspec/specs/composition/spec.md
@@
-`build_auth`, mount the five domain servers (orders, billing, admin,
-support, reports) un-namespaced, and register middleware with `AuditLog`
-added BEFORE `GroupTagFilter` — audit wraps outermost so even blocked calls
-are recorded.
+`build_auth`, mount the five domain servers (orders, billing, admin,
+support, reports) un-namespaced, publish the reports-scoped
+`handle-downloads` skill with `SkillProvider`, and register middleware with
+`AuditLog` added BEFORE FastMCP's `AuthMiddleware` so audit wraps outermost.
@@
 #### Scenario: Mounted domains expose their tools
@@
   `draft_refund_email`
+
+#### Scenario: Reports callers discover the companion skill
+- **GIVEN** a caller cleared for the `reports` tag
+- **WHEN** resources are listed and the skill resource is read
+- **THEN** `skill://handle-downloads/SKILL.md` is visible and readable
@@
-- **WHEN** `GroupTagFilter` rejects the call
+- **WHEN** `AuthMiddleware` rejects the call

diff --git a/openspec/specs/access-control/spec.md b/openspec/specs/access-control/spec.md
@@
-<!-- anchor: access.list-filter --> `GroupTagFilter.on_list_tools` SHALL return only the tools whose tags
-intersect the caller's allowed tags. An unauthenticated caller (empty tag
-set) sees an empty list.
+<!-- anchor: access.list-filter --> FastMCP `AuthMiddleware`, configured by
+`build_access_middleware`, SHALL list only tools, resources, and prompts whose
+tags intersect the caller's allowed tags. An unauthenticated caller sees no
+components.
@@
-<!-- anchor: access.call-block --> `GroupTagFilter.on_call_tool` SHALL resolve the requested tool and reject
-the call unless the tool exists and the caller is cleared for its tags. The
-rejection SHALL be a `ToolError` reading `Unknown tool: <name>` in every
-failure mode — uncleared, nonexistent, or an internal lookup error — so the
-response leaks neither a hidden tool's existence nor backend detail.
+<!-- anchor: access.call-block --> FastMCP `AuthMiddleware`, configured by
+`build_access_middleware`, SHALL reject direct use of any tool, resource, or
+prompt unless the component exists and `group_access` clears the caller. The
+built-in error may distinguish insufficient permission from an unknown
+component, but no denied component may execute or be read.
@@
-- **THEN** the call fails with `Unknown tool: issue_refund`
+- **THEN** the call fails with an authorization error
@@
-<!-- anchor: access.cleared-for --> Clearance SHALL be computed by `cleared_for`: a caller holding `ALL_TAGS` is
-cleared for every tool; otherwise clearance requires a non-empty
-intersection between the tool's tags and the caller's allowed tags.
+<!-- anchor: access.cleared-for --> Clearance SHALL be computed by
+`group_access`: no token is denied, `ALL_TAGS` clears every component, and
+other callers require an intersection between their allowed tags and the
+component tags. Skill resources also take their trusted tags from skill
+frontmatter until FastMCP projects those tags itself.

diff --git a/openspec/specs/auth/spec.md b/openspec/specs/auth/spec.md
@@
-<!-- anchor: auth.default-deny --> `allowed_tags` SHALL resolve the caller's tags from the access token in
-context: no token yields the empty set (default deny); a token yields the
-union of its groups' tags from `GROUP_TAGS` plus `PUBLIC_TAGS`, so every
-authenticated caller can use `public`-tagged tools and unknown groups get
-only those.
+<!-- anchor: auth.default-deny --> `tags_for_groups` SHALL return the union of
+the supplied groups' tags from `GROUP_TAGS` plus `PUBLIC_TAGS`. Unknown or
+malformed groups yield only `PUBLIC_TAGS`; `group_access` separately denies
+requests with no verified token.
@@
-- **WHEN** `allowed_tags` is called
-- **THEN** it returns the empty set
+- **WHEN** `group_access` is called
+- **THEN** access is denied
@@
-- **WHEN** `allowed_tags` is called
+- **WHEN** `tags_for_groups` is called

@github-actions

Copy link
Copy Markdown

Spec drift warnings

  • DRIFT composition.build-server — anchored code changed (src/acme_mcp/server.py:47-72) but its spec section in openspec/specs/composition/spec.md did not — update the section or confirm it still holds

Non-blocking. Anchors live in openspec/specs/anchors/; see AGENTS.md for the convention.

@MattJColes
MattJColes marked this pull request as ready for review July 17, 2026 03:26
@MattJColes
MattJColes merged commit 7438491 into main Jul 17, 2026
7 checks passed
@MattJColes
MattJColes deleted the feat/expose-companion-skill branch July 17, 2026 03:26
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.

1 participant