Implementing MCP in JavaScript

Building AIโ€‘Native Systems with the Model Context Protocol

Victor Nitu

  • AI Engineer @ IMTF
  • AI Solutions for RegTech
  • AI Agents for Anti-Money Laundering

Agenda

Part 1: Introduction and Context

Part 2: Implementation in JavaScript

Part 3: Examples and Wrap-Up

Part 1: Introduction and Context

Where Do We Need MCP?

Building AI Agents with ReAct (Reasoning and Acting) framework:
Facilitates the construction of AIโ€‘Native Systems

  • Goal-Oriented: Perform Decision Making
  • Autonomous and Adaptive
  • Tools and Resources: Research (RAG) / Persistance / Actions / ...

Why Do We Need MCP?

Before MCP:
Custom connectors to every tool (APIs, Databases, AI Models, ...)

  • Fragmented Integrations: M ร— N problem
  • Siloed Models: Hard to scale & connect
  • Context Loss: Stateless APIs break conversation flow

After MCP:
One unified interface โ€“ like USB-C for AI apps

What is the Model Context Protocol (MCP)?

  • Open Protocol by Anthropic
  • "USB-C for AI" โ€“ one interface to many tools
  • Uses JSON-RPC 2.0 (like REST but for LLMs)
  • Standardizes how LLMs talk to tools & data

MCP Architecture & Concepts

  • LLM Host (Client) โ†” MCP Server (Tool/Data)
  • 3 Core Capabilities:
    • Tools: Invoke actions
    • Resources: Fetch data
    • Prompts: Provide usage templates
  • Transport: JSON-RPC over SSE / STDIO

Part 2: Implementation in JavaScript

Building an MCP Server in Node.js

๐ŸŽฏ Goal: MCP server with tools/resources
๐Ÿ“ฆ Stack: Node + Express + MCP SDK
๐Ÿ“ฅ Install:

npm install @modelcontextprotocol/sdk

1. MCP Server Setup

import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';

const app = express();
const server = new McpServer({ name: "example-server", version: "1.0.0" });
const transports: {[sessionId: string]: SSEServerTransport} = {};

app.listen(3001);

2. Handling Incoming Connections

app.get('/sse', async (_req, res) => {
  const transport = new SSEServerTransport('/messages', res);
  transports[transport.sessionId] = transport;
  res.on("close", () => {
    delete transports[transport.sessionId]
  });
  await server.connect(transport);
});

3. Handling Incoming Messages

app.post('/messages', async (req, res) => {
  const sessionId = req.query.sessionId;
  const transport = transports[sessionId];
  if (!transport) {
    res.status(400).send("No transport for session");
    return
  }
  await transport.handlePostMessage(req, res);
});

๐Ÿ”ฅ Your server is now MCP-compatible!

  • GET /sse โ†’ establish stream
  • POST /messages โ†’ handle tool calls
  • Responses sent back via SSE

Registering Resources

server.resource(
  "partner-profile",
  new ResourceTemplate("partners://{name}/profile"),
  async (uri, { name }) => ({
    contents: [{
      uri: uri.href,
      text: await partners.findProfile(name)
    }]
  })
);

๐Ÿ“˜ Resources = read-only context
URI pattern: partners://Bankless/profile โ†’ โ€œOur Partner Bankless is ...โ€

Registering Tools

server.tool(
  "compensate-partner",
  {
    name: z.string().describe("Partner's name"),
    amount: z.number().describe("Amount of tokens")
  },
  async ({ name, amount }) => {
    const receipt = await wallet.compensatePartner(name, amount);
    return ({
      content: [{
        type: "text",
        text: `Transaction successful! Hash: ${receipt.transactionHash}`
      }]
    })
  }
);

Best Practices

  • โœ… Use schemas (zod) to validate input
  • ๐Ÿ”€ Tools = actions, Resources = context
  • ๐Ÿงฑ Keep tools small & composable
  • ๐Ÿ”’ Secure sensitive APIs / data

Testing & Iterating

  • ๐Ÿงช Test locally: curl or test client
  • ๐Ÿงญ Use MCP Inspector for manual tool calls
  • ๐Ÿค– Hook up Claude Desktop or another client
  • ๐Ÿ›  Log all JSON-RPC traffic for debugging

Part 3: Examples and Wrap-Up

Questions?