POST https://dash.dmly.io/mcp) and there are two
ways to authenticate to it: sign in with OAuth, or paste a dmly_ API key. That choice
also decides what the connection is allowed to do, so read MCP overview first if
you have not.
Which method
Connect over OAuth
DMLY publishes everything a client needs to register itself: the discovery documents at/.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource, dynamic
client registration at /oauth/register, and the authorization-code flow with PKCE (S256) plus
refresh tokens. Nothing has to be created in DMLY first; you give the client the address and it
does the rest. There is one scope, mcp:use, and the client is issued it automatically; it is not
something you pick, and it decides nothing on its own.
https:// callback always works; that is
what claude.ai and other hosted clients use. A native desktop client that registers a
private-use scheme instead (RFC 8252) is accepted for claude://, cursor:// and vscode://;
any other scheme is refused at registration with 400 invalid_redirect_uri, before you ever see
a consent screen./oauth/register allows 10 registrations an hour from one
address, inside a 200-an-hour ceiling across the platform that a caller rotating addresses cannot
sidestep. Past either, it answers 429 with {"error":"too_many_requests"} and Registration is
rate limited. A real client registers once per install, so this only bites a script in a loop:
register once and keep the client_id you were issued rather than re-registering per connection.Give the client the server address
https://dash.dmly.io/mcp is the same address for every workspace, including one whose people
reach DMLY on an agency’s own domain. In a hosted client this is the “custom connector” or
“remote MCP server” field; wording differs by client and changes with releases, so follow its
own documentation for where that lives.Sign in to DMLY
Choose the workspace
redirect_uri, the screen also
shows the host that code would be sent to: Access will be sent to host, followed by “If
you don’t recognise that address, close this page instead of connecting.” Read that line rather
than the name at the top. Registration is open by design, so the client name is whatever the
registering party typed and proves nothing; the callback host is the one element of the screen
a phisher cannot fake, which is why it is there.Ask it something read-only
What an OAuth connection may do
There is no scope to pick on the consent screen, because the one scope a connection carries (mcp:use) authorises nothing by itself. Your team role is the whole gate: the connection acts
as you, and gets the tools your role would let you use in the dashboard.
get_workspace_overview, which is the first call the
server tells every client to make. A connection missing it answers not found on that call and
carries on with the rest.
tools/list, and
calling it by name anyway answers not found, because the server resolves a call through the
same eligibility check as the listing. Change someone’s role and their connection changes with
it; nothing has to be reconnected.contacts.view_phone or contacts.view_email, phone numbers and email addresses come back
masked over MCP exactly as they do in the dashboard, so a connection really is bounded by what
its owner can see, PII included. The same holds on an API key, where the acting member is the one
who generated it. Only the REST API is unmasked.Connect with an API key
1. Mint a key with MCP access
MCP access is opt-in per key. A key with no scopes reaches the REST API and nothing else, which is where every key minted before the MCP server existed still sits.Open the API keys page
Name it after the client
Choose what this key can reach
Copy the key now
dmly_.Copy the endpoint
/mcp. That is what the client connects to.Read or read-and-act
This is the choice that matters on a key, and it is enforced by not existing rather than by refusing.mcp:readregisters up to 16 tools. The 21 write tools are not intools/listat all, so the model cannot call one, cannot hallucinate having called one, and cannot be talked into it. Nothing it does reaches a customer.mcp:writeregisters up to all 37, and implies read. It can message customers, take and cancel bookings, raise and send invoices, record payments and switch automations on or off, inside the safety model.
- Narrowing that member’s role narrows the key, on its next call. The role is read live on every request, so nothing has to be reissued.
- Removing them from the workspace stops the key at
/mcpentirely. Every request answers403with a message saying the key’s creator is no longer a member, and asking an admin for a new key. - Revoking the key is the only lever that stops it everywhere. The first two leave the key’s
REST API access untouched:
/api/v1neither resolves a member nor refuses a departed one, so the key keeps working there whatever happens to the person.
2. Give the client the address and the key
The server speaks Streamable HTTP: JSON-RPC 2.0 overPOST to /mcp. The key goes in a
header. Either works, and they are the same two the REST
API accepts:
x-api-key wins.
Most desktop and editor clients are configured from a JSON file that maps a server name to a
URL and headers. The shape below is the common one for a remote MCP server:
/mcp address, and the key as a header.
Verify it works
The cheapest check is that the endpoint is reachable and enforcing auth. With no credential it must answer401:
mcp:read, 37 on mcp:write, which is the fastest way
to confirm the key carries the scope you meant it to. A key generated by a custom role returns fewer,
because that role’s permissions apply as well. Over OAuth the count is whatever your role grants: 37
for an admin, 33 for a plain member, 15 for a viewer. A real client will run an initialize
handshake before this; curl is only here to prove reachability and auth.
Then ask the client a read-only question before you let it do anything: how many conversations
are open? The server’s own instructions tell it to call get_workspace_overview first, so a
correctly connected client answers with your business name and channel list.
Troubleshooting
Everything below is a plain JSON body, not an MCP protocol error, on purpose: a client that cannot get past these never completes a handshake, and a readable HTTP body is what surfaces in its connection dialog. Most come from/mcp itself; the 400 and the first 429 happen earlier,
at /oauth/register, while the client is registering itself.
It connected, but some tools are missing
It connected, but some tools are missing
mcp:read, and write tools are never registered for a
read-scoped key. Generate one at REST API + let an AI assistant read and act and move the
client onto it. If it already has that level, the limit is the team role of the member who
generated the key, whose permissions the key resolves: a key generated by a custom role gets only
that role’s tools, and it loses more if the role is narrowed later. Regenerate it as an admin, or
widen that role.Over OAuth: your team role does not grant those tools. A Viewer gets reads only; a Member
does not get the team-diary appointment tools by default. Ask an Admin to change your role or
give you a custom role with the permissions you need. See Roles and
permissions.It connected, but sees no contacts or conversations
It connected, but sees no contacts or conversations
Sends are being refused with a message about a limit
Sends are being refused with a message about a limit
A message was refused because the window is closed
A message was refused because the window is closed
send_template_message with an approved template is the only way through. Read
the dmly://reference/templates resource for the exact names and variable counts. See
Message templates.A broadcast was 'submitted' but nobody received it
A broadcast was 'submitted' but nobody received it
submit_broadcast creates the broadcast awaiting approval and
returns sent: false; someone has to approve it in Broadcasts before it goes out. The AI
cannot bypass this. See Create a campaign.Cutting off a client that has gone wrong
Cutting off a client that has gone wrong
/mcp and on the REST API, and it is the only lever with that reach. Two others bite on MCP
alone, which is what you want when the key also drives REST integrations you need left running:
narrow the role of the member who generated it, and the tools you are worried about stop being
registered, or remove them from the workspace, and /mcp refuses the key outright. Both are
re-checked on every request, and neither changes what the key can do on /api/v1.Over OAuth, the levers that bite immediately are on the DMLY side: remove the person from the
workspace, or narrow their role so the tools you are worried about stop being registered.
Both are re-checked on every request. Deleting the connector in the client is worth doing too,
but it is not a revocation: DMLY has no page listing OAuth connections and no revocation
endpoint, so an access token already issued stays valid until it expires, up to 15 days later.
