Skip to main content
Glama
ifsc-mcp

ifsc-mcp

by uttkarsh-26

ifsc-mcp

An MCP server that gives an LLM accurate Indian bank branch data: look up a branch by IFSC, validate a user-typed IFSC offline, find branches by bank/city/state, and map bank names to their 4-letter codes.

The problem

Every Indian payment integration needs IFSC data, and every team solves it badly. The usual options are a stale CSV someone committed in 2021, a paid API with a card on file, or scraping a bank website. Meanwhile the LLM in front of it hallucinates: it invents IFSC codes, guesses that a bank supports UPI, and returns branch addresses that were renamed years ago. IFSCs are 11 characters and unforgiving — one wrong character means a payment sits in a NEAF account and nobody knows why.

This server wraps the razorpay.com IFSC API and the published razorpay/ifsc dataset, so the model asks a tool instead of guessing. Invalid input is rejected locally before a request is made, and every answer carries the branch's real address, MICR and settlement-rail support.

Related MCP server: india-business-mcp-server

Install

Not on PyPI yet — install from source or run straight from the repo:

git clone https://github.com/uttkarsh-26/ifsc-mcp.git
cd ifsc-mcp
uv venv .venv && uv pip install -e ".[dev]"

Or without cloning, into any Python environment:

pip install "git+https://github.com/uttkarsh-26/ifsc-mcp"

MCP client config

Ready to paste into Claude Desktop, Cursor, VS Code or any MCP client:

{
  "mcpServers": {
    "ifsc": {
      "command": "ifsc-mcp",
      "transport": "stdio"
    }
  }
}

Via uvx, no install needed:

{
  "mcpServers": {
    "ifsc": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/uttkarsh-26/ifsc-mcp", "ifsc-mcp"]
    }
  }
}

Streamable HTTP instead of stdio:

ifsc-mcp --transport http --host 127.0.0.1 --port 8000 --path /mcp
{
  "mcpServers": {
    "ifsc": { "type": "http", "url": "http://127.0.0.1:8000/mcp" }
  }
}

Tools

Tool

Purpose

Key parameters

Notes

ifsc_lookup

Full details for one branch

ifsc

The only tool that confirms a branch exists. Returns address, MICR, SWIFT, contact, supports (IMPS/NEFT/RTGS/UPI/SWIFT).

ifsc_validate

Offline format check

ifsc

No network. 4 letters + 0 + 6 alphanumerics. Returns a stable reason code.

search_branches

Find branches by bank/city/state/branch

bank_code, city, state, branch, limit, offset

Filters are ANDed; at least one is required. Paged, with count and has_next.

branch_places

Discover valid filter spellings

bank_code, state, district, limit

Walks states → districts → branches. Use when a search returns nothing.

bank_directory

Bank name → 4-letter code

query, limit

1511+ banks, cached. Returns {code, name} pairs.

bank_metadata

Bank type and rail support

bank_code

Private/public/foreign/SFB/payments/co-op, plus UPI, ACH, NACH, APBS flags.

Every result carries ok: true. Every failure returns ok: false with a stable code: INVALID_INPUT, NOT_FOUND, UPSTREAM_TIMEOUT, UPSTREAM_UNAVAILABLE, UPSTREAM_ERROR, BAD_UPSTREAM_RESPONSE, UNKNOWN — never a traceback.

Configuration

All optional, all read from the environment:

Variable

Default

Purpose

IFSC_API_BASE_URL

https://ifsc.razorpay.com

Upstream API host.

IFSC_DATASET_BASE_URL

https://raw.githubusercontent.com/razorpay/ifsc/master/src

Bank dataset host.

IFSC_REQUEST_TIMEOUT

10

Read timeout, seconds.

IFSC_CONNECT_TIMEOUT

5

Connect timeout, seconds.

IFSC_MAX_RETRIES

2

Retries on timeout/429/5xx.

IFSC_DATASET_TTL

3600

Bank-directory cache TTL, seconds.

IFSC_LOG_LEVEL

INFO

DEBUG/INFO/WARNING/ERROR. Logs go to stderr.

IFSC_MCP_TRANSPORT

stdio

stdio or http.

IFSC_MCP_HOST / IFSC_MCP_PORT / IFSC_MCP_PATH

127.0.0.1 / 8000 / /mcp

HTTP binding.

Data sources

Source

Used for

Verified

ifsc.razorpay.com/{IFSC}

Single-IFSC lookup

200 + JSON

ifsc.razorpay.com/search

Branch search by bankcode/city/state/branch

200 + JSON, 400 on bad limit

ifsc.razorpay.com/places

State/district/branch name discovery

200 + JSON, 400 with no bankcode

raw.githubusercontent.com/razorpay/ifsc src/banknames.json

Bank code → name (1511 entries)

200 + JSON

raw.githubusercontent.com/razorpay/ifsc src/banks.json

Bank type, UPI/NACH/ACH, MICR, IIN

200 + JSON

Evals

The repo scores its own tool descriptions. evals/golden.json holds 32 natural-language queries with the expected tool and expected arguments, spread across all six tools. Two scorers run against it:

  • router — a deterministic, LLM-free keyword router. No network, no API key, same answer every run. This is the number that matters: it measures whether the descriptions make the tool boundaries legible.

  • live — an optional model runner using any OpenAI-compatible endpoint from OPENAI_API_KEY / OPENAI_BASE_URL. Skipped cleanly when unset.

# deterministic baseline, no network and no API key
.venv/bin/python -m evals.run --scorer router

# optional model-backed run
OPENAI_API_KEY=... OPENAI_BASE_URL=... .venv/bin/python -m evals.run --scorer live

Measured, on the committed golden set:

Scorer

Model

Passed

Total

Pass rate

Threshold

Verdict

router (offline baseline)

none — keyword rules

32

32

100.0%

85%

PASS

live

deepseek-v4.1-flash

31–32

32

96.9–100%

90%

PASS

Read those numbers honestly. The 32-case golden set is a smoke gate, not a benchmark, and the keyword baseline was tuned against this same set — it is a floor for "are the tool boundaries legible at all", not a generalisation claim. The live figure is the interesting one, and it is not stable: two consecutive runs scored 32/32 and 31/32. The one flaky case (search-05) is a real finding, not noise — given "find the Axis Bank branch named PALAKKAD KERALA", the model sometimes drops bank_code because the bank name is buried inside the branch name. Fixing that is on the roadmap below.

Results are written to evals/results/results.json (machine-readable) and evals/results/results.md (human table, including the per-case routing confusion list). The runner exits non-zero below the threshold in evals/config.json, so a regression fails CI instead of quietly being a number nobody reads.

See EVALS.md for the scoring rules, the per-tool breakdown, the routing-confusion list and what the eval caught in the tool design.

Verification

Everything in this README was executed, not assumed.

.venv/bin/pytest                 # 131 unit tests, hermetic and offline
.venv/bin/pytest -m live         # 6 live upstream smoke tests
.venv/bin/python scripts/verify_e2e.py    # real MCP client over stdio -> VERIFICATION.md
.venv/bin/python scripts/verify_http.py  # real MCP client over streamable HTTP

VERIFICATION.md holds a raw transcript: a real mcp.Client subprocess session performing a genuine handshake, tools/list and nine tools/call round trips against the live API.

Development

uv pip install -e ".[dev]"
.venv/bin/ruff check . && .venv/bin/ruff format --check .
.venv/bin/pytest                      # unit tests (live ones deselected)
.venv/bin/pytest -m live             # real network smoke tests

After editing the golden cases in evals/golden.py, regenerate the JSON that the runner and the tests read:

.venv/bin/python -c "
import json
from evals.golden import _CASES, MIN_CASES
with open('evals/golden.json','w') as f:
    json.dump({'version':1,'min_cases':MIN_CASES,'cases':list(_CASES)}, f, indent=2); f.write('\n')
"

Roadmap

  • Make search_branches infer bank_code from the branch string (or allow it to be omitted when branch is present) — the one case the live eval misses.

  • Bundle a pinned snapshot of the bank dataset so bank_directory works offline.

  • A nefc_advice tool: given account number + IFSC, compute the NEFC limit and the a/ and a@ sublet categories RBI assigns.

  • Search by pincode, which RBI publishes but the current API does not expose.

  • Prompt caching for bank_directory output to cut repeat token cost.

License

MIT — see LICENSE.

Upstream data is provided by razorpay/ifsc (MIT) and the razorpay.com IFSC API. This project is not affiliated with Razorpay or the RBI.

Available Tools

6 tools
bank_directoryList or search Indian bank codes and namesA
Read-onlyIdempotent

Look up the official 4-letter bank code for a bank, or list banks by name.

Every IFSC begins with a 4-letter bank code, and this is the tool that maps "HDFC Bank" -> "HDFC". Use it before ifsc_lookup or search_branches when the user gave a bank name rather than a code.

Returns ok: true with: banks (list of {"code", "name"}), count (total matches) and source (the upstream dataset URL).

Data comes from the published razorpay/ifsc dataset and is cached, so repeated calls are cheap. This returns banks, not branches - use search_branches or ifsc_lookup for a specific branch.

Args: query: Substring filter over code and name; omit for the full list. limit: Max rows, 1-500 (default 50, or 500 with no query).

Returns: Matching bank codes and names, or a structured error payload.

Example: Ask "what is the code for Kotak Mahindra?" -> call bank_directory(query="kotak") and read banks[0]["code"].

Inputs: limit, query. Returns: object

bank_metadataGet bank type and UPI/NACH/ACH support for a bank codeA
Read-onlyIdempotent

Get bank-level facts for a 4-letter bank code: its type, MICR/IIN, and payment-rail support.

Distinguishes bank classes that share an IFSC prefix pattern - private / public / foreign / small finance / payments / co-operative - and reports whether the bank as a whole supports UPI, ACH credit/debit, NACH debit and APBS.

Choose this over bank_directory when you need capabilities or bank type rather than the name; choose bank_directory when you need to go from a bank name to its code.

Returns ok: true with: bank_code, name, bank_type, micr, iin, capability_ifsc, and capabilities (upi, ach_credit, ach_debit, nach_debit, apbs).

Args: bank_code: The 4-letter bank code, e.g. "KKBK".

Returns: Bank metadata, or a structured error payload (INVALID_INPUT for a malformed code, NOT_FOUND for a code absent from the dataset).

Example: Ask "is HDFC a private bank and does it support UPI?" -> call bank_metadata(bank_code="HDFC").

Inputs: bank_code (required). Returns: object

branch_placesList valid states, districts and branch names for a bankA
Read-onlyIdempotent

Discover the exact district and branch-name spellings that search_branches matches on.

The branch search API matches city/district and branch verbatim, so a near-miss returns zero rows. This tool walks the same hierarchy the API uses, one level per call, and returns the accepted values at that level: bank_code alone -> states; add state -> districts; add district -> branches.

Returns ok: true with level ('states'|'districts'|'branches'), the values at that level, total found, truncated, and the echoed query.

Call this when a search returned nothing and you need to check spelling - it is cheaper than guessing several more search calls.

Args: bank_code: 4-letter bank code; required. state: ISO 3166-2 code to descend to districts, e.g. "IN-MH". district: District name to descend to branches, e.g. "MUMBAI". limit: Cap on returned values, 1-200 (default 200).

Returns: The place names valid at the requested level, or a structured error payload.

Example: Ask "which states does Kotak serve?" -> call branch_places(bank_code="KKBK").

Inputs: bank_code (required), limit, state, district. Returns: object

ifsc_lookupLook up one bank branch by IFSCA
Read-onlyIdempotent

Retrieve full details for the single bank branch identified by an IFSC code.

Use this whenever the user supplies an IFSC. It is the only tool that confirms a branch actually exists, and the only source of the branch's address, MICR, SWIFT/bic code, contact number and per-branch settlement-rail support (RTGS, NEFT, IMPS, UPI).

Returns ok: true with: ifsc, bank, bank_code, branch, centre, address, city, district, state, iso3166, micr, contact, swift, and supports (booleans for imps/neft/rtgs/upi/swift).

Failure codes: INVALID_INPUT (not 11 chars, or not 4 letters + '0' + 6 alphanumerics), NOT_FOUND (well-formed but no such branch), UPSTREAM_TIMEOUT / UPSTREAM_UNAVAILABLE / UPSTREAM_ERROR (service trouble).

Args: ifsc: The 11-character IFSC to resolve, e.g. "SBIN0000001".

Returns: The branch record, or a structured error payload with ok: false.

Example: Ask "what is the MICR of HDFC0000001?" -> call ifsc_lookup("HDFC0000001").

Inputs: ifsc (required). Returns: object

ifsc_validateValidate IFSC format offlineA
Read-onlyIdempotent

Check whether a string is structurally a valid IFSC, without any network call.

Applies the RBI/NEFC layout rules offline: exactly 11 characters, 4 leading letters for the bank code, a literal '0' as the 5th character, and 6 trailing alphanumeric branch characters.

Choose this over ifsc_lookup when you want to reject junk before spending a request, or to explain why a user-entered code is wrong. It does NOT confirm the branch exists - only ifsc_lookup does that.

Returns ok: true with: input, normalized, is_valid, bank_code, branch_code, reason and message. reason is a stable code: VALID, EMPTY, WRONG_LENGTH, INVALID_CHARACTERS, BAD_BANK_CODE, MISSING_BANK_SUBLET or BAD_BRANCH_CODE.

Args: ifsc: The candidate string to validate.

Returns: A validation report, always with ok: true - an invalid IFSC is a successful report, not an error.

Example: Ask "is HFDCC00001 a valid IFSC?" -> call ifsc_validate("HFDCC00001") and read reason == "MISSING_BANK_SUBLET".

Inputs: ifsc (required). Returns: object

search_branchesSearch branches by bank, city, state or branch nameA
Read-onlyIdempotent

Find bank branches when you have a bank, city, state or branch name but no IFSC.

Filters are ANDed. At least one of bank_code, city, state or branch is required - an unfiltered search is rejected with INVALID_INPUT because the dataset has ~170k rows and no endpoint supports listing all of them.

Returns ok: true with: branches (each row shaped like an ifsc_lookup result), count (total matches upstream), has_next (true when more pages exist), and the echoed query.

Prefer this over bank_directory when the user wants a branch; use bank_directory when they want a bank.

Args: bank_code: 4-letter bank code filter. city: City name filter. state: ISO 3166-2 state code filter, e.g. "IN-MH". branch: Exact branch-name filter. limit: Page size, 1-100 (default 10). offset: Row offset, for pagination.

Returns: Matching branches with pagination metadata, or a structured error payload.

Example: Ask "which Kotak branches are in Gurgaon?" -> call search_branches(bank_code="KKBK", city="GURGAON", limit=5).

Inputs: city, limit, state, branch, +2 more. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.4/5.0

    Scored across 6 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose: offline validation vs. online lookup, branch search vs. place-name discovery, bank-name-to-code mapping vs. bank-level capabilities. The descriptions explicitly guide when to prefer one over another, eliminating overlap. No two tools appear to do the same thing.

    Naming Consistency3/5

    Names mix conventions: ifsc_lookup and ifsc_validate use entity_action, search_branches uses verb_entity, and branch_places, bank_directory, bank_metadata use entity_noun. While all are snake_case and readable, there is no single predictable pattern. This makes the set feel slightly inconsistent despite clear individual names.

    Tool Count5/5

    Six tools is well-scoped for an IFSC/bank lookup service. Each tool serves a distinct, non-redundant role, covering validation, lookup, search, discovery, directory, and metadata. No tool feels extraneous or missing at this count.

    Completeness5/5

    The surface covers the full read-only lifecycle: offline validation, online branch resolution, filtered search with pagination, place-name discovery to aid search, bank name-to-code mapping, and bank-level capabilities. No obvious dead ends remain for typical IFSC lookups. Minor gaps like reverse MICR lookup are outside the stated domain.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      C
      maintenance
      Provides AI agents with Indian fintech utilities including IFSC bank lookups, PAN/GSTIN validation, mutual fund NAVs, UPI VPA identification, pincode lookups, and INR formatting, all with zero authentication and zero cost.
      7
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Enables LLMs to query real-time Indian bank branch details, postal PIN codes, validate GSTIN/PAN structures, check e-commerce serviceability, and compute GST breakdowns using free public APIs and offline verification logic.
      6
      9 npm
      1
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables Indian bank branch IFSC code lookup via Razorpay's open API, allowing AI agents to retrieve branch details using IFSC codes.
      136 npm
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables AI agents to validate IBANs, generate synthetic checksum-valid test IBANs, and look up bank registry records by BIC or domestic identifier, with optional paid capabilities for bank, BIC, and SEPA Instant/VoP reachability.
      6
      MIT