Skip to main content

Map tools to scopes

This is the slow step. Every other step is mostly mechanical; this one is design work that you do once and refine over time.

What the UI does

The Tools tab shows your inventory as a grid. For each tool you pick a scope from a dropdown, or mark it Public, or leave it Unmapped. The header counters at the top of the page — Mapped / Public / Unmapped — update as you go.

The activation preview at the top of the Launch tab pulls from the same data:

TotalMappedPublicUnmapped
1015196

In this example, 96 unmapped tools is blocking activation. You can't launch until every tool is either mapped or explicitly public.

The four states

StateWhat it means at runtime
MappedTool requires the configured scope on the token. SDK returns 403 insufficient_scope if missing.
PublicNo auth check on this tool. Even unauthenticated requests pass through. The SDK still validates the token if one is present — but a missing token is OK. Use sparingly.
UnmappedTool exists in inventory but cannot be called by anyone. SDK returns method_not_found / 403. This is the safe default — explicit allow is required.
Admin overrideAdmin manually set a mapping that differs from the SDK's suggested mapping. Survives manifest republishes — the SDK's suggestion never overwrites an admin override.

The Unmapped default is intentional: tools you haven't thought about yet are denied by default, not allowed. This matches OAuth best practice — explicit grant only.

How the data flows

The Tools tab calls PUT /authsec/resource-servers/:id/tool-scope-map when you change a mapping. The payload looks like:

{
"mappings": [
{"tool_name": "actions_get_workflow", "scope_id": "rs-xyz:tools:read"},
{"tool_name": "actions_create_pr", "scope_id": "rs-xyz:tools:write"},
{"tool_name": "health_check", "public": true}
]
}

Stored in mcp_tool_scope_maps with source = 'admin_override'. The Go SDK at runtime fetches GET /authsec/resource-servers/:id/sdk-policy, which compiles the live mappings into a flat tool→scope map.

Bulk mapping

The UI doesn't yet have a bulk-edit interface. For now, use the API directly with the admin JWT:

TOKEN="..."  # admin JWT from the auth flow
RS="525da3b4-4206-4070-ad68-90cc3a6de43b"

# Map all tools starting with actions_get_ to the read scope
curl -X PUT "https://api.authsec.dev/authsec/resource-servers/$RS/tool-scope-map" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mappings": [
{"tool_name": "actions_get_workflow", "scope_id": "rs-xyz:tools:read"},
{"tool_name": "actions_get_run", "scope_id": "rs-xyz:tools:read"},
{"tool_name": "actions_get_artifacts", "scope_id": "rs-xyz:tools:read"}
]
}'

You can script the same call from your local terminal using a saved JWT — see the auth headers your browser's devtools shows when you load the Tools tab.

What re-scan does to mappings

When you re-scan and a tool's inputSchema changes, the mapping is preserved (same tool name = same mapping). When a tool is renamed (different name), the old mapping points at a tool that no longer exists; it shows up as a stale mapping in the Tools tab. The activation preview ignores stale mappings.

Removing a mapped tool entirely (delete from the server, re-scan) doesn't delete the mapping row — it's preserved as audit trail. The Tools tab marks the row as referencing a missing tool.

Public tools — when to use

Public should be rare. Cases where it makes sense:

  • Health checks. A health or ping tool that returns server status with no business data.
  • Capability discovery. A tool that returns a list of supported features but no protected data.
  • Public read APIs. If your MCP server is fronting a public-by-design dataset (e.g. open weather data), some tools genuinely don't need scopes.

Things that should never be public:

  • Anything that modifies state.
  • Anything that returns user-specific data.
  • Anything that wraps an authenticated upstream API.