Skip to main content

What is a tool manifest?

If a scope is the unit of permission, a tool manifest is the inventory of things you might want to permit.

This page explains what a tool manifest is, why AuthSec needs one, and what happens when you publish it.

The problem

Your MCP server exposes a bunch of tools — list_repos, read_pr, comment_pr, merge_pr, delete_repo. AuthSec wants to enforce per-tool policy: "merging requires the pr.merge scope", "deleting requires repo.admin", and so on. To do that, AuthSec needs to know which tools exist on your server.

How does it find out?

Three options:

  1. You type each tool into the admin UI by hand. Tedious. Stale the moment you ship a new tool.
  2. AuthSec calls your server and asks. Doable for MCP (the tools/list JSON-RPC method exists), but breaks the moment your server requires auth on that endpoint.
  3. Your application pushes the list to AuthSec on startup. Authoritative, never stale, no special endpoints needed.

The third option is the tool manifest.

The definition

A tool manifest is the list of tools your application exposes, published to AuthSec as a single PUT request on application startup.

Each entry in the manifest carries:

  • tool_id — the canonical name of the tool. The same string the AI client sends in tools/call. Stable; don't rename.
  • description — what the tool does, in one sentence. Shown to operators in the admin UI, sometimes shown to end users on the consent screen.
  • scopes_required — the scopes needed to call this tool. AI clients without these scopes get a 403 with scope_insufficient.
  • input_schema (optional) — JSON Schema for the tool's arguments. AuthSec doesn't enforce against this today, but it's recorded for the admin UI and reserved for future per-argument policy.

The manifest is a flat array of these entries. ~3 lines per tool.

A concrete example

For the GitHub MCP server from the scopes explainer, the manifest published by the SDK looks like this:

[
{ "tool_id": "list_repos", "description": "List the user's repositories.", "scopes_required": ["repo.read"] },
{ "tool_id": "read_pr", "description": "Read a pull request by ID.", "scopes_required": ["repo.read"] },
{ "tool_id": "comment_pr", "description": "Comment on a pull request.", "scopes_required": ["pr.comment"] },
{ "tool_id": "merge_pr", "description": "Merge a pull request.", "scopes_required": ["pr.merge"] },
{ "tool_id": "delete_repo", "description": "Permanently delete a repository.", "scopes_required": ["repo.admin"] }
]

That's the whole manifest. Five tools, five scopes mapped.

The lifecycle

Watch what happens end to end.

  1. You write your application with the AuthSec SDK installed. In the mount_mcp (Python), MountMCP (Go), or mountMCP (TypeScript) call, you pass an array of tools.

  2. You restart your application. The SDK reads your tool array and PUTs it to AuthSec's manifest endpoint (AUTHSEC_MANIFEST_URL) using Basic auth with your introspection secret. This happens once on startup; it doesn't repeat per request.

  3. AuthSec stores the manifest. Each entry becomes a row in the admin UI's Tools tab on your Application detail page. Operators see the inventory immediately.

  4. An operator reviews the manifest. They can adjust scope mappings, mark tools as deprecated, or hide them. If a tool is in the manifest but has no scope mapping yet, it's denied by default — the application stays safe.

  5. An AI client calls tools/list. The SDK returns only the tools the caller's token has scopes for. Tools the user can't call don't even appear in the list.

  6. An AI client calls tools/call with a tool name. The SDK checks the manifest entry's scopes_required against the token's scopes. Match → run. No match → 403 with scope_insufficient and the missing scope listed.

  7. You ship a new tool. You add it to the array, redeploy. The SDK publishes the updated manifest; the admin UI shows the new row; operators wire its scope mapping; it's enforced.

That's it. You write tools, the SDK publishes them, operators define policy, the SDK enforces policy.

Why a manifest and not live tools/list

AuthSec also supports discovering tools by calling tools/list against your server (called "scan" in the admin UI). It's the fallback path, used when:

  • The application is on legacy code without the SDK.
  • The application's tools/list is unauthenticated and AuthSec can reach it.
  • You want a quick peek at what an unprotected server exposes.

The manifest path is preferred because:

  • Authority. The SDK knows the tools authoritatively; the scan has to infer from a JSON-RPC response.
  • No special endpoints. Your server doesn't need to expose tools/list to AuthSec unauthenticated.
  • It survives auth. Once the SDK is mounted, tools/list is OAuth-protected like every other tool call. Scans against it return 401. The manifest path doesn't care.
  • Schemas and descriptions. The manifest can carry richer metadata than tools/list returns over the wire.

For an operator's view of which path was used and how to switch, see Tool inventory and discovery.

What's in the entry, in detail

{
"tool_id": "merge_pr",
"description": "Merge a pull request after passing required checks.",
"scopes_required": ["pr.merge"],
"input_schema": {
"type": "object",
"properties": {
"repo": { "type": "string" },
"pr_number": { "type": "integer" }
},
"required": ["repo", "pr_number"]
}
}
  • tool_id is the string the AI client invokes. It's also the join key in the manifest — re-publishing with the same tool_id updates the existing row. Renaming a tool effectively deletes the old one and creates a new one (operators have to re-wire its scope mapping); avoid renames.

  • description is plain English, one sentence. This is what shows up in the operator's Tools tab and (for high-risk tools) in the consent screen the user sees. Write it for a non-technical reader; if you wouldn't put it in front of a customer, rewrite.

  • scopes_required is an array of scope IDs. The semantics are any — possessing any one of them lets the call through. (If you want all of two scopes, declare a third scope that bundles them and require that.) Map to the coarsest scope that still captures the intent.

  • input_schema is optional JSON Schema. Recorded for the admin UI; reserved for future per-argument policy (e.g. "this merge_pr call requires step-up MFA only when force=true").

Publishing the manifest

The SDK handles publishing automatically when you mount_mcp with publish_manifest=True (Python), PublishManifest: true (Go), or pass tools to mountMCP (TypeScript). The wire format is:

PUT {AUTHSEC_MANIFEST_URL}
Authorization: Basic <base64(rs_id:introspection_secret)>
Content-Type: application/json

[
{ "tool_id": "...", "description": "...", "scopes_required": [...] },
...
]

The endpoint replaces the existing manifest wholesale. If you ship 5 tools today, publish a new build with only 4, the 5th tool is dropped from the inventory.

You can also publish manually from a script — useful for CI smoke tests. See the SDK FAQ for the curl recipe.

What a manifest is not

  • A manifest is not the same as your tool registration. Your application registers its tool handlers with the MCP framework (FastMCP, the MCP SDK, etc.); the AuthSec manifest is a parallel registration that tells AuthSec what scopes those handlers need. The two lists should agree, but they're maintained separately.

  • A manifest is not enforced unilaterally. A tool that appears in the manifest but doesn't have an access-policy mapping yet is denied by default (scope_insufficient). Adding to the manifest is half the work; the other half is the access policy.

  • A manifest is not user-visible by default. End users don't see the manifest. They see the consent screen, which lists scope names — not tool names. Tool names show up only in the operator UI.

  • A manifest is not append-only. Re-publishing replaces. To drop a tool, remove it from the array on the next deploy.

Common mistakes

  • Publishing on every request instead of on startup. The SDK protects against this by default. If you call the publish function in a hot path, you'll rate-limit yourself and the admin UI will show "manifest drift" warnings.

  • Renaming a tool without updating the manifest. The AI client sends the new name, the manifest still has the old name, the SDK doesn't find a match → tool_not_found. Renames are coordinated: ship the new tool with a new ID, deprecate the old one, retire after one release.

  • Forgetting scopes_required. A tool with no scopes required is denied by default by AuthSec (it's safer to fail closed). The admin UI flags these as "needs scope mapping" on the Tools tab.