# Lazychat Erpnext

> LazyChat — an AI assistant docked inside your ERPNext desk. Chat with your data, draft reports, stage edits (Apply-gated). Bring your own LLM: any OpenAI-compatible or Anthropic key, stays in your browser. Also ships an MCP server (101 permission-scoped tools).

- **Type:** MCP server
- **Install:** `agentstack add mcp-soumyasethy-lazychat-erpnext`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [soumyasethy](https://agentstack.voostack.com/s/soumyasethy)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [soumyasethy](https://github.com/soumyasethy)
- **Source:** https://github.com/soumyasethy/lazychat-erpnext

## Install

```sh
agentstack add mcp-soumyasethy-lazychat-erpnext
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# lazychat-erpnext

**Talk to ERPNext like a senior consultant.**

95 permission-scoped tools · two-phase mutations · composer-critic verification · BYO LLM

    

↑ 78-second flagship walkthrough — stakeholder ask → tool dispatch → report URL → BYO LLM in one shot. (Want HD? ▶ download the MP4.)

↑ Still hero — the post-dispatch state of the panel, mid-conversation.

## From stakeholder request to delivered report — in minutes

> *"Hey, I need to verify which December purchase invoices have payment entries against them. Can we get a report by EOD?"*

That ask used to mean opening 3 Frappe doctypes, writing a custom Query Report, debugging joins, fixing field names, and probably a meeting. With lazychat, the consultant types the stakeholder's words verbatim into the chat panel.

  
    
      Step 1 — Type the stakeholder's request
      The consultant pastes the verbatim ask into the chat composer.
      
      
    
    
      Step 2 — Lazychat dispatches tools, returns data
      describe_doctype → run_sql_select → inline result table. Real data. No copy-pasting from /api/method.
      
      
    
  
  
    
      Step 3 — Stage the report, click Apply
      prepare_create_report validates the SQL via execute-probe, shows sample rows, the critic LLM grades it. One click commits.
      
      
    
    
      Step 4 — Share the URL with the stakeholder
      Copy the report URL. Done. Time elapsed: ~2 minutes.
      
      The same flow works for cross-doctype reconciliations, variance reports, ad-hoc audits, and bulk operations. 95 permission-scoped tools back the chat — see the catalog below for what each can do.
    
  

---

## Bring your own LLM in 30 seconds

Self-hosted LM Studio? Anthropic? NVIDIA NIM? OpenRouter? Together? Groq? You don't fill out a form. You **paste a `curl` snippet from the provider's own docs** — and lazychat parses it into endpoint, model, auth, headers, streaming flag, even provider-specific payload extras. **The API key stays in the browser** (browser-LLM path); no server-side credential storage, no shared org-key risk.

### The 4-step flow

  
    
      
      1. Open the model pickerFrom the chat composer, click the model chip (bottom-left) → + Add custom model.
    
    
      
      2. Paste any provider's curl into the right-hand panelEndpoint, model, auth, format, streaming, max_tokens, temperature, top_p, extra headers, extra payload — all auto-fill on the left.
    
  
  
    
      
      3. Click Test connection → green ✓ → Add modelTest connection makes a 1-token probe request, shows the HTTP status + first 800 chars of the response. Green check = ready to ship.
    
    
      
      4. Switch instantly, per sessionCustom models appear under a CUSTOM section in the picker. Click to switch — the chat composer's model chip updates immediately. Mix free / paid / local on different chats.
    
  

### Tested provider snippets (paste these verbatim)

NVIDIA NIM — auto-detects OpenAI format, picks up chat_template_kwargs for thinking-mode

```bash
curl -X POST "https://integrate.api.nvidia.com/v1/chat/completions" \
  -H "Authorization: Bearer nvapi-..." \
  -H "Accept: text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "moonshotai/kimi-k2.6",
    "messages": [{"role":"user","content":""}],
    "max_tokens": 16384,
    "temperature": 1.0,
    "stream": true,
    "chat_template_kwargs": {"thinking": true}
  }'
```

Auto-fills label `Kimi K2 (NVIDIA)`, endpoint, format=`openai`, streaming=✓, bearer token, max_tokens, temperature, plus `chat_template_kwargs` lands in the **Extra payload** field verbatim.

Anthropic (direct, no Frappe LLM Provider needed) — auto-detects messages format + x-api-key auth

```bash
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: sk-ant-..." \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-haiku-4-5",
    "max_tokens": 4096,
    "stream": true,
    "messages": [{"role":"user","content":""}]
  }'
```

Auth type → `API Key`, header name → `x-api-key`, format → `anthropic`, response parser → `anthropic-sse`. The custom `anthropic-version` header lands in **Extra headers**.

OpenAI

```bash
curl https://api.openai.com/v1/chat/completions \
  -H "Authorization: Bearer sk-..." \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-5","messages":[{"role":"user","content":""}],"stream":true}'
```

OpenRouter — gateway to 200+ models

```bash
curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer sk-or-..." \
  -H "HTTP-Referer: https://your-site.example.com" \
  -H "X-Title: Lazychat" \
  -d '{"model":"meta-llama/llama-3.3-70b-instruct","stream":true}'
```

The `HTTP-Referer` and `X-Title` extras are required by OpenRouter and land in **Extra headers**.

LM Studio (localhost) — fully local, no API key

```bash
curl http://localhost:1234/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"meta-llama-3-8b-instruct","stream":true}'
```

Auth type → `none`, format → `openai`, streaming → ✓. Browser-LLM path makes localhost work because requests originate from the user's browser, not the Frappe server.

### What the parser auto-fills from the curl

| Curl part | Form field |
|---|---|
| `https://api.anthropic.com/v1/messages` | URL ends `/messages` → **Format** = `anthropic` |
| `https://*/v1/chat/completions` | URL ends `/chat/completions` → **Format** = `openai` |
| `-H "Authorization: Bearer XYZ"` | **Auth type** = Bearer; **Token** = `XYZ` |
| `-H "x-api-key: XYZ"` | **Auth type** = API Key; **Header name** = `x-api-key`; **Token** = `XYZ` |
| `-H "Accept: text/event-stream"` | **Streaming** = ✓ |
| Any other `-H "X: Y"` (e.g. `anthropic-version`, `HTTP-Referer`) | **Extra headers** (kv list) |
| Payload `"model":"X"` | **Model** = `X` |
| Payload `"max_tokens":N` / `"temperature":N` / `"top_p":N` | **Defaults** block |
| Payload `"stream":true` | **Streaming** = ✓ |
| Any payload key not in the above | **Extra payload** (preserved as JSON, merged into request body verbatim) |
| Hostname (`api.anthropic.com`) | **Label** auto-generated as `Anthropic Direct`-style title |

> **Test connection** sends one `messages: [{"role":"user","content":""}]` request to the configured endpoint with the parsed auth + extras. Returns the HTTP status + first 800 chars of the response body in a green/red panel — instant proof the credential and endpoint work before you start using the model in real chats.

> Same auto-fill works for `requests.post(...)` snippets too — paste a Python snippet from a provider's quickstart and it parses identically.

---

## Charts and dashboards rendered in-chat

Tools like `make_chart`, `dashboard_chart_data`, and `number_card_value` don't return raw JSON to the user — the chat-ui's `ChartBlock` renders them inline as proper Vega charts and KPI cards, in the panel, alongside the conversation. The consultant asks *"chart of paid PIs by month"* and the chart appears as the answer. No tab-switching, no exporting to a BI tool, no Frappe Dashboard form-filling.

  
    
      
    
    
      
    
  
  
    
      
    
    
      
    
  

> Bar / line / area / donut / scatter all map to Frappe Charts via the `make_chart` tool. Multi-chart dashboards (with cards + charts grouped) ship via `prepare_create_dashboard` — you ask, lazychat stages, you click Apply, the dashboard appears at `/app/dashboard-view/` ready to bookmark.

---

## How tool results render in the chat

Tool calls don't return raw JSON dumps to the user. Lazychat's response renderer formats the LLM's reply using the full markdown stack — GitHub-flavored tables, syntax-highlighted code with [shiki](https://shiki.style/), [mermaid](https://mermaid.js.org/) diagrams, KaTeX math, blockquote callouts, structured headings — so a "tool result" reads like an answer, not a debug log.

  
    
      Markdown tables — currency-aligned columns + status badges
      The LLM emits a plain markdown table; the renderer auto-aligns numerics, bolds important columns, and inlines emoji status pills (🟢 OK / 🟠 Watch / 🔴 Overdue).
      
      
    
    
      Code blocks — full shiki highlighting + Copy button
      SQL / Python / TypeScript / JSON all light up with the same theme tokens as the surrounding panel. Language tag in the corner, Copy button inset, inline code spans (`like this`) keep the same palette.
      
      
    
  
  
    
      Mermaid diagrams — relationships + flows in-chat
      The LLM ships a ```mermaid fence; the renderer lazy-loads mermaid, applies the panel's brand-orange theme, and you get a real ER diagram or flowchart inline. Better than 200 words of "see, the join is..."
      
      
    
    
      Structured prose — headings, lists, blockquote callouts
      For analytical answers the LLM uses real document structure: H3 sub-headings, ordered lists with bold labels, italic emphasis, blockquote callouts for the recommended next action. The renderer applies the typographic hierarchy.
      
      
    
  

> Plus: KaTeX math (for reconciliation formulas), JSON tree expanders for raw tool outputs, automatic link-card previews for any URL the LLM cites, attachment renders for `extract_file_content` PDF/DOCX/CSV outputs, and a streaming markdown parser that keeps tables and code blocks structurally valid mid-token. The tool gives the data; the renderer makes it readable.

---

## Install

**Frappe Cloud:** install from the marketplace in one click.

**Self-hosted bench:** see **[INSTALL.md](INSTALL.md)** — the standard `bench` install (the pre-built chat-ui ships with the app, so no build step needed), building from source, and HMR dev are all documented there.

After install, the app seeds disabled-by-default LLM Provider rows (OpenAI, Anthropic, NVIDIA, OpenRouter, Vercel AI, LM Studio) — enable one and add your API key from `/app/llm-provider`, or skip server-side config entirely and let users bring their own keys via the chat-ui's model picker (browser-LLM path).

---

## Why lazychat-erpnext

- **Built for ERPNext, not bolted on.** Every tool runs as `frappe.session.user` — Frappe permissions, role checks, workflow guards, and the audit trail apply automatically. No god-mode bypass; no separate auth surface.
- **Mutations require explicit Apply.** The LLM stages every write to a Redis token; you click Apply (or the 3-second auto-Apply countdown for low-risk actions) to commit inside `frappe.db.savepoint`. A 30-second composer-critic LLM second-opinion shows up as an amber strip when it disagrees with the staged action.
- **Bring any model.** Anthropic Claude, OpenAI, NVIDIA NIM, OpenRouter, Vercel AI Gateway, Together, Groq, LM Studio. Same 95-tool registry; the API key never has to leave the browser if you don't want it to.

---

## What you get

  
    
      
      Live tool dispatchWatch the agent fetch real data with elapsed timers and inline result tables. No copy-pasting from /api/method.
    
    
      
      Mutations always Apply-gatedThe LLM stages writes to a Redis token; you click Apply. Audit-safe by default.
    
  
  
    
      
      Critic catches misalignmentA second LLM grades every staged action; mismatches show as an amber warning. You still get to decide.
    
    
      
      Plan modeFor multi-step tasks: emit a numbered plan first, you Approve, then it executes the steps in order.
    
  

Built-in: schema-aware SQL retry on `Unknown column`, two chat paths (server-orchestrated or browser-LLM), real-execution probe before staging Query Reports, structured form prefill for HTTP-414-defying URLs, knowledge bases with reindex, scheduled jobs, dashboards, custom fields, client scripts, and an admin panel that moves all configuration into the chat-ui itself.

### Token + cost tracking NEW

Every LLM turn writes a row to the `Lazychat Usage Log` doctype — model · provider · input/output tokens · cost estimate (USD) · session id · path (backend / browser). Cost auto-computed from `LLM Model.input_price_per_mtok` / `output_price_per_mtok` first, falls back to a built-in rate table for common Claude / GPT models, zero when both miss.

Open the chat-ui's **Command Palette → Account & usage…** to see:

- 4-tile totals (calls / input / output / cost)
- **By-model rollup** — sorted by total tokens, shows provider + cost per model
- **Daily breakdown** for the last 14 days
- Range picker (Today / 7d / 30d / 90d)
- System Manager users see ALL users' usage; everyone else sees their own

No setup required — works for Claude / OpenAI / NVIDIA / OpenRouter / Vercel / Anthropic / Together / Groq / LM Studio, regardless of whether you're on the backend-LLM or browser-LLM path. Set per-model rates on `/app/llm-model/` if you want exact billing.

---

## Architecture

  

The Frappe app ships a 280-line vanilla-JS shim ([`public/js/lazychat_panel.bundle.js`](lazychat_erpnext/public/js/lazychat_panel.bundle.js)) loaded via `app_include_js` on every Desk page. The shim mounts the chat-ui (a React app, sibling repo [lazychat.ai](https://github.com/soumyasethy/lazychat.ai), bundled into `public/lazychat_dist/`) as a same-origin iframe, sets up the postMessage protocol, and intercepts `/commit ` slash commands to call the server.

Tool dispatch goes through one of two paths, both backed by the same 95-tool registry:

| Path | LLM lives | Tool dispatch | Best when |
|---|---|---|---|
| **Backend-LLM** | Frappe (LLM Provider doctype) | `run_agentic_turn` calls `execute_tool` in-process | Org deployments, shared keys, central audit |
| **Browser-LLM** | chat-ui (BYO key in localStorage) | chat-ui calls `mcp.handle` JSONRPC per tool_use | Single-user / power-user; key never touches server |

Default `chat_path = auto`: chat-ui inspects the active model — built-in → backend; custom-added → browser. Both paths run with `frappe.session.user`'s permissions, both write to `Claude Conversation`, both share `tools.py`. **Zero drift, one implementation.**

---

## Configuration

  
  
  Server Config dialog inside the chat panel. Open via Command Palette → Server config. System Manager only.

**Primary admin surface (in-app):** Command Palette → **Server config** → 3 tabs:

- **General** — gates (`allow_email`, `allow_dangerous_tools`, `cycle9_enabled` …), `chat_path` radio, `iframe_base_url`, `llm_proxy_allowed_hosts` chip list.
- **LLM Providers** — Anthropic / OpenAI-compatible CRUD with Test Connection + Reveal API Key.
- **LLM Models** — model picker contents; `is_default` toggle (single-default invariant enforced).

**Or** edit the [`Lazychat Settings`](http://localhost:8000/app/lazychat-settings) doctype directly. All defaults are **allow-on** for self-hosted single-org installs; defense-in-depth is preserved (System Manager role check at tool dispatch + `/commit` confirmation per call).

| Field | Default | What it does |
|---|---|---|
| `enabled` | `true` | Master switch — mount the panel at all |
| `iframe_base_url` | `/assets/lazychat_erpnext/lazychat_dist/index.html` | Where chat-ui loads from. Override for HMR (`http://127.0.0.1:5173`) or remote chat-ui |
| `iframe_query_params` | `?frame=sidebar` | Appended to base_url |
| `chat_path` | `auto` | `auto` / `browser` / `backend` — see Architecture above |
| `mcp_endpoint` | `/api/method/lazychat_erpnext.desk_assistant.mcp.handle` | Read-only; browser-LLM path uses this |
| `legacy_widget_enabled` | `false` | Mount the OLD vanilla-JS widget INSTEAD of the iframe (mutually exclusive) |
| `allow_email` | `true` | Enable `prepare_send_email` |
| `allow_dangerous_tools` | `true` | Enable `prepare_run_sql` + `prepare_run_python` (still gated by System Manager role + `/commit`) |
| `allow_email_setup` | `true` | Enable `prepare_create_email_account` |
| `cycle9_enabled` | `true` | Enable composer-critic verdict, verification briefs, exemplar memory |
| `bulk_update_max_rows` | `500` | Ceiling for `prepare_bulk_update` blast radius |

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [soumyasethy](https://github.com/soumyasethy)
- **Source:** [soumyasethy/lazychat-erpnext](https://github.com/soumyasethy/lazychat-erpnext)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-soumyasethy-lazychat-erpnext
- Seller: https://agentstack.voostack.com/s/soumyasethy
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
