Metadata-Version: 2.5
Name: tally-write-mcp
Version: 0.1.0
Summary: Confirmation-gated, auditable write access to TallyPrime over MCP — draft, confirm, verify, with a full audit log.
Author-email: Harsh <singhdevgani@gmail.com>
License: MIT
License-File: LICENSE
Keywords: accounting,claude,gst,mcp,model-context-protocol,tally,tallyprime
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Accounting
Requires-Python: >=3.11
Requires-Dist: mcp<3,>=2.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: requests>=2.31
Provides-Extra: dev
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# tally-write-mcp

**Read and write TallyPrime data through Claude, with a draft-confirm gate and full audit log.**

An [MCP](https://modelcontextprotocol.io) server that connects any MCP-compatible
AI assistant to your local TallyPrime instance. Read your books, create vouchers
and ledgers, and manage GST — all through conversation, with the assurance that
nothing touches your data without an explicit confirmation step.

---

## Safety by design

Every write goes through a two-step flow:

1. **Stage** — the tool builds the Tally XML and returns a **draft** with a
   human-readable preview and a one-time confirmation phrase. **Nothing has
   been sent to Tally.**
2. **Confirm** — you re-issue the exact phrase. Only then does the server
   transmit to Tally.

There is no code path that writes to Tally in a single tool call. This is
enforced by the server's architecture — not by convention — and the test suite
proves it (`tests/test_no_single_call_write.py`).

Additional safety layers:

- **Config allowlist** — every operation (create, alter, delete) can be
  disabled. Disabled operations are refused at the server level, not in a
  prompt.
- **Audit log** — every write attempt is logged *before* transmission. If the
  log can't be written, the write is refused.
- **Post-write verification** — after every write, the server re-queries Tally
  to independently confirm the change landed.
- **Duplicate protection** — `create_ledger` refuses if a ledger of that name
  already exists (Tally's `ACTION="Create"` silently alters existing records
  otherwise).
- **Stale-read guard** — `alter_ledger` refuses if the caller's "current value"
  doesn't match Tally's live value.
- **GST-field refusal** — `alter_ledger` refuses to alter any ledger with GSTIN
  or state set, because Tally's XML alter replaces rather than merges, and we
  can't read back every field we'd need to preserve.
- **Typed confirmation for large amounts** — vouchers above a configurable
  threshold require the amount in the confirmation phrase.

## Requirements

- **TallyPrime (licensed — NOT Educational).** The EDU version accepts vouchers
  only on the 1st, 2nd, or last day of a month. It also has other restrictions
  that will corrupt your testing.
- **Tally XML server enabled.** In TallyPrime: `F1 → Settings → Connectivity →
  TallyPrime is acting as: Server`, port `9000`.
- **A company loaded in Tally.** Not just open at the gateway — actually loaded
  with the company's name visible at the top of the screen.
- **Python 3.11+** (only if installing from source).
- **Claude Desktop** (direct install, not Microsoft Store) or any other
  MCP-compatible client.

## Install

### Via `uvx` (recommended)

[Install uv](https://docs.astral.sh/uv/getting-started/installation/) once:

```powershell
# Windows PowerShell
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"


Then add this to your MCP client's config file. For Claude Desktop, that's
`%APPDATA%\Claude\claude_desktop_config.json` (direct install) or
`%LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude_desktop_config.json`
(Microsoft Store install — **not recommended**, the sandbox often blocks MCP
servers from launching).

```json
{
  "mcpServers": {
    "tally-write": {
      "command": "uvx",
      "args": ["tally-write-mcp"],
      "env": {
        "TALLY_MCP_CONFIG": "C:\\tally-write-mcp\\config.yaml"
      }
    }
  }
}

```
**Fully quit and relaunch Claude Desktop** after saving the config. Not just
"close the window" — right-click the tray icon and Quit, or kill it from Task
Manager. The config is only read at startup.

### Via pip

If you prefer pip over uvx:

```powershell
pip install tally-write-mcp
```

Then configure your client to launch it directly:

```json
{
  "mcpServers": {
    "tally-write": {
      "command": "tally-write-mcp",
      "env": {
        "TALLY_MCP_CONFIG": "C:\\tally-write-mcp\\config.yaml"
      }
    }
  }
}
```

## Configure

Copy `config.yaml.example` to `config.yaml` and edit:

```yaml
tally:
  company: ""
```

The `company` field is optional. Leave it blank to use whichever company is
currently open in Tally — that's the recommended setup for most users.

If you set it to a specific company name (exact, case-sensitive), reads will
target that company. **Writes always go to whichever company is currently
loaded in Tally** — there is no way to target a specific company on an Import
request. If you have multiple companies open, make sure the right one is
active before confirming any draft.

Out of the box, **alter and delete are disabled**. Create and read work
immediately. To enable the others, flip the flags in `config.yaml` and restart
Claude Desktop.

## Usage

Once configured, ask Claude:

> Run `tally_connection_check`

That should return your company name. Then try reads:

> List all ledgers under Sundry Debtors.

To create something — a Payment voucher, for example:

> Stage a Payment voucher dated 2026-09-01 for Rs. 5,000 to "ABC Suppliers"
> from "Bank Account". Narration: "invoice 1234".

Claude returns a **draft** — a preview, a `draft_id`, and a confirmation
phrase. **Nothing has been written.** Verify with:

> Preview the XML for that draft.

Then confirm:

> Confirm draft `<draft_id>`. The phrase is: `CONFIRM <id>`

Only then does the write go to Tally.
## Confirmation phrases

| Situation | Phrase format |
|---|---|
| Normal write | `CONFIRM <first 8 chars of draft_id>` |
| Voucher above `typed_confirmation_above` | `CONFIRM <amount> <first 8 chars>` |
| Delete ledger | `DELETE LEDGER <ledger name>` (exact) |
| Delete voucher | `DELETE VOUCHER <voucher number>` (exact) |

Delete phrases are typed-out English — no shortcut. That's deliberate.

## GST setup

For Sales and Purchase vouchers with GST, six ledgers must exist in Tally
under group **`Duties & Taxes`**:

- `Output CGST`, `Output SGST`, `Output IGST` — credited on Sales
- `Input CGST`, `Input SGST`, `Input IGST` — debited on Purchases

**Output and Input must be separate.** Combining them corrupts your GSTR-1 and
GSTR-3B returns.

Your Tally must have GST enabled (`F11 → Features → Statutory & Taxation →
Enable GST: Yes`) and each party ledger must have a state set.

## Education vs licensed TallyPrime

**This tool requires a licensed TallyPrime.** The Educational version:

- Accepts vouchers only on the 1st, 2nd, or last day of a month
- Reports "Voucher date is missing" for dates it rejects, which is misleading
- May cap daily voucher counts
- May purge data after a period

Reads work fine on EDU. Writes are unreliable. If Tally reports "Voucher date
is missing" on a date you know is in the XML, that's EDU's restriction.

## Logs

`logs/audit.jsonl` — one JSON object per line. Events:

- `draft_staged` — a draft was created (nothing sent to Tally)
- `draft_cancelled` — user discarded a draft
- `write_attempt` — **logged before transmission**. If this fails, the write
  is refused.
- `write_result` — the outcome (accepted or rejected by Tally)
- `write_transport_failure` — couldn't reach Tally (unknown state)

Review regularly in production:

```powershell
Get-Content logs\audit.jsonl |
    ConvertFrom-Json |
    Where-Object { $_.event -eq "write_result" } |
    Select-Object ts, summary, tally_ok, tally_message
```

## Architecture

```
Claude (or any MCP client)
    ↓  calls an MCP tool (e.g. create_payment_voucher)
MCP server (this package)
    ↓  stages a draft — returns preview + confirmation phrase
Claude reports draft to user
    ↓  user confirms with exact phrase
MCP server builds Tally XML <ENVELOPE>
    ↓  HTTP POST to localhost:9000
TallyPrime
    ↓  returns <STATUS> and counters
MCP server parses, verifies, and reports
```

The only code path that transmits to Tally is
`tally_write_mcp.tools.control.confirm_write`. All write tools in
`tally_write_mcp.tools.write` produce drafts only — the unit tests enforce
this invariant.

## Known limitations

- **Single company, single machine, local only.** Remote access and
  multi-company operation are out of scope for v1.
- **`alter_ledger` refuses ledgers with GST fields.** Tally's XML alter is a
  full replace; we can read back GSTIN and state but not registration type,
  PAN, address, or banking details. Use Tally's UI for those.
- **No dependency pre-check on delete.** Tally rejects the deletion with a
  clear error if the ledger has transactions. The tool surfaces that error
  faithfully.
- **GST return filing, bank reconciliation, and e-invoicing are out of scope.**
  Use Tally's native Connected GST / Connected Banking features.

## License

MIT — see [LICENSE](LICENSE).