Metadata-Version: 2.4
Name: zigura
Version: 0.1.1
Summary: Zigura agent kit — one tool layer behind an MCP server and the `zigura` CLI, shipped with the Zigura skill
License: Apache-2.0
Project-URL: Homepage, https://zigura.ai
Project-URL: Source, https://github.com/Fareground/zigura
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Version Control
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp<2,>=1.9
Requires-Dist: httpx>=0.27
Provides-Extra: agent-id
Requires-Dist: fg-agent-id>=0.2; extra == "agent-id"
Dynamic: license-file

# Zigura agent kit

Everything an agent needs to work in a Zigura workspace, in one installable
folder: **one tool layer**, **two front-ends**, **one skill**.

```
agent/
├── zigura_workspace_mcp/
│   ├── tools/            ← the tool layer. ONE implementation, the source of truth
│   │   ├── base.py         shared helpers, paging, handle→id resolution
│   │   ├── files.py        reads, writes, locks, diffs
│   │   ├── sync.py         pull / push — the primary loop
│   │   ├── tickets.py      work items
│   │   ├── proposals.py    reviewable change sets
│   │   ├── chat.py         threads and the inbox
│   │   └── meta.py         workspaces, members, search, knowledge, activity
│   ├── server.py         ← front-end 1: MCP (registers the tools)
│   ├── cli.py            ← front-end 2: `zigura` (GENERATED from the tools)
│   └── client.py           HTTP client, auth, retries
│   └── skill/            ← front-end 3: the agent content, INSIDE the package
│       ├── SKILL.md          the mental model and the working loop
│       └── references/
│           ├── tools.md          full surface (generated)
│           ├── collaboration.md  access, review, approval rules
│           └── recipes.md        worked sequences + failure recovery
└── README.md
```

## The one rule

**A capability is added to `tools/`, never to a front-end.**

The CLI is generated from `ZiguraTools` by introspection, so a new tool becomes
a command for free. The MCP server registers the same class and takes each
tool's description from its docstring via `_doc()`. The skill's reference is
generated from it too.

That means the **docstring on the tool method** is the single source of the
text an agent reads — in MCP, in `zigura <cmd> --help`, and in the skill. Write
it there and all three stay in step. Write it on an MCP wrapper instead and the
CLI ships with empty help, which is exactly the drift this layout removes.

`backend/tests/test_mcp_parity.py` enforces the contract.

## Install

Published as **`zigura`** — named for the command you type, not for the
import package:

```bash
npm install --global zigura-cli  # standalone CLI; no Python required
pipx install zigura              # Python distribution and MCP server
```

Both installs provide the same `zigura` command. Use npm when you want a
standalone native executable. Use `pipx` when Python is already part of your
tooling or when you want the bundled MCP server. `pip install zigura` works
inside a project's environment too.

From a checkout, for development:

```bash
pip install -e agent          # installs the `zigura` CLI and the MCP server
```

Keypair (`ZIGURA_AGENT_KEY`) auth is an optional extra. Not because it is
unavailable — `fg-agent-id` is on PyPI — but because the token agents the UI
mints never touch that code path, and making every install carry a crypto
dependency they will not use is a cost with no payer:

```bash
pipx install 'zigura[agent-id]'    # only if you authenticate with a keypair
```

Reaching for a keypair without it fails with that instruction rather than an
`ImportError`.

MCP server:

```bash
python -m zigura_workspace_mcp
```

| Env | Meaning |
|---|---|
| `ZIGURA_URL` | Backend base URL (default `http://localhost:8100`) |
| `ZIGURA_TOKEN` | A `zgw_` agent token — mint one on the Agents page |
| `ZIGURA_AGENT_KEY` | Path to an agent-id keypair; the server handles challenge/verify and refreshes the JWT itself |

## Hosting (multi-tenant)

Two ways to host it. **Mounted** on the API's own origin is what zigura.ai
runs — one process, one certificate, and the OAuth discovery documents end up
next to the resource they describe:

```bash
ZIGURA_MOUNT_MCP=1 ZIGURA_OAUTH_ENABLED=1 ZIGURA_URL=https://zigura.ai
# served at https://zigura.ai/mcp by the API process
```

Or **standalone**, if you want the MCP workload on its own service:

```bash
ZIGURA_MCP_TRANSPORT=streamable-http ZIGURA_MCP_HOST=0.0.0.0 ZIGURA_MCP_PORT=8200 \
  ZIGURA_URL=https://api.example.com ZIGURA_MCP_ALLOWED_HOSTS=mcp.example.com \
  python -m zigura_workspace_mcp
```

One hosted server serves **many** agents, so it holds no credentials of its
own. `ZIGURA_TOKEN` and `ZIGURA_AGENT_KEY` are **deleted from the environment
at startup**, and every tool call is authenticated by the `Authorization:
Bearer` header on its own HTTP request. A call with no bearer, a malformed
one, or one the API rejects is refused — there is no default identity to fall
back to. A single env token in a hosted server would serve every connecting
agent as the same identity: a cross-tenant breach, not a misconfiguration.

Connecting takes no install — point any remote-MCP client at the URL and give
it your agent token:

```json
{
  "mcpServers": {
    "zigura": {
      "type": "http",
      "url": "https://zigura.ai/mcp",
      "headers": { "Authorization": "Bearer zgw_..." }
    }
  }
}
```

| Env | Meaning |
|---|---|
| `ZIGURA_MCP_HOST` / `PORT` | Bind address (default `127.0.0.1:8200`) |
| `ZIGURA_MCP_ALLOWED_HOSTS` | Comma-separated public `Host` values to accept. Unset behind an LB disables Host checking — every call already carries its own bearer, so there is no ambient credential for a rebinding attack to borrow |
| `ZIGURA_MCP_STATELESS` | `1` (default) — each request stands alone, so any instance behind the load balancer can serve it. `0` keeps MCP sessions server-side (single instance only) |
| `ZIGURA_MCP_MAX_CALLS_PER_AGENT` | Concurrent calls one credential may hold (default 8) |
| `ZIGURA_MCP_MAX_WAITS_PER_AGENT` | Concurrent **long polls** one credential may hold (default 2). `wait_for_work` parks a worker for up to a minute, so without a tighter cap here one valid token could hold the whole pool and stall every other tenant. Over the cap fails fast rather than queueing |
| `GET /health` | Process liveness for the load balancer; no token, and it says nothing about the API behind it |

Differences from stdio, all of them consequences of "the machine is not
yours": `local_path` on `upload_file` / `download_file` /
`propose_binary_change` / `get_change_blob` is refused (it would name the
*server's* disk), and tool bodies run in a worker thread so one agent's
`wait_for_work` long poll cannot freeze everyone else.

stdio stays the default, so an existing local MCP client config keeps working
untouched: one process, one operator, credentials from the environment.

## How the skill reaches an agent

The skill lives **inside the package**, not beside the repo. A hosted server
has no checkout, so a skill on disk would simply not exist in deployment —
agents would get ninety-odd tools and no idea this is a shared space where
pushing `.env` leaks credentials and merging your own proposal defeats review.

It is delivered three ways, because clients differ in what they read:

| Channel | Reaches |
|---|---|
| Server `instructions` | Any client, at initialize — no call needed |
| `zigura://skill/...` resources | Clients that browse resources, on demand |
| The `guide` tool | Clients that read neither, by explicit call |

All three read the same packaged files, so they cannot disagree.

After adding or changing a tool:

```bash
python agent/zigura_workspace_mcp/skill/generate_tools_reference.py
```
