Skip to content

50% off your first year. Join before 2027. Claim →

Declare which tools need sign-in

This check verifies that every tool that needs sign-in says so in its definition, with a securitySchemes array, and that no tool declares two different things.

Check id declared_schemes (declared schemes). Results:

pass warn skipped

A tool definition in tools/list can carry a securitySchemes array: {"type": "noauth"} for a tool anyone can call, {"type": "oauth2", "scopes": [...]} for a tool that needs a token with those scopes, or both for a tool that works anonymously and does more after sign-in. The shape comes from SEP-1488, which is still an open draft.

  • ChatGPT reads securitySchemes on each tool to learn which tools need OAuth and which can run anonymously. Its sign-in prompt for a tool needs both halves: the declaration, and a tool error carrying _meta["mcp/www_authenticate"] when the tool is called without a token. Without both, ChatGPT does not show the linking UI for that tool (OpenAI: Authentication).
  • Claude reads no tool-level declaration. It learns that a tool needs sign-in only from the HTTP 401 when the tool is called (Claude: Lazy authentication). A declaration costs nothing there.
  • The MCP specification defines no tool-level declaration, so other agents are not expected to read it.

The warning does not stop sign-in. It means ChatGPT users cannot tell, before calling a tool, that it needs an account, and cannot get a sign-in prompt for it.

MCP Tower reads the tool list of the scan, merged from the anonymous pass and, when you scanned signed in, the signed-in pass. It makes no tool calls.

  1. Each tool gets an auth value. A tool needs sign-in (required or optional) when it declares oauth2, declares scopes in authorization.scopes, appears only to signed-in clients (see hidden tools), or the whole server answered 401 or 403 without a token.
  2. A declaration is read from the top-level securitySchemes field or from _meta.securitySchemes. When both are present and differ, the top level wins and the tool is listed as a conflict.
  3. authorization.scopes (SEP-1880, which was closed and not adopted) counts as declared oauth2 scopes, but it is not a securitySchemes declaration, so such a tool still warns.
  4. Every tool that needs sign-in without a securitySchemes declaration, and every conflict, is named in a warn. Otherwise the result is pass, with the number of tools that need sign-in.
  5. With no tools known, for example when the whole server needs a token and nobody has scanned signed in, the check is skipped.

A server where every request needs a token warns for every tool: the tools need sign-in, but they do not say so.

StatusMessageMeaningFix
passEvery protected tool declares securitySchemes (<n>).Every tool that needs sign-in declares it. <n> is how many such tools there are, and can be 0.
warnThese tools need sign-in but declare no securitySchemes: <tools>.ChatGPT cannot tell that these tools need an account.Declare securitySchemes
warnThese tools declare different securitySchemes at the top level and in _meta (the top level wins): <tools>.Two declarations disagree, and agents may read either one.Keep one declaration per tool
warnThese tools need sign-in but declare no securitySchemes: <tools>. These tools declare different securitySchemes at the top level and in _meta (the top level wins): <tools>.Both problems at once.Both sections below
skippedNo tools known yet.No pass listed any tools. Select Sign in to scan if your server needs a token for tools/list.

The next step for every warning is: “Add securitySchemes to these tools so ChatGPT can show ‘Requires sign-in’ before calling them, and keep one declaration per tool.”

  1. List the tools the warning names. For each, decide whether it needs a token always (oauth2) or works anonymously and does more after sign-in (noauth and oauth2).
  2. Add a top-level securitySchemes array to the tool’s definition, with the scopes the tool needs on the oauth2 entry.
  3. Keep checking the token in the tool itself. The declaration is a hint for agents, not access control.

The tool as tools/list should return it:

tools/list result (one tool)
{
"name": "get_order",
"description": "Get one of your orders.",
"inputSchema": {
"type": "object",
"properties": { "id": { "type": "string" } }
},
"securitySchemes": [{ "type": "oauth2", "scopes": ["orders:read"] }]
}

nginx does not build tool definitions. Add the declaration in your server code, as in the other tabs, and let nginx pass tools/list through unchanged.

If a tool only uses authorization.scopes, add securitySchemes with the same scopes. You can keep authorization.scopes for agents that read it.

Some servers put the declaration in _meta.securitySchemes, which OpenAI’s documentation does not describe. If a tool has both a top-level field and a _meta copy that differ, make them identical, or better, keep only the top-level field.

List the tools without a token, as an agent does before sign-in. Servers on protocol 2025-11-25 need an initialize first and the Mcp-Session-Id it returns.

Terminal window
curl -s https://mcp.example.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

If your server needs a token for tools/list, send the same request with -H 'Authorization: Bearer <access-token>'. Follow nextCursor until it is absent. The problem is fixed when every tool that needs sign-in has a top-level securitySchemes, and no tool’s _meta.securitySchemes differs from it:

"securitySchemes": [{ "type": "oauth2", "scopes": ["orders:read"] }]

Then select Scan again on the server’s page.