Skip to main content
Complete guide for integrating mcp-ts with Next.js applications (App Router and Pages Router).

Step 1: Create API Route

Create an API route handler at app/api/mcp/route.ts:
import { createNextMcpHandler } from '@mcp-ts/sdk/server';

export const dynamic = 'force-dynamic';
export const runtime = 'nodejs';

export const { GET, POST } = createNextMcpHandler({
  // Extract identity from request
  getIdentity: (request) => {
    return new URL(request.url).searchParams.get('identity');
  },

  // Optional: Custom authentication
  authenticate: async (identity, token) => {
    // Verify token with your auth system
    return true; // or throw error if invalid
  },

  // Optional: Heartbeat interval
  heartbeatInterval: 30000, // 30 seconds
});

Step 2: Create Client Component

Create a component at components/McpConnections.tsx:
'use client';

import { useMcp } from '@mcp-ts/sdk/client';

export function McpConnections({ identity }: { identity: string }) {
  const {
    connections,
    status,
    connect,
    disconnect,
    callTool,
  } = useMcp({
    url: `/api/mcp?identity=${identity}`,
    identity,
    autoConnect: true,
  });

  const handleConnect = async () => {
    await connect({
      serverId: 'my-server',
      serverName: 'My MCP Server',
      serverUrl: 'https://mcp.example.com',
      callbackUrl: window.location.origin + '/api/mcp/callback',
    });
  };

  return (
    <div>
      <div>
        <h2>MCP Connections</h2>
        <p>Status: <strong>{status}</strong></p>
        <button onClick={handleConnect}>
          Connect to Server
        </button>
      </div>

      {connections.map((conn) => (
        <div key={conn.sessionId}>
          <h3>{conn.serverName}</h3>
          <p>State: {conn.state}</p>
          <p>Available Tools: {conn.tools.length}</p>

          {conn.state === 'CONNECTED' && (
            <div>
              {conn.tools.map((tool) => (
                <button
                  key={tool.name}
                  onClick={() => callTool(conn.sessionId, tool.name, {})}
                >
                  {tool.name}
                </button>
              ))}
              <button onClick={() => disconnect(conn.sessionId)}>
                Disconnect
              </button>
            </div>
          )}
        </div>
      ))}
    </div>
  );
}

Step 3: Use in Page

Use the component in your page at app/page.tsx:
import { McpConnections } from '@/components/McpConnections';

export default function Home() {
  // Get identity from your auth system
  const identity = 'user-123'; // Replace with actual identity

  return (
    <main>
      <h1>My App</h1>
      <McpConnections identity={identity} />
    </main>
  );
}

AI SDK

To build agentic workflows that use tools from multiple MCP servers, use MultiSessionClient.
// app/api/chat/route.ts
import { MultiSessionClient } from '@mcp-ts/sdk/server';
import { AIAdapter } from '@mcp-ts/sdk/adapters/ai';
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

export async function POST(req: Request) {
  const { messages, identity } = await req.json();

  const client = new MultiSessionClient(identity);

  try {
    await client.connect();

    const adapter = new AIAdapter(client);
    const tools = await adapter.getTools();

    const result = streamText({
      model: openai('gpt-4'),
      messages,
      tools,
      onFinish: async () => {
        await client.disconnect();
      }
    });

    return result.toDataStreamResponse();
  } catch (error) {
    await client.disconnect();
    throw error;
  }
}
For more details, see the AI SDK Adapter documentation.

Pages Router

Step 1: Create API Route

Create pages/api/mcp/sse.ts:
import { createSSEHandler } from '@mcp-ts/sdk/server';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const identity = req.query.identity as string;

  if (!identity) {
    return res.status(400).json({ error: 'identity required' });
  }

  const sseHandler = createSSEHandler({
    identity,
    heartbeatInterval: 30000,
  });

  return sseHandler(req, res);
}

Step 2: Create Component

Same as App Router component above.

Step 3: Use in Page

import { McpConnections } from '@/components/McpConnections';

export default function Home() {
  const identity = 'user-123';

  return (
    <div>
      <h1>My App</h1>
      <McpConnections identity={identity} />
    </div>
  );
}

OAuth Callback Handler

Handle OAuth callbacks at app/oauth/callback-popup/page.tsx (for popups) or app/oauth/callback/page.tsx (for redirects):
'use client';

import { useEffect } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { useMcp } from '@mcp-ts/sdk/client';

export default function OAuthCallback() {
  const searchParams = useSearchParams();
  const router = useRouter();
  const { finishAuth } = useMcp({
    url: '/api/mcp',
    identity: 'user-123',
  });

  useEffect(() => {
    const code = searchParams.get('code');
    const state = searchParams.get('state');

    if (code && state) {
      finishAuth(code, state)
        .then(() => {
          router.push('/'); // Redirect back to main page
        })
        .catch((error) => {
          console.error('OAuth failed:', error);
        });
    }
  }, [searchParams, finishAuth, router]);

  return <div>Completing authentication...</div>;
}

Environment Variables

Add to .env.local:
# Redis connection
REDIS_URL=redis://localhost:6379

# Or for Upstash Redis
REDIS_URL=rediss://default:password@host.upstash.io:6379

Production Deployment

Vercel

  1. Add environment variable in Vercel dashboard:
    • REDIS_URL - Your Redis connection string
  2. Deploy:
vercel deploy

Other Platforms

Ensure your platform supports:
  • Node.js runtime (for API routes)
  • Environment variables
  • WebSocket/SSE connections

Complete Example

Here’s a full working example:
app/api/mcp/route.ts
import { createNextMcpHandler } from '@mcp-ts/sdk/server';

export const dynamic = 'force-dynamic';
export const runtime = 'nodejs';

export const { GET, POST } = createNextMcpHandler({
  getIdentity: (request) => {
    const identity = new URL(request.url).searchParams.get('identity');
    if (!identity) throw new Error('identity required');
    return identity;
  },
});
components/McpClient.tsx
'use client';

import { useMcp } from '@mcp-ts/sdk/client';
import { useState } from 'react';

export function McpClient({ identity }: { identity: string }) {
  const { connections, connect, callTool, status } = useMcp({
    url: `/api/mcp?identity=${identity}`,
    identity,
    autoConnect: true,
  });

  const [result, setResult] = useState<any>(null);

  const handleToolCall = async (sessionId: string, toolName: string) => {
    try {
      const res = await callTool(sessionId, toolName, {});
      setResult(res);
    } catch (error) {
      console.error('Tool call failed:', error);
    }
  };

  return (
    <div>
      <h2>MCP Client ({status})</h2>

      {connections.map(conn => (
        <div key={conn.sessionId}>
          <h3>{conn.serverName}</h3>
          <p>{conn.state}</p>

          {conn.tools.map(tool => (
            <button
              key={tool.name}
              onClick={() => handleToolCall(conn.sessionId, tool.name)}
            >
              {tool.name}
            </button>
          ))}
        </div>
      ))}

      {result && (
        <pre>{JSON.stringify(result, null, 2)}</pre>
      )}
    </div>
  );
}

Next Steps