MCP Server
Drive Tinct from your AI assistant. The Tinct MCP server lets Claude, Claude Code and other MCP-capable tools create campaigns, generate landing pages, edit them and publish them — in a conversation, with your own Tinct account.
https://api.tinct.ai/mcp —
that's the only thing you need to type into your assistant. There is no API key: the first
time you connect, your browser opens Tinct's sign-in page and you approve the connection.
MCP (Model Context Protocol) is the open standard AI assistants use to work with external tools. Tinct exposes its landing-page workflow through it, so any assistant that speaks MCP can connect — no plugin, no install. You act as yourself: the assistant can only do what your role in the workspace already lets you do in the Tinct app.
What you can do
Tinct has two kinds of campaign, and the assistant can drive both:
- Generate a new landing page. Tinct designs, writes and hosts a page from a brief and your brand. Best when you don't have a page for the initiative yet.
- Personalize the landing page you already have. Tinct identifies the personalizable blocks of your own page and generates one version per target company, served through the Tinct snippet on your site. Best for account-based campaigns on an existing page.
Everything a tool changes is visible in the Tinct app immediately, and vice-versa — the MCP server is just another way in. The full list is in the tool reference.
Connect your assistant
Pick your client. The URL and the sign-in flow are the same for all of them: the client opens Tinct in your browser, you log in (if you aren't already) and click Allow.
- In Claude, open Settings → Connectors.
- Click Add custom connector.
- Name it
Tinctand paste the URL:https://api.tinct.ai/mcp
Under the authentication options, choose No client ID: Claude registers itself with Tinct on first connection (dynamic client registration). Do not pick the option where the server reads Claude's client details from a URL Anthropic hosts (CIMD) — Tinct does not support it yet and the connection fails with an unknown client error. There is no client id or secret to enter either. - Click Add, then Connect. A Tinct sign-in tab opens; log in and click Allow.
- Back in a chat, enable the Tinct connector from the tools menu (the + / sliders button under the message box) and start typing.
From any terminal:
# register the server for your user, available in every project claude mcp add --transport http --scope user tinct https://api.tinct.ai/mcp
# register the server for the current project only (default scope) claude mcp add --transport http tinct https://api.tinct.ai/mcp
# write it to .mcp.json at the project root so teammates get it from git claude mcp add --transport http --scope project tinct https://api.tinct.ai/mcp
Then, inside a Claude Code session, run /mcp, select tinct and choose
Authenticate. Your browser opens on Tinct; log in and approve. The session picks the
tools up right away — no restart needed.
# equivalent .mcp.json entry, if you prefer editing the file by hand
{
"mcpServers": {
"tinct": {
"type": "http",
"url": "https://api.tinct.ai/mcp"
}
}
}
http://localhost:<port>) on the machine running Claude Code, so a browser
must be able to reach that machine's localhost — over SSH, forward the port the browser is
sent to, or simply run Claude Code locally for the first authentication.
Any client works as long as it supports these three things — all of them standard in the current MCP specification:
- Streamable HTTP transport (not the legacy SSE transport, not stdio).
- OAuth 2.1 with discovery: the client follows the
WWW-Authenticatehint to the protected-resource metadata, then the authorization-server metadata, and uses PKCE. - Dynamic client registration (RFC 7591) — the client registers itself; there is no
pre-shared client id. Its redirect URL must be
https://…or a loopback address (http://localhost:<port>/…,http://127.0.0.1:<port>/…). Custom URL schemes such asmyapp://callbackare refused. Client ID Metadata Documents (a URL used as the client id, without registration) are not supported yet: a client offering both must be set to register dynamically.
The generic configuration is the same JSON as above — a server named tinct, type
http, URL https://api.tinct.ai/mcp.
The endpoints your client discovers on its own, for reference:
| Purpose | URL |
|---|---|
| MCP endpoint | https://api.tinct.ai/mcp |
| Protected-resource metadata (RFC 9728) | https://api.tinct.ai/.well-known/oauth-protected-resource/mcp |
| Authorization-server metadata (RFC 8414) | https://api.tinct.ai/.well-known/oauth-authorization-server |
| Dynamic registration | https://api.tinct.ai/oauth2/register |
| Authorize / token | https://api.tinct.ai/oauth2/authorize · https://api.tinct.ai/oauth2/token |
/mcp with no trailing
slash, over https, on the host shown above. Access tokens are bound to that exact
URL; a variant (a trailing slash, a different host alias) is rejected with
401 even after a successful sign-in.
Generate a new landing page
The server ships its own instructions, so the assistant already knows the order of operations. You just describe what you want. A typical opener:
Behind that one message, the assistant walks this flow:
list_my_organizations — picks yours automatically if you only have one. A workspace whose brand was never extracted gets extract_organization_brand first (a few minutes): campaigns are generated against your brand.list_campaigns, then reuses a fitting one or calls create_campaign.update_campaign_context — it will ask you for anything missing, or propose drafts you can amend.set_campaign_brand with the site to design against (required).get_campaign — it shows you the campaign summary and waits for your explicit go-ahead.add_confirmed_step + generate_landing_page, then polls get_landing_page until the version is ready and shares the preview URL.publish_campaign_landing_page puts the page live at its public URL — the Tinct snippet ships inside it.queued for ten minutes or more before it switches to generating —
that is normal, not a stuck job. The assistant is told to say so and to check back every few
minutes rather than continuously. Closing the chat doesn't cancel anything; ask for the page
later and it will find it. Only a failed status, or nothing after 40 minutes, is
worth reporting.
list_landing_pages.
Useful follow-ups once the preview is up:
Editing & publishing
Every generated page is made of named sections (hero, features, cta, …) containing
addressable blocks (hero.h1, cta.cta-primary, …). The assistant
reads the structure with list_blocks and then uses the narrowest tool for the
change you describe:
| You say… | The assistant calls | Notes |
|---|---|---|
| "Change this headline / button text / paragraph" | edit_block | Copy edit of one block. Synchronous — the new version is ready when the tool returns. |
| "Center the hero", "make it two columns" | redesign_section | Layout of one section; copy is preserved. |
| "Put pricing before the FAQ" | move_section | Instant and exact. |
| "Drop the testimonials" | remove_section | Recoverable — earlier versions remain. |
| "Add a second features block like this one" | duplicate_section | Then edit_block to differentiate the copy. |
| "Publish it" / "Take it offline" | publish_landing_page · unpublish_landing_page | Publishes one ready version at its public URL. |
Each edit produces a new immutable version. Nothing is lost: list_versions
shows the history and any earlier version can be published again. Ask "show me the versions"
or "go back to the one before the layout change".
Personalize your existing landing page
You already have a landing page and a list of target accounts. The assistant turns that into one personalized version of your page per company, each with its own link. Everything runs through a campaign created with the page URL, and the assistant knows the order of operations. A typical opener:
create_campaign with landing_page_url — refused until the workspace's brand is extracted (extract_organization_brand). Optionally update_campaign_context with the brief that steers the copy.analyze_landing_page crawls your page and finds the personalizable blocks (1–3 minutes). list_campaign_blocks shows what it found. A page Tinct generated skips this step: its blocks are derived from the generated markup.list_audiences to reuse one, or create_audience + import_leads with the companies you give it (domains, up to 100 per call, polled with get_job). Then set_campaign_audience.validate_campaign_audience checks for companies already targeted by another campaign on the same page, creates one page per company and generates the first 5 as a sample for you to review.generate_campaign_pages does the rest in the background — a few minutes per page, in parallel. list_pages / get_page to review, update_page_block or regenerate_page to fix one.studio_url) where you look through the variants. Once you approve them explicitly, it records the validation step — nothing is published until then.publish_campaign puts every ready page live; export_campaign_page_links gives the per-company links for your emails, ads and sequences.?tinct_page_id=…; the Tinct snippet on that page swaps in the personalized
blocks. Without it every link shows the generic page. After publishing, get_campaign
reports snippet_status — not_detected means it is missing. Until a
page is published and the snippet is detected, the assistant is not given the
company link nor the two previews at all (it is told why instead), so it cannot hand you
a link that shows nothing; if you want them anyway, ask for them explicitly.
Which link is which
A personalized page has several links, and each one is meant for a different reader. The assistant gets them ready-made from the tools, so ask for the one you need by name:
| Link | Where it comes from | Use it to… |
|---|---|---|
| Studio | get_campaign → studio_url | Review and validate the generated variants in the Tinct app before publishing. The only preview that works for unpublished pages. |
| Preview on your site | list_pages → preview_url | Check a published page on the real site yourself: it is the company's link plus tinct_preview=1, so your visit is not counted in the analytics. |
| Preview in the app | list_pages → app_preview_url | Open a published page inside the Tinct app, with a top bar to move between the variants of the campaign. It loads your real page, so it needs the snippet too. |
| Company link | list_pages → url, export_campaign_page_links | The link to send to that company (email, ad, sequence): your page plus tinct_page_id. Not for your own checks — opening it counts as a visit. |
The two previews on your site require the page to be published and the snippet installed; until then they are not returned at all.
Useful follow-ups:
Add a company to a live campaign
Day to day the campaign is already published and one more company shows up in your CRM. Just ask; the assistant imports the company into the campaign's audience, waits for the import, finds the new page, generates it and publishes it, without touching the pages already live:
Under the hood: import_leads → get_job → find_audience_leads
→ list_pages with the lead → regenerate_page → get_page
until ready → publish_page. Importing a company that is already in the
audience is harmless.
The logos in the "trusted by" blocks come from your customers library, which is separate from
the audience. Ask to add a customer and the assistant runs import_customers →
get_job → rematch_campaign_logos so the live pages pick up the new
logo without regenerating; removing one (remove_customer) updates the pages by
itself.
Tool reference
What your assistant sees. Ids are UUIDs and always come from a previous tool result — you never need to type one.
| Tool | Inputs | Does |
|---|---|---|
| Workspace | ||
list_my_organizations | — | Workspaces you belong to, with ids, domain and brand_extraction state. Called first whenever an organization_id is needed. |
extract_organization_brand | organization_id, website_url? | Extracts your brand (voice, design, logos) from your website in the background; poll get_job. Required before the first campaign on an existing page; re-running replaces the brand. |
| Campaigns | ||
list_campaigns | organization_id, limit?, offset? | Campaigns with status, linked landing-page id and ready_to_generate. |
create_campaign | organization_id, name, landing_page_url? | New campaign. Without a URL it will generate a new page; with one it personalizes that existing page. |
get_campaign | campaign_id | Full state: landing page, audience, blocks, analysis / generation / publication progress, studio_url (review the variants before publishing), snippet status, a missing list, next_step, and the confirmation summary_markdown. The polling tool for campaign jobs. |
update_campaign_context | campaign_id, then any of name, value_proposition, goal, targeting_approach, key_message, differentiation, tone, keywords | Saves the brief. Partial: only the fields passed are updated. |
set_campaign_brand | campaign_id, source_url | Website whose brand the page is designed against. Required before generation. |
add_confirmed_step | campaign_id, confirmed_step | Records your confirmation; value_proposition is the gate to generation. |
generate_landing_page | campaign_id | New page: starts generation (first call) or regeneration. Returns immediately; about 20 minutes. |
publish_campaign_landing_page · unpublish_campaign_landing_page | campaign_id | New page: puts the generated page live at its public URL / takes it offline. The gate to its per-company variants. |
analyze_landing_page | campaign_id | Existing page only: finds the personalizable blocks (background job). Generated pages get theirs automatically. |
list_campaign_blocks | campaign_id | The personalizable blocks of the campaign's page (analyzed or generated), with their original content. |
set_campaign_audience | campaign_id, audience_id | Attaches the target-company list to the campaign. |
validate_campaign_audience | campaign_id, exclude_conflicts? | Conflict check, page creation and a 5-page sample. Returns conflicts and changes nothing when there are any. |
generate_campaign_pages | campaign_id | Generates every waiting page in the background; safe to re-run. |
publish_campaign · unpublish_campaign | campaign_id | Puts every ready page live / takes them all offline (background job). |
export_campaign_page_links | campaign_id, include_links_without_snippet? | Per-company links of the published pages — the ones to send, see which link is which. Refused while the snippet is not detected on your page, unless you explicitly ask. |
| Landing pages | ||
list_landing_pages | organization_id, limit?, offset? | Pages of a workspace, newest first, with status and current version. |
get_landing_page | landing_page_id | Status, published URL, current_version and latest_version. The polling tool for generation. |
list_versions | landing_page_id, limit? | Version history, newest first. |
get_version_html | landing_page_id, version_id | Full HTML of one version (large). |
list_blocks | landing_page_id | Editable sections and block paths of the latest ready version. |
edit_block | landing_page_id, block_path, instruction | Edits one block → new version. |
redesign_section | landing_page_id, section_name, instruction | Re-lays out one section → new version. |
move_section | landing_page_id, section_name, before_section_name? | Moves a section (to the end if no target) → new version. |
remove_section | landing_page_id, section_name | Removes a section → new version. |
duplicate_section | landing_page_id, section_name | Copies a section right after the original → new version. |
publish_landing_page | landing_page_id, version_id | Makes one ready version live; returns the public URL. |
unpublish_landing_page | landing_page_id | Takes the page offline; versions are kept. |
| Personalized pages (both flows) | ||
list_pages | campaign_id, lead_id?, limit?, offset? | One page per company with status, company and its links (url to send, preview_url / app_preview_url for you once published). |
get_page | page_id | The page and its generated blocks. Polling tool after regenerate_page. |
update_page_block | page_id, block_variant_id, value, smart_merge? | Edits one block of one page; a live page then needs republishing. |
regenerate_page | page_id, prompt? | Generates or redoes one page in the background. |
publish_page · unpublish_page | page_id | Puts one page live / takes it offline. |
exclude_page · include_page | page_id | Keeps a company out of the campaign / brings it back. |
| Audiences | ||
list_audiences | organization_id, term?, limit?, offset? | Target-company lists of the workspace with their import status. |
create_audience | organization_id, name | New empty audience. |
import_leads | audience_id, companies [{domain, name?}] | Adds up to 100 companies by domain (background job). The only way companies enter an audience. |
find_audience_leads | audience_id, term?, limit?, offset? | Companies of an audience, searchable by name or domain. |
| Customers library | ||
list_customers | organization_id, limit?, offset? | The companies whose logos your pages show in their social-proof blocks (not the audience), each with whether a logo is available. |
import_customers | organization_id, companies [{domain, name?}] | Adds up to 100 customers by domain and fetches their logos (background job). |
remove_customer | customer_id | Removes one customer; its logo leaves every page still showing it and those campaigns re-match by themselves. |
rematch_campaign_logos | campaign_id | After an import, refreshes the customer logos of a campaign's pages against the library (no regeneration, no credit; background job). |
| Jobs | ||
get_job | job_id | Status and progress of a background job (imports, analysis, publication). |
The server exposes tools only — no MCP resources or prompts.
Access & security
- You act as yourself. Every tool call is checked against your own permissions in
the workspace, exactly like the app. A viewer can list and inspect; creating campaigns,
editing and publishing pages each require the corresponding role permission. A refused
call comes back to the assistant as
Error: permission denied.— not as a disconnect. - Only your workspaces.
list_my_organizationsreturns the workspaces you are a member of, and every other tool is confined to them. - No secrets to store. There is no API key. The client holds a rotating refresh token for you, scoped to the MCP server alone (see tokens).
- Confirmation gates. Generation waits for your explicit confirmation of the brief, and nothing is published unless you ask. Your assistant may add its own approval prompts on top (Claude Code, for instance, asks before each write tool unless you allow it).
- Registrations are rate-limited (10 new client registrations per hour per IP) and unused registrations are purged after 30 days.
Disconnecting
- Claude: Settings → Connectors → Tinct → Remove (or Disconnect to keep the connector but drop the login).
- Claude Code:
claude mcp remove tinct(add--scope useror--scope projectto match how it was added), or clear just the login from/mcp→ tinct → Clear authentication. - Lost or shared device: removing the connector from the client is what revokes its tokens, and you can't do that from a device you no longer have. Contact Tinct support with the client name and the approximate time you connected so the registration can be revoked server-side; any access token still in flight expires within 15 minutes.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Client says the server needs authentication, but no browser opens | The client doesn't support MCP OAuth, or it is configured as SSE / stdio | Use a client listed above, and make sure the transport is http (streamable HTTP) |
Claude shows an unknown client / invalid_client error before the sign-in page | The connector was added with the CIMD option (client details read from a URL Anthropic hosts), which Tinct does not support | Remove the connector and add it again choosing No client ID |
Browser shows invalid_redirect_uri | The client's callback is not https or loopback http | Use a client with a standard callback; custom URL schemes are refused |
Sign-in succeeds, then 401 forever | The URL differs from the canonical one (trailing slash, other host) | Set the URL to exactly https://api.tinct.ai/mcp and reconnect |
429 Too Many Requests while connecting | More than 10 registrations in an hour from your IP (shared office network, repeated reconnects) | Wait an hour, or reuse the existing connection instead of re-adding it |
| Asked to sign in again after a while | Connector unused for 30 days — the refresh token expired | Just sign in again; nothing else changed |
Tool returns Error: permission denied. | Your role lacks that action, or the id belongs to another workspace | Ask a workspace admin to widen your role; check the assistant picked the right workspace |
list_my_organizations is empty | Your account isn't a member of any workspace yet | Accept your workspace invitation in Tinct first |
| Generation "never finishes" / "stuck in queued" | A new page takes about 20 minutes, and can stay queued for 10+ minutes first | Wait; ask again in a few minutes. Only failed, or nothing after 40 minutes, is a problem |
| Validation returns conflicts and does nothing | Another campaign on the same page already targets some of these companies | Decide: skip them (the assistant re-runs with exclude_conflicts) or change the audience |
| Personalized links show the generic page | Page not published yet, or the Tinct snippet isn't installed on your landing page (snippet_status: not_detected) | Publish, and install the snippet on the page |
| Tools don't appear in Claude | Connector added but not enabled in this chat | Enable it from the tools menu under the message box |
MCP vs. service accounts
Tinct has two ways to work outside the app. They don't overlap — pick by who is doing the work:
| MCP server (this guide) | Service accounts | |
|---|---|---|
| Who acts | A person, through an AI assistant | A machine — your pipeline, CRM sync, cron job |
| Credential | Your Tinct login, via OAuth in the browser | Client id + secret, created by a workspace admin |
| Permissions | Your own role | A fixed set granted at creation |
| Surface | Landing-page workflow tools | The whole REST API: campaigns, audiences, leads, pages… |
| Interchangeable? | No. MCP tokens are refused by the REST API, and a service account cannot sign in to the MCP server (it has no user to consent). | |
Appendix: how sign-in works
For the curious and for IT teams reviewing the connection. You never handle a credential yourself; this is what the client does when that browser tab pops up:
- The client calls
/mcpwithout a token and gets a401pointing at Tinct's OAuth metadata. - It registers itself as an OAuth client (public client, PKCE, scopes
mcpandoffline_access) and opens the authorization page. - You log in to Tinct if needed — the same login as the app — and see a consent screen: which client is asking (e.g. "Claude"), which account you're signed in as, and what it will be able to do (use Tinct on your behalf, stay connected). Click Allow.
- The client receives a short-lived access token and a refresh token, and connects.
| Token | Lifetime | What it means for you |
|---|---|---|
| Access token | 15 minutes | Renewed silently by the client; you won't notice. |
| Refresh token | 30 days, rotated on every use | Use the connector at least once a month and you never re-authenticate. After 30 days idle, the client asks you to sign in again. |
/mcp. Nothing an MCP client holds can be replayed anywhere else.