Skip to main content
Glama

Udhaar

A voice-first credit ledger (udhaar/khata) for small shopkeepers, built as a self-hosted Alexa+ MCP server over Streamable HTTP, backed by Amazon Bedrock for understanding mixed-language speech and drafting collection messages.

Built for Amazon's Build, Ship, Shape hackathon — Alexa+ track, plus the AWS Builder and Open Source mini-challenges.

The problem

Millions of small Indian shops run on a paper notebook (khata) of who owes what. It's error-prone, hard to search, impossible to back up, and useless hands-free when a shopkeeper is mid-sale with both hands full of goods. A voice assistant that can record "Ramu took two hundred rupees of rice on credit" while the shopkeeper keeps working — and later tell them who to follow up with — replaces that notebook without asking anyone to learn an app.

Related MCP server: voiceledger-mcp

The demo in 30 seconds

Open the web simulator (GET /, see Setup) and speak or type:

  1. "Ramu took two hundred rupees of rice on credit and Kumar paid five hundred" → both entries recorded in one sentence, parsed by Bedrock (or a regex fallback if Bedrock isn't configured).

  2. "How much does Ramu owe?" → spoken balance.

  3. "Who should I follow up with today?" → ranked list with a drafted, polite reminder message per customer.

  4. The ledger panel on the right updates live after every action.

Why this isn't a thin wrapper

  • Bedrock-backed understanding with a graceful fallback. Spoken, code-mixed sentences (English + transliterated Tamil/Hindi — udhaar, baaki, kadan) are parsed by a Bedrock ConverseCommand call (src/parser.ts). If Bedrock is unavailable, misconfigured, or disabled (UDHAAR_DISABLE_BEDROCK=1), a regex-based rules engine takes over automatically — the tool never crashes or goes silent, it just reports which engine it used (structuredContent.engine).

  • Proactive collection planning, not just a balance lookup. collection_plan ranks customers by balance × recency and drafts a warm, non-threatening reminder per customer — in the shopkeeper's requested language — via Bedrock, again falling back to a plain template if Bedrock is unavailable.

  • Multilingual by design, not just English with a translation layer bolted on: the Bedrock prompts are written to understand code-mixed Tamil/Hindi/English input directly, and collection_plan can draft output in a requested language.

  • Voice-shaped responses. Every tool returns one or two short spoken sentences (plus structuredContent for UIs) — not JSON dumps, not markdown tables, because the Alexa+ assistant on the other end has to read it aloud.

  • A real, spec-compliant MCP server, not a demo stub: stateless Streamable HTTP per MCP 2025-11-25, validated against the official @modelcontextprotocol/inspector CLI and the official SDK client end-to-end (src/smoke-test.ts), with bearer-token auth and a Docker/App Runner deployment path.

Architecture

flowchart LR
    subgraph Client
        Alexa["Alexa+ (voice)<br/>— unverified, see docs/ALEXA_SETUP.md"]
        Sim["Web simulator<br/>(public/, this repo)"]
        Inspector["MCP Inspector / SDK client"]
    end

    subgraph Server["Express server (src/server.ts)"]
        MCP["POST /mcp — Streamable HTTP<br/>(stateless, MCP 2025-11-25)"]
        Ask["POST /api/ask — intent router<br/>(src/intent.ts)"]
        State["GET /api/state"]
        Health["GET /health"]
    end

    subgraph Logic
        Parser["src/parser.ts<br/>parseUtterance()"]
        Planner["src/planner.ts<br/>collectionPlan()"]
        Ledger["src/ledger.ts<br/>JSON-file store"]
    end

    Bedrock["Amazon Bedrock<br/>ConverseCommand"]

    Alexa -->|Bearer auth| MCP
    Inspector -->|Bearer auth| MCP
    Sim --> Ask
    Ask -->|self-call, same auth| MCP
    MCP --> Parser
    MCP --> Planner
    MCP --> Ledger
    Parser -.fallback to regex.-> Parser
    Parser --> Bedrock
    Planner --> Bedrock
    Planner --> Ledger

Tools

Tool

Use when the shopkeeper says...

Notes

record_credit

"Ramu took 200 rupees of rice on credit"

Direct, structured input

record_payment

"Kumar paid 500"

Reports "fully settled" at balance ≤ 0

record_from_speech

Any free-form sentence, possibly multiple entries, possibly code-mixed

Bedrock or rules fallback

get_balance

"How much does Ramu owe?"

Includes last 3 movements

list_dues

"Who owes me money?"

Biggest balance first, optional overdue filter

daily_summary

"How was today?"

Credit, payments, entry count, total outstanding

payment_reminder

"Remind Ramu to pay"

Optional UPI deep link (upi://pay?...)

collection_plan

"Who should I follow up with today?"

Ranked by balance × recency, Bedrock-drafted or templated messages

Setup and run

npm install
cp .env.example .env    # fill in what you need; see below
npm run dev              # tsx src/server.ts, serves on :3000

Open http://localhost:3000/ for the web simulator, or point an MCP client at http://localhost:3000/mcp.

Everything works with no AWS credentials at all if you set UDHAAR_DISABLE_BEDROCK=1 — speech parsing and reminder drafting fall back to deterministic regex/template logic, useful for offline development or a no-AWS demo.

Bedrock setup

  1. In the Bedrock console for your target region, go to Model access and request access to an Anthropic Claude model (usually instant approval).

  2. Find a valid, invokable model ID for your region/credentials:

    aws bedrock list-inference-profiles --region <your-region> \
      --query "inferenceProfileSummaries[].inferenceProfileId"

    Many current Claude models require an inference profile ID (us.anthropic.claude-..., eu.anthropic.claude-..., global.anthropic.claude-...), not the raw foundation-model ID.

  3. Set in .env:

    AWS_REGION=<your-region>
    BEDROCK_MODEL_ID=<the inference profile ID from step 2>
  4. Standard AWS credential chain only (aws configure, AWS_PROFILE, instance/task role) — no credentials are ever hardcoded.

Environment variables

See .env.example for the full, documented list (PORT, AWS_REGION, BEDROCK_MODEL_ID, UDHAAR_API_KEY, UDHAAR_DISABLE_BEDROCK, UDHAAR_DATA_FILE).

Tests

npx tsc --noEmit    # type check
npm run test:unit   # node:test unit tests — ledger, parser, planner, intent router
npm test             # end-to-end: spins up a real server, drives it with the
                      # official MCP SDK client over Streamable HTTP, asserts
                      # on actual tool results (not just "it responded")

Deploy

Docker + AWS App Runner, with an IAM instance role scoped to exactly bedrock:InvokeModel, a health-check wired to /health, and a cost/shutdown note: see deploy/README.md. The Docker image has been built and smoke-tested locally (non-root user, /health and /mcp both verified inside the container); the App Runner deployment steps themselves require an AWS account and have not been executed by anyone but you.

Security notes

  • No hardcoded credentials anywhere; AWS access only via the standard SDK credential chain.

  • /mcp and /api/* require Authorization: Bearer <UDHAAR_API_KEY> when that variable is set (constant-time comparison); unset means auth is open — set it before exposing the server publicly. /health always stays open for load balancer health checks.

  • The bearer-token scheme here is not full OAuth 2.1 — Alexa+'s documented requirement for real device registration is OAuth 2.1 with PKCE, which this repo does not implement (see docs/ALEXA_SETUP.md for the honest gap analysis).

  • The JSON-file ledger store is demo-grade (not concurrent-write-safe, not durable across container restarts on App Runner) — see the DynamoDB note in deploy/README.md for the production path.

What's verified vs simulated

  • Verified: Streamable HTTP MCP server behavior (Inspector CLI + the official SDK client, both over real HTTP), bearer auth (401/200 paths), Bedrock parsing/drafting with regex/template fallback, the Docker image running as non-root locally.

  • Simulated: the "Alexa+" experience is demonstrated via the included web simulator (public/), which speaks to the same /mcp tools over the same Streamable HTTP protocol an Alexa+ registration would use — browser speech recognition/synthesis stand in for the device. Real Alexa+ device registration has not been completed; see docs/ALEXA_SETUP.md for exactly what that would take and why it's marked unverified.

Hackathon mapping

  • Alexa+ track (primary): self-hosted MCP server, Streamable HTTP, MCP spec 2025-11-25, voice-shaped tool responses.

  • AWS Builder mini-challenge: Amazon Bedrock (ConverseCommand) for speech parsing and message drafting; AWS App Runner + ECR deployment path.

  • Open Source mini-challenge: see the project's hackathon submission notes for the external contribution made during the hackathon window (tracked separately from this repo's own commits).

License

MIT — see LICENSE.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables users to track personal expenses through natural language interactions with comprehensive category support and financial summaries. Provides both local and remote MCP server options with SQLite storage for fast expense management operations.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables assistants to answer spoken questions about a cash trader's sales, expenses, stock levels, and outstanding debts from an append-only ledger, with deterministic figures for speech.
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables MCP clients to let shop owners ask voice-first questions about KiotViet inventory, sales, and reordering, including drafting and confirming purchase orders.
    7
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables small dairy and crop farmers in India to talk to a voice-friendly farm ledger, with every number verified rather than guessed. It is a self-hosted MCP server over Streamable HTTP, planned to run on AWS Lambda with DynamoDB and Amazon Bedrock.
    MIT