Skip to main content
Glama
tejash-bhandari-dev

tally-mcp-server

README.md
# tally-mcp-server

An MCP (Model Context Protocol) server that exposes TallyPrime data to any
MCP-compatible client (Claude Desktop, Claude Code, etc.) over Tally's
**classic XML HTTP API** - the integration method supported by every
Tally.ERP 9 / TallyPrime release.

## Available tools

| Tool key              | What it returns                                              |
| ---------------------- | ------------------------------------------------------------ |
| `list_companies`       | Companies known to the running Tally instance                |
| `list_ledgers`         | All ledger accounts, with opening/closing balances           |
| `list_groups`          | All accounting groups (chart-of-accounts categories)         |
| `list_stock_items`     | All inventory items, with closing qty/rate/value             |
| `day_book`              | Vouchers posted between two dates (`fromDate`, `toDate`)     |
| `outstanding_balances` | Ledgers in a group with closing balance (receivables/payables) |

## How it talks to Tally

TallyPrime can act as an HTTP server and accept an XML "envelope":
`<ENVELOPE><HEADER>...</HEADER><BODY>...</BODY></ENVELOPE>`. Two request
shapes are used here:

- **Built-in reports** (e.g. `list_companies`) - just name the report,
  no TDL needed.
- **Custom collections** (everything else) - an inline TDL
  `<COLLECTION>` naming a native Tally object type (`Ledger`, `Group`,
  `StockItem`, `Voucher`) and the fields to fetch. This is the standard
  technique for "give me all X" pulls and is what most third-party Tally
  integrations use.

Docs: https://help.tallysolutions.com/developer-reference/introduction/integration-with-tallyprime/

## 1. Enable Tally as an HTTP server

In TallyPrime: **F1 (Help) → Settings → Connectivity → Client/Server
configuration** → set "TallyPrime acts as" to **Server**, and note the port
(default `9000`).

## 2. Install

```bash
npm install
cp .env.example .env
# edit .env if your Tally isn't on localhost:9000
```

## 3. Run standalone (for testing)

```bash
npm start
```

The server speaks MCP over stdio, so running it directly won't print
anything to stdout - that's expected. `Ctrl+C` to stop.

## 4. Connect it to an MCP client

Example Claude Desktop config (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "tally": {
      "command": "node",
      "args": ["/absolute/path/to/tally-mcp-server/src/index.js"]
    }
  }
}
```

## Project structure

```
src/
├── index.js                 # MCP server bootstrap - wires everything together
├── config.js                 # Reads TALLY_HOST / TALLY_PORT / etc. from .env
├── tally/
│   ├── client.js              # Generic transport: builds/sends the XML envelope,
│   │                           # parses the XML response into a plain object
│   └── endpoints/
│       ├── index.js            # Registry - list every supported Tally API here
│       ├── companies.js        # Built-in report example
│       ├── ledgers.js          # Custom TDL collection example
│       ├── groups.js
│       ├── stockItems.js
│       ├── dayBook.js          # Collection example with runtime params (dates)
│       └── outstanding.js      # Collection example with a filter
└── tools/
    └── index.js                # Turns every registered endpoint into an MCP tool
```

The split matters:

- **`tally/client.js`** only knows how to *transport* a request (build the
  XML envelope, POST it, parse the XML response, surface errors). It has
  zero Tally-report-specific knowledge.
- **`tally/endpoints/*.js`** each describe *one* Tally API: what to
  request (`request: { kind, ... }`), any input parameters it needs, and
  how to turn Tally's parsed XML into a clean shape.
- **`tools/index.js`** loops over the endpoint registry and calls
  `server.tool(...)` for each one - so a new endpoint becomes a working MCP
  tool automatically, with no changes to the server or client code.

## Adding a new Tally API

Say you want to add "List Cost Centres":

1. Create `src/tally/endpoints/costCentres.js`, modeled on `groups.js`:

   ```js
   import { asArray } from "../client.js";

   export default {
     key: "list_cost_centres",
     title: "List Cost Centres",
     description: "Returns cost centres from TallyPrime.",
     inputSchema: {},
     request: {
       kind: "collection",
       collectionName: "MCP CostCentres",
       type: "CostCentre",
       fetch: ["NAME", "PARENT", "CATEGORY"],
     },
     parseResponse(parsedXml) {
       const entries = asArray(parsedXml?.ENVELOPE?.COSTCENTRE);
       return entries.map((e) => ({
         name: e["@_NAME"] ?? e.NAME,
         parent: e.PARENT,
         category: e.CATEGORY,
       }));
     },
   };
   ```

2. Register it in `src/tally/endpoints/index.js` (import + add to the
   `endpoints` array).

3. Restart the server. `list_cost_centres` is now a callable MCP tool -
   nothing else needs to change.

If an endpoint needs runtime parameters (dates, a filter value, etc.),
add zod validators to `inputSchema` and read them in `request.staticVariables(params)`
and/or `request.buildFilterValue(params)` - see `dayBook.js` and
`outstanding.js` for examples.

## Troubleshooting

- **"Could not reach TallyPrime"** - Tally isn't running, isn't configured
  as a server, or `TALLY_HOST`/`TALLY_PORT` in `.env` don't match.
- **Empty results or `<LINEERROR>`** - the report/collection `id` or TDL
  field names may need adjusting for your Tally release or company
  configuration. Turn on Tally's "Test Client" / check the Tally log
  screen while a request runs to see exactly what it received.
- **Unexpected shape in `list_companies`** - `parseResponse()` tries a
  few likely XML shapes and falls back to returning the raw parsed
  payload with a `warning` field so you can see exactly what Tally sent
  back and adjust the parser.
- **Numbers coming back as strings/numbers inconsistently** - Tally's XML
  doesn't strongly type values; `fast-xml-parser` does its best guess.
  If you need guaranteed types, cast explicitly inside `parseResponse()`.

## To test MCP sever in browser
```
npx @modelcontextprotocol/inspector node src/index.js
```

TDQS

A4.1/5.0

Scored across 6 tools

Disambiguation5/5

Each tool targets a distinct Tally entity: companies, ledgers, groups, stock items, vouchers, and balance status. There is no overlap in the data returned, so an agent can easily select the right tool.

Naming Consistency4/5

Most tools follow a list_* pattern (list_companies, list_ledgers, list_groups, list_stock_items), but day_book and outstanding_balances deviate. Despite this, the names remain descriptive and predictable.

Tool Count5/5

Six tools is a well-scoped set for a TallyPrime integration, covering the primary read operations without being sparse or overwhelming.

Completeness4/5

The tool set provides comprehensive read-only coverage of accounting entities: masters, transactions, and balances. Minor gaps exist, such as no voucher-level detail beyond day_book, but the core reporting needs are met.

Maintenance

ActivitySlowing
ResponsivenessNo issues