Skip to main content
Glama
README.md
# icfmcp

An MCP ([Model Context Protocol](https://modelcontextprotocol.io)) server that lets AI harnesses — Claude Code, Claude Desktop, and any other MCP client — **search and read [Indent Comma Format (ICF)](https://icformat.org) knowledge files via their ICX indexes**.

Point it at one or more `.icf` files (or directories of them) and it exposes tag search, one-line summaries, full-text search, record retrieval, and validation as MCP tools over stdio. When a sibling `.icx` index file exists it is used directly; otherwise an index is generated in memory with [icf.js](https://www.npmjs.com/package/icf.js).

## Requirements

- Node.js >= 20

## Install & run

```bash
npm install
npm run build

# serve a directory (scanned recursively for *.icf) and/or individual files
node dist/index.js path/to/knowledge-dir another/file.icf
# or, via the bin:
npx icfmcp path/to/knowledge-dir
```

The server speaks MCP on stdin/stdout; status lines go to stderr.

## Registering with Claude Code

```bash
claude mcp add icf -- node <absolute-path>/icfmcp/dist/index.js <absolute-path-to-knowledge-dir>
```

Or in Claude Desktop's `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "icf": {
      "command": "node",
      "args": ["<absolute-path>/icfmcp/dist/index.js", "<absolute-path-to-knowledge-dir>"]
    }
  }
}
```

## Tools

| Tool | Input | Behavior |
| --- | --- | --- |
| `list_documents` | — | Registry overview: name, path, record count, schema ids, whether the index came from a `.icx` file or was generated, tag count. |
| `list_tags` | `document?` | All tags with the number of records carrying each, sorted by count descending. |
| `search_by_tag` | `tag`, `document?`, `matchMode?` | Records carrying a tag. `exact` (default) is case-insensitive equality; `contains` is a case-insensitive substring match. Returns document, recordId, uuid, summary, tags, and Line/Offset/Size when known. |
| `get_record` | `document`, `recordId` | One record in full: resolved data as JSON, `@record` attributes, its index row, and the raw ICF block text. Also resolves master rows by id. |
| `get_summaries` | `document?`, `tag?` | recordId → one-line summary for every record that has one, optionally filtered by tag. |
| `search_text` | `query`, `document?` | Case-insensitive full-text search over raw record blocks; returns matching lines with recordId and absolute line numbers. |
| `validate_document` | `document?` or `icf?` | Errors and warnings from the ICF validator, for a served document or inline ICF text. |

Every tool returns a single JSON text block. Recoverable problems (unknown document or record, missing arguments) come back as `{ "error": "..." }` with `isError: true` — never a protocol failure.

## How the ICX 1.2 Tags/Summary flow works

[ICX](https://icformat.org) is ICF's companion index format. Version 1.2 adds two optional per-row fields and two inverted collections that make an index self-sufficient for triage (ICX v1.2 §7–§9, §15):

- **`Tags`** — search keywords per index row, joined with `+` (a literal `+` is escaped as `\+`). Tags are ideally *typed master references* like `Project:ICF`, so they resolve against the index's own master rows.
- **`Summary`** — a one-line synopsis of the record.
- **`tagindex[]`** — inverted map: tag → `+`-joined record ids.
- **`summaryindex[]`** — record id → summary.

The intended harness flow — and what this server implements for you:

1. Read the small index once (`list_documents`, `list_tags`).
2. Filter rows by tag (`search_by_tag`) — from the per-row `Tags` field *and* `tagindex[]`, merged.
3. Triage candidates by summary (`get_summaries`) without opening the ICF.
4. Fetch only the winning records (`get_record`) — the raw block, resolved data, and byte positions.

When no `.icx` exists, the server generates the index in memory and fills the same information straight from the source: every field value that is a typed reference to a declared master type becomes a tag (ICX §7 generator behavior), and a record's `summary` attribute or `Summary` field becomes its summary. Documents and indexes are cached by mtime+size and reloaded automatically when files change — including when a `.icx` appears next to an `.icf` after startup.

## Development

```bash
npm run dev -- tests/fixtures   # run from source (tsx)
npm test                        # vitest
npm run typecheck               # src + tests
npm run build                   # tsc -> dist/
```

See `CLAUDE.md` for the architecture notes.

## License

MIT © Edison Williams