Skip to content

Hosted MCP servers and OAuth

Nothing in an existing mcp.json has to change. Three keys are new and optional, per server:

key what it does
auth OAuth settings: clientId, clientSecret, tokenAuthMethod, clientIds (per issuer), clientMetadataUrl (CIMD), redirectUri, mode (auto | pause), omitRefreshResource, allowHttp (tests only)
toolSearch true replaces the server’s tools with <server>_search_tools and <server>_call_tool
description what the server is for; the search tool’s description uses it
{
"mcpServers": {
"docs": {
"url": "https://mcp.example.com/mcp",
"auth": { "clientId": "YOUR_CLIENT_ID" },
"toolSearch": true,
"description": "the product docs"
}
}
}

The authorization server has to know your app. toolnexus uses, in order:

  1. a pre-registered clientId (or clientIds[<issuer>]), with clientSecret if the app has one;
  2. a Client ID Metadata Document URL (clientMetadataUrl), when the server advertises CIMD support.

Dynamic Client Registration is not supported, on purpose. The MCP spec has deprecated it, and GitHub, for example, supports neither DCR nor CIMD, so a registered clientId is the universal answer. With neither available you get an error that names the issuer and tells you to set auth.clientId.

  • auto (default): toolnexus opens the browser with openUrl and takes the callback on http://127.0.0.1:<random>/callback. Good for CLIs and desktop apps.
  • pause: no browser. Your host receives a §10 request {kind: "authorization", id: "auth-…", url} through waitFor. Send the person to url; when the authorization server redirects to your redirectUri, answer with {id, ok: true, data: {callbackUrl: "<the full URL it redirected to>"}}. The paused tool call then runs once. Hosts that already answer URL elicitation get this for free.

Tokens go into a tokenStore (default: in memory, per load). They are refreshed before they expire, re-requested with a wider scope on insufficient_scope, and never written to logs or error text.

import { Toolkit } from "toolnexus"
const toolkit = await Toolkit.create({
mcpConfig: "mcp.json",
waitFor, // MRTR input + pause-mode authorization
tokenStore, // { load(resource), save(resource, token), delete(resource) }
openUrl: (url) => open(url), // auto mode; default is the OS opener
})

A 2026 server can answer tools/call with input_required (“what name?”). toolnexus turns each question into a §10 request through your waitFor: a form becomes kind: "input" (the JSON schema is in data.schema), a URL becomes kind: "authorization". Without a waitFor the call returns {status: "pending"}, and resuming it continues from the round it stopped at, so earlier answers are never asked again.

toolnexus never re-sends a tools/call after a timeout, a 5xx or a dropped connection, because the server may already have run it. It re-sends only after a 401 or a 403 insufficient_scope, which a server returns before it runs anything.

A server that offers the MCP skills extension adds its skills to the same skill tool as your local skills/ folder. Each download is checked against its sha256 digest and must stay inside the skill.

With "toolSearch": true the model sees two tools for that server instead of hundreds:

  • docs_search_tools {query, limit?} returns a JSON array of the best matches (name, description, inputSchema, server), ranked by BM25 over the tool name and description.
  • docs_call_tool {name, arguments} runs one of them. The result is the same as a direct call.

Your per-server tools filter and disableTools apply first, so a hidden tool can be neither found nor called.

2026-07-28 server older server
tools (list, call, filters, naming) yes yes, the client you had before
questions mid-call (input_required / elicitation) yes, via waitFor yes, via waitFor
OAuth login yes yes in JS, Python, Java, C#; Rust, Elixir and Clojure send the token as a fixed header, without refreshing it mid-session
skills over MCP yes no
tool search yes yes
list_mcp_resources / read_mcp_resource not yet yes
progress notifications no (dropped by the 2026 spec) yes
roots, sampling, logging, Tasks, DCR no no

Serving a toolkit as a 2026-07-28 MCP server, with auth protecting it, is the next step; until then serve /mcp speaks the previous protocol. Contract: SPEC §2 and §2A.