Metadata-Version: 2.4
Name: columna-server
Version: 0.6.0
Summary: Columna MCP server — a library of Manifolds exposed to AI agents over MCP, one contract (the four moods as data).
Author: Datumwise
License-Expression: Apache-2.0
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: columna-core>=0.11.0
Requires-Dist: duckdb<2.0,>=1.0
Requires-Dist: mcp>=1.0
Requires-Dist: tomli>=2.0; python_version < '3.11'
Provides-Extra: agent
Requires-Dist: anthropic>=0.40; extra == 'agent'
Provides-Extra: http
Requires-Dist: starlette>=0.37; extra == 'http'
Requires-Dist: uvicorn>=0.23; extra == 'http'
Provides-Extra: test
Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

# columna-server

The **Columna MCP server** (ADR-032 D8): a library of Manifolds exposed to AI agents over MCP, with
**one contract** — every tool returns the same outcome/disclosure structure the Python API returns.
This is the wedge product: the first metrics MCP server that can say *"it depends."*

## Run

```bash
pip install columna-core columna-server
columna-server demo --play                    # the packaged demo: clarify -> refuse -> disclose -> serve
columna-server demo                           # serve the packaged demo over MCP stdio (no path args)
columna-server mcp --manifolds <dir>          # serve your own manifolds dir over stdio
columna-server mcp --manifolds <dir> --http :8000   # streamable-http, gated by COLUMNA_MCP_TOKEN

# Natural-language agent (a true MCP client over the server); needs the [agent] extra + a key:
pip install "columna-server[agent]"
ANTHROPIC_API_KEY=... columna-server agent    # chat REPL over the packaged demo
```

The **agent** turns natural language into a *proposed* Frame-QL query, spawns the server over stdio,
and lets the four moods drive the conversation — it never touches the engine in-process. The model
proposes, the planner disposes, the human decides (clarifies are relayed, never auto-picked; every
number comes verbatim from the wire). See [`demos/agent_transcript.md`](demos/agent_transcript.md).
Model via `COLUMNA_AGENT_MODEL` (default `claude-opus-4-8`).

**Richer run.** The packaged demo ships a small (~330 KB) warehouse. To run the same benchmark
Manifold over the full 4.7 MB warehouse (299,934 transactions), point `--manifolds` at a directory
whose `data.toml` warehouse path is the repo's `packages/columna-core/demos/warehouse`.

> **From source (contributors):** `git clone https://github.com/datumwise/columna && cd columna`,
> then `pip install -e packages/columna-core -e "packages/columna-server[test]"` and
> `pytest packages/columna-server -q`.

`<dir>` holds `<id>/manifold.cml` + `data.toml` (connector type + data path). Manifolds are parsed
and connected once at startup.

## Tools (five, read-only — no SQL, no writes)

| tool | touches data? | returns |
|---|---|---|
| `list_manifolds()` | no | id, name, description, measure count, universes |
| `describe_manifold(manifold_id)` | no | dimensions/levels, edges (+ lineage), universes (+ predicate), measure index |
| `describe_measure(manifold_id, measure)` | no | family triple, per-member anchors, dtype, v-anchor `{universe, grain}`, m-anchor, provenance |
| `query(manifold_id, frameql, universe?)` | yes | the wire contract (serve/disclose/clarify/refuse/error) |
| `explain(manifold_id, frameql, universe?)` | **no** (`fetches_delta: 0`) | the would-be outcome + disclosures |

`frameql` is `"<columns> @ <anchor>"` (e.g. `"rate: revenue / level.last @ store, day"`); the
envelope is parsed here and every expression is delegated to columna-core — one expression dialect.

## The contract

Outcomes are data (the four moods), and disclosures are structured `{code, materiality, severity,
category, detail, remedy, source, rel_error}` via `columna_core.disclosure_wire` — the same
serialization every surface shares. Clarify alternatives are mechanically substitutable (a universe
pin carries `apply: {"universe": U}`).

See [`demos/mcp_claude_desktop.md`](demos/mcp_claude_desktop.md) for a Claude Desktop config and a
real clarify → answer transcript.
