Build a Server-Side AI-Agentic API for Web Apps
Move beyond prompts. Turn natural language into real database actions with a simple agentic API.

๐ ๏ธ 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 /chatrequest 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_itemsandadd_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






