Authentication and Security
How Semaphor MCP identifies each caller, and how your security policies, SQL access and write permissions apply to every call.
Every MCP query passes through the same security enforcement as your dashboards. Semaphor builds a caller profile from the credential on each request, then applies your existing policies, with no MCP-specific configuration.
Credentials
Semaphor MCP accepts two credentials, sent on every request as Authorization: Bearer <token>. Which one you use depends on who is calling.
| Caller | Credential | Reach |
|---|---|---|
| You, in Claude Code, Codex, claude.ai, ChatGPT or Cursor | OAuth login with your Semaphor account | Every project you belong to |
| Automation (CI, schedulers, scripts) | MCP project token for an organization user | One project, fixed by the token |
| An agent in your product, acting for an end user | MCP project token for a tenant user | One project and that user's tenant |
Hiding a tool from a caller is for clarity, not the security control. The server still authorizes every call against the caller's role, scopes and permissions.
OAuth Login
People in an MCP client sign in with their Semaphor account in the browser, and the client stores and refreshes the login. OAuth login is for organization users. See Connect an AI Client for each client's steps.
Scopes
| Scope | Grants |
|---|---|
mcp:read | Every read tool: discovery, queries and dashboards. Required for every request. |
mcp:write | The change tools that create dashboards and add relationships. |
A client that doesn't ask for a scope gets mcp:read only. To use the change tools, the client must request mcp:read mcp:write at login. Write scope is never enough on its own: the change tools also need an Author role or higher, and your permissions still decide what you can change.
OAuth Endpoints
Most clients discover these from the server's metadata. They're listed for client developers; on a self-hosted install, replace https://semaphor.cloud with your Semaphor URL.
| Parameter | Value |
|---|---|
| MCP endpoint | https://semaphor.cloud/api/mcp |
| Protected resource metadata | https://semaphor.cloud/.well-known/oauth-protected-resource |
| Authorization server metadata | https://semaphor.cloud/.well-known/oauth-authorization-server |
| Authorization endpoint | https://semaphor.cloud/api/mcp/oauth/authorize |
| Token endpoint | https://semaphor.cloud/api/mcp/oauth/token |
| Client registration | https://semaphor.cloud/api/mcp/oauth/register (dynamic client registration) |
| Grant types | authorization_code, refresh_token |
| PKCE | S256 |
| Scopes | mcp:read, mcp:write |
MCP Project Tokens
Automation and agents in your product use a short-lived token your backend mints with the project secret. Minting, request fields and mint errors are on Connect with a Token.
Isolation
Organization
- Every caller is scoped to its organization. Cross-organization access isn't possible.
- Interactive callers reach only projects they belong to, checked on each call that passes a
projectId. - Token callers are pinned to the token's project. Another
projectIdreturns an error.
Tenant
- An end-user token acts for one end user in one tenant, with the token's security settings applied.
- Tenant callers never get change tools, and an end-user token can't be minted with write scope.
- Dashboard access summaries list only the rules that reach the caller's tenant.
Security Policies
Semaphor enforces three layers of policy on every query, from every query tool, including semaphor_query_sql_advanced:
| Policy | What it controls |
|---|---|
| CLS (connection-level) | Which connections a caller can use |
| SLS (schema-level) | Which schemas, tables and columns are visible |
| RLS (row-level) | Which rows are returned, through filters injected into the SQL |
Two callers can get different numbers for the same question. That's expected: each sees only their own data.
SQL Access
SQL and physical table discovery reach your database more directly than the semantic model, so they're limited:
- Organization users need an Author role or higher. Viewers stay on the semantic model.
- Tenant users have SQL unless the token turns it off.
- Any token minted with
"mcp": { "sql": false }has no SQL or physical discovery.
Some tools every caller sees also accept arguments that reach physical tables, such as physical mode on semaphor_get_dataset_schema. Without SQL access, those arguments return capability_required, and the semantic form of the tool keeps working.
Semantic Domain Access
When you mint a token, semanticDomainAccess controls which semantic domains the agent can see:
{
"projectId": "p_...",
"projectSecret": "<project-secret>",
"tenantId": "tenant_123",
"endUserEmail": "maria@acme.com",
"mcp": { "scopes": ["mcp:read"] },
"semanticDomainAccess": { "mode": "include", "domains": ["sales", "marketing"] }
}| Mode | Behavior |
|---|---|
all | Every semantic domain (default) |
none | No semantic domains |
include | Only the listed domains |
exclude | Every domain except the listed ones |
A token whose domain access excludes every domain doesn't fall back to physical tables: semaphor_get_analysis_context reports recommendedPath: "restricted".
Permission and Approval
Writes raise two separate questions: may this caller write, and has someone approved this particular change?
Permission: enforced by Semaphor
A change needs the mcp:write scope, an Author role or higher, the per-kind credential rule (semantic model changes need an interactive login), and the caller's project permissions. Every change is previewed, then applied exactly as previewed.
Approval: a client behavior
semaphor_apply_change is marked destructive and semaphor_propose_change read only. These hints don't force a confirmation, and Semaphor can't prove a person saw the preview. Whether you're asked depends on your client: see Making Changes.
Headless write tokens are delegated authority. Minting a token with mcp:write approves that automation's writes in advance, with no per-change human approval. Grant write scope only to jobs that need it, with the shortest practical tokenExpiry.
Session Security
- Logins are tied to the user's Semaphor account. The transport is stateless, and project access is checked on every call.
- Tokens are signed, carry their project, identity, scopes and SQL setting, and expire within 24 hours. A token that fails verification gets
401with "MCP project token is invalid or expired." - Proposals live 30 minutes and can only be applied by the user who proposed them, in the same project.
Best Practices
- Grant read unless you need writes. Add
mcp:writeonly for automation that creates dashboards. - Turn off SQL for end users who don't need it. Set
"sql": falseso the agent stays on the semantic model. - Limit domains. Use
semanticDomainAccessto show the agent only the domains it needs. - Keep tokens short-lived. Mint per job or per user session, with the shortest
tokenExpirythat works. - Prefer
semaphor_analyze. It uses governed semantic domains, relationship diagnostics, and all three policy layers. - Store secrets safely. Use environment variables or a secret manager, never commit tokens or project secrets, and keep MCP traffic on HTTPS.