A minimal AI agent in TypeScript that connects to the upi-mcp
MCP server and uses an LLM to call its tools — turning a plain-English request into a real
UPI payment link or QR code.
Built from scratch (no agent framework) to show the core of how agents actually work: the agent loop + an MCP client. The AI decides which tools to use — you don't tell it.
You give it a request in plain English, e.g.:
"Make a UPI link to pay rahul@oksbi ₹1500"
…and the agent figures out the rest on its own — picking the right tool, calling it, and replying with the result.
1. Send the conversation + available tools to the LLM
2. LLM replies → tool request OR final answer
3. If a tool: run it via the MCP server, feed the result back
4. Repeat until the LLM gives a final answer
The tools live in a separate MCP server (upi-mcp); this agent is the MCP client that launches
it, lists its tools, and runs them when the LLM asks.
- TypeScript (strict)
@modelcontextprotocol/sdk— MCP client (connects toupi-mcp)openaiSDK pointed at Groq (free, OpenAI-compatible API)dotenv— loads the API key
- Node.js 18+
- A free Groq API key — get one at
console.groq.com(no credit card) - The
upi-mcpserver — pulled from npm automatically, or built locally
git clone https://github.com/<your-username>/upi-agent.git
cd upi-agent
npm installCreate a .env file with your key:
GROQ_API_KEY=your_groq_key_here
⚠️ .envis gitignored — never commit your key.
Build:
npm run buildnode dist/index.jsWatch the agent decide which tool to call, run it via upi-mcp, and return the link/QR.
- The request: edit the
messagesarray insrc/index.ts. - The model:
llama-3.3-70b-versatile(any Groq tool-capable model works). - Where the MCP server comes from — two options in the
StdioClientTransport:- From npm (portable, works anywhere):
command: "npx", args: ["-y", "upi-mcp"] - From a local build:
command: "node", args: ["/path/to/upi-mcp/dist/index.js"]
- From npm (portable, works anywhere):
Open-weight models occasionally malform a tool call (Groq returns tool_use_failed). This is
LLM non-determinism, not a code bug. Mitigations used / recommended:
temperature: 0for more deterministic output- a retry around the LLM call (production agents should retry, since no model is 100%)
- Interactive mode — currently runs one hardcoded request
- Retry wrapper baked in
MIT