dustinedwards-mcp

dustinedwards-mcp

MCP wrapper over the dustinedwards.info operator publish API, exposing five tools for managing blog posts (list, get, save, sync, delete) while containing no policy of its own.

Category
访问服务器

README

dustinedwards-mcp

An MCP wrapper over the dustinedwards.info operator publish API. It exposes five tools to an AI agent and contains no policy of its own.

House standard: Capsid capsid/mcp-wrapper-standard.md, ratified 2026-07-30. This repo is the reference implementation that standard describes; the foxing Workers port and the foxhound ops MCP copy its shape at their cutovers.

Status: VERIFIED END TO END, 2026-07-30

Run from a real MCP client through the real OAuth flow, against production.

Step Result
OAuth 2.1 flow CIMD client resolved by name, PKCE S256, GitHub admin gate, token issued
Legacy initialize (2025-11-25) 200
Modern server/discover (2026-07-28) 200, supportedVersions: ["2026-07-28"]
tools/list 5 tools, annotations correct (delete_post destructive, reads read-only)
list_posts / get_post / sync_status correct, headSha matching the repo
save_post create draft commit 4686349c, askSync uploaded 0 (drafts stay out of the AI index)
first-publish attempt refused 403 first-publish-requires-admin, prose verbatim
save_post edit commit 8043683f
wide-dash gate refused, verbatim, naming line 10 column 18
invalid slug refused at the schema, before any HTTP round trip
delete_post commit e2417aa5
Commits on main all three carry [operator:cb8eb348], each atomic (markdown + artifact)
Site's check:content afterwards green, so the operator path never leaves the repo mid-gate
Rate limiter 45 concurrent requests: 28 allowed, 17 refused with Retry-After

The resource risk did not materialise. Token exchange succeeded with a path-qualified resource against a PRM advertising origin-only, so resourceMatchOriginOnly is NOT needed and remains unset at the strict RFC 8707 default.

Two findings from the run, neither a wrapper bug.

  1. _meta belongs inside params, not at the top level of the JSON-RPC message. A top-level _meta is rejected -32600 "not a valid JSON-RPC message", which reads like a server fault and is a malformed client. Exactly the class of mistake the conformance suite exists to catch.
  2. Every pre-existing post has first_published: null, including the one that is live, because they predate the stamping mechanism. So operatorMayPublish is false for all of them, and if the live post were ever unpublished an operator could not republish it. That is an app-side data gap, not a wrapper concern.

Not verified: the adds-nothing claim by running the same operations RAW against the operator API and diffing. That needs OPERATOR_TOKEN in a shell. What is verified is that the commits, the gate prose, the policy name and the [operator:...] marker are the API's own, unmodified.

How it was built: measurement stage

The era this wrapper targets was decided by MEASUREMENT before a line of the tool surface was written, and that sequencing was deliberate. The MCP 2026-07-28 revision shipped final on 2026-07-28 and removed the initialize handshake, sessions, the GET stream and server-initiated requests. The spec's own compatibility matrix says a legacy client talking to a modern server fails, with no fall-forward. So the first commit in this repo was the probe in src/probe.ts, not the wrapper, and the probe is still here as the instrument that answers the shim's removal condition.

See Era targeting for what it measured.

The law

The wrapper contains no policy, no gates, and no second write path. Every tool call becomes an authenticated HTTP request to POST /api/operator on the dustinedwards Worker, where all rules live. If this Worker vanished, nothing about what is allowed would change.

  AI agent
     |  client leg: identifies WHO is operating
     v
  dustinedwards-mcp  (this Worker)         no D1, no R2, no GitHub token
     |  API leg: OPERATOR_TOKEN, identifies WHAT it may do
     v
  POST /api/operator  on the dustinedwards Worker
     |
     +-> zod frontmatter gate, wide-dash check, first-publish policy,
         one atomic Git Data commit, D1 sync, AI index sync

It is a separate Worker in its own repo for a structural reason, not a stylistic one: the only path from here to the publish machinery is the API. Mounted as a route on the app Worker, the cheap path would be an in-process call that silently skips operator authentication and the rate limiter. Here that call is not merely discouraged, it is unreachable.

The contract between wrapper and app is the API's documented payloads, never shared code. This Worker validates responses at its edge like the outside caller it is.

Two legs, two credentials

Leg Credential Answers
Client (agent to wrapper) per the era measurement, see below who is operating
API (wrapper to app) OPERATOR_TOKEN bearer what the wrapper may do

Never the same value. A client-leg identity failure must read differently from the API refusing an operation, because they mean different things and an agent that conflates them cannot correct itself.

Era targeting

Measured, dated, and re-measurable with scripts/probe-report.mjs.

Claude Code 2.1.203, measured 2026-07-29: opens initialize declaring 2025-11-25, sends notifications/initialized, sends no Mcp-Method or Mcp-Name, and opens a GET /mcp SSE stream. Every one of those was removed, or made required, by 2026-07-28. It is a legacy-era client. It does, however, walk RFC 9728 correctly: 401 to protected resource metadata to authorization server metadata.

claude.ai, measured 2026-07-30 against this probe on the live deploy. Its clientInfo is {name: "Anthropic", version: "1.0.0"} and it is also a legacy-era client: opens initialize declaring 2025-11-25, sends neither Mcp-Method nor Mcp-Name, and carries no _meta protocolVersion. Two agents are involved: server-side discovery from python-httpx/0.28.1, then the consent redirect in the operator's own browser.

Two independent clients on different infrastructure, both legacy. Consequence: 2026-07-28 is the primary and design-center era, and prior-era support lives in exactly one seam, src/legacy-era.ts, carrying its own removal condition. The shim is load-bearing today, not vestigial. It goes when the probe shows the target clients opening with server/discover, which is why the probe stays in the repo as the instrument that answers the question.

Client-leg auth: the spec's OAuth story, because the client completes it

The house standard makes this conditional on measurement: the spec's own OAuth 2.1 resource server story, with a bearer fallback ONLY if the measured client cannot complete the flow. It can, so there is no fallback and no dated debt.

Measured against claude.ai, 2026-07-30:

Requirement Result
RFC 9728 protected resource metadata YES, and it requests the path-scoped /.well-known/oauth-protected-resource/mcp FIRST
RFC 8414 authorization server metadata YES
RFC 8707 audience binding YES, resource = this server's /mcp URL
PKCE S256 YES
Client identity CIMD, not DCR. client_id is the URL https://claude.ai/oauth/mcp-oauth-client-metadata, and /register was never called
Redirect URI https://claude.ai/api/mcp/auth_callback

Two consequences for the build. Dynamic client registration is not needed, which matches 2026-07-28 deprecating RFC 7591 in favour of CIMD. And the PRM route must answer the path-scoped form, not only the bare one; a server that only handles /.well-known/oauth-protected-resource would miss claude.ai's first request.

@cloudflare/workers-oauth-provider 0.8.3 supports this directly via clientIdMetadataDocumentEnabled, which requires the global_fetch_strictly_public compatibility flag. That is the same library the capsid MCP uses, so the precedent covers the measured flow.

Caveat on the instrument. The probe serves PRM unconditionally, so its open phase was never truly authless, which is why claude.ai authenticated despite a 200 on initialize. That produced both measurements in one connector add. It also means whether claude.ai would accept a genuinely authless server is UNMEASURED here, and is not claimed either way.

Gates

Gate Proves State
npm run typecheck wrangler types && tsc -b green
npm run build wrangler deploy --dry-run. 1,146.62 KiB raw, 205.49 KiB gzip green
npm run check:wrapper the no-policy law, mechanically. 223 assertions green, 11/11 planted violations caught
npm run check:wrapper:plant that check:wrapper actually fails 11 caught, 0 missed
npm run check:conformance the official suite against the real protocol layer, per era baseline, not a clean sweep

npm run build exists because typecheck is not a build. Two commits shipped with the runtime dependencies missing from package.json and typecheck passed throughout, because node_modules still held them locally. Only a real bundle catches that.

check:wrapper fails in both directions: a forbidden binding fails, and so does a missing tool, a second call site on the era shim, or a policy statement dropped from a tool description. Verified by planting eleven violations. The first attempt at that harness was itself wrong, and fixing it exposed four real blind spots in the gate, all from one cause: the comment-stripper also removed string literals, so assertions searching for a URL or an option value were reading text with that value already deleted. There are now two strippers and a comment saying which to use when.

check:conformance is a baseline, and says so

@modelcontextprotocol/conformance is genuinely runnable (server / client / authorization subcommands, --spec-version filtering). It runs against test/conformance-entry.ts, which mounts the real handler and the real five tools with only the client leg and the limiter removed, and the API leg aimed at a local stub. So what is certified is the code that ships, not a stand-in.

Era Scenario Result
2026-07-28 server-stateless (SEP-2575) 24/28 baseline
2025-11-25 server-initialize 1/1

The 4 shortfalls are upstream: @modelcontextprotocol/server 2.0.0 does not implement MissingRequiredClientCapabilityError (-32021). Attributed upstream rather than to this repo because a dependency-free draft server written in the same session scored the same 24/28 on that scenario. The gate fails on a regression and on an improvement, so when the SDK fixes it the baseline gets tightened rather than quietly over-passing.

Known gaps, not passes. http-header-validation measured 13/13 standalone but returns no parseable count through this harness; caching, dns-rebinding-protection, json-schema-2020-12 and the tools-call scenarios are not yet wired in. A full sweep exceeded nine minutes, hence the short list and the per-scenario timeout.

That library-versus-hand-roll comparison is also what settled the implementation choice:

Scenario hand-rolled draft agents + MCP SDK v2
server-stateless 0/8 24/28
http-header-validation 3/8 13/13

A 2026-07-28 server owes about twenty discrete MUSTs across three SEPs, including details no prose surfaced: cacheScope must be "public" or "private", and JSON-RPC method values are case-sensitive. Hand-rolling was a correctness liability, not a line-count saving.

Deploy state and the one open risk

Deployed at https://dustinedwards-mcp.dustin-edwards.workers.dev, version c2b3a458-f26c-4480-bc25-983ca86751bc, Worker startup 108 ms.

Verified on the live deploy with no secrets set, which is the fails-closed property rather than an accident:

  • /mcp unauthenticated returns 401 with WWW-Authenticate pointing at the path-scoped PRM, which is the form claude.ai requests first.
  • /authorize with no GitHub credentials returns 503. Not configured means not open.
  • /probe/mcp still answers both eras.

OPEN RISK, unresolved until the first real connection. The library advertises resource in its PRM as the origin, while claude.ai was measured sending resource=<origin>/mcp with the path, and workers-oauth-provider defaults to strict RFC 8707 exact matching. If the token exchange fails, the first thing to try is resourceMatchOriginOnly: true in the OAuthProvider options. It is deliberately not set pre-emptively: it loosens audience binding, and setting it on a guess would weaken the exact property the spec's auth story exists to provide.

Scope exclusions

Considered and deliberately excluded until a measured need exists. Recorded here so a future session does not helpfully add them.

  • MCP Apps (SEP-1865). A publish wrapper has no UI to host.
  • Tasks (SEP-2663). The longest operation here is one commit.
  • MRTR / input_required (SEP-2322). Policy decisions belong to the API and to the human, never to an interactive negotiation with the wrapper.
  • Resources and Prompts primitives. The API offers tools; mirroring it as resources would be a second surface with no second caller.
  • Response caching, retries, multi-project multiplexing. The client already retries, and composition is the agent's job.
  • Any tool the operator API does not already offer. A tool the API lacks is a policy decision, and policy does not live here.

Operating the probe

The probe has no HTTP control plane, deliberately. Its capture log describes a client's authentication behaviour and should not be reachable from the internet, and this way there is no probe credential to exist or leak. Read it through the Cloudflare API instead:

node scripts/probe-report.mjs               # the analysed report
node scripts/probe-report.mjs --phase auth  # force the OAuth discovery walk

Phase open answers everything and captures the protocol opening. Phase auth returns 401 with a WWW-Authenticate pointer and captures the OAuth 2.1 resource server walk. Two phases because one connector add cannot show both.

Not advertised

This endpoint is absent from llms.txt, the sitemap, and every public affordance on dustinedwards.info. An authenticated operator surface is not a public affordance. That is a rule from the house standard, not an oversight.

Workflow

Mainline only while pre-production, per Capsid dustinedwards/workflow-mainline.md: no feature branches, no PRs, commit and push immediately, gates before every push. That ruling sunsets at the dustinedwards.info DNS cutover.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选