Authentication
How your AI host signs in to Stensul with OAuth, what a token can do, which scopes to grant, and what a 401 or 403 means.
The Stensul MCP server uses the OAuth 2.0 authorization-code flow. A person signs in to Stensul in their browser, approves the connection, and from then on the AI host acts as that person, with that person’s permissions. There is no API-key or client-credentials path for MCP: there is always a signed-in user behind every call.
The flow
You ──ask──▶ AI host ──(1) no token yet──▶ Stensul MCP server
│ │
│◀──(2) sign-in URL─────────────┘
│
browser ─────▼───(3) log in + approve on your Stensul instance
│
└──(4) code ─▶ host exchanges it for tokens ─▶ (5) every tool call
carries the token
- The host connects to your per-instance endpoint,
https://mcp.stensul.com/<your-instance>/mcp, and finds it needs a sign-in. - It opens Stensul’s authorization URL,
https://mcp.stensul.com/<your-instance>/oauth/authorize. - You log in to your own Stensul instance and approve.
- Stensul redirects back with a code, and the host exchanges it at
https://mcp.stensul.com/<your-instance>/oauth/token. - The host sends the token with every request. When it expires, the host refreshes it or asks you to sign in again.
Hosts that support remote OAuth connectors (Claude, ChatGPT, Writer, Copilot Studio, Claude Code) do all of this for you. The two sign-in tools in the reference exist for hosts that can’t.
Three ways a client identifies itself
| Client type | Used by | What the admin creates | Secret? |
|---|---|---|---|
| Public client with PKCE | Claude, ChatGPT, Claude Code, Gemini Enterprise, Cursor, VS Code, Windsurf, Amazon Quick | An Authorization Code connection with PKCE (Public) on | No, the Client ID is enough |
| Confidential client | Writer, Copilot Studio | An Authorization Code connection with PKCE off | Yes, shown once at creation |
| Dynamic Client Registration | Atlassian Rovo, and Claude Code when it runs without a Client ID | Nothing; the client registers itself | No |
Admins create connections in Admin > Settings > API connections. The connection’s redirect URI must include your instance name: https://mcp.stensul.com/<your-instance>/oauth/callback. A redirect URI without it fails with a mismatch.
What a token can do
Every request passes two independent checks, and both must pass:
| Scope | Permission | |
|---|---|---|
| Belongs to | The connection | The signed-in person |
| Answers | Is this integration allowed to do this at all? | Is this person allowed to do this? |
| Set by | An admin, on the API connection | The person’s role in Stensul |
| Changed in | Admin > Settings > API connections | Admin > Users & Roles |
| Error when missing | 403, Invalid scope(s) provided. | 403, Action is unauthorized. |
The scope is checked first. So the same connection behaves differently for different people: a connection with emails:write lets a campaign manager create emails and gives a read-only user nothing. The MCP can never do more than the person could do in the Stensul app.
Scopes
| Scope | Grants | Tools |
|---|---|---|
libraries:read |
List libraries and read their configuration | stensul-get-libraries |
emails:read |
Read emails, layouts and rendered previews; run quality checks | stensul-list-emails, stensul-read-email, stensul-preview-email, stensul-run-quality-check |
emails:write |
Create emails, edit content and settings, delete modules, copy emails | stensul-build-email, stensul-create-email, stensul-copy-email, stensul-update-email-content, stensul-update-email-settings, stensul-add-email-module, stensul-delete-email-module, stensul-apply-email-variation |
images:write |
Upload images and request upload links | stensul-upload-images, stensul-image-upload-session |
dam:read |
Browse the image gallery | stensul-list-gallery-images (and gallery search in stensul-upload-images) |
dynamic-content:read |
List a library’s Segment Sets | stensul-list-segment-sets |
dynamic-content:write |
Apply Segment Sets and write per-segment content | stensul-apply-segment-set, stensul-update-segment-content |
api-read and api-write are for server-to-server integrations only. They do nothing on an MCP connection, and a connection created with only those two is rejected with Invalid scope(s) provided.
For drafting emails, libraries:read, emails:read, emails:write and images:write cover the usual flow. Add dam:read for the gallery and the two dynamic-content scopes for audience versions.
Grant fewer scopes on purpose
- Read-only integrations (reporting, audits) need
emails:readandlibraries:readand nothing else. - A scope is a hard stop, not an instruction the model can reinterpret. An assistant that should draft copy but never touch assets is a connection without
images:write. - One connection per tool or team lets you revoke one without breaking the others, and the audit trail shows which integration acted.
Changing scopes later
- Adding a scope revokes every token for that connection. People sign in again on their next request and get the new scope.
- Removing a scope takes effect immediately for those capabilities.
Dynamic Client Registration
Clients that self-register get every scope Stensul defines, and a person approves or declines the whole set at sign-in. DCR clients don’t appear in the API connections list and can’t be narrowed. What still limits them is the signed-in person’s permissions. If you need a connection limited to specific scopes, create a static connection and configure the client with its Client ID.
What an admin has to switch on
- The external API on your instance, and the
access_admin_api_connectionspermission for whoever creates connections. Your Stensul customer success manager enables these. - An API connection for each host (or rely on DCR where the host supports it).
- Host-side settings: for Claude, allow
<your-instance>.stensul.comunder Capabilities > Additional allowed domains so image uploads work. - User permissions: the people using it need the same permissions they’d need in the app, for example
edit_campaignto create or edit emails,access_dashboardto list emails, andduplicate_email_between_librariesto copy across libraries.
When a call fails
| Status | What it means | What to do |
|---|---|---|
| 401 | No valid token: it expired or was revoked (for example after a scope was added) | Sign in again. Hosts usually prompt you. The server tells the model to re-check sign-in on any 401 or 403 |
| 403, Insufficient OAuth scope or Invalid scope(s) provided. | The connection lacks the scope | An admin adds it, then everyone signs in again |
| 403, Action is unauthorized. | The person’s role lacks the permission | Grant it in Admin > Users & Roles. Signing in again won’t help |
| 403, Sent emails cannot be edited. | The email was already sent | Copy it and edit the copy |
| 403, Your IP address is not in the allowed list | The external API IP allowlist blocks the caller | An admin updates Settings > External API > Allowed IPs |
| 410 | A client is calling the removed shared /mcp endpoint with headers |
Use your per-instance URL with OAuth |
Quick test: if it fails for everyone on a connection, it’s a scope. If it fails for one person but works for another, it’s a permission. If it works in the Stensul app but not in your AI host, it’s a scope: the app doesn’t use API scopes.
Rate limits
The server limits requests per signed-in identity and, for the OAuth endpoints, per IP address. Stensul hasn’t published the numbers yet. If you hit a limit, slow down and retry.