Currently Available: Need a skilled Software Developer for your next project?
Categories
Coding Agents

How to Build a Read-Only MCP Server When an API Lacks a Read-Only OAuth Scope

A Model Context Protocol (MCP) server lets an AI client call named tools, which are functions that connect to external services. If the service’s API has no read-only OAuth scope, the access token available to the MCP server may authorize both retrieval and changes. A read-only MCP server limits the tools the client can call, but that limit alone does not remove write permission from the upstream token.

Expose a fixed set of retrieval operations. Keep the API credential on the server, and reject every request outside that set. For stronger protection, make the API or an independent gateway reject writes too. An OAuth scope is a permission attached to a token; when the provider offers no read-only scope, the MCP server must not claim that its narrower interface has changed the token’s permissions.

Separate MCP access from API access

There are two authorization boundaries. The MCP server decides which authenticated clients can call its tools. The upstream API decides what the server’s API token can do. Restricting one boundary does not automatically restrict the other.

Authenticate MCP clients using the MCP server’s own authorization setup. Validate the incoming token according to that setup. Check its issuer (who issued it), audience (the service it is meant for), expiration, and permissions. Then use a separate, server-side credential for the upstream API. Do not pass the MCP client’s bearer token through to the API: it was issued for a different purpose and may have different permissions. MCP authorization guidance describes the server’s role in validating tokens; it does not change the upstream API’s authorization rules. See the MCP authorization guidance.

Before building a wrapper, check whether the provider supports a read-only account, a more narrowly authorized token, or a separate gateway. OAuth token exchange and rich authorization requests let a client ask for narrower permissions, when the authorization server and API support and enforce those limits. Neither mechanism creates read-only authority when the provider does not recognize or enforce it. The OAuth security best current practice, token exchange standard, and rich authorization requests standard all support minimizing permissions, but the provider’s implementation determines which restrictions actually apply.

Map each MCP tool to an approved read

Give the MCP server a small, explicit set of named tools, such as get_record or list_records. Each tool should call a known API endpoint with a fixed method and a constrained set of inputs. For example, a tool for retrieving one record should accept a validated record identifier and pass it to the specific API route for that record.

Avoid a general-purpose tool that accepts a URL, HTTP method, headers, and request body. That design lets callers choose operations beyond the server’s intended read interface. Also avoid arbitrary query-language tools unless the upstream API and the query layer independently restrict what the query can do.

Make the server deny anything it has not explicitly approved. Each tool handler, the code that runs when a client calls a tool, should validate its inputs, build the API request itself, and return only the fields the client needs. Limit how many pages a caller can request, the size of each response, how long requests can take, and how often callers can send requests. Do not let caller-controlled input choose the upstream host or add arbitrary headers.

Check endpoint behavior, not just HTTP methods

Use HTTP methods as an initial check, then verify what each API endpoint actually does. HTTP classifies methods such as GET as “safe,” meaning they are intended to retrieve data without changing server state. But an endpoint can still cause side effects. For example, an endpoint might use a query parameter to request an action and trigger a change even when the request uses GET. The HTTP semantics standard explains why a method allowlist alone cannot establish that an operation is read-only.

Check the API documentation and test the selected endpoints to confirm that they retrieve data without changing state or triggering workflows. Review exports, administrative routes, and endpoints that start background work with the same care. OWASP’s Broken Function Level Authorization guidance supports granting access to specific functions and denying unapproved ones. A fixed mapping from each approved API route to an MCP tool is easier to review than a rule that assumes a route is safe based on its name or HTTP method.

Keep the upstream token out of tool results and error messages. Avoid logging the token or unnecessary sensitive response data. Retrieved records also remain untrusted input: their text can try to influence an AI client or its later tool calls. Read-only describes whether an operation changes upstream state; it does not mean the returned data is safe to follow or share.

Add an enforcement point the MCP server cannot bypass

A fixed list of tools and carefully written handlers reduce the risk of accidental or ordinary misuse. They do not make a broad upstream token read-only if an attacker compromises the MCP server or its credentials. The server could then use the token outside the intended tool path.

For a stronger guarantee, ask the API provider for a read-only identity, or have a separate gateway enforce which routes and actions the server can use. The MCP server must not be able to bypass that gateway to reach the API directly. If neither the API nor an independent boundary enforces the restriction, describe the server accurately as exposing a read-only interface, while recognizing that its upstream credential remains write-capable.

MCP tool annotations, metadata labels for tools, can describe how a tool is intended to behave. Set readOnlyHint only for a handler that has been constrained and tested. The MCP discussion of tool annotations treats annotations as hints for clients; handlers can still make write requests.

Test allowed reads and rejected requests

Test every approved retrieval tool, then verify that the server rejects unapproved tools, routes, methods, and parameters. Include attempts to substitute a method, add action-like query parameters, select another host, provide malformed identifiers, or request excessive pagination. Test authentication failures too, including missing, expired, wrong-issuer, and wrong-audience MCP tokens.

Use a test environment for endpoint behavior checks when one is available. When the API changes, review the tool-to-endpoint mapping and repeat the relevant tests. These checks provide evidence that the server follows its intended boundary; they do not prove that a broad upstream credential remains harmless after a server compromise.

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Email updates

Usually a new article and a few links I found interesting.

No spam. Unsubscribe with one click.

Leave a Reply

Your email address will not be published. Required fields are marked *