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:
Why it matters
Section titled “Why it matters”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
securitySchemeson 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
401when 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.
What MCP Tower checks
Section titled “What MCP Tower checks”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.
- Each tool gets an
authvalue. A tool needs sign-in (requiredoroptional) when it declaresoauth2, declares scopes inauthorization.scopes, appears only to signed-in clients (see hidden tools), or the whole server answered401or403without a token. - A declaration is read from the top-level
securitySchemesfield or from_meta.securitySchemes. When both are present and differ, the top level wins and the tool is listed as a conflict. authorization.scopes(SEP-1880, which was closed and not adopted) counts as declaredoauth2scopes, but it is not asecuritySchemesdeclaration, so such a tool still warns.- Every tool that needs sign-in without a
securitySchemesdeclaration, and every conflict, is named in awarn. Otherwise the result ispass, with the number of tools that need sign-in. - 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.
Results
Section titled “Results”| Status | Message | Meaning | Fix |
|---|---|---|---|
| pass | Every 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. | |
| warn | These tools need sign-in but declare no securitySchemes: <tools>. | ChatGPT cannot tell that these tools need an account. | Declare securitySchemes |
| warn | These 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 |
| warn | These 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 |
| skipped | No 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.”
How to fix
Section titled “How to fix”Declare securitySchemes
Section titled “Declare securitySchemes”- 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 (noauthandoauth2). - Add a top-level
securitySchemesarray to the tool’s definition, with the scopes the tool needs on theoauth2entry. - 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:
{ "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.
server.registerTool( "get_order", { description: "Get one of your orders.", inputSchema: { id: z.string() }, securitySchemes: [{ type: "oauth2", scopes: ["orders:read"] }], }, getOrder,);GET_ORDER = { "name": "get_order", "description": "Get one of your orders.", "inputSchema": {"type": "object", "properties": {"id": {"type": "string"}}}, "securitySchemes": [{"type": "oauth2", "scopes": ["orders:read"]}],}
@app.post("/mcp")async def mcp(request: Request): body = await request.json() if body.get("method") == "tools/list": return {"jsonrpc": "2.0", "id": body["id"], "result": {"tools": [GET_ORDER]}} ...If a tool only uses authorization.scopes, add securitySchemes with the same scopes. You can
keep authorization.scopes for agents that read it.
Keep one declaration per tool
Section titled “Keep one declaration per tool”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.
Check it yourself
Section titled “Check it yourself”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.
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.
References
Section titled “References”- SEP-1488: securitySchemes in tool metadata, open draft
- SEP-1880: tool-level scope requirements, closed
- OpenAI: Authentication, “Triggering authentication UI”
- Claude: Lazy authentication, “Answer a protected call with 401 before the MCP SDK runs”
- MCP authorization
2026-07-28, which defines call-time challenges but no tool-level declaration