# Building an Agentic Web App: Connecting MCP Server and MCP Client over HTTP

In this guide, we'll walk through how to move from a **local-only** Model Context Protocol (MCP) setup (where MCP Client and Server talk over stdio pipes) to a **networked** version where they communicate over **HTTP/HTTPS**. This allows you to deploy your MCP Server and Client independently across the internet.

We'll cover:

* Setting up an MCP Server that listens over HTTP
    
* Updating the MCP Client to connect over HTTP
    
* Structuring tools like MongoDB item fetch and insert
    
* Making it production-ready
    

## **Why Shift to HTTP?**

When MCP Client and MCP Server talk over `StdioClientTransport`, they can only work locally. To support cloud deployments and make your application scalable, you must:

* Host MCP Server independently.
    
* Connect to it over HTTP (or HTTPS in production).
    

This allows your Express app (MCP Client) to orchestrate conversations with Anthropic Claude while dynamically calling tools deployed anywhere!

---

# **Understanding the Setup**

In our setup:

* **MCP Server**: Hosts tools like `get_items` and `add_item` that interact with a MongoDB database. It listens over an HTTP server and can be deployed independently.
    
* **MCP Client**: An Express app that serves endpoints for interacting with Anthropic Claude. It connects to the MCP Server over HTTP, dynamically listing and calling tools when needed.
    
* **Anthropic Claude**: Acts as the reasoning engine that decides whether a tool should be called based on user prompts.
    
* **MongoDB**: Serves as the backend database where our tools perform CRUD operations.
    

This architecture makes your agentic app flexible, modular, and cloud-ready.

Key Components:

* **Tools**: Small, focused server-side functions like `get_items`, `add_item`.
    
* **HTTP Communication**: MCP Client talks to MCP Server over a simple HTTP POST-based protocol.
    
* **Deployment Ready**: MCP Server and Client can scale independently.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1745700628474/009eb584-9705-4d79-8e64-05b8f0bdc955.png align="center")

---

# **1\. Setting up MCP Server**

**server.ts**

```javascript
// server.ts

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { MongoClient } from "mongodb";
import http from "http";
import dotenv from "dotenv";

dotenv.config();

// MongoDB Connection Setup
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;
};

// Define Tools
const tools = [
  {
    name: 'get_items',
    description: 'Fetch all items from MongoDB',
    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 MongoDB',
    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();
      }
    },
  },
];

// Create MCP Server
const mcpServer = new Server({
  name: "mongo-tools-server",
  version: "1.0.0",
});

// Register tools
for (const tool of tools) {
  mcpServer.registerTool({
    name: tool.name,
    description: tool.description,
    inputSchema: tool.input_schema,
    run: tool.run,
  });
}

// Start HTTP server
const httpServer = http.createServer();
mcpServer.listenHttpServer(httpServer);

const MCP_SERVER_PORT = process.env.MCP_SERVER_PORT || 4000;
httpServer.listen(MCP_SERVER_PORT, () => {
  console.log(`🚀 MCP Server listening on http://localhost:${MCP_SERVER_PORT}`);
});

```

---

# **2\. Setting up MCP Client (Express App)**

**index.ts**

```javascript
// index.ts

import { Anthropic } from "@anthropic-ai/sdk";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { HTTPClientTransport } from "@modelcontextprotocol/sdk/client/http.js"; // <-- Change here
import express from "express";
import type { RequestHandler } from "express";
import cors from "cors";
import dotenv from "dotenv";

dotenv.config();

// Validate keys
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
const MCP_SERVER_URL = process.env.MCP_SERVER_URL; // Example: http://localhost:4000/mcp
if (!ANTHROPIC_API_KEY) throw new Error('ANTHROPIC_API_KEY not set');
if (!MCP_SERVER_URL) throw new Error('MCP_SERVER_URL not set');

class MCPClient {
  private mcp: Client;
  private llm: Anthropic;
  private transport: HTTPClientTransport | null = null;
  public tools: any[] = [];

  constructor() {
    this.llm = new Anthropic({ apiKey: ANTHROPIC_API_KEY });
    this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
  }

  async connectToServer() {
    try {
      this.transport = new HTTPClientTransport({
        url: MCP_SERVER_URL, // <-- Now URL
      });
      await this.mcp.connect(this.transport);

      const toolsResult = await this.mcp.listTools();
      this.tools = toolsResult.tools.map((tool) => ({
        name: tool.name,
        description: tool.description,
        input_schema: tool.inputSchema,
      }));

      console.log("✅ Connected to MCP Server at", MCP_SERVER_URL);
      console.log("Available tools:", this.tools.map(t => t.name));
    } catch (e) {
      console.error('Failed to connect to MCP Server:', e);
      throw e;
    }
  }

  async processQuery(query: string) {
    const messages = [{ 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 result = await this.mcp.callTool({
          name: content.name,
          arguments: content.input as any,
        });
        finalText.push(`[Tool Output] ${JSON.stringify(result.content)}`);
      }
    }

    return finalText.join("\n");
  }

  async cleanup() {
    await this.mcp.close();
  }
}

// App Start
async function main() {
  const app = express();
  const port = process.env.PORT || 3000;

  const mcpClient = new MCPClient();

  try {
    await mcpClient.connectToServer();

    app.use(cors());
    app.use(express.json());

    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) {
          res.status(400).json({ error: 'Query is required' });
          return;
        }
        const response = await mcpClient.processQuery(query);
        res.json({ response });
      } catch (e) {
        console.error("Chat error:", e);
        res.status(500).json({ error: "Failed to process query" });
      }
    });

    app.listen(port, () => {
      console.log(`🚀 Express app running at http://localhost:${port}`);
    });

  } catch (e) {
    console.error('Startup error:', e);
    process.exit(1);
  }
}

main();

```

---

# **3\. Environment Variables (**`.env`)

```javascript
MONGODB_URI=mongodb://localhost:27017
MCP_SERVER_PORT=4000
MCP_SERVER_URL=http://localhost:4000/mcp
ANTHROPIC_API_KEY=your_real_anthropic_api_key_here
```

---

# **4\. How to Run**

1. Start the MCP Server:
    

```javascript
ts-node server.ts
```

2. Start the MCP Client (Express App):
    

```javascript
ts-node index.ts
```

3. Send a Chat Query:
    

```javascript
curl -X POST http://localhost:3000/chat \
  -H "Content-Type: application/json" \
  -d '{"query": "Fetch all items from the database"}'
```

---

# **Conclusion**

By shifting to **HTTPClientTransport** and **listenHttpServer()**, you made your MCP Server scalable, cloud-ready, and production-grade. Now your LLM applications can dynamically invoke powerful tools deployed independently!

This unlocks real **agentic architecture** for the web.
