accessibility-mcp
README.md
# accessibility-mcp
A [Model Context Protocol](https://modelcontextprotocol.io/) server (built with [FastMCP](https://www.npmjs.com/package/fastmcp)) that exposes **WAI-ARIA Authoring Practices Guide (APG)** patterns: narrative requirements, keyboard/ARIA guidance as Markdown, **official example source** (HTML, CSS, JS) from [w3c/aria-practices](https://github.com/w3c/aria-practices), and **RAG** via **Ollama** + LangChain.js (`apg_semantic_search`; the chunk index is prebuilt in **`data/rag/chunks.json`** for releases).
This is **APG** (widget patterns), not the full WCAG spec. For WCAG success criteria text, use W3C’s WCAG materials separately; APG is the right source for patterns like [Carousel](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/examples/carousel-1-prev-next/) and the [patterns index](https://www.w3.org/WAI/ARIA/apg/patterns/).
**Codebase documentation:** [docs/README.md](docs/README.md) (architecture, data pipeline, MCP tools).
**Tests:** `npm test` runs unit tests plus an **MCP stdio integration** check (`src/mcp-stdio.integration.test.ts`): builds **`dist/cli.js`**, spawns the server with **`@modelcontextprotocol/sdk`**, completes the initialize handshake, and **`callTool(apg_meta)`** — this matches what Claude Code uses at the protocol level. **Manual:** `npm run mcp:try` (see [Try tools without Claude](#try-tools-without-claude-cli)).
**Claude Code CLI (optional):** the real **`claude -p`** binary can drive the same server with **`--mcp-config`** + **`--strict-mcp-config`** (see [headless / `-p`](https://docs.claude.com/en/docs/claude-code/headless)). Uses your **normal Claude Code login** (same as the REPL)—no **`ANTHROPIC_API_KEY`** required unless you use **`--bare`** (API-key-only / CI). To smoke-test end-to-end:
```bash
CLAUDE_CODE_MCP_SMOKE=1 npm run test:claude-mcp
```
Exits **0** with a skip message if **`CLAUDE_CODE_MCP_SMOKE`** is unset (default in CI).
**Data / RAG quality:** `npm run validate:data` (structure checks; no Ollama). **`npm run eval:rag`** runs a labeled benchmark and writes **`reports/rag-eval.html`** (bar chart of top-1 scores + MRR / Hit@k). There is no training **loss** curve—embeddings are frozen; see [docs/evaluation.md](docs/evaluation.md).
## Dataset
- **`npm run ingest`** — shallow-clones `w3c/aria-practices` into `.cache/`, writes:
- `data/manifest.json` — compact index (ids, titles, example slugs, bundle paths)
- `data/patterns/<id>.md` — pattern doc as Markdown (from `*-pattern.html`)
- `data/bundles/<id>/<example>.json` — referenced HTML/CSS/JS per demo (binary assets listed but omitted)
- **`npm run rag:index`** — (after ingest + `.env`) calls **Ollama** embeddings and writes **`data/rag/chunks.json`**: chunked pattern docs plus one combined text blob per example (HTML/CSS/JS). Maintainers run this before releases; **re-run** after ingest or when you change **`OLLAMA_EMBEDDING_MODEL`**.
- Vendored **`data/`** (including **`data/rag/chunks.json`**) is **committed** and **published** so end users are not required to ingest or index locally.
## Usage
```bash
npm install
npm run ingest # refresh from GitHub (re-run when you want newer APG)
npm run rag:index # rebuild RAG index (maintainers / custom models; shipped index in releases)
npm run build
npm start # stdio MCP server
```
### npx (after publish to npm)
```bash
npx -y accessibility-mcp
```
After the package is on npm, most clients can use **`command` + `args`** with `npx` / `-y` / `accessibility-mcp` instead of a local `node` path.
### Publishing (npm tarball)
The package ships **`dist/`**, **`data/manifest.json`**, **`data/patterns/`**, **`data/bundles/`**, and **`data/rag/chunks.json`** (see **`files`** in `package.json`). That last file is the **precomputed embedding index** so installers do **not** need to run **`npm run rag:index`** themselves.
**Runtime note:** **`apg_semantic_search`** still uses **Ollama** to embed the **user query** at request time (the index only stores chunk vectors). Point **`OLLAMA_EMBEDDING_*`** at the **same embedding model** the index was built with (see `embeddingModel` inside `chunks.json`). Users without Ollama can set **`OLLAMA_SKIP_PULL=1`** and use the non-RAG tools only.
**Before `npm publish`:**
1. **`npm run ingest`** — refresh APG text and bundles.
2. **`npm run rag:index`** — rebuild **`data/rag/chunks.json`** (needs Ollama once, on the maintainer machine).
3. **`npm test`** (optional but recommended).
4. **`npm publish`** — **`prepack`** runs **`npm run build`**.
Inspect the tarball: **`npm pack --dry-run`**.
### MCP Inspector (dev)
The [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) is a dev dependency. After **`npm run build`**:
```bash
npm run mcp:inspect
```
Opens a local web UI to exercise tools and resources against **`node dist/cli.js`** (Ollama runs on first connect like **`npm start`**).
### Try tools without Claude (CLI)
**`npm run mcp:try`** runs a tiny [MCP SDK](https://github.com/modelcontextprotocol/typescript-sdk) client that spawns **`dist/cli.js`**, completes the handshake, and calls a tool (same mechanism as the integration test).
```bash
npm run build
npm run mcp:try -- --list
npm run mcp:try
npm run mcp:try -- apg_list_patterns '{"query":"carousel"}'
```
See **`scripts/mcp-client-demo.ts`**. **`apg_semantic_search`** still needs a reachable Ollama embedding endpoint at call time.
### Environment (`.env`)
At startup the server loads **`.env`** from the **package root** (same folder as `package.json`). Copy **`.env.example`** → **`.env`** and adjust.
| Variable | Purpose |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`OLLAMA_BASE_URL`** | Default Ollama HTTP API root, e.g. `http://127.0.0.1:11434` (no trailing slash). Use your LAN IP if Ollama runs on another machine. Used for chat and embeddings when the overrides below are unset. |
| **`OLLAMA_CHAT_BASE_URL`** | Optional. Chat-only host (e.g. a GPU box). Defaults to **`OLLAMA_BASE_URL`**. |
| **`OLLAMA_EMBEDDING_BASE_URL`** | Optional. Embeddings-only host. Defaults to **`OLLAMA_BASE_URL`**. Set both overrides to split chat vs embed across two servers. |
| **`OLLAMA_CHAT_MODEL`** | Chat model id (default **`llama3.1:8b`**). Pick any [Ollama library](https://ollama.com/library) model you have pulled. |
| **`OLLAMA_EMBEDDING_MODEL`** | Embedding model for RAG (default **`nomic-embed-text`**). **Re-run `npm run rag:index`** after changing it so vectors match. |
| **`OLLAMA_SKIP_PULL`** | If `1` / `true` / `yes`, **does not contact Ollama at startup** (skips model checks and pulls). |
| **`OLLAMA_VERBOSE`** | If `1` / `true` / `yes`, log extra Ollama status to **stderr** (default: quiet). |
| **`APG_MCP_DATA_DIR`** | Optional. Directory that contains **`manifest.json`** (defaults to `data/` next to `dist/`). |
The MCP **stdio handshake** runs first; **Ollama** model checks and pulls run **after** that (async). If Ollama is unreachable, APG list/read tools still work; RAG needs Ollama when invoked.
### Without Ollama (no local LLM)
Listing patterns, reading specs, and fetching example sources use **only** the bundled **`data/`** files—**no** model and **no** network at query time.
1. Install [Node.js](https://nodejs.org/) 20+.
2. Run the server from the published package, e.g. **`npx -y accessibility-mcp`** (after you publish), or **`node dist/cli.js`** from a git checkout after **`npm install`** + **`npm run build`**.
3. Optionally set **`OLLAMA_SKIP_PULL=1`** so startup never contacts Ollama (otherwise unreachable Ollama only logs a warning by default).
Do **not** rely on **`apg_semantic_search`** without Ollama: it needs a running embedding endpoint at call time. Ignore that tool or expect errors if invoked.
**Ollama + LangChain.js** helpers (for RAG scripts or future MCP tools):
- `loadEnv()` — load `.env` explicitly (also runs via `getOllamaConfig()` / `resolveDataDir()`).
- `getOllamaConfig()` — parsed `{ baseUrl, chatBaseUrl, embeddingBaseUrl, chatModel, embeddingModel }`.
- `ensureOllamaModels()` — `GET /api/tags` + `POST /api/pull` for missing models (same as MCP startup).
- `createChatOllama()` / `createOllamaEmbeddings()` — `@langchain/ollama` instances using those settings.
```ts
import { createChatOllama, createOllamaEmbeddings } from "accessibility-mcp";
```
The **`apg_semantic_search`** tool calls **Ollama** at query time (embed query → cosine similarity vs `data/rag/chunks.json`). **`npm run rag:index`** builds that index with **`createOllamaEmbeddings()`**.
**Sanity check** (requires Ollama reachable at `OLLAMA_CHAT_BASE_URL` or `OLLAMA_BASE_URL` with `OLLAMA_CHAT_MODEL` pulled):
```bash
cp .env.example .env # then edit if needed
npm run ollama:smoke
```
---
## IDE and agent setup
MCP wiring differs by product: some use a top-level **`mcpServers`** object; **VS Code** uses **`servers`** inside `mcp.json`. Below, replace `/absolute/path/to/accessibility-mcp` with your clone (or use `npx` once published).
Use an **absolute** path to **`dist/cli.js`** in **`args`** (or **`npx -y accessibility-mcp`**). A relative path like **`dist/cli.js`** is resolved from the client’s workspace and usually fails outside this repo.
### Shared snippets
**Stdio via local build** (`mcpServers` shape — Cursor, Claude Desktop, Claude Code, Gemini CLI):
```json
{
"mcpServers": {
"apg-patterns": {
"command": "node",
"args": ["/absolute/path/to/accessibility-mcp/dist/cli.js"]
}
}
}
```
**Stdio via npx** (after npm publish):
```json
{
"mcpServers": {
"apg-patterns": {
"command": "npx",
"args": ["-y", "accessibility-mcp"]
}
}
}
```
**Custom data directory** (any client that supports `env` on the server process):
```json
{
"mcpServers": {
"apg-patterns": {
"command": "node",
"args": ["/absolute/path/to/accessibility-mcp/dist/cli.js"],
"env": {
"APG_MCP_DATA_DIR": "/absolute/path/to/accessibility-mcp/data"
}
}
}
}
```
### Visual Studio Code (GitHub Copilot agent / MCP)
VS Code stores MCP config in **`mcp.json`** using a **`servers`** object (not `mcpServers`). See [Add and manage MCP servers in VS Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) and the [MCP configuration reference](https://code.visualstudio.com/docs/copilot/reference/mcp-configuration).
- **Workspace:** `.vscode/mcp.json`
- **User:** Command Palette → **MCP: Open User Configuration**
Example (local checkout):
```json
{
"servers": {
"apg-patterns": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/accessibility-mcp/dist/cli.js"]
}
}
}
```
Example (npx, after publish):
```json
{
"servers": {
"apg-patterns": {
"type": "stdio",
"command": "npx",
"args": ["-y", "accessibility-mcp"]
}
}
}
```
You can also use **MCP: Add Server** in the Command Palette or install from the Extensions view (`@mcp` gallery) if this server is listed there.
### Cursor
Cursor merges MCP config from:
- **Project:** `.cursor/mcp.json`
- **Global:** `~/.cursor/mcp.json` (project entries override global)
Use the **`mcpServers`** JSON shape from the shared snippets above. See [Model Context Protocol (MCP) | Cursor Docs](https://docs.cursor.com/context/model-context-protocol). Restart Cursor after changes if tools do not appear.
### Claude Desktop
Edit the Claude desktop config file and merge under **`mcpServers`**:
| OS | Typical path |
| ------- | ----------------------------------------------------------------- |
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
| Linux | `~/.config/Claude/claude_desktop_config.json` |
Use the shared **`mcpServers`** snippet. Restart Claude Desktop after saving.
### Claude Code
Claude Code supports project **`.mcp.json`**, local entries in **`~/.claude.json`**, and **user** scope; stdio servers use **`command` + `args`** like other clients. See [Connect Claude Code to tools via MCP](https://docs.claude.com/en/docs/claude-code/mcp).
**Prerequisites:** `npm install`, `npm run build`, and a **`.env`** next to `package.json` (or pass Ollama settings with repeated `--env KEY=value`; the server also loads **`.env`** from the package root automatically).
Put options (**`--transport`**, **`--scope`**, **`--env`**) **before** the server name; use **`--`** before the process to spawn ([documented ordering](https://docs.claude.com/en/docs/claude-code/mcp#option-3-add-a-local-stdio-server)).
**Local / project** (from this repo; records a **relative** `dist/cli.js` — only works when that workspace is this package):
```bash
cd /absolute/path/to/accessibility-mcp
claude mcp add --transport stdio apg-patterns -- node dist/cli.js
```
```bash
claude mcp add --transport stdio apg-patterns --scope project -- node dist/cli.js
```
**User scope** (recommended — works from any folder; use your real path):
```bash
claude mcp add --transport stdio apg-patterns --scope user -- node /absolute/path/to/accessibility-mcp/dist/cli.js
```
**After publish to npm:**
```bash
claude mcp add --transport stdio apg-patterns -- npx -y accessibility-mcp
```
Then **`claude mcp list`** or **`/mcp`** in Claude Code to confirm. If the server won’t start, check that **`args`** points at the built **`cli.js`**. **`npm test`** and **`npm run mcp:try`** exercise the server without the Claude UI.
### Gemini CLI
Configure **`mcpServers`** in Gemini CLI settings. User vs project scope:
- **User:** `~/.gemini/settings.json`
- **Project:** `.gemini/settings.json` in the repo
Details: [MCP servers with the Gemini CLI](https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html).
CLI (stdio; user scope — writes `~/.gemini/settings.json`):
```bash
gemini mcp add --scope user apg-patterns node /absolute/path/to/accessibility-mcp/dist/cli.js
```
Use **`--scope project`** to write `.gemini/settings.json` instead. Run **`gemini mcp add --help`** for flags (`-e` for `env`, `--trust`, etc.).
### OpenAI Codex (CLI and IDE extension)
Codex stores MCP servers in **`config.toml`**, default **`~/.codex/config.toml`**, or project **`.codex/config.toml`** on trusted projects. CLI and IDE share this file. See [Model Context Protocol – Codex](https://developers.openai.com/codex/mcp).
**TOML example (stdio):**
```toml
[mcp_servers.apg-patterns]
command = "node"
args = ["/absolute/path/to/accessibility-mcp/dist/cli.js"]
```
**CLI:**
```bash
codex mcp add apg-patterns -- node /absolute/path/to/accessibility-mcp/dist/cli.js
```
### Other editors
- **Windsurf / JetBrains / etc.:** If the product documents MCP stdio support, reuse the same **`command` / `args`** as above; the wrapper key name may differ—check that product’s MCP docs.
- **VS Code discovery:** With **`chat.mcp.discovery.enabled`**, VS Code can pick up MCP definitions from some other apps (e.g. Claude Desktop). See the [VS Code MCP article](https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_automatically-discover-mcp-servers).
## Tools
| Tool | Purpose |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apg_meta` | Source commit, generation time, patterns index URL |
| `apg_list_patterns` | All pattern ids/titles; optional `query` filter |
| `apg_get_pattern` | Markdown spec + example list (`patternId`, optional `includeMarkdown`) |
| `apg_get_example` | Example sources (`patternId`, `exampleSlug`, `format`: json \| markdown) |
| `apg_semantic_search` | RAG: natural-language search (`query`, optional `k`, `maxCharsPerHit`); needs **`data/rag/chunks.json`** (shipped) + **Ollama** to embed the query |
## Resources
- `apg://manifest` — full manifest JSON
- `apg://pattern/{patternId}` — pattern Markdown
- `apg://example/{patternId}/{slug}` — example sources as Markdown
## License
ISC (this package). APG content is W3C documentation; see [W3C document license](https://www.w3.org/copyright/document-license/).
TDQS
A4.2/5.0
Scored across 5 tools
Disambiguation5/5
All five tools have clearly distinct purposes: metadata, listing patterns, getting full pattern docs, getting example source, and semantic search. No overlap or ambiguity.
Naming Consistency5/5
All tool names follow a consistent 'apg_verb_noun' pattern in snake_case (e.g., apg_list_patterns, apg_get_example), making the set predictable.
Tool Count5/5
Five tools is well-scoped for a read-only APG dataset access server. Each tool occupies a necessary role without excess or deficiency.
Completeness5/5
The tool surface covers the full lifecycle of discovering and retrieving APG patterns and examples: metadata, list, detail, example source, and semantic search. No obvious gaps.
Maintenance
ActivityInactive
ResponsivenessNo issues