OKF Wiki MCP Server
by SwePalm
README.md
# OKF Wiki — MCP App
A living knowledge base served as an [MCP App](https://modelcontextprotocol.io/): the wiki renders as an interactive UI inside any MCP Apps host, the model reads and *expands* it through tools, and every interaction — a slider explored, an insight saved, a rule distilled — leaves the wiki smarter than it found it.
The content is an **OKF wiki** (Organizational Knowledge Framework): a directory of markdown nodes with YAML frontmatter that binds playbook text to UI behavior. The wiki directory is a *runtime input* — the server re-reads it on every tool call, so ingested or hand-edited nodes are live immediately, with no rebuild or restart.
> Origin: this combines two earlier experiments — the Level 2/3 generative-UI architecture from [gen-ui-wiki-playground](https://github.com/SwePalm/gen-ui-wiki-playground) and the MCP Apps plumbing (single-file UI, host shim, runtime design tokens) from [mcp-apps-demo-engine](https://github.com/SwePalm/mcp-apps-demo-engine).
## The three levels of generative UI, MCP Apps edition
| Level | What it means | How this app does it |
| --- | --- | --- |
| **1 — Static** | Readable playbook text | Markdown rendered client-side (`marked`), widget source blocks stripped |
| **2 — Adaptive parameters** | UI controls generated from data | Sliders from frontmatter `variables`; outcomes computed by declarative frontmatter `rules` the app merely interprets |
| **3 — Generative components** | Bespoke per-node UI created at runtime | Ingest stores a self-contained ` ```html ` widget → served inside the tool result → rendered in a **sandboxed sub-iframe** with the app's theme variables injected |
The Level 3 translation is the interesting constraint: an MCP App is one sandboxed single-file resource — no dev server, no JSX compilation, no network. So a widget's source form is *self-contained HTML* (markup + inline style + inline script, no imports, no external requests).
## The compounded-learning loop
```
explore sliders → save finding → distill into rule → sliders teach the next reader
↑ │
└──────────── gaps panel recommends what to explore ─────┘
```
- **Findings write back.** An insight discovered at the sliders is one click from permanent: it lands in the node's frontmatter with date + settings provenance and renders for every future reader.
- **Findings become rules.** Level 2 heuristics are *data, not code*: nodes carry declarative `rules` (`when` slider-settings match → outcomes + note; most specific rule wins). A "Distill findings into rules" button asks the model to promote insights via `record_rule` — validated against the node's actual variable space so unfireable rules are rejected.
- **The wiki detects its own gaps — ranks and explains them.** Dangling relations, unported widgets, unexplored variable sets, and unlinked nodes are scored by leverage (a gap on a node referenced by 2 others outranks a leaf) and rendered as expandable cards: what the topic covers, why it matters, the specifics, and what closing it will do. Every card has a ready-to-send prompt behind an "Ask assistant" button.
- **Growth is visible.** `wiki/log.md` records every ingest, update, finding, rule, and link, rendered as a "How this wiki grew" timeline.
- **The chat knows what you're looking at.** The app pushes compact state (current node, slider settings, outcomes, findings/rules counts) via `ui/update-model-context` on navigation and slider moves.
## Tools
| Tool | Callable by | Purpose |
| --- | --- | --- |
| `open_wiki` | model (renders UI) | Overview of all nodes + gaps + timeline; optionally opens a node by id |
| `get_node` | app only | Sidebar navigation via `app.callServerTool` |
| `ingest_document` | model | Add/update a playbook: classifies type, extracts variables/metrics, installs an ` ```html ` widget. Re-ingest preserves findings, rules, and relations |
| `record_finding` | model + app | Persist an insight with the slider settings it was observed under |
| `record_rule` | model + app | Add a declarative Level 2 heuristic (`when` → `outcomes` + note) |
| `link_nodes` | model + app | Add a typed relation between nodes |
All tool results include a plain-text/markdown fallback, so UI-less hosts still get useful answers.
## Quick start (local, no host required)
```bash
npm install && npm run build
npm run serve:http # Terminal 1: MCP server on :3001
npm run shim # Terminal 2: reference host on :4180
# open http://localhost:4180/host-shim.html
```
The ~100-line shim does what a real host does — fetches the UI via `resources/read`, completes the MCP Apps handshake, proxies tool calls — and logs every message the app sends upward, including the context-sync and hand-back traffic.
For development with UI rebuild-on-change: `npm run dev`.
## Connect to a real MCP Apps host
Streamable HTTP (server on `http://localhost:3001/mcp`):
```bash
npm run serve:http
# e.g. Claude Code:
claude mcp add --transport http okf-wiki http://localhost:3001/mcp
```
stdio:
```json
{
"mcpServers": {
"okf-wiki": {
"command": "node",
"args": ["/path/to/okf-wiki-mcp-app/dist/main.js", "--stdio"]
}
}
}
```
Then ask the host's assistant to *"open the wiki"* — the `open_wiki` tool renders the app.
## Bring your own wiki
The bundled `wiki/` is demo content. To serve a different (e.g. live, generated) wiki, point `WIKI_ROOT` at any directory with this layout:
```
my-wiki-root/
├── wiki/ # the OKF nodes (*.md) + log.md ← required
├── raw/ # source documents (ingest writes here)
└── widgets/ # Level 3 widgets, <widgetId>.html
```
```bash
WIKI_ROOT=/path/to/my-wiki-root npm run serve:http
```
Everything the app writes (findings, rules, relations, ingests, log) goes to that directory, so a git-tracked wiki gets reviewable diffs of what the assistant and users learned. `PORT` overrides the HTTP port (default 3001).
### OKF node format
One markdown file per node in `wiki/`, YAML frontmatter + playbook body:
```markdown
---
type: pattern-analysis # concept | pattern-analysis | sector-deepdive
subtype: tactical # pattern-analysis only: strategic | tactical
id: my-node-id # unique slug; must match how others relate to it
title: "Human-Readable Title"
created: "2026-07-10"
source_files:
- "raw/my-node-id.md"
variables: # optional → Level 2 sliders
task_complexity: [Low, Medium, High]
metrics: # optional → baseline outcome cards
efficiency_gain: "Shown when no rule matches"
rules: # optional → declarative Level 2 heuristics
- when: { task_complexity: High }
outcomes:
efficiency_gain: { value: "12% (escalation overhead)", status: warning }
note: "High complexity needs human escalation."
- when: {} # empty when = baseline rule
outcomes:
efficiency_gain: { value: "34%", status: success }
findings: # accumulated insights (written by the app)
- date: "2026-07-10"
text: "High complexity erodes the efficiency gain."
settings: { task_complexity: High }
relations: # typed edges to other node ids
- type: depends-on
id: some-other-node-id
widgetId: Widget_my_node_id # optional → widgets/Widget_my_node_id.html
---
# Human-Readable Title
Playbook markdown here…
```
Notes:
- **Rules** match when *all* `when` entries equal the current slider values; the most specific matching rule (most entries) wins. `status` is `success | warning | danger`.
- **Widgets** (`widgets/<widgetId>.html`) must be self-contained: markup + inline `<style>` + inline `<script>`, no imports, no external requests. They render in a sandboxed iframe that receives the app's CSS custom properties (`--color-accent`, `--color-background-primary`, …), so use those with fallbacks for automatic theming.
- **`wiki/log.md`** lines have the form `## [YYYY-MM-DD] action | ID: node-id | Title: …` — the app appends `ingest`, `update`, `finding`, `rule`, and `link` entries and renders them as the growth timeline.
- `wiki/index.md` and `wiki/log.md` are not treated as nodes.
### Ingesting via the model
Ask the connected assistant to call `ingest_document` with a markdown document. It will classify the type, extract Level 2 variables from `- **Name**: A / B / C` bullet lines, and install a Level 3 widget from a fenced ` ```html ` block. Legacy ` ```jsx ` blocks are skipped with a note — this runtime cannot compile JSX.
## Styling
`DESIGN.md` ([design.md spec](https://github.com/google-labs-code/design.md)) is a runtime input like the wiki: its tokens compile to a CSS-variable override block injected into the served UI resource on every `resources/read`. Swap the file (samples in `designs/`), re-render, new brand — no rebuild. Host-provided style variables still win.
## Repo layout
```
server.ts MCP tools + UI resource registration (DESIGN.md injection)
main.ts transport bootstrap (Streamable HTTP / stdio)
wiki.ts wiki store: parsing, gaps, timeline, ingest, findings/rules/links
design.ts DESIGN.md → CSS variables compiler
mcp-app.html app shell
src/mcp-app.ts the single-file wiki browser (Vite + vite-plugin-singlefile)
src/*.css app styles on host/design-token CSS variables
tools/host-shim.html minimal reference host for local testing
wiki/ raw/ widgets/ demo OKF content (replaceable via WIKI_ROOT)
```
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues