What happens when you connect Versely MCP
Clicking Connect runs OAuth 2.1 with dynamic client registration. Here is what the consent screen grants and where the resulting token is scoped.
The Connect button on a Claude connector is not "save this URL." It starts an OAuth 2.1 authorization-code flow with PKCE, registers Claude as a client on the fly, parks the request for ten minutes, and sends you to a consent page on Versely that lists the scopes about to be granted. Allow, and the client swaps a one-time code for an access token whose audience is the MCP server, not "the whole API." Deny, and the redirect goes back with access_denied and nothing is issued.
If you have used the MCP page setup (Settings → Connectors, paste https://mcp.versely.studio/mcp, Connect, sign in), that is the handshake you already completed. This post is what it did.
The handshake, in the order it actually runs
The authorization server metadata lives at https://api.versely.studio/.well-known/oauth-authorization-server (RFC 8414). Clients read it rather than hardcoding paths. It advertises:
authorization_endpoint:https://api.versely.studio/oauth/authorizetoken_endpoint:https://api.versely.studio/oauth/tokenregistration_endpoint:https://api.versely.studio/oauth/registerresponse_types_supported:codegrant_types_supported:authorization_code,refresh_tokencode_challenge_methods_supported:S256token_endpoint_auth_methods_supported:none,client_secret_postscopes_supported: the eight API scopes (see below)
Dynamic client registration (RFC 7591). Before authorize, the client POSTs /oauth/register with at least one redirect_uris entry. Each URI must be https, or http on localhost. The server inserts an oauth_clients row and returns a client_id prefixed vsly_. If the client asked for a confidential auth method, a client_secret is issued once. Public clients (method none) get an id and no secret, which is the usual MCP connector shape: PKCE is the proof, not a stored secret.
Authorize. GET /oauth/authorize requires response_type=code, a known client_id, a redirect_uri that is on that client's registered list and on the server allowlist, and a PKCE code_challenge with code_challenge_method=S256. Anything else 400s before you see a consent page. The allowlist defaults to https://claude.ai/, https://claude.com/, https://platform.claude.com/, and localhost. A redirect to some other host is rejected even if the client registered it.
The request is parked under a random state id for ten minutes. You are 302'd to https://app.versely.studio/oauth/consent?state=…. If you are not signed in, that page sends you through sign-in and back.
Consent. The page loads /oauth/consent-info?state=… and renders the client name, your email, and the scopes. Allow or Deny posts /oauth/authorize/decision with your session JWT. Deny returns a redirect to the client with error=access_denied. Allow mints a one-time authorization code (five-minute lifetime, stored hashed), then redirects with code. Codes are single-use: a second exchange gets Code already used.
Token. POST /oauth/token with grant_type=authorization_code, the code, the same redirect_uri, the client_id, and the code_verifier. PKCE is verified. If the client also sent a resource URL, it must match https://mcp.versely.studio/mcp (trailing slashes and case are ignored; a literal "undefined" is treated as omitted, which is a real client bug this endpoint already special-cases). The response is an access token, a refresh token, expires_in: 3600, and the granted scope string.
That is the entire Connect click. The MCP session that follows is just Bearer-auth'd tool calls using that access token.
What the consent screen is actually granting
The eight scopes are the same catalog API keys use. The consent screen lists a short English line per scope. What they actually unlock:
| Scope | What the client can do with it |
|---|---|
generate |
Create new media: images, video, audio, music, movies, avatars, B-roll, batch jobs, dubbing |
post |
Create, schedule, list, fetch, and delete social posts |
manage_accounts |
Connect, refresh, list, and disconnect social accounts |
slideshow |
Create and manage slideshows |
ugc |
UGC overlays, captions, TTS, background removal |
workflows |
Run saved workflows, templates, and workflow assets |
analytics |
Social analytics and trend data |
read |
Profile, model registry, agentic routes, media library, fonts, feature flags |
The list on screen is the list the client requested, filtered to names in that table. If the client sent all, it is expanded to the eight. If the client sent nothing, the token falls back to read. read is enough to talk to /agentic and to look at the catalog. It is not enough to generate. If Connect succeeded and every generation tool comes back 403 OAuth token lacks required scope: generate, the consent screen granted a token that cannot spend credits, and the fix is to reconnect with a client that asks for generate (or all), not to paste a different URL.
Allowing consent spends nothing. The next tool call that actually generates will, against the same credit balance as the app. There is no MCP-only wallet, and every generation on the connector costs credits. See credits if the balance is the part you are checking.
Where the resulting token is scoped
The access token is a JWT signed by Versely. Claims that matter:
iss:https://api.versely.studioaud:https://mcp.versely.studio/mcpsub: your user idazp: thevsly_…client idscope: the granted space-separated listexp: one hour from issuetoken_use:access
The audience is the MCP resource, not a wildcard. A token minted for MCP will not satisfy a caller that presents a different aud. On each request, the API verifies the signature, then checks that the route's required scope is in scope. Longest-prefix wins, same as API keys: /generate needs generate, /social/posts needs post, /agentic needs read.
The refresh token is a random 32-byte value, stored hashed, good for 30 days. Refresh rotates: the old token is revoked, a new pair is issued. Reusing a refresh token after rotation fails with Refresh token revoked. Refresh may narrow scope; it may not broaden it. If you need a scope you did not grant at consent, you authorize again. You cannot "upgrade" a read token by asking nicely at the token endpoint.
The MCP server URL you pasted, https://mcp.versely.studio/mcp, is the resource those claims are bound to. The CLI path (versely auth login then versely install) is a different credential shape: a long-lived key on disk, written into agent configs, no consent screen. The skills path is procedures on top of a credential, not a third OAuth dance. Pick one on purpose; CLI, MCP, or API is the decision table.
After Connect, what you can actually call
A working connector means the client can list Versely tools and invoke them as you. Generation tools spend credits. check_credits and estimate_cost do not generate, and they are the first calls worth making in a new session: if read is all you granted, those still work and the generate calls will not.
The connector is a live session. When the access token hits one hour, the client is supposed to refresh. If it cannot (refresh expired, refresh already rotated, client lost the token), Connect again. There is no "remember this machine" cookie that bypasses OAuth; there is a 30-day refresh, and then you are done.
If you are wiring this from a desktop agent rather than claude.ai's connector UI, you are probably not on this handshake at all. The CLI path writes an HTTP MCP entry with a long-lived vsk_ key into the agent's config. Mixing the two (an expired OAuth session and a key in a JSON file) is the usual way a "it was working yesterday" report gets opened. The MCP / API / web UI comparison is the map; this post is only the OAuth door.
FAQ
Does clicking Deny delete anything on my account?
No. Deny sends access_denied to the client and issues no tokens. Your credit balance, your keys, and your conversations are untouched. Connect again when you mean to grant it.
Why did I have to sign in if I was already in Claude?
Claude is not Versely. The consent page runs on app.versely.studio and needs a Versely session so the authorization code is bound to your user id (sub on the token). Without that sign-in, the decision endpoint returns login_required and no code is minted.
Can I grant generate but not post?
Yes, if the client asks for that subset. The server filters requested scopes to the valid list and will not add ones the client omitted. A token with generate and read can make media and cannot publish. That is the split you want for an assistant you do not want posting. If the client only knows how to request all, you get all eight; the fix is a client that sends a narrower scope, not a toggle on the consent page.
How long do I have to click Allow?
The parked authorize request lasts ten minutes. After that, /oauth/consent-info returns expired_or_unknown and the page tells you to go back to the app and try again. The code you get after Allow lasts five minutes and is single-use. If the client is slow to exchange it, Connect again; do not retry the same code.