← All articles
MCPAI AgentsSaaS Development

Build Your First MCP Server: Connect AI Agents to Your App

Step-by-step guide to building an MCP server that lets AI agents securely query your SaaS data and take real actions.

Build Your First MCP Server: Connect AI Agents to Your App

The Model Context Protocol (MCP) is an open standard that lets AI agents securely access external data and tools. Think of it as USB-C for AI — a standardized connection between large language models and the systems they need to interact with.

When you build an AI-powered feature, the agent often needs more than just the prompt you send it. It needs to look up a customer record, query a database, or take action in your application. MCP defines how that connection works: the server exposes tools and resources, and the AI client (like Claude) discovers and calls them.

Anthropic released MCP in late 2024, and Claude's desktop client already supports MCP servers. Other AI providers are adopting it as well. The protocol is open and transport-agnostic, so you're not locked into one model provider.

Why Build an MCP Server for Your SaaS Product

If your product has an AI component, you'll eventually hit a wall: the model knows what you tell it in the prompt, but it can't reach your real data. You can stuff more context into prompts, but that gets expensive and unreliable. MCP solves this by letting the agent fetch only what it needs, when it needs it.

For SaaS products, this matters in concrete ways:

  • Customer support agents can pull real order histories instead of guessing
  • Data analysis agents can run actual database queries instead of working from summaries
  • Workflow automation can check current system state before taking action

Without MCP, you're building bespoke integrations for every AI provider or embedding sensitive data in prompts. With MCP, you write the server once and any compatible AI client can use it.

Setting Up Your Development Environment

You'll need Node.js (v18 or later) and a code editor. MCP servers can be built in TypeScript, Python, or other languages, but the TypeScript SDK is the most mature at this point.

Start by creating a new project:

mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk

Add TypeScript:

npm install -D typescript @types/node
npx tsc --init

In your tsconfig.json, set the target to ES2022 and module to Node16. Those are safe defaults for current Node versions.

Create your entry point at src/index.ts. This is where your MCP server will live.

Defining Your MCP Server's Tools

An MCP server exposes capabilities through tools and resources. Tools are functions the AI can call. Resources are data the AI can read. Start with tools — they're what most applications need.

Here's a minimal MCP server that exposes a tool:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "my-saas-server",
  version: "0.1.0",
});

server.tool(
  "get-customer",
  "Retrieve a customer by their ID",
  { customerId: z.string().describe("The customer's unique ID") },
  async ({ customerId }) => {
    const customer = await fetchCustomer(customerId);
    return {
      content: [{ type: "text", text: JSON.stringify(customer, null, 2) }],
    };
  }
);

The z.string().describe(...) pattern matters — the description is what the AI reads to understand when and how to use your tool. Write clear, specific descriptions. "The customer's unique ID" is better than just "customerId".

Implementing Data Access Patterns

Your MCP server is a bridge between AI and your data. The server itself shouldn't be your data layer — it should call your existing APIs or database layer.

This separation matters for two reasons:

  1. You probably already have auth, caching, and validation in your API layer
  2. You don't want to duplicate business logic

Here's how to wire up real data access:

async function fetchCustomer(customerId: string) {
  const response = await fetch(
    `https://api.yoursaas.com/v1/customers/${customerId}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.SAAS_API_KEY}`,
        "Content-Type": "application/json",
      },
    }
  );

  if (!response.ok) {
    throw new Error(`Failed to fetch customer: ${response.status}`);
  }

  return response.json();
}

For sensitive data, scope your API tokens. Create a service account with read-only access to the data your AI agent needs — not full admin credentials.

If you're querying a database directly instead of going through an API, use parameterized queries. Never concatenate user input into SQL, even if the "user" is an AI:

async function queryOrders(customerId: string, limit: number) {
  const result = await db.query(
    "SELECT id, total, status FROM orders WHERE customer_id = $1 LIMIT $2",
    [customerId, limit]
  );
  return result.rows;
}

Registering Resources for Read-Only Data

Tools are for actions. Resources are for querying structured data the AI might need to reference. Think of resources as the AI's reference library — static or semi-static information that provides context.

server.resource(
  "product-catalog",
  "saas://products/catalog",
  async () => {
    const products = await fetchProductCatalog();
    return {
      contents: [
        {
          uri: "saas://products/catalog",
          mimeType: "application/json",
          text: JSON.stringify(products),
        },
      ],
    };
  }
);

Resources use URIs for identification. Pick a scheme that makes sense for your domain (like saas:// above). The AI client can list available resources and read them when it needs background context.

There's an important distinction: if the AI needs to actively look something up with parameters, use a tool. If it's reference data the AI should know exists, use a resource.

Connecting Your MCP Server to Claude

The Claude desktop app supports MCP servers natively. To connect yours, configure it in Claude's settings.

Edit claude_desktop_config.json (location varies by OS — on macOS it's ~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "my-saas-server": {
      "command": "node",
      "args": ["/absolute/path/to/my-mcp-server/dist/index.js"],
      "env": {
        "SAAS_API_KEY": "your-api-key-here"
      }
    }
  }
}

Use absolute paths for the command arguments. Relative paths can cause issues depending on where Claude runs the process.

After updating the config, restart the Claude desktop app. You should see your tools listed when you click the tools button (the hammer icon).

If the server doesn't appear, check the Claude logs. On macOS, they're at ~/Library/Logs/Claude/. The logs will tell you if there's a startup error or connection issue.

For programmatic access (like running Claude in your own backend), initialize the MCP client in your code:

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "node",
  args: ["/path/to/my-mcp-server/dist/index.js"],
});

const client = new Client({
  name: "my-saas-client",
  version: "0.1.0",
});

await client.connect(transport);

This approach lets you run your own AI pipeline that uses MCP, independent of the Claude desktop app.

Testing and Debugging Your Server

Before connecting to a live AI client, test your server directly. MCP provides an inspector tool for this exact purpose:

npx @modelcontextprotocol/inspector node dist/index.js

This opens a web interface where you can:

  • List your server's tools and resources
  • Call tools with custom parameters
  • See the raw JSON responses

Test your tools with edge cases: empty inputs, IDs that don't exist, and malformed data. The AI will try all of these eventually.

Add logging to your server for production debugging. MCP servers communicate over stdout, so your logs need to go to stderr. If you write logs to stdout, you'll break the protocol:

function log(message: string) {
  process.stderr.write(`[my-saas-server] ${message}\n`);
}

Use this function in your tool handlers to trace execution without interfering with MCP communication.

Deployment Considerations

For local development, the stdio transport (running as a subprocess) works well. For production, you'll likely want a remote server accessible over HTTP.

MCP supports a Streamable HTTP transport. Set it up like this:

import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamable-http.js";
import express from "express";

const app = express();
app.use(express.json());

const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: undefined,
});

await server.connect(transport);

app.post("/mcp", (req, res) => {
  transport.handleRequest(req, res);
});

app.listen(3000);

For production deployments, address these concerns:

  • Authentication: Add API key validation before processing MCP requests. The MCP spec doesn't define auth — that's your responsibility.
  • Rate limiting: AI agents can be chatty. Set per-client limits.
  • CORS: If your MCP server is called from a browser-based client, configure CORS headers.
  • Monitoring: Track tool call frequency, error rates, and latency. This tells you which tools the AI actually uses and where bottlenecks are.

One architectural note: your MCP server should be stateless. Each request should carry everything needed to execute. If you need session context, pass it as a parameter or store it somewhere the tool can retrieve it independently.

What to Build Next

You now have a working MCP server that exposes tools and resources. Pick one real piece of functionality from your product — a customer lookup, a data query, a status check — and implement it as an MCP tool. Don't try to expose your entire API at once. Start with the highest-value action, test it with Claude, and iterate.

From there, look at the MCP specification for advanced patterns: prompt templates (pre-built prompts the AI can use), sampling (letting your server ask the AI questions mid-tool-execution), and composable servers. But those come later. Ship one tool first.

The MCP spec and SDK are on GitHub under modelcontextprotocol. The TypeScript SDK lives at github.com/modelcontextprotocol/typescript-sdk. If you run into issues, the community Discord is active and responsive.