Build a Server-Side AI-Agentic API for Web Apps

๐ ๏ธ Build a Server-Side Agentic API for Web Apps
Introduction
Imagine being able to send a simple text query like "Add an item to the database" โ and having your app understand it, act on it, and respond back intelligently.
In this tutorial, you'll learn how to build a server-side agentic API using:
Anthropic SDK to process natural language
Tool calling to trigger real MongoDB actions
Express server to expose endpoints for your web apps
โจ What Are We Building?
We'll create a lightweight server that:
Accepts a
POST /chat
request with a user queryPasses the query and available tools to Anthropicโs LLM
Lets the LLM choose a tool and auto-handle database operations
Sends user-friendly final responses back to the client
Example:
User Query | Action | Final Response |
Add item "iPhone" with price 1000 | Inserts into MongoDB | "Successfully added item: iPhone" |
Tell me the total price | Aggregates items in DB | "Total price is 1000" |
๐๏ธ Project Setup
1. Install Dependencies
npm init -y
npm install express cors dotenv @anthropic-ai/sdk mongodb
2. Create .env
file
MONGODB_URI=your_mongodb_connection_string
ANTHROPIC_API_KEY=your_anthropic_api_key
๐ฎฉ Code Walkthrough
๐ index.ts
โ Setting up the Express Server
import express from 'express';
import type { RequestHandler } from 'express';
import cors from 'cors';
import MCPClient from './MCPClient';
async function main() {
const app = express();
const port = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
const mcpClient = new MCPClient();
app.get('/health', (req, res) => {
res.json({
status: 'ok',
tools: mcpClient.tools.map((t) => t.name),
});
});
app.post('/chat', (async (req, res) => {
try {
const { query } = req.body;
if (!query) {
return res.status(400).json({ error: 'Query is required' });
}
const response = await mcpClient.processQuery(query);
res.json({ response });
} catch (error) {
console.error('Error processing query:', error);
res.status(500).json({ error: 'Failed to process query' });
}
}) as RequestHandler);
app.listen(port, () => {
console.log(`Server running on port ${port}`);
console.log(`Health check: http://localhost:${port}/health`);
console.log(`Chat endpoint: http://localhost:${port}/chat`);
});
}
main();
Explanation:
/health
: Checks server status + lists tool names./chat
: Accepts a query, processes it using the AI agent, and returns the AI's final response.
๐ MCPClient.ts
โ Anthropic Agent & Tool Orchestration
import { Anthropic } from '@anthropic-ai/sdk';
import { MessageParam, Tool } from '@anthropic-ai/sdk/resources/messages/messages.mjs';
import { tools as importedTools } from './tools';
interface CustomTool extends Tool {
run: (args: any) => Promise<{ content: string }>;
}
class MCPClient {
private llm: Anthropic;
public tools: CustomTool[] = importedTools as CustomTool[];
constructor() {
this.llm = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
}
async processQuery(query: string) {
const messages: MessageParam[] = [
{ role: 'user', content: query },
];
const response = await this.llm.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1000,
messages,
tools: this.tools,
});
const finalText = [];
for (const content of response.content) {
if (content.type === 'text') {
finalText.push(content.text);
} else if (content.type === 'tool_use') {
const toolName = content.name;
const toolArgs = content.input;
const tool = this.tools.find((t) => t.name === toolName && typeof t.run === 'function');
if (!tool) {
console.error(`[Tool Error] Tool "${toolName}" not found`);
continue;
}
const result = await tool.run(toolArgs);
messages.push({
role: 'user',
content: result.content,
});
const followUp = await this.llm.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1000,
messages,
});
if (followUp.content[0]?.type === 'text') {
finalText.push(followUp.content[0].text);
}
}
}
return finalText.join('\n');
}
}
export default MCPClient;
Explanation:
Sends the query and available tools to Anthropic.
Executes the tool if the LLM decides to use one.
Sends the tool output back to LLM for final human-readable text.
Returns the final output to the web app.
๐ tools.ts
โ MongoDB Tool Definitions
import { MongoClient } from 'mongodb';
const uri = process.env.MONGODB_URI;
if (!uri) throw new Error('MONGODB_URI environment variable is not set');
const dbName = 'mcptool';
const collectionName = 'items';
const validateMongoDBUri = (uri: string) => {
if (!uri.startsWith('mongodb://') && !uri.startsWith('mongodb+srv://')) {
throw new Error('Invalid MongoDB URI format.');
}
return uri;
};
export const tools = [
{
name: 'get_items',
description: 'Fetch all items from the MongoDB collection',
input_schema: {
type: 'object',
properties: {},
},
run: async () => {
const client = new MongoClient(validateMongoDBUri(uri));
try {
await client.connect();
const collection = client.db(dbName).collection(collectionName);
const items = await collection.find().toArray();
return { content: JSON.stringify(items, null, 2) };
} catch (error) {
console.error('[MongoDB Error - get_items]:', error);
return { content: 'Failed to fetch items.' };
} finally {
await client.close();
}
},
},
{
name: 'add_item',
description: 'Add a new item to the MongoDB collection',
input_schema: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
price: { type: 'number' },
},
required: ['id', 'name', 'price'],
},
run: async ({ id, name, price }: { id: string; name: string; price: number }) => {
const client = new MongoClient(validateMongoDBUri(uri));
try {
await client.connect();
const collection = client.db(dbName).collection(collectionName);
const existingItem = await collection.findOne({ id });
if (existingItem) {
return { content: `Item with ID ${id} already exists.` };
}
const newItem = { id, name, price, createdAt: new Date() };
await collection.insertOne(newItem);
return { content: `Successfully added item: ${name}` };
} catch (error) {
console.error('[MongoDB Error - add_item]:', error);
return { content: 'Failed to add item.' };
} finally {
await client.close();
}
},
},
];
Explanation:
Defines MongoDB tools
get_items
andadd_item
.Handles database connection validation and execution per call.
๐ Final Demo Example
Request
POST /chat
{
"query": "Add an item called MacBook Pro with a price of 2500"
}
Response
{
"response": "Successfully added item: MacBook Pro"
}
๐ Final Thoughts
With just a few simple tools and a smart LLM, we created an agentic server API that can:
Understand user intent
Execute real-world database operations
Reply intelligently with user-friendly messages
This is just the beginning โ you can easily extend this by:
Adding more tools (update, delete, search)
Handling chained tasks (multi-turn actions)
Integrating authentication
Deploying it online (Render, Railway, AWS)
The future of intelligent backend development is here โ and itโs agentic.
๐ฆ Full Folder Structure
/your-app
โโโ MCPClient.ts
โโโ tools.ts
โโโ index.ts
โโโ .env
โโโ package.json
Subscribe to my newsletter
Read articles from Adeesh Sharma directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Adeesh Sharma
Adeesh Sharma
Adeesh is an Associate Architect in Software Development and a post graduate from BITS PILANI, with a B.E. in Computer Science from Osmania University. Adeesh is passionate about web and software development and strive to contribute to technical product growth and decentralized communities. Adeesh is a strategic, diligent, and disciplined individual with a strong work ethic and focus on maintainable and scalable software.