# 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 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 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

```bash
npm init -y
npm install express cors dotenv @anthropic-ai/sdk mongodb
```

## 2\. Create `.env` file

```bash
MONGODB_URI=your_mongodb_connection_string
ANTHROPIC_API_KEY=your_anthropic_api_key
```

---

# 🮩 Code Walkthrough

## 📄 `index.ts` — Setting up the Express Server

```typescript
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

```typescript
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

```typescript
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

```json
POST /chat
{
  "query": "Add an item called MacBook Pro with a price of 2500"
}
```

### Response

```json
{
  "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

```javascript
/your-app
  ├── MCPClient.ts
  ├── tools.ts
  ├── index.ts
  ├── .env
  └── package.json
```
