DEV Community

Cover image for How to create an MCP server from your REST API (with OAuth, scopes, and risk classes)
Ashish maurya
Ashish maurya

Posted on AI-assisted

How to create an MCP server from your REST API (with OAuth, scopes, and risk classes)

Every team I talk to wants the same thing right now: let Claude, Cursor, or an internal agent look up an order, check a customer, or open a ticket by calling the APIs they already have. And every security team asks the same question back: what stops the model from calling the endpoint that deletes things?

The Model Context Protocol (MCP) answers half of that. It gives AI clients a standard way to discover and call tools. The other half, deciding which tools exist, who can call them, and what a "dangerous" call looks like, is on you.

This post shows how to create an MCP server from a REST API you already have, and how to handle MCP authorization and authentication properly: OAuth for clients, scopes and claims per tool, and risk classes for anything that changes data. I'll use Koodisi, where an MCP server is generated from a published API collection. (Disclosure: I work on Koodisi at iVoyant. The patterns apply whatever you build with.)

The example: an orders API

Say you have a small orders API with three operations:

Operation What it does Risk
GET /orders/{id} Read an order Read
POST /orders/{id}/refund Issue a refund Write
DELETE /customers/{id} Delete a customer Destructive

The goal: Claude can look up orders freely, can request refunds with a confirmation step, and should probably never see the delete endpoint at all.

Step 1: Start from a published API and a security profile

An MCP server here needs three things to exist first:

  1. A published API collection with a generated OpenAPI spec. Each operation in the spec becomes a candidate tool.
  2. A security profile (Settings → Organization → Security Profiles). It defines the identity provider, the scopes (for example orders:read, orders:write), and the claims (for example tenant_id) that tokens can carry.
  3. An environment to run in.

The security profile matters more than it looks. A server binds to exactly one profile, and you can only grant scopes and claims that profile defines. So nobody can hand an AI client a permission your identity provider never issues.

Step 2: Create the server and pick the tools

In API Manager → MCP Servers → Create, you name the server, choose the environment and security profile, and select which published operations to expose. I exposed only the first two:

  • GET /orders/{id} → tool, risk class Read
  • POST /orders/{id}/refund → tool, risk class Write
  • DELETE /customers/{id} → not exposed

That last line is the most important security decision in this whole post. The cheapest guardrail is not exposing an operation in the first place.

Risk classes are Read, Write, or Destructive. They travel with each tool, so a client can put a confirmation step in front of anything that changes data, like the refund.

Each API operation becomes a tool with its own risk class; the destructive one is simply not exposed.

Step 3: MCP authorization with scopes and claims per tool

On the APIs & Tools tab you set defaults for the whole spec (every tool inherits them) and add extra requirements per tool:

Defaults for this spec:   scopes: orders:read      claims: tenant_id
refund tool (extra):      scopes: orders:write
Enter fullscreen mode Exit fullscreen mode

Claims are the part people skip and regret. The claim values attached to a caller are forwarded, encrypted, to your downstream API, so your backend still receives the caller's tenant and can apply its normal rules. The model can't talk its way into another tenant's orders, because the tenant comes from the token, not the prompt.

Step 4: Give the tools a downstream credential

Tools need to authenticate to your real API. On Downstream Credentials you enter an API key, bearer token, or basic auth value per security scheme. It's encrypted at rest and never shown again.

Note the separation: clients authenticate to the MCP server with OAuth applications; tools authenticate to your backend with downstream credentials. The AI client never sees the backend key.

Step 5: MCP authentication with OAuth applications

On Client Access → New OAuth application, you set the scopes and default claims, assign users, and get a client_id and client_secret (shown once, so copy it).

For people outside your organization, you can turn on Accept external clients and share a registration link. Registrations land as Pending, and until someone approves them, sign-in returns a 401. That's the review queue I wish every API had.

Step 6: Test it before wiring up Claude

The Connect tab can generate a short-lived test token, so you can hit the server with plain JSON-RPC. MCP uses JSON-RPC 2.0, so listing tools looks like this:

curl -s "$MCP_ENDPOINT" \
  -H "Authorization: Bearer $TEST_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Enter fullscreen mode Exit fullscreen mode

You should see exactly two tools. If you see three, go back to Step 2.

Step 7: Connect Claude (or Cursor) to your MCP server

The same Connect tab generates a config snippet for Claude, Cursor, Gemini CLI, mcp-remote, Python, or cURL. For Claude Desktop: Settings → Developer → Edit Config, paste the snippet into claude_desktop_config.json, fill in your client ID and secret, and restart. Claude signs you in through OAuth and the tools show up.

Then try the obvious thing:

"What's the status of order 1042? If it hasn't shipped, refund it."

Claude looks the order up with the read tool, and the refund tool, marked Write, is the one a client can gate behind your confirmation. Either way, the delete endpoint isn't on the menu.

Reads run straight away; the Write-class refund is where a client asks for confirmation.

Step 8: Watch what it actually does

The Analytics tab shows total calls, success rate, errors, blocked calls, and p95 latency, broken down by tool or by user, and an Activity log lets you find individual invocations. When someone asks "did the AI issue that refund?", you can answer with a record instead of a guess.

What I'd take away from this

  • Expose less. The safest destructive tool is the one that doesn't exist.
  • Label risk. Read, Write, and Destructive give clients a standard reason to stop and ask.
  • Bind identity, not prompts. Scopes and forwarded claims keep the model inside the caller's permissions.
  • Separate the two credentials. Clients get OAuth; your backend key stays server-side.
  • Log every call. Agents act at machine speed, so per-call records aren't optional.

If you're building MCP servers some other way, I'd love to hear how you're handling scopes and confirmations in the comments.

Further reading

Top comments (2)

Collapse
 
srkdan_writes profile image
SRKDAN •

Splitting tools into read, write and destructive classes is a sensible default. I'd also keep the destructive endpoints out of the generated tool list entirely, as you suggest for the customer delete, instead of relying on a confirmation prompt. Models tend to be persuaded by their own context, so a tool that doesn't exist is safer than one that asks first. How do you handle scope changes for tokens that are already issued?

Collapse
 
suppdevbot profile image
DEV SUPPORTS •
You need to verify your account.
Enter fullscreen mode Exit fullscreen mode

tr.ee/dev-to