docsOperate

Connecting an Editor

How the MemQL extension for Visual Studio Code and Cursor signs in to a cluster, what it needs from that cluster (nothing), who is allowed to do it, and what to do when it refuses.

The short version: an editor connects to a cluster with no configuration on either side. If that is not what you are seeing, the troubleshooting section names the two failures worth recognising.


Materializer collects the brief, sources, and output settings. Its finished files open directly in the chosen editor for review and editing. ZIP outputs use the regular authenticated download path and remain intact.

The two flows, and when each engages

FlowWhat happensWhen it runs
Browser code flowThe extension binds a loopback listener on an ephemeral port, opens https://identity.<domain>/authorize in your browser, and receives the authorization code back on that portThe default, on a laptop or a desktop
Device flow (RFC 8628)The extension shows a short code; you approve it in a browser on any device; the extension polls for the resultAutomatically, when the loopback path cannot serve

You do not choose between them. The extension tries loopback, and falls back to the device flow when the host cannot do loopback -- either because no port would bind, or because there is no browser to open. Remote-SSH, Codespaces and dev containers are the ordinary case for the fallback: the browser runs on a different machine from the extension host, so a callback to 127.0.0.1 reaches the wrong computer.

Both flows end the same way: an OAuth authorization code, redeemed at POST /oauth/token for an access token and a refresh token, which the extension stores for you.

VS Code for the Web uses device approval with PKCE; a browser extension cannot bind a native loopback listener. Enter the cluster domain in MemQL: Add Cluster, then sign in. The web extension keeps its cluster list in the editor profile and credentials in VS Code SecretStorage. Native local-cluster installation remains a desktop operation. Desktop and web support macOS and Linux; Windows is outside the supported platform matrix.


The built-in client: memql-vscode

Every identity node carries the editor as a compiled-in first-party OAuth client. There is no operator step: a cluster serves it on the day it is installed, and MEMQL_IDENTITY_OAUTH_DCR_ENABLED has nothing to do with it.

PropertyValueWhy
client_idmemql-vscodeFixed. A released extension carries this string, so changing it strands every editor already installed
Display nameMemQL for Visual Studio Code and CursorWhat the consent page shows
Redirect URIhttp://127.0.0.1/callbackLoopback only, and portless -- see below
Client typePublic (no secret)The extension ships to every user's machine, so a baked-in secret would be a secret in name only
PKCERequired (S256)What actually binds the authorization code to the process that asked for it
Sign-in accessA recognized cluster roleProductivity and development share one connection; individual operations keep their own permissions

The portless redirect URI is load-bearing. The loopback listener takes whatever ephemeral port the kernel hands it, so the URI the browser returns to carries a different number every sign-in. RFC 8252 section 7.3 grants an any-port exception to a registered loopback URI with no explicit port; one that carries a port opts back into exact matching. A port added to that value would break every callback -- and the failure would appear on the second sign-in, not the first.

Why this is not dynamic client registration

POST /register (RFC 7591) is for clients nobody configured in advance: a third-party MCP connector added from someone else's product. It is an unauthenticated write, and the caller chooses the client_name a human is later shown when asked to approve access, so it is off by default and belongs only on clusters that expose an MCP surface. The reasoning is in identity-service.md.

First-party editors never use it. The editor ships with the product, so identity knows it the way it knows any operator-configured relying party. The extension makes zero requests to /register, and enabling DCR neither helps nor hinders editor sign-in.


Who may connect

The MemQL extension connects both development tools and Productivity Tools. Readers and writers can sign in without being made developers.

RoleMay connect an editorTypical authority after connecting
owneryesCluster ownership and authorized document work
adminyesAdministration and authorized document work; no DSL authoring by default
developeryesDevelopment and authorized document work
writeryesView, edit, and comment on authorized documents
readeryesView authorized documents
assignable custom roleyesOnly the capabilities granted to that role
missing or unrecognized rolenoAsk an owner or admin to check account access

Signing in does not change a role or grant additional capabilities. Backend checks still govern each operation and each document. A reader cannot save a file or add feedback merely because the editor connected. A writer cannot manage a cluster or author DSL merely because Productivity Tools shares the core extension's connection. Organization and row access remain in force. Native cluster installation is available only on supported desktop hosts; it is a separate user action, not a side effect of connecting to another cluster.

Every refusal writes an audit event (editor_signin_refused_role, category identity) carrying the client id, the role required and the role held.

Two things the floor does not do:

  • It does not touch any other client. Static clients from MEMQL_IDENTITY_REGISTERED_CLIENTS and self-registered DCR clients are unaffected, so the console and every MCP connector behave exactly as before.
  • It does not re-check on token refresh. The floor runs at approval time, when a human is present and can be told why. A role lowered after sign-in takes effect at the person's next sign-in; to cut an existing session immediately, revoke it (identity's own /me/devices, or v1:identity:authSession).

An operator who needs different policy shadows the client id in MEMQL_IDENTITY_REGISTERED_CLIENTS -- a static entry replaces the built-in whole, and carries no floor. That is an explicit, visible act rather than a default nobody reviewed.


The clusters.yaml fields that matter

The desktop registry lives at ~/.memql/clusters.yaml. MemQL and Cockpit share cluster definitions, with a cross-process lock and atomic writes that preserve unknown fields. Selecting an editor connection does not redirect a Cockpit worker or change its enrollment.

FieldWhat it isWho writes it
domainThe cluster's domain. Everything else derives from ityou
issuerThe identity service URL. Defaults to https://identity.<domain>you, only for a non-standard front door
endpointThe gRPC front door. Defaults to api.<domain>:443you, only for a non-standard front door
token / refresh_tokenCredentials belonging to another registry client, when presentThe client that owns them; editor sign-in uses SecretStorage
client_idAnother tool's OAuth client (the Cockpit writes cockpit). The editor does not use itthe tool that owns it

Set a name and a domain, then run MemQL: Sign In. Editor access and refresh tokens stay in VS Code SecretStorage; a locked or unavailable secret store produces an actionable error instead of falling back to a plaintext file. Signing out of the editor clears its credentials, leaving Cockpit's session and worker connection alone.

The editor always signs in as memql-vscode. clusters.yaml is shared with the MemQL Cockpit, which records its own client there (client_id: cockpit, registered for the Cockpit's own callback path). A client_id in the file belongs to the tool that wrote it, so the editor never signs in or refreshes with it. It keeps the client each refresh token was issued to beside that token in its own secret storage, and presents it on refresh.

MemQL Productivity Tools consumes the core extension's versioned connection API. It has no separate sign-in, cluster registry, or credential store. An open document retains the cluster and connection it came from; switching clusters cannot retarget its save. A reconnect requires comparing the latest revision before a stale edit can be saved. See productivity tools.


Troubleshooting

.../register returned 403: registration_disabled

You are on a cluster that predates this feature. The extension you are running is old enough to self-register, or the cluster is old enough not to carry the built-in client. The message reads like an instruction to enable registration; it is not. Enabling MEMQL_IDENTITY_OAUTH_DCR_ENABLED would open an unauthenticated write endpoint to work around a problem that has a better answer.

Update the engine and the extension. If you cannot yet, use the interim workaround.

"This cluster doesn't accept sign-in from VS Code."

Before it opens a browser, the editor asks the identity service whether it accepts the sign-in request, the way MemQL OS does. This message is its answer when identity refused the client or its redirect URI. The cluster does not carry memql-vscode (the engine predates the built-in client), or a shadowing MEMQL_IDENTITY_REGISTERED_CLIENTS entry lost the portless redirect URI (see below). Identity logs each refusal at INFO as authorize refused, with the client id and redirect URI, and MemQL OS now shows the refusal's own heading ("Invalid redirect URI", "Unknown client") rather than "Bad Request".

The cluster does not carry memql-vscode: the engine predates the built-in client. Update the engine, or use the interim workaround.

Invalid redirect URI

The registered redirect URI has stopped being portless, which breaks the RFC 8252 any-port exception. If you shadowed memql-vscode in MEMQL_IDENTITY_REGISTERED_CLIENTS, your entry's redirectURIs must contain http://127.0.0.1/callback with no port.

Sign-in was refused and named my role

That is the role floor. Ask a cluster owner or admin to check that your account has a recognized cluster role. Readers and writers can connect; changing a role to developer is not necessary for document work.

dial ... failed (missingCredential) after signing in

Sign-in stored a token and the dial found none, which means the two are looking at different cluster entries -- usually a duplicate name in clusters.yaml. Check that the entry you signed in to is the one that is selected.


Interim workaround for clusters that predate this feature

A cluster running an engine older than the built-in client can still serve the editor today: configure memql-vscode as a static client, which every released engine already supports.

On the identity deployment:

text
MEMQL_IDENTITY_REGISTERED_CLIENTS='[{"clientId":"memql-vscode","redirectURIs":["http://127.0.0.1/callback"],"name":"MemQL for Visual Studio Code and Cursor"}]'

The portless redirect URI is load-bearing here for the reason given above: with a port, RFC 8252's any-port matching does not apply and every callback fails validation.

On the cluster entry in ~/.memql/clusters.yaml:

yaml
clusters:
- name: production
domain: example.com
clientId: memql-vscode

clientId here makes an extension old enough to self-register skip /register entirely. A current extension ignores it and signs in as memql-vscode regardless.

Two caveats. This is a full replacement of the client rather than a pre-seeding of it, so MEMQL_IDENTITY_REGISTERED_CLIENTS must list every other static client the cluster needs in the same JSON array. And the role floor does not exist on those releases -- any role that can sign in at all can connect an editor.

Once the cluster carries the built-in client, remove both: the env var, so the floor applies, and the clientId override, so the default is what runs.


See also

Materializer's Compose and template-binding forms require an organization. Saving a completed email composition as a recipe retains that organization, the email output type, and the selected reference files. Explicit content and image-inclusion choices are preserved: an inspiration image does not silently become an image to embed. Query sources resolve again when the recipe runs; reference files are read through the runner's current cluster permissions.

Include images in a generated email

In Create Email from Examples, first choose the organization and references. In Images to include, select logos or photos that should appear in the email; leave screenshots of example layouts unselected. Selecting a ZIP here makes its supported image members eligible for inclusion. Ordinary ZIP opening still only downloads it intact.

MemQL reads and freezes the selected bytes before AI work starts. The resulting editable HTML carries included images, so preview and scheduled sends do not need permission-bearing Library URLs or a separate image host. Source, Preview, and Split continue to share the same draft. The human still saves or publishes the reviewed Campaigns revision; generation never sends a message.

Included PNG/JPEG/GIF images are bounded to eight images and 1 MiB total; the complete email template remains bounded to 2 MiB. Larger example images can remain design references. SMTP/Graph sending converts embedded images to inline attachments; ACS uses CID attachments. Microsoft currently labels ACS inline attachments a preview feature; validate rendering with your recipient clients before enabling production sending. See Microsoft's inline attachment documentation.