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

Adeesh SharmaAdeesh Sharma
5 min read

๐Ÿ› ๏ธ 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 query

  • Passes 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 QueryActionFinal Response
Add item "iPhone" with price 1000Inserts into MongoDB"Successfully added item: iPhone"
Tell me the total priceAggregates 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 and add_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
0
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.