MCP Proxy API

Your gateway handles sign-in. Miso handles MCP.

Pick the setup by who handles sign-in and who handles MCP:

Setup What you build Who connects Sign-in handled by MCP protocol handled by Usage reports
Internal Use Nothing. Paste one URL Your team, or one app No one. The URL carries the key and token Miso Per app
For your Subscribers A sign-in hand-off on your login page Each subscriber Your existing login. Miso runs OAuth Miso, on your domain Per subscriber and tier
MCP Proxy API (this page) A gateway that signs users in and forwards calls Your users Your gateway Miso Per user your gateway names
Tools API An MCP server that calls Miso's tools Your users Your MCP server Your MCP server Per app or per user

Your users connect their assistant to an address you run. Your gateway signs them in, then passes each MCP call to Miso with your Secret API Key. Miso speaks the MCP protocol: it reads the call, runs the search, and answers in MCP. Your gateway hands that answer straight back. Below we build the gateway.

The split is what sets this setup apart from the Tools API. There, your own MCP server speaks the protocol and calls Miso for search. Here, your gateway decides who gets in, and Miso does everything after that.

Your Secret API Key stays on your server. This page is for a gateway you run. Never put the secret key into an agent, an MCP URL, or a browser. To give people a URL they can paste into an assistant, use MCP — Internal Use or MCP — for your Subscribers instead.


How a call travels

The proxy suits a pass-through. Every MCP call is one JSON-RPC POST that answers with JSON. There is no session to carry and nothing streams on the POST, so your gateway forwards the body as it is and adds three things.


What your gateway adds

What Where Notes
Your Secret API Key Authorization: Bearer <SECRET_API_KEY>, or the X-API-Key header No MCP token. The secret key opens the proxy alone.
The user X-MCP-User-Id header, or ?user_id= Optional. Accepted only with the secret key. The header wins when both are sent.
A filter ?fq= on the URL Optional. Narrows what this call can search.

The proxy's address is the same endpoint every MCP client uses:

POST https://api.askmiso.com/v1/ask/mcp

All three ride outside the JSON-RPC body. The body belongs to the protocol, and the tool arguments inside it belong to the assistant, which fills them in. A user_id or fq at the top level of the body is refused with a message naming where it goes.


Tell Miso who the user is

Send your own id for the user on every call:

X-MCP-User-Id: user-123

Miso records it beside every search and every full-text read, so your usage report splits by user. Use an opaque id rather than an email address. It can be up to 256 characters, with no control characters.

The user id is your assertion. Miso trusts it because it arrives with your secret key, which only your server holds. With a publishable key the call is refused with 401.


Add a filter in Lucene syntax as ?fq=:

POST https://api.askmiso.com/v1/ask/mcp?fq=tags:public

The filter can only narrow. Miso adds it as one clause and keeps its own filter for your app on top, so a call never reaches content your app's setup excludes. Your gateway chooses the filter per call, so it can give each user the slice their plan allows.

Brackets must open and close within the filter, and phrases and regular expressions must close. A filter that breaks either rule, ends with a backslash, or runs past 2,000 characters is refused with 400 before any search runs. ?type= still works beside it, and the two combine.


Full text follows the same rules

The detail tool only returns a passage that a search surfaced in the last hour. Behind the proxy, that rule also checks the filter, the ?type= and the user. A passage that one user's search returned is not readable under another user, or under a different filter. So the filter you set on search holds for full text too.

Keep the same fq, type and user id on the search and on the detail call that follows it. Your gateway does this naturally when it sets them per user.


The gateway

In the following gateway, we sign the user in, pick their filter, and forward the call:

import os
import httpx
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse

MISO_MCP = "https://api.askmiso.com/v1/ask/mcp"
MISO_SECRET_API_KEY = os.environ["MISO_SECRET_API_KEY"]

app = FastAPI()
client = httpx.AsyncClient(timeout=60)


def sign_in(request: Request) -> dict:
    """Your own check. Return the user, or raise."""
    user = your_auth.user_from(request)  # whatever your site already uses
    if not user:
        raise HTTPException(401, "Sign in first")
    return user


@app.post("/mcp")
async def mcp(request: Request) -> JSONResponse:
    user = sign_in(request)
    reply = await client.post(
        MISO_MCP,
        params={"fq": "tags:public"} if user["plan"] == "free" else {},
        headers={
            "Authorization": f"Bearer {MISO_SECRET_API_KEY}",
            "X-MCP-User-Id": user["id"],
        },
        json=await request.json(),
    )
    return JSONResponse(reply.json(), status_code=reply.status_code)

your_auth.user_from stands for whatever your site already does to recognise a signed-in user: a session cookie, a token, or your auth library.


When something is refused

Status Message Cause
401 MCP token required A publishable key with no MCP token. Use the secret key.
401 Invalid MCP token A token was sent and it is not this app's. With the secret key, send no token.
401 MCP is not enabled for this app MCP is not set up for this app. Contact Miso.
401 A user id is only accepted with the secret API key A user id arrived with a publishable key.
400 Names the problem with the filter A bracket, phrase or regular expression does not close. Or the filter ends with a backslash, or is too long.
200, JSON-RPC error Names the header or parameter user_id or fq was sent in the body.

Next

  • Tools API — when your own MCP server speaks the protocol and calls Miso for search.
  • MCP — Internal Use — the search and detail tools in full.