Wiki source code of Architecture
Last modified by Vincent Massol on 2026/09/04 19:19
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 1 | The MCP server is four thin layers riding existing XWiki infrastructure, plus an extension point for custom tools. | ||
| 2 | |||
| 3 | == Module layout == | ||
| 4 | |||
| 5 | {{code}} | ||
| 6 | application-ai-llm-mcp/ | ||
| 7 | ├── application-ai-llm-mcp-api/ MCPTool @Role (the extension point), | ||
| 8 | │ MCPToolSupport, the public access doors | ||
| 9 | ├── application-ai-llm-mcp-server/ endpoint, manager, config reader, | ||
| 10 | │ access-door implementations, space | ||
| 11 | │ filter, 18 bundled tools, the shared | ||
| 12 | │ tool seams (internal) | ||
| 13 | └── application-ai-llm-mcp-ui/ admin UI + config pages (XAR) | ||
| 14 | {{/code}} | ||
| 15 | |||
| 16 | |||
| 17 | The server module has no dependency on the index/RAG stack. It is installable standalone. Bundled tools query XWiki's built-in Solr index. | ||
| 18 | |||
| 19 | == Four-layer architecture == | ||
| 20 | |||
| 21 | A tool call crosses four layers, each with one job. Every one of them is parameterized by the same per-wiki configuration document, and a save of that document is what makes a running server pick the change up. | ||
| 22 | |||
| 23 | {{plantuml}} | ||
| 24 | @startuml | ||
| 25 | !theme bluegray | ||
| 26 | skinparam componentStyle rectangle | ||
| 27 | |||
| 28 | actor "AI agent" as AGENT | ||
| 29 | component "**DefaultMCPResource**\n/rest/wikis/{wiki}/aiLLM/mcp\nidentity and routing" as RES | ||
| 30 | component "**XWikiMCPServerManager**\none SDK server per wiki, built lazily\nlifecycle and middleware" as MGR | ||
| 31 | component "**MCPTool** components\ncomponent hint = tool id\nthe capabilities" as TOOLS | ||
| 32 | component "**Access doors**\nMCPDocumentAccess · MCPWikiReach\nMCPSpaceFilter · MCPDocumentSearch\nshared authorization" as DOORS | ||
| 33 | database "XWiki documents\nand the built-in Solr index" as XWIKI | ||
| 34 | component "**MCPServerConfiguration**\none AI.MCP.Code.MCPServerConfig per wiki" as CONF | ||
| 35 | component "**MCPConfigChangeEventListener**" as EVL | ||
| 36 | |||
| 37 | AGENT --> RES : JSON-RPC over HTTP POST | ||
| 38 | RES --> MGR : the target wiki, set on the context | ||
| 39 | MGR --> TOOLS : executeWrapped, on the request thread | ||
| 40 | TOOLS --> DOORS : every reference, every query | ||
| 41 | DOORS --> XWIKI : as the authenticated user | ||
| 42 | CONF ..> RES : enable gate | ||
| 43 | CONF ..> MGR : which tools are registered | ||
| 44 | CONF ..> DOORS : space filter, cross-wiki reach | ||
| 45 | CONF ..> EVL : on save | ||
| 46 | EVL ..> MGR : invalidates the cached server | ||
| 47 | @enduml | ||
| 48 | {{/plantuml}} | ||
| 49 | |||
| 50 | What each layer is responsible for: | ||
| 51 | |||
| 52 | * **Identity and routing** — {{code}}DefaultMCPResource{{/code}} applies the per-wiki enable gate (a disabled wiki returns 404 before anything else), advertises the OAuth/OIDC discovery metadata (RFC 9728), rejects unauthenticated callers when an OIDC provider is present, and sets the target wiki on the {{code}}XWikiContext{{/code}}. | ||
| 53 | * **Lifecycle and middleware** — {{code}}XWikiMCPServerManager{{/code}} keeps one SDK server per wiki, built lazily and cached, registers only that wiki's enabled {{code}}MCPTool{{/code}} components on it, and routes every call through {{code}}executeWrapped(){{/code}}, which audits it and normalizes the exceptions it throws. | ||
| 54 | * **The capabilities** — the tools themselves: 18 bundled ones in four categories (Help, Search and Navigation, Structured Data, Authoring), of which {{code}}list_wikis{{/code}} is reach-gated, plus {{code}}list_collections{{/code}} and {{code}}search_collections{{/code}} contributed by the index module when it is installed. The full list is on [[MCP Tool API>>doc:documentation.extensions.dev.llm.mcp-server.mcp-tool-api.WebHome]]. | ||
| 55 | * **Shared authorization** — the access doors, which every tool goes through instead of resolving a reference itself: the reach gate, then the rights check, then the space filter, in that order, always as the authenticated user and through XWiki's own rights model. | ||
| 56 | |||
| 57 | == Design principles == | ||
| 58 | |||
| 59 | Every hard problem is delegated to something XWiki already has: | ||
| 60 | |||
| 61 | * Authentication rides the existing filter chain (OIDC bridge). | ||
| 62 | * Authorization rides {{code}}SecureQuery{{/code}} and {{code}}ContextualAuthorizationManager{{/code}}. | ||
| 63 | * Save attribution rides {{code}}com.xpn.xwiki.api.Document{{/code}}. | ||
| 64 | * Extensibility rides the component manager. | ||
| 65 | * Configuration rides a per-wiki wiki document plus an event listener. | ||
| 66 | |||
| 67 | == Request lifecycle == | ||
| 68 | |||
| 69 | 1. HTTP request arrives at {{code}}DefaultMCPResource{{/code}} ({{code}}/rest/wikis/{wiki}/aiLLM/mcp{{/code}}). | ||
| 70 | 1. Enable gate: disabled wiki returns 404 before anything else. | ||
| 71 | 1. Authentication has already happened (filter chain). Guest + OIDC → 401. | ||
| 72 | 1. Resource sets the target wiki on the XWikiContext and calls {{code}}XWikiMCPServerManager.handleRequest(){{/code}}. | ||
| 73 | 1. Manager resolves the wiki's cached SDK server (built lazily, one per wiki). | ||
| 74 | 1. SDK parses the JSON-RPC message and dispatches {{code}}tools/call{{/code}} to the registered handler. | ||
| 75 | 1. Handler is the middleware ({{code}}executeWrapped{{/code}}), which invokes the tool's {{code}}execute(){{/code}} synchronously on the request thread. | ||
| 76 | 1. Result serialized back as HTTP response. | ||
| 77 | |||
| 78 | == Threading == | ||
| 79 | |||
| 80 | Tool handlers run inline on the HTTP request thread, where XWiki's thread-locals (current user, wiki) natively live. The SDK builder sets {{code}}immediateExecution(true){{/code}} to prevent offloading to Reactor worker threads. | ||
| 81 | |||
| 82 | == Server lifecycle == | ||
| 83 | |||
| 84 | * {{code}}XWikiMCPServerManager{{/code}} owns a {{code}}ConcurrentHashMap{{/code}} of SDK servers, one per wiki. | ||
| 85 | * {{code}}buildServer(wikiId){{/code}}: reads config, enumerates {{code}}MCPTool{{/code}} components, filters by enabled tool ids, registers each on a fresh transport. | ||
| 86 | * Registration is fault-isolated per tool: a throwing tool is skipped with a WARN. | ||
| 87 | * Invalidation is event-driven: {{code}}MCPConfigChangeEventListener{{/code}} watches saves of {{code}}AI.MCP.Code.MCPServerConfig{{/code}}. Main wiki saves invalidate all servers (cross-wiki reach may have changed); other wiki saves invalidate only that wiki. | ||
| 88 | |||
| 89 | == Shared tool seams == | ||
| 90 | |||
| 91 | Mechanics that several tools must implement identically live in exactly one place each, so the tools cannot drift apart on them: {{code}}MCPSourceText{{/code}} in the api module (line-ending normalization, line numbering, the output budget), and in the server module's internal tool package {{code}}MCPContentWindow{{/code}} (the range-read window grammar), {{code}}MCPTranslationSupport{{/code}} (exact-match locale resolution), {{code}}MCPWriteSupport{{/code}} (write rights, the {{code}}[AI]{{/code}} version comment, the {{code}}base_version{{/code}} conflict messages), {{code}}MCPTextGuards{{/code}} (neutralizing echoed wiki-authored text), and {{code}}MCPAttachmentSupport{{/code}}, {{code}}MCPHistorySupport{{/code}} and {{code}}MCPLinksSupport{{/code}}. | ||
| 92 | |||
| 93 | == Farm administration == | ||
| 94 | |||
| 95 | The main wiki's MCP admin section is scripted through {{code}}MCPFarmScriptService{{/code}} ({{code}}$services.mcp{{/code}}), which reads and applies the per-wiki enable and cross-wiki reach flags across the farm. | ||
| 96 | |||
| 97 | == MCP SDK == | ||
| 98 | |||
| 99 | Version 2.0.0 of {{code}}io.modelcontextprotocol.sdk:mcp-core{{/code}}. The version is owned by the {{code}}mcp-bom{{/code}} import in the root POM. Two SDK defaults are overridden: | ||
| 100 | |||
| 101 | * {{code}}immediateExecution(true){{/code}} — tools run on the request thread, not Reactor workers. | ||
| 102 | * {{code}}validateToolInputs(false){{/code}} — the MCPToolSupport declarative parameter layer is the designed validation gate. |