feat(mcp): arium-mcp resource-server crate + rmcp_example

feat(mcp): arium-mcp resource-server crate + rmcp_example

#18 in tonybierman/arium — merged 2026-05-27

What

Adds OAuth 2.0 resource-server support for remote MCP servers, as a reusable crate plus a runnable example.

crates/arium-mcp (new)

An rmcp-version-agnostic axum/tower layer that turns any MCP HTTP endpoint into an OAuth 2.0 Resource Server:

  • Per-request Authorization: Bearer dxsk_… validation against arium's api_keys table.
  • 401 + WWW-Authenticate: Bearer resource_metadata="…" challenge on failure.
  • RFC 9728 Protected Resource Metadata at /.well-known/oauth-protected-resource.
  • Access auditing via arium's existing audit_events log — mcp.access.denied always, mcp.access.granted opt-in (arium's anti-flood convention), with client IP + User-Agent.
  • Public API: ResourceMetadata, AriumMcpState, metadata_router, protect. Full rustdoc + cargo-rdme README.

crates/arium (small change)

Exposes authenticate_token(pool, token) — extracted from the pub(crate) bearer_auth middleware so out-of-tree consumers (like arium-mcp) share one validation path. Re-exported from the crate root.

examples/rmcp_example (new)

Self-contained streamable-HTTP MCP server (rmcp 1.7) gated by arium-mcp: boots SQLite, runs arium::migrator(), seeds a demo user, mints + prints a dxsk_ token, and exposes trivial ping/echo tools behind the gate.

Scope / non-goals

Resource-server mode only — tokens are minted out-of-band. This is not an OAuth Authorization Server (no /authorize, /token, dynamic client registration, or PKCE auth-code flow). MCP clients that accept a manual bearer token/header (Inspector, Claude Code --header, IDEs, programmatic) work today; GUI "click Connect" connectors that require the interactive flow do not. The 401 + Protected Resource Metadata scaffolding is the clean, additive foundation for that follow-up.

Verification

  • cargo check/clippy -D warnings/fmt/cargo-machete clean on both crates; arium-mcp doctest passes; cargo rdme --check -w arium-mcp in sync (added to the CI readme job).
  • Unified-graph check (arium-mcp + rmcp_example + arium-dioxus with arium/mail on) compiles — so rmcp_example needs no CI --exclude.
  • Live run: discovery returns RFC 9728 JSON; no-token/bad-token → 401 + WWW-Authenticate; valid token → 200 + SSE initialize; audit_events shows the expected mcp.access.denied/mcp.access.granted rows (with IP captured).

🤖 Generated with Claude Code

Last updated 2026-05-28