---
title: "Building a Monetized API, Part 3: Adding a Gated MCP Server"
description: "Add an MCP server to your monetized API and gate access to paid subscribers using custom code and the MonetizationInbound policy's subscription data."
canonicalUrl: "https://zuplo.com/blog/2026/04/02/building-a-monetized-api-part-3"
pageType: "blog"
date: "2026-04-02"
authors: "martyn"
tags: "API Monetization, MCP"
image: "https://zuplo.com/og?text=Building%20a%20Monetized%20API%2C%20Part%203%3A%20MCP%20Server"
---
This is Part 3 of the "Building a Monetized API" series. In
[Part 1](/blog/2026/03/31/building-a-monetized-api-part-1), we set up the API
gateway with authentication, consumer isolation, and rate limiting. In
[Part 2](/blog/2026/04/01/building-a-monetized-api-part-2), we added meters,
plans, Stripe integration, and a self-serve developer portal. Now we're adding
an MCP server on top of the same API and gating access so only paid subscribers
can use it.

<CalloutVideo
  variant="card"
  title="Building a Monetized API, Part 3"
  description="Watch the video walkthrough of adding an MCP server and gating access to paid plan subscribers with custom code."
  videoUrl="/videos/building-a-monetized-api-part-3"
  thumbnailUrl="https://img.youtube.com/vi/tjZ3Sed6svQ/maxresdefault.jpg"
  duration="15:23"
/>

## Why gate MCP access behind a paid plan?

In Part 2, we set up three subscription tiers: Free, Starter, and Pro. The
Starter and Pro plans include a boolean feature called `mcp_server` with an
entitlement set to `true`. The Free plan doesn't have it.

This is a deliberate product decision. The API itself is available on every plan
(with different usage limits), but MCP server access is a premium feature.
Offering AI-powered tooling as a paid upgrade is a natural way to add value to
higher tiers without changing the underlying API at all.

<CalloutAudience
  variant="useIf"
  items={[
    "You have an existing Zuplo project with monetization already configured (see Part 2)",
    "You want to expose your API as an MCP server that AI assistants can use",
    "You need to restrict MCP access to specific subscription tiers using entitlements",
  ]}
/>

## Step 1: Adding the MCP server

Zuplo has native MCP server support built into the gateway. Adding one takes
about 30 seconds.

Go to your project's route list and click the **Add** tab. You'll see an **MCP
Server** option. Select it, and configure the basics:

- **Name**: `ChangeLoggle MCP Server`
- **Version**: `0.0.1`
- **Description**: `Create, update, and discover team changelog info.`

That creates the MCP server endpoint at `/mcp` on your gateway.

![The MCP Server Options panel in Zuplo showing the server name, version, and description fields](/media/posts/2026-04-02-building-a-monetized-api-part-3/mcp-server-setup.png)

<CalloutDoc
  title="MCP Servers with Zuplo"
  description="Learn how to turn your existing API gateway into a toolset that AI systems can discover and use."
  href="/docs/mcp-server/introduction"
/>

## Step 2: Selecting which routes to expose as tools

After creating the MCP server, you need to choose which API routes become MCP
tools. Click **Select Tools** to see all available routes.

You can be selective here. If you only want a read-only MCP experience, pick
just the GET endpoints. If you want full read-write access (create projects,
update changelogs, search entries), select everything.

For our changelog API, we're exposing all 12 endpoints as tools. Every operation
that's available via the REST API is also available through MCP.

Be thoughtful about this in your own projects. Each tool adds to the context
that AI assistants need to process. If you have dozens of endpoints, ask
yourself whether all of them make sense as MCP tools, or whether a focused
subset delivers a better experience.

Click **Update Tools** to save your selection.

![The MCP Tools panel showing all 12 API routes selected as MCP tools with their HTTP methods](/media/posts/2026-04-02-building-a-monetized-api-part-3/mcp-tools-selection.png)

## Step 3: Testing the MCP server

Before adding access controls, verify that the MCP server works. You can test it
with any MCP client. In the video, we use [MCP Jam](https://www.mcpjam.com/),
but any compatible client works.

To connect:

1. Use your gateway's deployment URL with `/mcp` appended as the endpoint
2. Set the connection type to **HTTP**
3. Set authentication to **Bearer Token** and paste in an API key from the
   developer portal

If the connection succeeds, you can interact with your API through the MCP
client. Try asking it to list projects or create a new one. Every request flows
through the same gateway, uses the same API key authentication, and hits the
same origin API.

At this point there's no entitlement check on the MCP route, so any valid API
key works regardless of plan. That's fine for testing. We'll lock it down in the
next step.

![MCP Jam showing a successful list projects request returning the Changeloggle project with its details](/media/posts/2026-04-02-building-a-monetized-api-part-3/mcp-jam-working.png)

## Step 4: Writing the access check policy

To gate MCP access, we need a custom inbound policy that inspects the
subscriber's entitlements and blocks the request if they don't have MCP access.

The key is the `MonetizationInboundPolicy` class from `@zuplo/runtime`. Because
the [MonetizationInbound policy](/docs/policies/monetization-inbound) runs
before this custom code, it has already authenticated the API key and loaded the
subscriber's plan data. Calling
`MonetizationInboundPolicy.getSubscriptionData(context)` gives you the full
subscription context for the current consumer, including their entitlements.

Create a new module in your Zuplo project called `check-mcp-access`:

```typescript
import {
  MonetizationInboundPolicy,
  ZuploContext,
  ZuploRequest,
} from "@zuplo/runtime";

export default async function policy(
  request: ZuploRequest,
  context: ZuploContext,
  options: never,
  policyName: string,
) {
  const subscription = MonetizationInboundPolicy.getSubscriptionData(context);

  if (!subscription?.entitlements?.mcp_server?.hasAccess) {
    return new Response(
      JSON.stringify({
        error:
          "MCP access requires a Starter or Pro plan, please consider upgrading.",
      }),
      {
        status: 403,
        headers: { "Content-Type": "application/json" },
      },
    );
  }

  return request;
}
```

Here's what's happening:

1. `MonetizationInboundPolicy.getSubscriptionData(context)` retrieves the
   subscription for the consumer making the request. The MonetizationInbound
   policy that runs before this code has already authenticated the API key and
   populated the context with subscription data.
2. The code checks `subscription.entitlements.mcp_server.hasAccess`. The
   `mcp_server` key matches the feature we set up in Part 2 when configuring the
   Starter and Pro plans. Entitlements are accessed as properties on the
   `entitlements` object, keyed by the feature's identifier.
3. If the entitlement doesn't exist (Free plan) or `hasAccess` is `false`, the
   request gets a 403 JSON response telling the consumer to upgrade.
4. If the entitlement exists and `hasAccess` is `true`, the request passes
   through to the MCP server handler.

<CalloutDoc
  title="Monetization Inbound Policy"
  description="Full policy reference including metering options, subscription data access, and exposed functions."
  href="/docs/policies/monetization-inbound"
/>

## Step 5: Wiring the policies to the MCP endpoint

Unlike the policies we added in Parts 1 and 2 (which apply to every route), the
MCP access check only needs to go on one endpoint: the `/mcp` route.

Open the MCP server route and add two inbound policies in this order:

1. **Monetization Inbound** (existing policy): handles API key authentication
   and request metering. This needs to run first so the consumer's identity and
   subscription are available to downstream policies.
2. **Custom Code Inbound** pointing to `check-mcp-access`: runs the entitlement
   check we just wrote.

The Custom Code Inbound policy just needs to point at the module:

```json
{
  "export": "default",
  "module": "$import(./modules/check-mcp-access)"
}
```

Save and deploy. The MCP endpoint now authenticates the request, meters it, and
checks entitlements before the MCP server handler ever runs.

<CalloutDoc
  title="Custom Code Inbound Policy"
  description="Write custom request handling logic that runs as part of your policy pipeline."
  href="/docs/policies/custom-code-inbound"
/>

## Step 6: Testing the access gate

With the policies in place, we can verify that free-plan subscribers are blocked
and paid-plan subscribers get through.

**Free plan test.** Connect an MCP client using an API key from a free-plan
subscription. The connection attempt fails with a 403 and the message telling
the consumer to upgrade. The MCP server never processes the request.

![MCP Jam showing a failed connection with a 403 error: MCP access requires a Starter or Pro plan](/media/posts/2026-04-02-building-a-monetized-api-part-3/mcp-jam-free-plan-blocked.png)

**Paid plan test.** Either upgrade the same subscription to Starter or Pro
through the developer portal, or use an API key from an existing paid
subscription. Nothing changes about the connection setup: same URL, same API
key. This time, the connection succeeds and the MCP client can list tools and
make requests.

![MCP Jam showing a successful connection on a paid plan with the tools list response visible](/media/posts/2026-04-02-building-a-monetized-api-part-3/mcp-jam-paid-plan-connected.png)

The API key itself doesn't change when a subscriber upgrades plans. The
entitlement check happens at request time against the current subscription data,
so the moment someone upgrades, their existing API key starts working with the
MCP server.

## What we built in Part 3

The monetized API now has an MCP server with plan-gated access:

- [x] MCP server added to the gateway with all API routes exposed as tools
- [x] Custom access policy using `MonetizationInboundPolicy.getSubscriptionData`
      to check entitlements
- [x] Free plan subscribers blocked from MCP access with a clear upgrade message
- [x] Paid plan subscribers (Starter and Pro) get full MCP server access
- [x] MCP requests are metered the same way as standard API requests

The MCP server sits on a single endpoint, so the access check only needed to be
added once. Every other route in the project continues to work exactly as
before. And because the MonetizationInbound policy handles both authentication
and metering, MCP requests count toward the subscriber's usage just like any
other API call.

## What's next

In [Part 4](/blog/2026/04/03/building-a-monetized-api-part-4), we'll finish the
developer portal. Right now it has the default documentation that Zuplo
generates from the OpenAPI spec, but it needs polish: better descriptions,
branded styling, and a layout that makes the API easy to explore. That's the
last step before this is production-ready.