Skip to content

Syndication slice 2: admin page + MCP tools on blog:syndicate - #2

Merged
DanMat merged 1 commit into
mainfrom
feat/syndication-slice-2
Sep 7, 2026
Merged

DanMat merged 1 commit into
mainfrom
feat/syndication-slice-2

Conversation

@DanMat

@DanMat DanMat commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Wires the slice-1 Syndicator to its two surfaces, one service behind both.

  • MCP (ADR 0016): BlogToolset with syndicate_post {slug, target} and syndication_status {slug}, both gating on nimbuscms.blog:syndicate. A content wildcard can neither see nor call them (wildcard-immune); a denied call reports as an unknown tool (non-enumerating).
  • Admin (ADR 0020): a Syndication page listing published posts with a button per post per target (Post / Update), the external link, and last status; the push action inherits the page capability + CSRF; an unconfigured target shows as unavailable. Nonce'd styles.
  • Both constructed from one Syndicator in register() (Dev.to key from DEVTO_API_KEY, published-only reader, canonical from Config::appUrl()).

Test fakes extracted to their own files so they autoload across suites. BlogToolsetTest proves the gate: a syndicate-scoped token sees and calls the tools; a *:write token sees nothing and is refused. 32 tests green, PHPStan L6 + cs-fixer clean.

Next: slice 3 (Hashnode adapter + operator-config docs), then slice 4 (HN/Reddit share links + the Reddit-auto-submit chat).

🤖 Generated with Claude Code

Wires the slice-1 Syndicator to its two surfaces, sharing one service, capability,
and audit.

- MCP (ADR 0016): BlogToolset with syndicate_post {slug, target} and
  syndication_status {slug}, both gating on nimbuscms.blog:syndicate. A content
  wildcard can neither enumerate nor call them (wildcard-immune); a denied call
  reports as unknown (non-enumerating).
- Admin (ADR 0020): a Syndication page listing published posts with a button per
  post per target (Post / Update), the external link, and last status; the push
  action inherits the page capability + CSRF. Targets not configured show as
  unavailable. Nonce'd styles (admin CSP drops inline style=).
- Both built from one Syndicator wired in register() (DevToTarget from DEVTO_API_KEY,
  published-only reader, canonical from Config::appUrl()).

Test fakes extracted to their own files (FakeHttpClient/FakeTarget/FakeStore) so
they autoload across suites. BlogToolsetTest proves the MCP gate (syndicate token
sees + calls; a content wildcard sees nothing and is refused). 32 tests green,
PHPStan L6 + cs-fixer clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@DanMat
DanMat merged commit 163566f into main Sep 7, 2026
2 checks passed
@DanMat
DanMat deleted the feat/syndication-slice-2 branch September 7, 2026 00:36
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