Architecture
This page describes how marko/codeindexer, marko/mcp, and marko/lsp fit together, and how marko/devai orchestrates the whole system.
Component overview
Section titled “Component overview”┌──────────────────────────────────────────────────────────────────┐│ AI Agent ││ (Claude Code / Codex / Cursor / Copilot / Gemini CLI / Junie) │└───────────┬──────────────────────────┬───────────────────────────┘ │ MCP (stdio/SSE) │ LSP (stdio) ▼ ▼┌───────────────────┐ ┌───────────────────────┐│ marko/mcp │ │ marko/lsp ││ mcp:serve │ │ lsp:serve │└─────────┬─────────┘ └──────────┬────────────┘ │ │ └──────────┬────────────────┘ │ reads ▼ ┌───────────────────────┐ │ marko/codeindexer │ │ SQLite index │ │ (FTS5 or vec) │ └──────────┬────────────┘ │ indexes ▼ ┌───────────────────────┐ │ Project source │ │ + vendor resources/ │ │ + Marko docs │ └───────────────────────┘marko/codeindexer
Section titled “marko/codeindexer”The codeindexer is the shared data layer. It walks every installed module and scans:
- PHP source files for attributes (observers, plugins, preferences, commands, routes)
config/directories for config key definitionsresources/views/for template entriesresources/translations/for translation keys- Module metadata (
module.php,composer.jsonfiles)
It serializes the result to a file cache at .marko/index.cache in the project root.
Lazy-load with auto-rebuild: The IndexCache loads the cache from disk the first time any consumer (the MCP server, LSP server, etc.) reads from it. If the cache file is missing or stale (any tracked source file is newer than the cache), IndexCache automatically runs a full build before returning data. No explicit rebuild step is required in normal development.
To pre-warm or force a rebuild explicitly:
marko indexer:rebuildSee the marko/codeindexer README for full configuration.
marko/mcp
Section titled “marko/mcp”The MCP server exposes codeindex data to AI agents through the Model Context Protocol. It runs as a long-lived process communicating over stdio.
How it reads the index: Every MCP tool call reads from the IndexCache. The cache is loaded lazily on first access and rebuilt automatically if stale. No writes happen through MCP.
Always-registered tools (13):
Ten tools are backed by IndexCache: check_config_key, find_event_observers, find_plugins_targeting, get_config_schema, list_commands, list_modules, list_routes, resolve_preference, resolve_template, and validate_module.
Three runtime tools are always registered: app_info, read_log_entries, and run_console_command.
Conditional tools:
query_database— registered whenmarko/databaseis bound in the containersearch_docs— registered when aDocsSearchInterfacebinding is present (provided bymarko/docs-ftsor another docs driver package)
Fetching the most recent error: There is no dedicated error tool. Agents call read_log_entries(level: 'error', limit: 1), which works against any LogReaderInterface implementation (the default FileLogReader parses storage/logs/).
Start the MCP server:
marko mcp:serveSee the marko/mcp README for the full tool reference.
marko/lsp
Section titled “marko/lsp”The LSP server exposes codeindex data to editors through the Language Server Protocol. It runs as a long-lived process communicating over stdio.
How it reads the index: Like MCP, the LSP server reads from the IndexCache, which lazy-loads and auto-rebuilds as needed.
Wired LSP methods:
textDocument/didOpen, textDocument/didChange, textDocument/didClose, textDocument/completion, textDocument/definition, textDocument/hover, textDocument/diagnostic, textDocument/codeLens
Advertised capabilities:
completionProvider— trigger characters",',:,.definitionProvider: truehoverProvider: truecodeLensProviderdiagnosticProvider
Start the LSP server:
marko lsp:serveSee the marko/lsp README for editor setup and feature reference.
marko/devai
Section titled “marko/devai”devai is the orchestrator — it does not read the codeindex directly. Its job is to wire everything together at install time:
- Detects which agents are present
- Writes agent configuration files that point to
marko mcp:serveandmarko lsp:serve - Merges guidelines from
resources/ai/guidelines.mdfiles across all installed packages into the agent guidelines files - Registers skills from
resources/ai/skills/so agents can load them on demand
After devai:install runs, the MCP and LSP servers start and stop on demand as the agent needs them. The codeindexer runs once up-front and again whenever you run it explicitly or via codeindexer:watch.
See the marko/devai README for the full installer reference.
Data flow for a search_docs call
Section titled “Data flow for a search_docs call”- Developer asks agent: “How does Marko handle events?”
- Agent calls
search_docstool via MCP - MCP server delegates to the bound
DocsSearchInterfacedriver (e.g.,docs-fts) - Driver returns ranked chunks from Marko docs and package guidelines
- MCP returns chunks to agent
- Agent synthesizes an answer using the retrieved content
The search_docs tool is only registered when a DocsSearchInterface binding exists. If no docs driver is installed, the tool is unavailable.
Data flow for a config key completion
Section titled “Data flow for a config key completion”- Developer types
config('in a PHP file - Editor sends
textDocument/completionto the LSP server - LSP server reads config key entries from
IndexCache - Returns a list of completion items with types and descriptions
- Editor renders the completion dropdown