MCP to Provide Additional Capabilities to the AI Agent

Part of the series AI Agents and MCP Server: Teaming Up for the Agentic Web

We built our first AI Agent with an addition tool. In this article, we will move that tool into an MCP server, where it can be discovered and used through a standard protocol.

A quick reminder about MCP: MCP is an open-source standard for connecting AI applications to external systems. An MCP server provides tools, while an MCP client connects to that server and calls its tools on the agent's behalf. In this series, our AI application contains the agent and its MCP client; the MCP server exposes the capabilities the agent needs. Visually, it looks like this:

AI application interacting with the real world through MCP.
AI application interacting with the real world through MCP.

You might wonder why we need an MCP server. We do not strictly need one for this small addition tool: a direct tool is simpler when the capability belongs only to one tightly coupled application. MCP becomes useful when a capability should be shared, independently deployed, or consumed by compatible clients.

  1. Support compatible clients: By providing MCP, you allow AI applications and coding tools that support the protocol to use your capabilities. For example, a component-library MCP server can make its documentation and tools available in a code editor.
  2. Use external MCP servers: Your agent can also connect to an MCP server you did not create to integrate with an existing system or third-party capability.
  3. Decouple capabilities: The agent only needs to know a tool's name, description, input, and output. The MCP server owns its implementation and can evolve independently.

For this series, MCP gives us a clear boundary between the agent and the capabilities it can use.

Building the MCP server

Within our Nitro application, we can create a new route for the MCP server.

bash
mkdir -p server/routes
touch server/routes/mcp.ts

Then, we have to install the MCP SDK:

bash
pnpm add @modelcontextprotocol/sdk@^1.29.0

This is the official TypeScript SDK for building MCP servers.

Now, we can create our MCP server within the server/routes/mcp.ts file:

ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { defineEventHandler } from 'h3'

export default defineEventHandler(async (event) => {
  const server = new McpServer({
    name: 'ai-agent',
    version: '1.0.0'
  })
})

We now have an MCP server object, but it cannot receive requests yet. MCP separates the server, which defines the protocol capabilities and tools, from the transport, which carries requests and responses. In our case, the Nitro route will host a Streamable HTTP transport so an MCP client can reach the server over HTTP.

Note

Streamable HTTP is useful here because it allows a remotely hosted MCP server to communicate through regular HTTP requests and responses.

Fortunately, the MCP SDK provides everything we need to add an HTTP transport layer.

ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
import { defineEventHandler, readBody } from 'h3'

export default defineEventHandler(async (event) => {
  const server = new McpServer({
    name: 'ai-agent',
    version: '1.0.0'
  })

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

  event.node.res.on('close', () => {
    transport.close()
    server.close()
  })

  await server.connect(transport)

  const body = await readBody(event)

  await transport.handleRequest(event.node.req, event.node.res, body)
})

With server.connect(transport), we attach the server to the HTTP transport. With transport.handleRequest(event.node.req, event.node.res, body), we pass the incoming request to that transport, which sends the response back to the client. This example creates a stateless transport for each request, so sessionIdGenerator: undefined disables session IDs. When the response ends or terminates prematurely, we close the transport and server to free their resources.

The request body is important because tool calls can include input parameters.

With this setup, we can now try the MCP.

Trying the MCP

To try the MCP, we can use the official MCP Inspector:

bash
npx @modelcontextprotocol/inspector@latest

Note

Use the inspector to verify an MCP server before connecting it to an AI application. A model may decide not to call a particular tool, while the inspector lets you test the protocol and tools directly.

Then, we can enter the MCP URL and ping the server to make sure it's working.

Pinging the MCP server.

The server is now reachable through its HTTP transport.

Adding a tool

Now that we have a working MCP server, we can move our addition tool from the AI Agent to the MCP server.

Within the server/api/chat.ts file, remove tool from the AI SDK import, remove the Zod import, and delete the addition tool registration:

ts
import { createOpenAI } from '@ai-sdk/openai'
import { convertToModelMessages, stepCountIs, streamText } from 'ai'
import { defineEventHandler, defineLazyEventHandler, readBody } from 'h3'
import { useRuntimeConfig } from 'nitropack/runtime'
import { z } from 'zod'

export default defineLazyEventHandler(() => {
  const runtimeConfig = useRuntimeConfig()

  const model = createOpenAI({
    apiKey: runtimeConfig.openAiApiKey,
  })

  return defineEventHandler(async (event) => {
    const { messages } = await readBody(event)

    return streamText({
      model: model('gpt-5-nano'),
      system: `You are a helpful assistant. You can use the tool to add two numbers together.`,
      tools: { 
        addition: tool({ 
          description: 'Adds two numbers', 
          inputSchema: z.object({ 
            a: z.number().describe('The first number'), 
            b: z.number().describe('The second number'), 
          }), 
          execute: ({ a, b }) => ({ 
            a, 
            b, 
            result: a + b 
          }), 
        }), 
      }, 
      stopWhen: stepCountIs(2),
      messages: convertToModelMessages(messages),
    }).toUIMessageStreamResponse()
  })
})

In the server/routes/mcp.ts file, register the addition tool with the MCP server.

ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { defineEventHandler } from 'h3'
import { z } from 'zod'

export default defineEventHandler(async (event) => {
  const server = new McpServer({
    name: 'ai-agent',
    version: '1.0.0'
  })

  server.tool(
    'addition',
    'Adds two numbers',
    {
      a: z.number().describe('The first number'),
      b: z.number().describe('The second number')
    },
    async (params) => {
      return {
        content: [
          {
            type: 'text',
            text: String(params.a + params.b)
          }
        ]
      }
    }
  )

  // ...
})

Tool registration is deliberately similar to the direct tool we used in Part 1. The implementation and input schema have moved to the MCP server, while its output now follows MCP's standardized content format. The agent is not connected to this server yet; we will do that in the next article.

Now we can manually use our addition tool:

Listing and using the addition tool.

We now have a working MCP server that exposes the addition tool. Next, we will give our agent an MCP client so it can discover and use that tool.

PP

Thanks for reading! My name is Estéban, and I love to write about web development and the human journey around it.

I've been coding for several years now, and I'm still learning new things every day. I enjoy sharing my knowledge with others, as I would have appreciated having access to such clear and complete resources when I first started learning programming.

If you have any questions or want to chat, feel free to comment below or reach out to me on Bluesky, X, and LinkedIn.

I hope you enjoyed this article and learned something new. Please consider sharing it with your friends or on social media, and feel free to leave a comment or a reaction below, it would mean a lot to me! If you'd like to support my work, you can sponsor me on GitHub!

Continue readingPair AI Agents with MCP to Access the Outside World

Reactions

Discussions

Add a Comment

You need to be logged in to access this feature.