Tools API

You host the MCP server. Miso does the searching.

Which setup?

Tools API (this page)

Your MCP server calls Miso tools.

Use the Tools API when you run your own MCP server and want Miso search as one of its tools. Your server keeps the transport, the tool names and the sign-in. Miso answers two HTTP calls:

  • GET /v1/ask/mcp/tools lists the tools.
  • POST /v1/ask/mcp/tools/{name} runs one tool.

The search tool takes a question in the words of a reader. It returns ranked passages from your archive, and each passage has a title, a date and a URL to cite.

Below we make both calls, then put them inside a working MCP server.


How a call travels

Your server owns both sign-in and the MCP protocol. Miso sees plain HTTP calls and returns JSON.


The two calls

Call What it gives you
GET /v1/ask/mcp/tools Every tool your app exposes, with a JSON Schema for its arguments.
POST /v1/ask/mcp/tools/{name} The result of running one tool.

Both live on https://api.askmiso.com. Both take your Secret API Key, in either header:

X-API-Key: YOUR_SECRET_API_KEY
Authorization: Bearer YOUR_SECRET_API_KEY

Without a key the reply is 401. The key is a server credential, so these calls belong on your back end.


List the tools

curl -s https://api.askmiso.com/v1/ask/mcp/tools \
  -H "X-API-Key: YOUR_SECRET_API_KEY"
{
  "tools": [
    {
      "name": "ask_your_site",
      "description": "Search the Your Site archive — news, analysis and reference.",
      "input_schema": {
        "type": "object",
        "properties": { "question": { "type": "string" } },
        "required": ["question"]
      }
    },
    {
      "name": "get_your_site_article",
      "description": "Get the full text of a specific snippet returned by ask_your_site.",
      "input_schema": {
        "type": "object",
        "properties": {
          "product_id": { "type": "string" },
          "offset": { "type": "integer" }
        },
        "required": ["product_id", "offset"]
      }
    }
  ],
  "version": "1.0.0"
}

Two tools come back. A search tool, named for your brand, and a detail tool that returns the full text of one result. The names and descriptions are the ones Miso set up with you, so ask_your_site above stands for your own tool name.

input_schema is JSON Schema. Hand it to your MCP server as the tool's schema rather than retyping it, and a change Miso makes reaches your agent on your next start.


Run a tool

Arguments go in an arguments object:

curl -s https://api.askmiso.com/v1/ask/mcp/tools/ask_your_site \
  -H "X-API-Key: YOUR_SECRET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"arguments": {"question": "What happened at the 2012 Olympics opening ceremony?"}}'
{
  "success": true,
  "result": "{\"query\": \"...\", \"results\": [ ... ], \"citation_format\": \"...\"}"
}

result is a string. It holds JSON, so parse it once more before you read it. Returning it as a string is what an MCP client expects to receive, so a server that passes it straight through needs no parsing at all.

Inside, a search result carries the fields your agent cites from:

Field What it is
product_id The article. Pass it back to get_snippet_detail.
offset Which passage of that article matched. get_snippet_detail needs it too.
product_title The headline, for the citation.
url Where the citation points.
published_date The date, for recency questions.
snippet_preview The passage that matched, to show or to judge relevance.
relevance_score How well it matched, between 0 and 1.

get_snippet_detail takes product_id and offset from a result and returns the same fields plus content, the full text.


Wrap them in your server

The whole integration is one function that forwards a call, plus one tool declaration for each Miso tool. In the following server, we expose both tools under our own names:

import os, requests
from mcp.server.mcpserver import MCPServer

MISO_BASE = "https://api.askmiso.com/v1/ask/mcp/tools"
HEADERS = {"X-API-Key": os.environ["MISO_SECRET_API_KEY"]}

mcp = MCPServer("your-site")


def call_miso(name, arguments):
    response = requests.post(
        f"{MISO_BASE}/{name}", headers=HEADERS, json={"arguments": arguments}, timeout=60
    )
    response.raise_for_status()
    body = response.json()
    if not body["success"]:
        raise RuntimeError(body["error"])
    return body["result"]


@mcp.tool()
def search(question: str) -> str:
    """Search our archive. Returns snippets with titles, dates and URLs."""
    return call_miso("ask_your_site", {"question": question})


@mcp.tool()
def get_article(product_id: str, offset: int) -> str:
    """Return the full text of one result from search."""
    return call_miso("get_your_site_article", {"product_id": product_id, "offset": offset})


if __name__ == "__main__":
    mcp.run()

Your agent now sees search and get_article on your server. The tools keep Miso's behaviour, and the name, the description and the transport are yours.

Your key stays on your server, and your subscribers never hold a Miso credential. Access is yours to control: put your own API key or your own sign-in in front of this server, and it decides who gets in.


Narrow a connection to content types

Add type= to either call, and the tools answer only from those content types. The value is a comma-separated list, matched exactly as your content is indexed:

curl -s "https://api.askmiso.com/v1/ask/mcp/tools?type=COURSE,SERVICE" \
  -H "X-API-Key: YOUR_SECRET_API_KEY"

Send the same parameter on the call that runs a tool. A listing narrowed this way also says so in the tool description, so the agent knows what it is searching. This is how one app serves two servers: a wide one for your own team, and a narrow one for a customer-facing agent.


Narrow a call with a filter

Add a filter in Lucene syntax as ?fq= on the call that runs a tool:

curl -s "https://api.askmiso.com/v1/ask/mcp/tools/ask_your_site?fq=tags:public" \
  -H "X-API-Key: YOUR_SECRET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"arguments": {"question": "What changed in the 2024 rules?"}}'

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 that your app excludes. Your server chooses the filter for each call. For example, it can give each user only the content that their plan allows.

Brackets must open and close within the filter, and phrases and regular expressions must close. If a filter breaks one of these rules, ends with a backslash, or is longer than 2,000 characters, the call is refused with 400. No search runs. ?type= still works beside ?fq=, and the two combine.

The listing call reads ?type= only. ?fq= changes the results, and the tool descriptions stay the same.


Tell Miso who the user is

Send your own id for the user on the call that runs a tool:

X-MCP-User-Id: user-123

You can also send it as ?user_id=. If you send both, the header wins. Miso records the id with each search and each full-text read, so your usage report splits by user. Use an opaque id, such as a database key, and do not use an email address. The id can be up to 256 characters, with no control characters.

The user id is your assertion. Miso accepts it only with your Secret API Key. With a publishable key, the call is refused with 401.

Put ?fq= on the URL and the user id in the header. If you put fq or user_id next to arguments in the body, the call is refused with 400. The error message tells you where the value goes.


Full text follows the same rules

get_snippet_detail returns the full text of a result only when the two calls match. The call to get_snippet_detail must have the same user id, the same ?fq= and the same ?type= as the search that returned the result. The search must also be less than one hour old.

If one of these is different, the call still answers "success": true, but result holds "error": "Snippet not accessible". Search again with the same parameters. In the server above, add the parameters in call_miso, so that both tools send them.


When something fails

What you get What happened
400 ?fq= is not valid, or fq or user_id is in the body. detail says which.
401 No key, a key that is not this app's secret key, or a user id with a publishable key.
404 with Tool not found The tool name is not one from the listing.
200 with "success": false The tool ran and failed. error says why.

The third one is the one to handle deliberately. A missing required argument arrives here, with success: false and a message naming the argument. Raise it as a tool error in your server so the agent can correct itself and try again.


Next