Imported from bnomei/kirby-mcp (
src/Mcp/AGENTS.md). Install upstream withnpx skills add bnomei/kirby-mcp --skill Mcp. Copyright stays with the author.
MCP Layer Guidelines
Mission
Maintain a stable and secure MCP surface: tools, resources, and completions for Kirby projects.
System
- Tools live in
src/Mcp/Tools/as public methods annotated with#[McpTool]and#[McpToolIndex].src/Mcp/ToolIndex.phpdiscovers them via reflection. ServerFactorysupports profiles. The defaultprojectprofile exposes the full project/runtime surface. Theglobal-referenceprofile is projectless and must expose only reference/K.B./docs/search tools and static resources; keepServerProfileallowlists andToolIndexfiltering aligned when adding reference-safe entries.McpToolIndexkeyword matching is token-based; avoid multi-word keywords and add single-token synonyms for tool suggestion queries (e.g. matrix/ratings/score).- Prompt generators remain in
src/Mcp/Prompts/(annotated with#[McpPrompt]) but are not registered with the MCP server. - Resources live in
src/Mcp/Resources/and exposekirby://...URIs. - UUIDs for new content/blocks are generated via
kirby://uuid/newusing Kirby's UUID generator (no project-level uniqueness check). - Content field guides live in
kb/update-schema/and are exposed viakirby://fields/update-schemaandkirby://field/{type}/update-schema. - Blueprint update guides live in
kb/update-schema/blueprint-*.mdand are exposed viakirby://blueprints/update-schemaandkirby://blueprint/{type}/update-schema. - KB document list/read resources:
kirby://kbandkirby://kb/{path}(path relative tokb/, no.md). - Blueprint/page content outputs may include
fieldSchemasmaps with_schemaRefpointers to both panel refs and update schemas. - Command execution is routed through
src/Cli/and guarded bysrc/Mcp/Policies/. src/Mcp/ToolIndex.phpmay add curated “instance” entries for common resource templates (e.g.kirby://section/pages) to improvekirby_tool_suggest; keep these aligned with the corresponding docs/index sources.- Tool methods should accept
Mcp\Server\RequestContextwhen they need session/client access (for example, structured output). Do not type-hintClientGatewaydirectly. DocsToolsandOnlinePluginsToolsare intentionally extensible so tests can override their HTTP fetches; keep network calls out of unit tests.
Workflows
- Add/modify a tool:
- Implement in
src/Mcp/Tools/*and keep the toolname(kirby_*) backward compatible when possible. - Add/adjust completions in
src/Mcp/Completion/*for any user-facing params. - Add/adjust tests in
tests/Unit(pure logic) ortests/Integration(runtime/CLI). - Update
README.mdwhen tool names, params, or outputs change.
- Implement in
- If discovery/indexing looks stale, clear caches (
ToolIndex::clearCache()) or restart the server.
Guardrails
- Treat tool names, parameter schemas, and
kirby://...URIs as public API; changes must be reflected in tests + docs. - Keep tool input schemas aligned with actual payload handling (e.g.
kirby_update_page_content.dataaccepts an object and a JSON string for compatibility; expose both types in schema and parse strings explicitly). - Any write-capable tool/command must be explicitly gated (allowlist + confirmation) and reviewed for abuse paths.
- If you add MCP elicitation to a confirm-gated tool, keep explicit
confirm=truesupport and preserve dry-run fallback when elicitation is unavailable/declined. - Bind modern confirmation keys to all security-relevant operation inputs. If a retry supplies confirmation input for different arguments, return the marked dry-run preview (
confirmationStatus=stale_input_ignored,retryWithoutInputResponses=true) instead of executing or adding persistent request state for the single-ask flow. - Keep
kirby_run_cli_commanddefaults minimal; prefer dedicated tools/resources over broad allowlist patterns (especially formcp:*runtime wrappers). - Return structured data; avoid
echo/side effects from tools/resources. - Treat query evaluation tools (e.g.
kirby_query_dot) as sensitive; keep confirm gating and document default enablement/disable switches. - Handshake-era tool calls (except
kirby_init) are init-guarded. Modern2026-07-28calls are stateless and may callkirby_initonly for audit/guidance. - In
global-referencemode,kirby_initmust not require or discover a Kirby project. It should describe the reference-only scope and explicitly direct project work to a separate project-local MCP server. - Init gating is session-scoped via
SessionInterface; useRequestContextto access per-session state from tools when needed. - Dump trace convenience is handshake-session scoped. Stateless calls must correlate explicitly with
traceIdorpath; do not persist modern dump state. - Provide tool output schemas via
#[McpTool(outputSchema: ...)](SDK v0.3+); keepstructuredContent+ JSON text in sync. - Modern structured tool results may append resource links for concrete
kirby://URIs from explicit semantic fields; preserve structured fields and JSON text, filter templates, and never scan arbitrary content. - SDK v0.4 validates tool input before method execution and adds resource subscribe/unsubscribe handlers; when behavior depends on legacy-compatible inputs or mutable resources, reflect that in schemas and tests.
- SDK v0.5 exposes top-level
titleon tools/prompts; keep#[McpTool(title: ...)]and#[McpPrompt(title: ...)]populated and aligned with display titles. - Prefer SDK v0.5 titled enum elicitation schemas for choice-style client prompts; keep legacy explicit parameters (e.g.
confirm=true) working. - SDK v0.6 renames
Mcp\Schema\ResourcetoResourceDefinitionand removes the manual-registration flag fromRegistry::registerResource(); keep sized manual resources registered after discovery so they override discovered definitions. - SDK v0.6 adds default Streamable HTTP middleware.
HttpMcpHandlerdisables the transport's default CORS/DNS middleware so this repo's outer auth/origin/CORS/host controls remain authoritative. - SDK v0.8 serves both handshake and modern/stateless protocol eras. Keep POST classification/validation in the SDK and retain custom validation for GET/SSE.
- Modern global-reference discovery/list results and immutable bundled resource reads may carry public cache hints. Keep project-specific, externally fetched, runtime, auth, session, user, and mutable results at the SDK default (
ttlMs: 0, private), and do not leak modern hints onto handshake-era responses. - Supported older initialize revisions remain handshake-compatible; modern
2026-07-28requests use stateless dispatch. Keep MCP logging disabled; diagnostics go to stderr. - Diagnostic correlation may include only a validated W3C v00
traceparent; allow the native header through HTTP CORS, and never logtracestateorbaggage. - Successful mutations publish resource updates to the modern notification bus and retain direct handshake notifications for session subscribers. Require each subscribed resource URI's normal bearer scope. HTTP uses bounded local-filesystem sharing with portable per-file sequence cursors; it is not multi-host, NFS, durable, or exactly-once.
kirby_ide_helpers_statustemplate/snippet PHPDoc warnings must be usage-aware: only PHP-code references to$kirby,$site, and$pagerequire matching@varhints.resources/listentries must stay Codex-compatible plain descriptors:uri,name,title,description, andmimeTypeonly. Strip descriptor-levelannotations,size,icons, and_metaat list serialization time; keep richer metadata onresources/readcontents when available.- Resource and resource-template definitions should include MCP
titlevalues; keep attribute titles and sized manualResourceDefinitiontitles aligned. - HTTP
/mcprequests are auth-gated before MCP protocol handling: validate Origin, reject query-string credentials, require Bearer auth, attachoauth.*request metadata, and enforce operation scopes without hiding tools/resources. - Kirby HTTP exposure is an explicit copied route via
KirbyMcpRoutes::routes()orKirbyMcpRoute::handle(); do not auto-register routes from this Composer dependency. Keep the route disabled by default through config and fail closed when enabled config is invalid. - Route actions returned by
KirbyMcpRoutes::routes()must remain non-static closures so Kirby can bind them to its route instance before invocation. KirbyMcpRoutes::routes()also exposes the optional built-in OAuth provider routes for Claude Desktop/Claude.ai custom connectors. Keephttp.oauthProvider.enableddisabled by default; when enabled, clients, auth codes, refresh tokens, sessions, remembered consents, and signing keys must stay under.kirby-mcp/oauth. Consent must default to an explicit approve/deny step (snippet, with built-in form fallback);autois only for trusted private deployments.- Keep HTTP as a transport wrapper around the same MCP server surface as stdio. Do not remove tools/resources for scoped clients; fail unauthorized operations with structured 403/
insufficient_scoperesponses. - HTTP session state is per MCP session and uses the
MCP-Session-Idheader contract across POST/GET/DELETE requests. Init gating, dump trace IDs, subscriptions, and confirm state must remain session-scoped. - File-backed HTTP session stores should use
ServerFactory::HTTP_SESSION_TTL_SECONDSandServerFactoryshould pass explicit SDK v0.6 GC settings throughBuilder::setSession(). - Shared-token HTTP auth is loopback/local-development only. The Kirby route must reject shared-token requests unless PHP reports
REMOTE_ADDRas loopback and the request host is an exact loopback host (localhost,::1, or a valid IPv4 literal in127.0.0.0/8; never a DNS name that merely starts with127.). Public header-capable clients may use explicitremote-tokenauth with hashed token records, HTTPS for non-loopback requests, and normal scope checks. OAuth JWT validation and the built-in OAuth provider remain the Claude Desktop/Claude.ai custom connector path. - Keep init/info payloads lean; omit heavy blobs like
composer.lockfrom tool/resource outputs (composer audit does not return lock data).