tally-write-mcp
Read and write TallyPrime data through Claude, with a draft-confirm gate and full audit log.
An MCP 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:
- 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.
- 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_ledgerrefuses if a ledger of that name already exists (Tally'sACTION="Create"silently alters existing records otherwise). - Stale-read guard —
alter_ledgerrefuses if the caller's "current value" doesn't match Tally's live value. - GST-field refusal —
alter_ledgerrefuses 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, port9000. - 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 once:
# 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:
pip install tally-write-mcp
Then configure your client to launch it directly:
{
"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:
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 SalesInput 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 draftwrite_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:
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_ledgerrefuses 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.
Release files for tally-write-mcp 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tally_write_mcp-0.1.0.tar.gz | 33.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tally_write_mcp-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 71.2 kB
Release files / tally_write_mcp-0.1.0.tar.gz
| Download URL | tally_write_mcp-0.1.0.tar.gz |
|---|---|
| Size | 33.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d8fbeaa0e146dac49b7b1166d597f62532df150c52b73a51b6dfc026e3200be5
|
|
BLAKE2b-256 checksum How to use checksums |
d200246da2bec76862dd3719089b142ce69da25513908ac8e8de4df5d9503607
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.2
|
Release files / tally_write_mcp-0.1.0-py3-none-any.whl
| Download URL | tally_write_mcp-0.1.0-py3-none-any.whl |
|---|---|
| Size | 38.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a17b6b8e889a2b5c7c74c43cca1b4d378b671bddda8a1274af6df02bcbd3f5e3
|
|
BLAKE2b-256 checksum How to use checksums |
cc8bfce829de740e0ed794f9f1ae2f88b05911e86310bb5853c454e5a712ea91
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.2
|