Preview — Token Scoping is in preview. API surface may change.
Join our Discord for support and feedback.
Service tokens let you safely expose Smithery to browsers, mobile apps, and AI agents without leaking your API key. Each token carries constraints that restrict which namespaces, resources, operations, and metadata it can access.
For general Smithery Connect setup, see Smithery Connect.
Scope a Token to a User
When your app serves multiple users, you’ll want each user’s token to only access their own connections. You do this by tagging connections with metadata (e.g., { userId: "user-123" }) when you create them, then creating a token with the same metadata constraint. The token will only be able to see connections whose metadata matches.
smithery auth token --policy '[{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "userId": "user-123" },
"ttl": "1h"
}]'
const { token, expiresAt } = await smithery.tokens.create({
policy: [
{
namespaces: 'my-app',
resources: 'connections',
operations: ['read', 'execute'],
metadata: { userId: 'user-123' },
ttl: '1h',
},
],
})
// Send `token` to your client — safe for browser use
curl -X POST https://api.smithery.ai/tokens \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"policy": [
{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "userId": "user-123" },
"ttl": "1h"
}
]
}'
This token can list and call tools on connections in my-app where metadata.userId is user-123 — nothing else. Even if the client tries to access another user’s connection, the request is denied.
You can also match on multiple metadata fields at once. Fields within a single metadata object are AND’d, so the token below only matches connections where both userId and tier match:
smithery auth token --policy '[{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "userId": "user-123", "tier": "pro" },
"ttl": "1h"
}]'
const { token } = await smithery.tokens.create({
policy: [
{
namespaces: 'my-app',
resources: 'connections',
operations: ['read', 'execute'],
metadata: { userId: 'user-123', tier: 'pro' },
ttl: '1h',
},
],
})
curl -X POST https://api.smithery.ai/tokens \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"policy": [
{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "userId": "user-123", "tier": "pro" },
"ttl": "1h"
}
]
}'
Multi-Level Access (Workspace / Org)
Real apps often have multiple access levels. For example, a user should see:
- Their own connections
- Connections shared with their workspace
- Global connections configured by an admin
Pass multiple constraints in the policy array. Each constraint is an independent grant — the token can access anything that matches any of them.
smithery auth token --policy '[
{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "userId": "user-123" },
"ttl": "1h"
},
{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "workspaceId": "ws-acme" },
"ttl": "1h"
},
{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "scope": "global" },
"ttl": "1h"
}
]'
const { token } = await smithery.tokens.create({
policy: [
// Grant 1: user's own connections
{
namespaces: 'my-app',
resources: 'connections',
operations: ['read', 'execute'],
metadata: { userId: 'user-123' },
ttl: '1h',
},
// Grant 2: workspace-shared connections
{
namespaces: 'my-app',
resources: 'connections',
operations: ['read', 'execute'],
metadata: { workspaceId: 'ws-acme' },
ttl: '1h',
},
// Grant 3: global connections (admin-configured)
{
namespaces: 'my-app',
resources: 'connections',
operations: ['read', 'execute'],
metadata: { scope: 'global' },
ttl: '1h',
},
],
})
curl -X POST https://api.smithery.ai/tokens \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"policy": [
{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "userId": "user-123" },
"ttl": "1h"
},
{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "workspaceId": "ws-acme" },
"ttl": "1h"
},
{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "scope": "global" },
"ttl": "1h"
}
]
}'
The token holder sees connections matching any of the three grants. This replaces the need for separate tokens per access level.
For this to work, tag your connections with the right metadata when you create them:
- User connections:
metadata: { userId: 'user-123' }
- Workspace connections:
metadata: { workspaceId: 'ws-acme' }
- Global connections:
metadata: { scope: 'global' }
Narrow a Token
You can create a narrower token from an existing service token. The new token can only have equal or fewer permissions — it cannot exceed the parent token’s scope.
This is useful when your backend holds a broad token and needs to hand out more restricted tokens per request. For example, using the multi-level token from the previous section as the starting point:
# Narrow the broad token to just one user's connections
SMITHERY_API_KEY=$BROAD_SERVICE_TOKEN smithery auth token \
--policy '[{
"resources": "connections",
"operations": "read",
"metadata": { "userId": "user-123" },
"ttl": "20m"
}]'
// Broad token from earlier — covers user, workspace, and global connections
const smitheryWithBroadToken = new Smithery({ apiKey: token })
// Narrow it to just this user's connections, read-only, shorter TTL
const { token: userToken } = await smitheryWithBroadToken.tokens.create({
policy: [
{
resources: 'connections',
operations: 'read',
metadata: { userId: 'user-123' },
ttl: '20m',
},
],
})
// userToken can only read user-123's connections —
// the workspace and global grants from the parent are excluded
# Narrow the broad token to just one user's connections
curl -X POST https://api.smithery.ai/tokens \
-H "Authorization: Bearer $BROAD_SERVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"policy": [
{
"resources": "connections",
"operations": "read",
"metadata": { "userId": "user-123" },
"ttl": "20m"
}
]
}'
When to use this: Your backend mints one broad token at startup (e.g., all connections in a namespace). Per request, it narrows that token for the specific user or context before passing it to client code.
Operation Scoping
Control what operations a token can perform on each resource.
| Resource | Operations | Description |
|---|
connections | read | List and get connections |
connections | write | Create and delete connections |
connections | execute | Call MCP tools through a connection |
servers | read, write | Server metadata and configuration |
namespaces | read, write | Namespace management |
Read-Only Dashboard Token
smithery auth token --policy '[{
"namespaces": "my-app",
"resources": "connections",
"operations": "read",
"ttl": "1h"
}]'
const { token } = await smithery.tokens.create({
policy: [
{
namespaces: 'my-app',
resources: 'connections',
operations: 'read',
ttl: '1h',
},
],
})
curl -X POST https://api.smithery.ai/tokens \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"policy": [
{
"namespaces": "my-app",
"resources": "connections",
"operations": "read",
"ttl": "1h"
}
]
}'
Execute-Only Agent Token
smithery auth token --policy '[{
"namespaces": "my-app",
"resources": "connections",
"operations": "execute",
"metadata": { "userId": "user-123" },
"ttl": "30m"
}]'
const { token } = await smithery.tokens.create({
policy: [
{
namespaces: 'my-app',
resources: 'connections',
operations: 'execute',
metadata: { userId: 'user-123' },
ttl: '30m',
},
],
})
curl -X POST https://api.smithery.ai/tokens \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"policy": [
{
"namespaces": "my-app",
"resources": "connections",
"operations": "execute",
"metadata": { "userId": "user-123" },
"ttl": "30m"
}
]
}'
Multi-Resource Token
A single token can grant access to multiple resources by passing multiple constraints in the policy array:
smithery auth token --policy '[
{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "userId": "user-123" },
"ttl": "1h"
},
{
"namespaces": "my-app",
"resources": "servers",
"operations": "read",
"ttl": "1h"
}
]'
const { token } = await smithery.tokens.create({
policy: [
{
namespaces: 'my-app',
resources: 'connections',
operations: ['read', 'execute'],
metadata: { userId: 'user-123' },
ttl: '1h',
},
{
namespaces: 'my-app',
resources: 'servers',
operations: 'read',
ttl: '1h',
},
],
})
curl -X POST https://api.smithery.ai/tokens \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"policy": [
{
"namespaces": "my-app",
"resources": "connections",
"operations": ["read", "execute"],
"metadata": { "userId": "user-123" },
"ttl": "1h"
},
{
"namespaces": "my-app",
"resources": "servers",
"operations": "read",
"ttl": "1h"
}
]
}'
MCP Request Matching (Experimental)
Unstable — MCP request matching is experimental. The rpcReqMatch field shape may change in future releases.
By default, a token with connections:execute can invoke any tool through a connection. The rpcReqMatch field lets you restrict which JSON-RPC requests a token can make, gating at the request level.
Match rules inspect the JSON-RPC body. Keys are dot-paths (e.g. params.name targets the tool name in a tools/call request). Values are regex patterns. All entries within a constraint are AND’d.
const { token } = await smithery.tokens.create({
policy: [
{
namespaces: 'my-app',
resources: 'connections',
operations: 'execute',
metadata: { userId: 'user-123' },
rpcReqMatch: { 'params.name': '^(search|get_page)$' },
ttl: '1h',
},
],
})
// This token can only call the "search" and "get_page" tools — nothing else
curl -X POST https://api.smithery.ai/tokens \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"policy": [
{
"namespaces": "my-app",
"resources": "connections",
"operations": "execute",
"metadata": { "userId": "user-123" },
"rpcReqMatch": { "params.name": "^(search|get_page)$" },
"ttl": "1h"
}
]
}'
Mixed Permissions: Read + Restricted Execute
A token that can list connections freely but only execute specific tools requires two constraints — one for read access and one for restricted execute:
const { token } = await smithery.tokens.create({
policy: [
// Grant 1: list connections (no rpcReqMatch restriction)
{
namespaces: 'my-app',
resources: 'connections',
operations: 'read',
metadata: { userId: 'user-123' },
ttl: '1h',
},
// Grant 2: execute only "search" tool
{
namespaces: 'my-app',
resources: 'connections',
operations: 'execute',
metadata: { userId: 'user-123' },
rpcReqMatch: { 'params.name': '^search$' },
ttl: '1h',
},
],
})
curl -X POST https://api.smithery.ai/tokens \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"policy": [
{
"namespaces": "my-app",
"resources": "connections",
"operations": "read",
"metadata": { "userId": "user-123" },
"ttl": "1h"
},
{
"namespaces": "my-app",
"resources": "connections",
"operations": "execute",
"metadata": { "userId": "user-123" },
"rpcReqMatch": { "params.name": "^search$" },
"ttl": "1h"
}
]
}'
Match Patterns
Match values are regex patterns. Use anchors (^...$) for exact match:
| Pattern | Example Values | Meaning |
|---|
"^search$" | "search" | Exact match |
"^(search|get_page)$" | "search", "get_page" | One of several values |
"^create_" | "create_issue", "create_user" | Prefix match |
".*" | anything | Match all |
Match entries are AND’d within a constraint. Multiple rpcReqMatch keys mean the request must satisfy all of them:
rpcReqMatch: {
'params.name': '^create_issue$',
'params.arguments.repo': '^my-org/my-repo$',
}
// Only allows tools/call where name is "create_issue" AND repo is "my-org/my-repo"
Dot-separated paths match into the JSON-RPC request body. For a tools/call request like {"method":"tools/call","params":{"name":"search","arguments":{"query":"test"}}}, the path params.name matches "search" and params.arguments.query matches "test".
Constraint Reference
The policy array contains constraints — each one is a self-contained grant describing what the token can access.
interface Constraint {
namespaces?: string | string[]
resources?: 'connections' | 'servers' | 'namespaces' | 'skills'
| ('connections' | 'servers' | 'namespaces' | 'skills')[]
operations?: 'read' | 'write' | 'execute'
| ('read' | 'write' | 'execute')[]
metadata?: Record<string, string>
| Record<string, string>[]
rpcReqMatch?: Record<string, string> // experimental — regex patterns for MCP request matching
ttl?: string | number // e.g., "1h", "30m", "20s", 3600
}
Two rules govern every constraint:
- Adding a field narrows (AND). Each field adds a condition. More fields = more restrictive.
- Adding to a list widens (OR). Each list element adds an alternative. More elements = more permissive.
// Adding fields narrows the grant
{ resources: 'connections' }
// → any operation on connections
{ resources: 'connections', operations: 'read' }
// → only read connections
{ resources: 'connections', operations: 'read', metadata: { userId: 'user-123' } }
// → only read user-123's connections
// Adding to a list widens the grant
{ operations: ['read', 'write'] }
// → read OR write
{ metadata: [{ userId: 'user-123' }, { workspaceId: 'ws-acme' }] }
// → userId=user-123 OR workspaceId=ws-acme
When you pass multiple constraints in the policy array, each is an independent grant. The token can access anything matching any constraint.
# Two grants: read alice's connections OR read/write servers
smithery auth token --policy '[
{
"resources": "connections",
"operations": "read",
"metadata": { "owner": "alice" }
},
{
"resources": "servers",
"operations": ["read", "write"]
}
]'
// Two grants: read alice's connections OR read/write servers
const { token } = await smithery.tokens.create({
policy: [
{
resources: 'connections',
operations: 'read',
metadata: { owner: 'alice' },
},
{
resources: 'servers',
operations: ['read', 'write'],
},
],
})
# Two grants: read alice's connections OR read/write servers
curl -X POST https://api.smithery.ai/tokens \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"policy": [
{
"resources": "connections",
"operations": "read",
"metadata": { "owner": "alice" }
},
{
"resources": "servers",
"operations": ["read", "write"]
}
]
}'
Metadata within a single object is AND’d: { owner: 'alice', env: 'prod' } means owner is alice and env is prod. Use a list for OR: [{ owner: 'alice' }, { env: 'prod' }] means owner is alice or env is prod.
Security Best Practices
- Always set a TTL. Tokens expire after the TTL (max 24 hours). Shorter is better — mint fresh tokens per session.
- Scope to the minimum needed. A token for calling tools only needs
connections:execute, not connections:write.
- Use metadata for row-level filtering. Don’t rely on connection IDs alone — metadata constraints are enforced server-side.
- Narrow before passing to untrusted code. If you hand a token to a browser, agent, or sandbox, restrict it to the specific user and operations needed.
- Tokens cannot mint other tokens from nothing. Only API keys or existing tokens can create tokens, and child tokens can never exceed their parent’s scope.