MCP server
Connect an AI agent to a practice over the Model Context Protocol, with OAuth discovery, dynamic registration and three generic tools.
Each regional API serves a remote Model Context Protocol server, so an AI agent can operate a practice through the same API the rest of this reference describes. Sign-in is automatic: the agent discovers Smileline's OAuth server, registers itself and sends the person through the usual sign-in, practice picker and consent. For the practice-facing steps, see the AI agents guide.
Endpoint
| Region | MCP server URL |
|---|---|
| Europe | https://api-eu.smileline.io/mcp |
| United States | https://api-us.smileline.io/mcp |
The server speaks stateless Streamable HTTP: POST /mcp only (GET and
DELETE answer 405). It accepts OAuth access tokens only — an API key, a
session cookie or a session bearer is refused. A request carrying an Origin
header is refused with 403 (the MCP rule against DNS rebinding); supported
clients call from a server or a native app.
Discovery
A request without a valid token answers 401 with:
WWW-Authenticate: Bearer resource_metadata="https://api-eu.smileline.io/.well-known/oauth-protected-resource/mcp", scope="read write offline_access"| Document | URL (Europe) |
|---|---|
| Protected resource metadata (RFC 9728) | https://api-eu.smileline.io/.well-known/oauth-protected-resource/mcp |
| Authorization server metadata (RFC 8414) | https://api-eu.smileline.io/.well-known/oauth-authorization-server/auth |
The protected resource's resource is the MCP server URL and its
authorization_servers names the issuer https://api-eu.smileline.io/auth.
Both documents are public and cacheable.
Registration
Clients register with dynamic client registration (RFC 7591) at
POST /auth/oauth2/register, without authentication. Smileline registers every
such client the same way, whatever the request asks for:
- a public client (
token_endpoint_auth_method: "none") with PKCES256; - grants
authorization_codeandrefresh_token; - scopes
read,writeandoffline_access; client_nametrimmed to 80 characters; logos, URIs and contacts are dropped, and the consent screen marks the app Unverified app.
A registration names one to three redirect URIs, and each must be one of:
| Client | Redirect URI |
|---|---|
| Claude (web, desktop, mobile) | https://claude.ai/api/mcp/auth_callback |
| ChatGPT | https://chatgpt.com/connector_platform_oauth_redirect, or under https://chatgpt.com/connector/oauth/ |
| Cursor | https://www.cursor.com/agents/mcp/oauth/callback, cursor://anysphere.cursor-mcp/oauth/callback |
| VS Code | https://vscode.dev/redirect |
| Tools on the user's computer | http://localhost, http://127.0.0.1 or http://[::1], any port and path |
Any other redirect URI is refused with invalid_redirect_uri, so a stranger
cannot register a look-alike app that sends the practice's approval to their
own site. Registration is limited per client address block, like the other
unauthenticated OAuth endpoints (see Rate limits). A
registration that nobody approves within 24 hours is removed; a client holding
it gets invalid_client and should register again.
Tokens
The authorization code and refresh requests may carry resource (RFC 8707);
it must be the MCP server URL, or the request fails with invalid_target.
Tokens are the ordinary OAuth tokens described under
Authentication: an access token lasts one hour, a
refresh token 30 days and rotates on every use. The consent binds the
connection to one practice, and only owners and managers can give it.
Agency connections
A partner agency's owner or member can instead consent on the agency, for
read, write and offline_access only. Without a practice argument, a
call acts on the agency: GET /partner/practices, POST /partner/practices
and the rest of its account. Every tool also takes practice: an
organizationId from GET /partner/practices. With it, search_operations
lists a practice's operations, and read and write act inside that practice
with the approving person's current role there. Any customer practice currently
linked to the approved, active agency that the person still belongs to can be
reached, including an existing practice linked later or moved from another
agency. Linking a practice does not grant a membership. read or write with
any other id answers 404 PRACTICE_NOT_FOUND. search_operations returns operation
metadata only and does not check the id. GET /partner/practices carries
ownerJoined for each practice: whether an owner from outside the agency has
joined. The link, membership, role and agency standing are checked on every
call, so unlinking, moving the practice away or losing membership ends access
on the next request.
POST /partner/practices needs the practice owner's ownerEmail, which
receives a 48-hour owner invitation. Repeating an identical create answers
200 with the practice it made. An agency's agents can create 100 practices
per rolling 24 hours; past that, the create answers 429
PARTNER_CREATE_LIMIT with Retry-After. POST /leads and managed webhook
subscriptions are not available through an agency connection.
Until the owner joins, POST /partner/practices/:organizationId/owner-invitation
with ownerEmail sends the practice's owner invitation again, or to a
corrected address, and DELETE on the same path withdraws it. Both act on the
agency: call them without practice and put the id in params. These
owner-handover operations reach only practices the agency created that remain
linked to it and that the approving person belongs to; any other id answers 404
PRACTICE_NOT_FOUND. The same address renews the invitation for another 48
hours. A new address first cancels every other pending owner invitation, and
any other pending invitation to that address, so the old link stops working.
The answer carries sent and delivery, the email's state: pending,
publishing, published or parked. A repeat within an hour of the last
email answers sent: false and emails nobody, and so does a repeat while that
email is being handed to the queue. If the last email failed or was parked,
the call sends that same email again rather than a new one: the owner receives
it at most once. DELETE answers canceled, the number withdrawn. Once the
owner has joined, both answer 409 PRACTICE_OWNER_JOINED, and an address
that belongs to a member of the practice answers 409 OWNER_ALREADY_MEMBER.
One address receives at most three owner invitation emails for the same
practice in 24 hours; past that, POST answers 429 OWNER_INVITATION_LIMIT
with Retry-After.
Check a practice's setup
With practice set, read on GET /settings/practice/setup-status returns a
practice's whole setup in one call. It changes nothing: unlike the website
settings page, it never creates the practice's website tracking settings.
| Field | Holds |
|---|---|
guide | The setup guide: which tasks are done and the practice's answers. |
owner | joined, true once the practice has an owner from outside the agency's team, and the newest owner invitation: email, status (pending, expired, accepted, rejected or canceled), expiresAt and the state of its email (delivery). |
team | How many members the practice has and how many invitations are still open (pendingInvitations). |
website | The website checklist: tracking script seen, forms found, test lead received, mapping confirmed. |
leadAds | Each connected ad platform and its status. |
conversions | Conversion setup for each ad platform. |
emailDomains | The practice's sending domains and their verification. |
pendingAgreements | Documents the practice has not accepted in their current version. An owner accepts them in Smileline; no connection can. |
For a practice the agency created, while joined is false, send the owner
invitation again with
POST /partner/practices/:organizationId/owner-invitation when it is
expired or its newest email is parked (it may not have gone out). Only
owners, managers and Agency members can read the setup status; any other role
gets 403.
Tools
| Tool | Does |
|---|---|
search_operations | Finds API operations by keyword, ten per page with nextOffset. Each result carries its operation id and input JSON Schema. |
read | Runs a GET operation: { operation, params?, query?, range?, etag? }. |
write | Runs a POST, PUT, PATCH or DELETE operation: { operation, params?, query?, body? }. |
An operation id is its method and path, for example GET /patients/:id. Each
call runs through the regular API route, so it gets the same validation,
permission checks, tenant isolation and audit as a direct request; an API error
comes back as the tool's error result with the API's error body. One tool call
counts as one request against the connection's
300 requests per minute. Send one JSON-RPC message
per request: a batch (a JSON array) is refused with error -32600.
A connection granted only read can search and use read; change operations
are not listed for it and write refuses them.
A reply is returned whole up to 16 MiB. A stored file — an export download, for
example — comes back in parts: the first result carries totalBytes, etag
and nextOffset, and the next read of the same operation passes
range: { offset, length } with that etag. Each part re-checks permissions,
and a changed file answers "start again from offset 0". Text parts always end
on a whole character. A write whose reply is too large to return still
reports the change as applied, with its status.
Not available to agents
- Core Practice Transfer (
/transfer/core-*). - The PMS workspace and clinical referrals, which refuse machine credentials.
- Calls and recordings, which need the
dialerscope no agent holds. - Account-wide surfaces such as
/userand/organization(BROWSER_SESSION_REQUIRED).
Submit for approval
What to have ready before you submit, what a reviewer looks at, and what changes when an app is approved, sent back or disabled.
List patients GET
Paginated list. q is a typo-tolerant (trigram) search over name, email, phones, fiscal code and address (street, city, postal code). An all-digit q also matches the patient number exactly, and a date the date of birth exactly: YYYY-MM-DD, or day and month with a four-digit year, read month first for a US practice and day first elsewhere. `filters` is URL-encoded JSON matching the PatientFilters schema; keys are AND-ed. sort=relevance ranks by similarity to q. embed=tags attaches each row's tags; embed=clinic the clinic's columns (dentist, hygienist, price list, next and last visit, recall, balance), each absent when the caller may not read it. Archived patients are excluded unless includeArchived=true or a status filter asks for archived.