S'inscrire

MCP server over an existing API

This article is not available in French yet. It is shown in English.

An MCP server over an existing API is an MCP server that gives a language model access to a service through the service's existing REST, GraphQL or RPC interface. The server does not copy the API one to one. It selects the operations worth exposing, turns them into tools that a model can choose and fill in correctly, and shapes the results to fit the model's context.

The MCP specification does not define how such a server should be designed. It defines the format of tools, the separation of errors, caching hints and security requirements; the rest is design guidance. This article follows the specification version 2026-07-28 and the guide "Writing effective tools for agents", published by Anthropic in September 2025. Basic concepts of the protocol are described in the article What is MCP, and the fields of a tool definition in the article Tools in MCP.

Overview

Property Value
Object MCP server that wraps the API of an existing service
Main primitive Tools; resources and prompts are optional
Requirements Specification 2026-07-28: tools, caching, authorization, Security Best Practices
Design guidance "Writing effective tools for agents", Anthropic, September 2025
Key decisions Set of tools, descriptions, parameters, response size, errors, authorization, state
Specification version 2026-07-28

Why an API is not exposed as is

An API is designed for programs. A developer reads the documentation once and writes code that calls endpoints in a fixed order, stores identifiers, walks through every page of a list and maps numeric codes to meanings.

A model chooses a tool in every conversation, from the names, descriptions and input schemas in its context. Every tool definition and every result takes space in that context. A long list of similar tools makes the choice harder, a page of raw JSON consumes tokens, and an opaque identifier is easy to confuse with another one.

The Anthropic guide names tools that merely wrap existing endpoints as a common mistake and recommends a few tools aimed at specific high-impact workflows. The MCP server becomes an adaptation layer between the model and the unchanged API.

History

The table lists the changes of the specification that matter for the design of tools.

Date Event
26 March 2025 Specification 2025-03-26: tool annotations that describe the behavior of a tool
18 June 2025 Specification 2025-06-18: structured tool output (outputSchema, structuredContent), resource links in results, the title field
11 September 2025 Anthropic publishes the guide "Writing effective tools for agents"
25 November 2025 Specification 2025-11-25: input validation errors are returned as tool execution errors (SEP-1303)
28 July 2026 Specification 2026-07-28: stateless protocol, explicit state handles, caching hints ttlMs and cacheScope, deterministic order of tools

Choosing the operations

Workflows instead of endpoints

A tool covers a task that a user asks for, not a single HTTP call. In the examples of the Anthropic guide, one schedule_event tool finds a free slot and creates the event instead of separate tools for listing users, listing events and creating an event; a search_logs tool returns only relevant lines instead of read_logs.

A tool that combines several API calls moves the logic from the model to the server: the server performs the calls in the right order and joins the data. The guide also warns that too many tools, or tools with overlapping purposes, distract the model from efficient strategies. It prefers a search with filters to a full list, which makes the model read records that do not matter for the task.

Read and write operations

Operations that read data and operations that change it carry different risks. A server can expose read operations broadly and only those write operations that the target workflows need. A rarely needed, irreversible or administrative operation can stay outside the server: the user performs it in the service's own interface.

The final control stays with the host: the specification recommends that a human always be able to deny a tool call.

Annotations for destructive operations

A tool definition can carry annotations, optional properties that describe its behavior. The schema of version 2026-07-28 defines five of them:

Annotation Meaning Default
title Human-readable name of the tool none
readOnlyHint The tool does not modify its environment false
destructiveHint The tool may perform destructive updates; false means only additive updates true
idempotentHint A repeated call with the same arguments has no additional effect false
openWorldHint The tool interacts with an open world of external entities, such as the web true

destructiveHint and idempotentHint are meaningful only when readOnlyHint is false. The defaults describe the most cautious case, so a server sets readOnlyHint: true on read-only tools and destructiveHint: false on tools that only add data.

Annotations are hints. Clients must treat them as untrusted unless the server is trusted, and an annotation does not replace access control on the server.

Descriptions and names

Tool names

Two connected servers can expose tools with the same name, such as search, and the specification lets clients prefix tool names with a server identifier. The Anthropic guide recommends namespacing on the server side as well: by service (asana_search, jira_search) and by resource (asana_projects_search, asana_users_search). According to the guide, the choice between prefixes and suffixes had a noticeable effect in its evaluations and differs between models.

Tool descriptions

The description is the main source from which the model learns when to use a tool; the schema calls it a hint to the model. The Anthropic guide recommends describing a tool the way one would explain it to a new member of the team. What an experienced user of the API knows implicitly is written out: query formats, domain terms, relationships between entities, limits. According to the guide, even small refinements of descriptions can lead to large improvements in how a model uses the tools.

Information that concerns the whole server, rather than one tool, can go into the instructions field of the server/discover result. The schema describes it as natural-language guidance that helps the model use the server and that should not duplicate tool descriptions.

Parameter names

Parameter names are part of the description: according to the guide, a parameter named user_id is clearer than user. Each parameter has its own description with the expected format and an example. A schema with additionalProperties: false rejects unknown arguments.

Parameters

Enumerations and default values

An API often accepts codes and flags that only make sense with documentation. In a tool they become explicit choices: a status that the API encodes as a number becomes an enum of readable values, and the server maps them to the codes. The model chooses from a closed list instead of guessing.

Parameters that most calls do not need get default values. The JSON Schema default keyword states the value in the schema, and the server applies it when the argument is missing.

Readable identifiers

APIs identify objects with internal identifiers, often UUIDs. According to the Anthropic guide, resolving UUIDs into meaningful names, or even into a simple numbering, significantly improves the precision of the model in retrieval tasks and reduces hallucinations.

A server over an API therefore accepts the identifiers people use: a project key, an issue key such as WEB-142, a user name, the word "me" for the current user. The server resolves them into internal identifiers itself.

Response size

Filtering and limits

A tool that can return a large amount of data limits it with pagination, range selection, filtering or truncation and sensible defaults. For Claude Code, the guide states, tool responses are limited to 25,000 tokens by default.

The protocol defines cursor pagination only for list operations such as tools/list, not for the content of a tools/call result. A search tool therefore has its own parameters, such as a limit and a cursor that the result returns for the next call.

Truncation with guidance

When a result is cut, the tool says so and explains how to get the rest: narrow the filter, request the next page, search for a specific item. The guide recommends such text to steer the model toward small targeted searches instead of one broad one.

Concise and detailed formats

The guide describes a response_format parameter with the values "concise" and "detailed". The concise format returns only what the model needs to answer, for example the text of messages. The detailed format adds identifiers and fields that are needed for follow-up calls. In the guide's example, the concise response took about a third of the tokens of the detailed one.

The fields of the response are chosen for the model, not copied from the API: the guide prefers name and file_type to uuid and mime_type.

Errors the model can act on

The specification separates protocol errors, such as an unknown tool, from tool execution errors, such as an API failure, a business logic error or invalid arguments. The latter are returned in the tool result with isError: true, and the specification describes them as actionable feedback that the model can use to correct the call.

A server over an API therefore translates the errors of the API. An HTTP status with an internal code tells the model little; a text that names the wrong value and the valid ones lets it recover. The guide recommends specific, actionable messages instead of opaque error codes and stack traces.

JSON
{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "resultType": "complete",
    "content": [
      {
        "type": "text",
        "text": "Project \"WEBB\" not found. Available project keys: WEB, API, MOBILE. Call tracker_search_issues again with one of these keys in the project parameter."
      }
    ],
    "isError": true
  }
}

Authorization and user identity

How an MCP server over HTTP obtains and validates tokens is described in the article Authorization in MCP. For a server over an API, two rules of the specification matter most.

First, the server accepts only tokens issued for itself and validates every incoming token. The identity of the user is taken from the verified token, not from a tool argument. The list of tools may depend on the authorization of the request, for example on the granted scopes.

Second, the server must not pass the token it received from the client on to the API (token passthrough). When the server calls the upstream API, it may act as an OAuth client of that API and uses a separate token issued by the API's own authorization server. The specification does not prescribe how the server obtains that token or how it links the user to an account in the API.

A server that connects clients to a third-party API with one static client identifier is called an MCP proxy server in Security Best Practices. Such a server is exposed to the confused deputy problem, and the document requires it to keep per-client consent and to check it before the third-party authorization flow starts. The document also recommends a minimal initial set of scopes, with additional scopes requested when a privileged operation is first attempted. Security risks are described in the article Security of MCP. A local server over stdio takes credentials, such as an API key, from its environment.

State between calls

Since 2026-07-28 the protocol has no session; the changes are described in the article Stateless MCP. A server that needs state between calls, such as a draft or an open transaction, returns an explicit handle from the tool that creates the state and accepts it as an argument of later calls. The model carries the handle forward.

The specification gives non-normative guidance for handles:

  • Authorization. On an authenticated server a handle is a name, not a permission: the server checks the caller's rights to the handle on every call.
  • Opacity. A handle does not encode internal structure that invites parsing or guessing.
  • Lifetime. The retention period is stated in the description of the creating tool, for example "drafts expire after 24 hours of inactivity", so that the model sees it.
  • Expiry. A call with an expired or unknown handle returns a tool execution error that says so, and the model can create a new handle.

Security Best Practices adds that possession of a handle is not authentication. Handles come from a secure random generator and are bound on the server to the user from the verified token, for example by storing the state under the key <user_id>:<handle>.

Structured output

A tool can declare outputSchema, the JSON Schema of its result. The structured result is returned in structuredContent and must conform to the schema; for compatibility the tool should also return the serialized JSON in a text block. The fields themselves are described in the article Tools in MCP.

For a server over an API, the output schema is a separate contract, not a copy of the API response: it contains the fields that the tool returns after filtering and renaming.

Caching hints

Since 2026-07-28 the results of server/discover, list methods and resources/read carry caching hints: ttlMs, the time in milliseconds during which the result stays fresh, and cacheScope. The results of tools/call are not on this list.

cacheScope: "public" means that the result contains no user-specific data and any shared cache may serve it to any user. "private" allows reuse only within the same authorization context, so a list of tools that depends on the scopes of the token is "private". cacheScope does not replace access control. Tools are returned in a deterministic order, which, according to the specification, also improves the hit rate of the model's prompt cache.

Example with an issue tracker API

The example uses a fictional API of an issue tracker at api.tracker.example. It is not a real service. The API has four endpoints:

Endpoint What it does
GET /v2/projects/{project_id}/issues List of issues; filters state (code 1, 2 or 3) and assignee_id (UUID); page, per_page; every issue with all its fields
POST /v2/projects/{project_id}/issues Creates an issue
POST /v2/issues/{issue_id}/transitions Changes the status by the identifier of a transition
DELETE /v2/issues/{issue_id} Deletes an issue

Before the redesign

A direct mirror produces one tool per endpoint. The names repeat the paths, the descriptions repeat the HTTP method, and the parameters require UUIDs and numeric codes that the model can only guess.

JSON
[
  {
    "name": "get_v2_projects_project_id_issues",
    "description": "GET /v2/projects/{project_id}/issues",
    "inputSchema": {
      "type": "object",
      "properties": {
        "project_id": { "type": "string" },
        "state": { "type": "integer" },
        "assignee_id": { "type": "string" },
        "page": { "type": "integer" },
        "per_page": { "type": "integer" }
      },
      "required": ["project_id"]
    }
  },
  {
    "name": "post_v2_issues_issue_id_transitions",
    "description": "POST /v2/issues/{issue_id}/transitions",
    "inputSchema": {
      "type": "object",
      "properties": {
        "issue_id": { "type": "string" },
        "transition_id": { "type": "string" }
      },
      "required": ["issue_id", "transition_id"]
    }
  },
  {
    "name": "delete_v2_issues_issue_id",
    "description": "DELETE /v2/issues/{issue_id}",
    "inputSchema": {
      "type": "object",
      "properties": {
        "issue_id": { "type": "string" }
      },
      "required": ["issue_id"]
    }
  }
]

After the redesign

The redesigned server has three tools with the tracker_ prefix. They accept project and issue keys, readable statuses and user names; the server resolves them into identifiers, codes and transitions and walks through the pages of the API itself. Deletion is not exposed and stays in the tracker's own interface.

JSON
[
  {
    "name": "tracker_search_issues",
    "title": "Search issues",
    "description": "Search issues in one project of the tracker. Returns at most `limit` issues, newest first. Use the concise format to answer questions and the detailed format when you need issue fields for a follow-up call. If more issues match, the result says so; narrow the query instead of raising the limit.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "project": {
          "type": "string",
          "description": "Project key, for example WEB"
        },
        "status": {
          "type": "string",
          "enum": ["open", "in_progress", "done"],
          "description": "Filter by status. Omit to search all statuses"
        },
        "assignee": {
          "type": "string",
          "description": "Username of the assignee, or me for the current user"
        },
        "query": {
          "type": "string",
          "description": "Words to find in the title or description"
        },
        "limit": {
          "type": "integer",
          "minimum": 1,
          "maximum": 50,
          "default": 20
        },
        "response_format": {
          "type": "string",
          "enum": ["concise", "detailed"],
          "default": "concise"
        }
      },
      "required": ["project"],
      "additionalProperties": false
    },
    "outputSchema": {
      "type": "object",
      "properties": {
        "issues": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "key": { "type": "string" },
              "title": { "type": "string" },
              "status": { "type": "string" },
              "assignee": { "type": "string" }
            },
            "required": ["key", "title", "status"]
          }
        },
        "truncated": { "type": "boolean" }
      },
      "required": ["issues", "truncated"]
    },
    "annotations": {
      "readOnlyHint": true,
      "openWorldHint": false
    }
  },
  {
    "name": "tracker_create_issue",
    "title": "Create issue",
    "description": "Create an issue in a project. Returns the key of the new issue, for example WEB-143. Each call creates a new issue, so do not repeat a call that succeeded.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "project": { "type": "string", "description": "Project key, for example WEB" },
        "title": { "type": "string", "maxLength": 200 },
        "description": { "type": "string" },
        "priority": {
          "type": "string",
          "enum": ["low", "medium", "high"],
          "default": "medium"
        }
      },
      "required": ["project", "title"],
      "additionalProperties": false
    },
    "annotations": {
      "readOnlyHint": false,
      "destructiveHint": false,
      "idempotentHint": false,
      "openWorldHint": false
    }
  },
  {
    "name": "tracker_change_issue_status",
    "title": "Change issue status",
    "description": "Move an issue to another status. The previous status is replaced. Moving an issue to the status it already has changes nothing.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "issue": { "type": "string", "description": "Issue key, for example WEB-142" },
        "status": { "type": "string", "enum": ["open", "in_progress", "done"] }
      },
      "required": ["issue", "status"],
      "additionalProperties": false
    },
    "annotations": {
      "readOnlyHint": false,
      "destructiveHint": true,
      "idempotentHint": true,
      "openWorldHint": false
    }
  }
]

The annotations follow the definitions of the schema. Creation only adds data and is not idempotent, because each call creates a new issue. The status change replaces the previous value, which is not an additive update, so destructiveHint stays true; a repeated call with the same status changes nothing, so the tool is idempotent. The tracker is a closed domain, so openWorldHint is false.

REST API endpoint and MCP tool

The table compares a typical endpoint of an existing API with a tool of a server designed over it.

REST API endpoint MCP tool over the API
Consumer Code written by a developer A model choosing in each conversation
Unit One resource and one HTTP method One task, possibly several API calls
Interface description Documentation, OpenAPI Name, description, inputSchema, read by the model
Identifiers Internal, often UUIDs Readable keys and names; resolved by the server
Volume of results Full objects, pages chosen by the client Selected fields, limits, concise and detailed formats
Errors HTTP status and error code isError: true with a text that explains the fix
Side effects Described in documentation Annotations readOnlyHint, destructiveHint, idempotentHint
Credentials Token of the API Token issued for the MCP server; separate token for the API
State Resources and identifiers of the API Explicit handles passed as arguments

The endpoint remains the basis: the logic of the service, its validation and its access control stay in the API. The tool adds the layer that the model needs: a choice of operations, wording, simplification and limits.

The two interfaces evolve separately: a new tool description can change the behavior of the model without any change in the API, so tools are tested with models, as described in the article Testing MCP servers. General methods of evaluating applications based on language models are described in the article Testing LLM applications.

Terms

Term Meaning
MCP server over an existing API MCP server that gives a model access to a service through its existing API and adapts its operations into tools
Tool annotations Optional hints in a tool definition about its behavior: readOnlyHint, destructiveHint, idempotentHint, openWorldHint
Tool namespacing Common prefix or suffix in tool names that groups them by service or resource
Response format Tool parameter that lets the model choose a concise or a detailed result
Tool execution error Tool result with isError: true and a text that the model can use to correct the call
Token passthrough Forwarding the client's token to another service; forbidden by the specification
MCP proxy server MCP server that connects clients to a third-party API and acts as a single OAuth client of that API
State handle Identifier that a tool returns and the model passes back in later calls

Sources

Series

Model Context Protocol (MCP)
  1. What is MCP (Model Context Protocol)
  2. Tools in MCP
  3. Resources and resource templates in MCP
  4. Prompts in MCP
  5. MCP transports: stdio and Streamable HTTP
  6. Stateless MCP: changes in 2026-07-28
  7. Authorization in MCP
  8. MCP security
  9. MCP extensions
  10. MCP server over an existing APICurrent
  11. Testing MCP servers: the basics
  12. Evaluating an MCP server with models
  13. Regression testing of an MCP server
  14. Publishing an MCP server to the MCP Registry

Wiki articles are written and maintained by the ComplexQA team.