Skip to main content
Glama
garland3

atlas-introspect-mcp

by garland3
README.md
# atlas-introspect-mcp

An MCP server that lets [ATLAS](https://github.com/sandialabs/atlas-ui-3) look
at — and edit — itself:

| surface | what you get | how |
|---|---|---|
| **conversations** | list, search, read full chat history | ATLAS REST API (read-only) |
| **`config/mcp.json`** | read, add/replace/delete a server, replace the file | on disk, atomic + backed up |
| **workspaces** | list, create, update, delete | ATLAS REST API |
| **custom prompts** | the per-user prompt library | ATLAS REST API |
| **prompt files** | the on-disk `prompts/*.md` defaults | on disk, backed up |

## Why it goes through the HTTP API

Chat history, workspaces, and prompts all live in one DuckDB file
(`data/chat_history.db`). DuckDB is single-writer and the ATLAS process holds
that write lock for its entire lifetime — a second process opening the file,
**even read-only**, fails with a lock conflict. So this server asks ATLAS over
loopback HTTP instead. That also means per-user scoping is enforced by ATLAS
rather than reimplemented here.

Only `mcp.json` and `prompts/*.md` are touched directly; ATLAS has no API for
either.

Being a separate process is what makes calling back into ATLAS safe: ATLAS
awaits this server's stdio reply while its own event loop stays free to serve
the request. An in-process tool doing the same thing would deadlock.

## Identity

Every query is scoped to one user, sent as `X-User-Email`. With
`DEBUG_MODE=true` ATLAS honours that header; with it off, the header is what
the auth proxy would have supplied, so the same flag works either way. The
default is `test@test.com` — the test user ATLAS falls back to in debug mode,
and therefore the owner of the conversations created through the UI on this
host.

## Install

```bash
git clone https://github.com/garland3/atlas-introspect-mcp
cd atlas-introspect-mcp
uv venv .venv
uv pip install -e ".[dev]" --python .venv/bin/python
```

Standalone check (with ATLAS running):

```bash
.venv/bin/atlas-introspect-mcp --list-tools
.venv/bin/python -c "
import asyncio; from fastmcp import Client
from fastmcp.client.transports import StdioTransport
async def m():
    async with Client(StdioTransport(command='.venv/bin/atlas-introspect-mcp', args=['--stdio'])) as c:
        print(await c.call_tool('atlas_status', {}))
asyncio.run(m())"
```

## Register with ATLAS

Add to your ATLAS `config/mcp.json` (absolute paths — the command is looked up
on the service manager's `PATH`, not your shell's), then restart ATLAS:

```json
"atlas_introspect": {
  "command": [
    "/abs/path/to/atlas-introspect-mcp/.venv/bin/atlas-introspect-mcp",
    "--stdio",
    "--atlas-url", "http://127.0.0.1:8090",
    "--user", "test@test.com"
  ],
  "cwd": "/abs/path/to/atlas-introspect-mcp",
  "transport": "stdio",
  "groups": ["users"],
  "description": "Introspect and configure this ATLAS instance: browse chat history, read/edit config/mcp.json, manage workspaces and custom prompts.",
  "short_description": "ATLAS self-introspection",
  "compliance_level": "Public"
}
```

Add `"--read-only"` to the command for a view-only build; every mutating tool
then refuses instead of writing.

Ports: stdio needs none. `--transport http` defaults to **8101**. It refuses
port 8080 outright — on a host running k3s, klipper-lb DNATs 8080 to Traefik
*including on 127.0.0.1*, so a server bound there receives nothing and every
request comes back as an empty 500 with no line in your log.

## Configuration

Every flag has an environment-variable equivalent.

| flag | env var | default |
|---|---|---|
| `--atlas-url` | `ATLAS_URL` | `http://127.0.0.1:8090` |
| `--user` | `ATLAS_USER` | `test@test.com` |
| `--auth-header` | `ATLAS_AUTH_HEADER` | `X-User-Email` |
| `--atlas-home` | `ATLAS_HOME` | `~/ATLAS-GROUP/atlas-ui-3` |
| `--config-dir` | `ATLAS_CONFIG_DIR` | `<atlas-home>/config` |
| `--prompts-dir` | `ATLAS_PROMPTS_DIR` | `<atlas-home>/prompts` |
| `--timeout` | `ATLAS_TIMEOUT` | `30` seconds |
| `--max-output-chars` | `ATLAS_MAX_OUTPUT` | `100000` |
| `--read-only` | `ATLAS_INTROSPECT_READ_ONLY` | off |
| `--transport` | `ATLAS_INTROSPECT_TRANSPORT` | `stdio` |
| `--port` | `ATLAS_INTROSPECT_PORT` | `8101` (http transport only) |

## Tools

**Conversations (read-only)** — `list_conversations`, `search_conversations`,
`get_conversation`, `list_conversation_tags`

**`config/mcp.json`** — `get_mcp_config`, `upsert_mcp_server`,
`delete_mcp_server`, `replace_mcp_config`

**Workspaces** — `list_workspaces`, `create_workspace`, `update_workspace`,
`delete_workspace`

**Custom prompts** — `list_custom_prompts`, `get_custom_prompt`,
`create_custom_prompt`, `update_custom_prompt`, `delete_custom_prompt`

**On-disk prompt files** — `list_prompt_files`, `read_prompt_file`,
`write_prompt_file`

**Health** — `atlas_status`

## Things that will bite

- **`mcp.json` edits are not live.** ATLAS spawns stdio servers at startup.
  Every write tool says so in its result; apply with
  `systemctl --user restart atlas` or `./redeploy.sh --no-pull`.
- **Editing your own entry works, but only takes effect on restart** — at which
  point a broken entry means this server is gone and you cannot fix it from
  inside ATLAS. Backups land next to the file as `mcp.json.bak-<stamp>`.
- **`upsert_mcp_server` replaces an entry wholesale**, it does not merge. Read
  it first, change it, send the whole thing back. Same for a workspace `config`
  and a prompt's `content`.
- **Conversation tools are read-only by design.** There are delete endpoints in
  ATLAS; they are deliberately not exposed here.
- **`write_prompt_file` changes every chat in the instance.** It backs up first
  and, like `mcp.json`, applies on the next restart.

## Tests

```bash
.venv/bin/pytest
```

They cover the file-editing and validation logic (atomic write, backup,
traversal refusal, lint warnings, read-only mode) against a temp directory —
no running ATLAS needed.

TDQS

A3.9/5.0

Scored across 21 tools

Disambiguation5/5

Each tool targets a distinct resource and action: conversations, workspaces, custom prompts, prompt files, and MCP config are cleanly separated. Even similar operations like search_conversations vs list_conversations are differentiated by their descriptions (search vs browse). No two tools appear to do the same thing.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern (list_, get_, create_, update_, delete_, read_, write_, search_, upsert_, replace_) with snake_case throughout. Only 'atlas_status' deviates slightly, but it's a special health-check tool and still fits the imperative style.

Tool Count4/5

At 21 tools, the server is on the heavier side, but the scope (managing conversations, workspaces, prompts, prompt files, and MCP config) justifies the count. Each tool addresses a specific need, and there is no obvious duplication or bloat.

Completeness4/5

Workspaces, custom prompts, and MCP config all have full CRUD (or equivalent) coverage. Conversations lack create/delete, and tags are read-only, but those may be intentionally absent for an introspection tool. The ability to read and write prompt files covers the on-disk prompt management need. Minor gaps exist, but agents can work around them.

Maintenance

ActivityMaintained
ResponsivenessNo issues