MCP Server
Produktly provides a Model Context Protocol (MCP) server that lets you connect AI assistants like Claude, Cursor, Windsurf, and others directly to your Produktly data.
With the MCP server you can query your widgets, responses and cross-feature usage stats through natural language — and have your AI assistant build them for you. Product tours, onboarding checklists, smart tips, announcements, micro surveys, NPS widgets, feedback widgets and changelog posts can all be created and edited through the server.
Working in a codebase with a coding agent? See the Produktly agent skill, which teaches Claude Code, Cursor, and similar tools the whole flow: installing the snippet, identifying users, picking stable selectors, and authoring widgets.
Endpoint
https://api.produktly.com/api/mcp
The server uses the Streamable HTTP transport (stateless mode).
Authentication
The MCP server authenticates using your Produktly API key as a Bearer token.
- Go to Settings > API keys in your Produktly dashboard
- Generate a new key (give it a name like "MCP")
- Use the key as a Bearer token in your MCP client configuration
Treat the key like a password: it can read and write all of your Produktly data. It is a different token from the Client Auth Token in your install snippet, which is public by design.
Setup
Claude Code (CLI)
Run this command in your terminal:
claude mcp add produktly --transport http --url https://api.produktly.com/api/mcp --header "Authorization: Bearer YOUR_PRIVATE_KEY"
Or add it manually to your Claude Code MCP settings:
{
"mcpServers": {
"produktly": {
"type": "streamable-http",
"url": "https://api.produktly.com/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_PRIVATE_KEY"
}
}
}
}
Cursor / Windsurf
Add to your MCP configuration file:
{
"mcpServers": {
"produktly": {
"url": "https://api.produktly.com/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_PRIVATE_KEY"
}
}
}
}
GitHub Copilot / VS Code
In VS Code, open the Command Palette and run MCP: Add Server. Select HTTP as the transport type, then enter the URL https://api.produktly.com/api/mcp.
Or add it manually to your .vscode/mcp.json:
{
"servers": {
"produktly": {
"type": "http",
"url": "https://api.produktly.com/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_PRIVATE_KEY"
}
}
}
}
The Produktly tools will be available in Copilot's Agent mode.
Other MCP clients
Any MCP client that supports the Streamable HTTP transport can connect. Set the URL to https://api.produktly.com/api/mcp and include the Authorization: Bearer YOUR_PRIVATE_KEY header.
ChatGPT / Gemini
ChatGPT and Gemini require OAuth-based authentication for MCP servers, which is not yet supported. We're working on adding OAuth support — stay tuned.
Targeting
Every widget type only shows where you tell it to. Pass exactly one of these three forms:
{ "showOnAllPages": true }
{
"rules": [
{ "type": "url", "comparisonOperator": "contains", "value": "/dashboard" },
{ "type": "custom::::plan", "comparisonOperator": "one_of", "value": ["pro", "enterprise"] }
],
"logicalOperator": "AND"
}
{ "userSegmentIds": [3, 7] }
Rule types include url, device_type, language, location, screen_width, screen_height, current_time, produktly_user_created_at, and custom::::<attribute> for any attribute you send with identifyUser. See Targeting rules for the full list.
A widget with no targeting never shows anywhere. Publishing requires targeting to be set.
Publishing
Widgets created through the API are drafts by default. Drafts never render on your site.
To publish, pass active: true. The widget must be publishable: targeting is always required, plus whatever that type needs to render.
| Widget | Also needs |
|---|---|
| Product tour | at least one step |
| Checklist | at least one item |
| Smart tip | settings.cssSelector |
| Announcement | title, plus content for popup and toast |
| Micro survey | at least one question |
| Feedback widget | at least one option |
| NPS widget | nothing further |
If anything is missing, the widget stays a draft and the response note explains why — the work is never lost, it just waits for the missing piece.
Once a widget is live, the API rejects updates that would introduce a new publishing problem: clear a live feedback widget's options, or drop the targeting from a live tour, and you get a not_publishable error instead of a silently broken widget. Set active: false first if you want to take it down.
A good habit with AI assistants is to ask for drafts by default and publish yourself from the dashboard after reviewing — every create and update response includes a dashboardUrl for exactly this.
Available tools
| Area | Tools |
|---|---|
| Setup guidance | get_setup_guide |
| Product tours | list_product_tours, get_product_tour, create_product_tour, update_product_tour |
| Checklists | list_checklists, get_checklist, create_checklist, update_checklist |
| Smart tips | list_smart_tips, get_smart_tip, create_smart_tip, update_smart_tip |
| Announcements | list_announcements, get_announcement, create_announcement, update_announcement |
| Micro surveys | list_micro_surveys, get_micro_survey, create_micro_survey, update_micro_survey |
| NPS widgets | list_nps_widgets, get_nps_widget, create_nps_widget, update_nps_widget, get_nps_score, get_nps_responses |
| Feedback widgets | list_feedback_widgets, get_feedback_widget, create_feedback_widget, update_feedback_widget, get_feedback_responses |
| Changelogs | list_changelogs, get_changelog_posts, create_changelog_post, update_changelog_post, list_tags |
| Roadmaps | list_roadmaps, get_roadmap |
| Stats | get_widget_stats, get_stats_summary |
Widgets you create through the API start as drafts — see Publishing.
get_setup_guide
Get the official Produktly setup guide, written for AI agents. Read the relevant topic before installing Produktly or creating your first widget.
| Parameter | Type | Required | Description |
|---|---|---|---|
topic | enum | Yes | One of install, identify, selectors, widgets |
install— the script snippet, per-framework instructions (React, Next.js, Vue, GTM, WordPress), CSP hosts, and how to verify the install.identify— theidentifyUsersignature and when to call it.selectors— how to choose CSS selectors that survive a redesign.widgets— every widget type you can create, what each one needs before it can be published, targeting, and the draft → review → publish flow.
list_product_tours
List all product tours (id, name, active status, dates, folder).
get_product_tour
Get one tour with its full steps, settings, and targeting rules. Fetch a tour before updating it — updates replace steps wholesale.
| Parameter | Type | Required | Description |
|---|---|---|---|
productTourId | number | Yes | The ID of the product tour |
create_product_tour
Create a product tour. Saved as a draft unless active: true is passed.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Tour name (internal) |
steps | object[] | No | Steps in order — see Step types |
targeting | object | No | Where the tour shows — see Targeting |
settings | object | No | Tour behavior (start trigger, button labels, colors, …) |
themeId | number | No | Attach an existing theme |
folderId | number | No | File the tour into a folder |
active | boolean | No | Publish immediately. The tour must have at least one step and targeting set |
The response includes a dashboardUrl — open it to review the tour in the builder.
update_product_tour
Update a tour. Only the fields you provide are changed, but steps and targeting replace their stored values wholesale: fetch with get_product_tour first, and keep the existing step id values so analytics stay intact. settings are merged.
| Parameter | Type | Required | Description |
|---|---|---|---|
productTourId | number | Yes | The ID of the tour to update |
name, steps, targeting, settings, themeId, folderId, active | No | Same as create_product_tour |
Tours that are already live can be edited, but the API rejects an update that would leave a live tour unpublishable — see Publishing.
Step types
Every step has a type. Cards accept title (plain text) and content (HTML), plus optional width, height, button labels (nextBtnText, backBtnText, finishBtnText), and a secondary action button.
| Type | Purpose | Required fields |
|---|---|---|
welcome | Centered intro card | — |
highlight | Spotlights an element on the page | domQuery (CSS selector) |
modal | Centered card | — |
toast | Small card in the bottom-right corner | — |
redirect | Navigates the user, renders nothing | redirectUrl |
Highlight steps also accept direction (auto, top, right, bottom, left), highlight, allowClickEvents, skipIfNotFound, and actionListeners for auto-advancing when the user interacts with the element. Call get_setup_guide with topic: "selectors" for guidance on choosing a reliable domQuery.
list_checklists
List all checklists (id, name, active status, dates, folder).
get_checklist
Get one checklist with its full items, settings, and targeting rules.
| Parameter | Type | Required | Description |
|---|---|---|---|
checklistId | number | Yes | The ID of the checklist |
create_checklist
Create an onboarding checklist. Saved as a draft unless active: true is passed.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Shown as the checklist panel header |
items | object[] | No | Checklist items — see below |
targeting | object | No | Where the checklist shows — see Targeting |
settings | object | No | Position, display mode, colors, button text |
themeId | number | No | Attach an existing theme |
folderId | number | No | File the checklist into a folder |
active | boolean | No | Publish immediately. The checklist must have at least one item and targeting set |
Each item needs a title, a type (start-tour, open-url, or custom), and a completionType:
completionType | Completes when | Also required |
|---|---|---|
manual-click | The user ticks it off | — |
page-event | A DOM event fires on your site | pageEventElementSelector, optionally eventType (click, hover, element-visible) |
tour-complete | A product tour is finished | tourToCompleteId |
Items with type: "start-tour" need a tourId; items with type: "open-url" need a redirectToUrl.
update_checklist
Update a checklist. Same replace-vs-merge rules as update_product_tour: items and targeting replace wholesale (preserve item id values — completion state is keyed on them), settings merge.
| Parameter | Type | Required | Description |
|---|---|---|---|
checklistId | number | Yes | The ID of the checklist to update |
name, items, targeting, settings, themeId, folderId, active | No | Same as create_checklist |
list_smart_tips / get_smart_tip
List all smart tips, or fetch one with its full settings and targeting rules. Fetch before updating: settings merge, targeting replaces.
create_smart_tip
Create a smart tip: a small contextual card anchored to one element on the page.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Internal name |
settings.cssSelector | string | Yes | The element the tip anchors to. Without it nothing renders |
title / content | string | No | Card title (plain text) and body (HTML) |
settings | object | No | Trigger (displayOn), beacon style and offsets, card placement, actions, colors |
targeting | object | No | See Targeting |
themeId, folderId, active | No | As with other widgets |
displayOn accepts click, hover, focus, beacon_click and beacon_hover. Turning the beacon off with displayBeacon: false while using a beacon trigger is rejected, since the tip would have nothing to open it.
update_smart_tip
Update a smart tip. settings merge, targeting replaces wholesale.
| Parameter | Type | Required | Description |
|---|---|---|---|
smartTipId | number | Yes | The ID of the smart tip |
name, title, content, settings, targeting, themeId, folderId, active | No | Same as create_smart_tip |
list_announcements / get_announcement
List all announcements, or fetch one with its full settings and targeting rules.
create_announcement
Create an announcement: a topbar, popup or toast message.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Internal name |
title | string | No | Headline. The only text shown on topbar styles |
content | string | No | HTML body. Rendered for popup and toast only |
settings.type | enum | No | topbar (default), topbar-hovering, popup, toast |
settings | object | No | Action buttons, dimensions, colors, display cadence |
targeting | object | No | See Targeting |
Topbar styles render the title only, so content is stored but not displayed there; the response note tells you when that happens. Showing an action button requires giving it a destination, either actionLink or actionType: "tour" with actionTourId.
update_announcement
Update an announcement. settings merge, targeting replaces wholesale.
| Parameter | Type | Required | Description |
|---|---|---|---|
announcementId | number | Yes | The ID of the announcement |
name, title, content, settings, targeting, themeId, folderId, active | No | Same as create_announcement |
list_micro_surveys / get_micro_survey
List all micro surveys, or fetch one with its full questions, settings and targeting rules.
create_micro_survey
Create a micro survey: a short in-app survey shown one question at a time.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Internal name |
settings.questions | object[] | Yes | At least one question, asked in order |
settings | object | No | Trigger, position, auto-close, colors, completion action |
targeting | object | No | See Targeting |
Each question needs a question string and a type: emoji, thumbs, rating (fixed 1-5), text or multi-choice. Only emoji takes options (each with a name and emoji), and only multi-choice takes multiChoiceOptions. Questions run as a flat list with no branching.
Unless settings.displayTrigger is immediate, time_delay or manual, you must also supply settings.cssSelector, because the default click trigger needs an element to attach to.
update_micro_survey
Update a micro survey. Sending settings.questions replaces the whole question list.
| Parameter | Type | Required | Description |
|---|---|---|---|
microSurveyId | number | Yes | The ID of the micro survey |
name, settings, targeting, themeId, folderId, active | No | Same as create_micro_survey |
list_nps_widgets
List all your NPS widgets (id, name, active status, creation date).
get_nps_widget
Get one NPS widget with its full settings and targeting rules. Fetch before updating: settings merge, targeting replaces.
| Parameter | Type | Required | Description |
|---|---|---|---|
npsWidgetId | number | Yes | The ID of the NPS widget |
create_nps_widget
Create an NPS widget: a rating prompt with an optional follow-up question.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Internal name |
settings.selectedScale | enum | No | ten (0-10, the standard NPS scale, default), five (1-5) or emojis (0-2) |
settings | object | No | Question text, email and comment collection, display style, trigger, cadence, colors |
targeting | object | No | See Targeting |
Scores from get_nps_score are calculated per scale, so a five or emojis widget is scored on its own range rather than the 0-10 one.
update_nps_widget
Update an NPS widget. settings merge, targeting replaces wholesale.
| Parameter | Type | Required | Description |
|---|---|---|---|
npsWidgetId | number | Yes | The ID of the NPS widget |
name, settings, targeting, themeId, folderId, active | No | Same as create_nps_widget |
get_nps_score
Get the NPS score and response breakdown for an NPS widget. NPS = % promoters (rating 9-10) − % detractors (rating 0-6); passives are 7-8.
| Parameter | Type | Required | Description |
|---|---|---|---|
npsWidgetId | number | Yes | The ID of the NPS widget |
startDate | string | No | Filter responses from this date (ISO 8601) |
endDate | string | No | Filter responses until this date (ISO 8601) |
Returns npsScore, totalResponses, and counts/percentages for promoters, passives, and detractors.
get_nps_responses
Get individual responses for an NPS widget.
| Parameter | Type | Required | Description |
|---|---|---|---|
npsWidgetId | number | Yes | The ID of the NPS widget |
startDate | string | No | Filter responses from this date (ISO 8601) |
endDate | string | No | Filter responses until this date (ISO 8601) |
latestCount | number | No | Return only the latest X responses (max 100) |
list_feedback_widgets
List all your feedback widgets (id, name, active status, creation date).
get_feedback_widget
Get one feedback widget with its full options, settings and targeting rules. Fetch before updating: options and targeting replace wholesale, settings merge.
| Parameter | Type | Required | Description |
|---|---|---|---|
feedbackWidgetId | number | Yes | The ID of the feedback widget |
create_feedback_widget
Create a feedback widget: a floating beacon that collects categorized feedback.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Internal name |
options | object[] | No | The answer options. Each needs a name, optionally an emoji |
optionsPreset | number | No | Use a built-in set instead of listing options: 1 issue/idea/other, 2 three emojis, 3 five emojis, 4 four emojis, 5 feelings, 6 issue/idea/happy/angry/other |
settings | object | No | Question text, beacon placement, email collection, screenshots, colors, text overrides |
targeting | object | No | See Targeting |
Pass either options or optionsPreset, not both. Publishing needs at least one option plus targeting.
update_feedback_widget
Update a feedback widget. Sending options replaces the whole list.
| Parameter | Type | Required | Description |
|---|---|---|---|
feedbackWidgetId | number | Yes | The ID of the feedback widget |
name, options, optionsPreset, settings, targeting, themeId, folderId, active | No | Same as create_feedback_widget |
get_feedback_responses
Get responses for a specific feedback widget.
| Parameter | Type | Required | Description |
|---|---|---|---|
feedbackWidgetId | number | Yes | The ID of the feedback widget |
startDate | string | No | Filter responses from this date (ISO 8601) |
endDate | string | No | Filter responses until this date (ISO 8601) |
latestCount | number | No | Return only the latest X responses (max 100) |
list_changelogs
List all your changelogs (id, name, active status, creation date).
get_changelog_posts
Get posts for a specific changelog.
| Parameter | Type | Required | Description |
|---|---|---|---|
changelogId | number | Yes | The ID of the changelog |
startDate | string | No | Filter posts from this date (ISO 8601) |
endDate | string | No | Filter posts until this date (ISO 8601) |
latestCount | number | No | Return only the latest X posts (max 100) |
create_changelog_post
Create a new changelog post. Posts default to draft (active: false) unless active: true is explicitly passed.
| Parameter | Type | Required | Description |
|---|---|---|---|
changelogId | number | Yes | The ID of the changelog to add the post to |
title | string | Yes | Post title |
description | string | No | Post description as HTML (e.g. <p>, <ul>, <strong>) |
date | string | No | Post date (ISO 8601). Defaults to now |
active | boolean | No | Whether the post is live. Defaults to false |
tagNames | string[] | No | Tag names to attach. Must match existing tags (case-insensitive) |
Unknown tagNames return an error listing the unresolved names — use list_tags first.
update_changelog_post
Update an existing changelog post. Only the fields you provide are changed.
| Parameter | Type | Required | Description |
|---|---|---|---|
changelogItemId | number | Yes | The ID of the post to update |
title | string | No | New title |
description | string | No | New HTML description |
date | string | No | New post date (ISO 8601) |
active | boolean | No | Whether the post is live |
tagNames | string[] | No | Replacement tag names. Omit to leave tags unchanged; pass [] to clear |
list_tags
List all tags in the company (id, name, colors). Useful for looking up tag names to pass to create_changelog_post and update_changelog_post.
list_roadmaps
List all your roadmaps with their sections (id, name, public name, active status, sections).
get_roadmap
Get a full roadmap with all sections and items. Supports search and tag filtering within the roadmap.
| Parameter | Type | Required | Description |
|---|---|---|---|
roadmapId | number | Yes | The ID of the roadmap |
startDate | string | No | Filter items updated from this date (ISO 8601) |
endDate | string | No | Filter items updated until this date (ISO 8601) |
latestCount | number | No | Return latest X items per section (max 100) |
query | string | No | Case-insensitive substring match on item name or description |
tagNames | string[] | No | Filter items whose tag name matches any of these (case-insensitive exact match) |
get_widget_stats
Get event counts and unique users per widget for a given widget type. Returns one entry per widget with a breakdown by event type. Useful for comparing widgets of the same type (e.g. "which tours have the best completion rate?") or inspecting a single widget when entityId is provided.
| Parameter | Type | Required | Description |
|---|---|---|---|
entityType | enum | Yes | One of tour, checklist, smartTip, announcement, changelog, npsWidget, roadmap |
entityId | number | No | Filter to a single widget |
startDate | string | No | Start date (ISO 8601). Defaults to 30 days ago |
endDate | string | No | End date (ISO 8601). Defaults to today |
Max range 90 days. uniqueUsers is a per-day sum approximation — it may double-count users active across multiple days.
get_stats_summary
Get a company-wide stats summary across all widget types. Returns primary and secondary metrics (completion rate, open rate, action rate), event counts, period-over-period comparison, and a trend series for each widget type.
| Parameter | Type | Required | Description |
|---|---|---|---|
startDate | string | No | Start date (ISO 8601). Defaults to 7 days ago |
endDate | string | No | End date (ISO 8601). Defaults to today |
The trend series is daily when the range is 21 days or less, weekly otherwise. Max range 90 days.
Example usage
Once connected, you can ask your AI assistant things like:
- "What are our latest changelog posts?"
- "Show me feedback from the last week"
- "What items are on our product roadmap?"
- "Search the roadmap for items about 'integration' tagged as 'New Feature'"
- "What's our NPS score this quarter, and what are the detractors saying?"
- "Which product tour has the best completion rate over the last month?"
- "How did our overall engagement change this week vs last week?"
- "Summarize the feedback we got this month"
- "Draft a changelog post about the webhook improvements we shipped this week, tag it as Improvement, leave it as a draft"
- "Build a 4-step welcome tour for our dashboard and show it on all pages — leave it as a draft for me to review"
- "Create an onboarding checklist: take the welcome tour, invite a teammate, connect an integration"
- "Our NPS detractors keep mentioning the export flow. Draft a smart onboarding checklist that walks new users through it"
- "Add a fifth step to the welcome tour highlighting the new Reports button"
- "Add a smart tip on the billing page pointing at the invoice download button"
- "Announce Tuesday's maintenance window as a topbar on every page, as a draft"
- "Create a 2-question micro survey asking why people visited the pricing page"
- "Set up an NPS widget on a five-point scale, shown once a quarter to users on the Pro plan"