Manage QR Codes via Scanova API
Scanova MCP Server lets AI IDEs create, design, and manage Scanova QR codes, folders, lead forms, and analytics via the Scanova API.
2.8.0Add to Favorites
Why it matters
Integrate with the Scanova API to programmatically create, manage, and download QR codes. This asset provides tools for developers to automate QR code generation and management within their IDEs.
Outcomes
What it gets done
Create new QR codes with custom URLs.
List, update, and retrieve existing QR codes.
Download QR code images in various formats.
Activate and deactivate QR codes.
Source
Get it from source
Spark does not host a copy of it.
Open sourceReports
Agent outcome reports
No reports yet
Capabilities
Tools your agent gets
Create a new QR code
Show existing QR codes
Update an existing QR code
Get QR code details
Download QR code image
Activate a QR code
Deactivate a QR code
Overview
Scanova MCP Server
Scanova MCP Server gives an AI IDE full control over a Scanova QR code account through the Scanova API: creating and designing QR codes, organizing them into folders, managing lead-capture forms, exporting analytics, and administering users. A documentation bridge also lets the agent query Scanova's own docs. Use it when an agent needs to manage an existing Scanova account's QR codes, leads, or analytics; it requires a Scanova API key and account, not a standalone QR generator.
What it does
Scanova MCP Server lets AI IDEs like Cursor, VS Code, and Claude Desktop manage a Scanova QR code account through the Scanova API: creating, designing, and running the full lifecycle of QR codes; organizing them into folders; managing lead-capture forms and lead lists; pulling analytics; and administering account users - plus a documentation bridge that lets the agent query Scanova's own docs via a second MCP server.
When to use - and when NOT to
Use it when an agent needs to create, customize, activate/deactivate, or delete QR codes on your behalf, organize them into folders, attach lead-capture forms or lead lists to them, pull scan analytics, or manage who has account access - all through your existing Scanova account rather than the Scanova web dashboard. It requires a Scanova API key from app.scanova.io and an MCP-compatible IDE.
Don't use it if you don't already have (or want) a Scanova account - it's a management layer over the Scanova platform specifically, not a general QR-code generator, and every tool call operates against your live Scanova data.
Capabilities
QR code management covers creating, listing, updating, retrieving, activating/deactivating, and permanently deleting QR codes, downloading them as PNG/JPG/PDF/SVG/EPS or a print-optimized PDF, browsing QR categories (URL, vCard, WiFi, etc.), and attaching or detaching lead-capture forms and lead lists. QR design tools list available visual options (patterns, eye shapes, frames, gradients, error correction) and apply a chosen design to an existing code. Folder management creates, lists, updates, and deletes folders, and moves or unassigns QR codes between them. Forms and lead lists can be listed, retrieved, updated, and deleted independently of any QR code. Analytics tools pull account-level usage stats and per-QR performance metrics by device, geography, and date, and export reports as XLSX/PDF or raw scan logs as CSV/XLSX. Account administration lists users and roles, invites or removes users, and updates role assignments. A documentation bridge (probe_docs_mcp, query_docs) checks connectivity to and queries Scanova's own docs MCP server.
How to install
Get an API key at https://app.scanova.io/settings/api, then add the server to your IDE's MCP config. For Cursor (~/.cursor/mcp.json), VS Code (~/.vscode/mcp.json), or Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json), the configuration is identical across all three:
{
"mcpServers": {
"scanova-mcp": {
"transport": "http",
"url": "https://mcp.scanova.io/mcp",
"headers": {
"Authorization": "YOUR_SCANOVA_API_KEY_HERE"
}
}
}
}
For local development, clone the repository, run uv sync, then start it with uv run cloud_server.py for HTTP mode or uv run main.py for stdio mode; a Docker image can also be built with docker build -t mcpserver:local . and run with docker run -d --name mcpserver -p 8000:8000 mcpserver:local or docker compose up -d, verified with curl http://localhost:8000/health. The deployed server exposes POST /mcp as the main JSON-RPC endpoint, GET /health for health checks, and GET / for service info. The project is MIT licensed.
Who it's for
Teams and marketers running Scanova QR code campaigns who want an AI agent to create and manage QR codes, lead-capture forms, folders, and analytics directly from their IDE instead of the Scanova web dashboard.
Source README
Scanova MCP Server
A Model Context Protocol (MCP) server for managing your Scanova QR code platform using the Scanova API. This server provides tools for QR code creation, design, and lifecycle management; folder organization; lead-capture forms and lead lists; analytics exports; and account user administration, through MCP-compatible IDEs like Cursor, VS Code, and Claude Desktop.
Features
- ✅ QR Codes: Create, list, retrieve, update, activate/deactivate, and delete QR codes; download them as PNG/JPG/PDF/SVG/EPS or as print-ready PDFs; and browse QR categories
- ✅ QR Design: Customize QR code visuals - patterns, eye shapes, frames, gradients, and error correction
- ✅ Folders: Create, list, update, and delete folders, and move or unassign QR codes between them
- ✅ Forms & Lead Capture: List, retrieve, update, and delete lead-capture forms and lead lists, and attach or detach either from a QR code
- ✅ Analytics: Pull account-level usage stats and per-QR performance metrics, and export analytics reports or raw scan logs
- ✅ Account Administration: List users and roles, invite or remove users, and update user role assignments
- ✅ Documentation Bridge: Query Scanova's docs via a live bridge to the Scanova docs MCP server
Prerequisites
- Scanova API key (get one from https://app.scanova.io/)
- MCP-compatible IDE (Cursor, VS Code, Claude Desktop, etc.)
Quick Setup
Step 1: Get Your Scanova API Key
- Visit https://app.scanova.io/
- Sign up or log in to your account
- Navigate to API settings [https://app.scanova.io/settings/api] and generate your API key.
Step 2: Configure Your IDE
Add the following configuration to your IDE's MCP settings:
For Cursor (~/.cursor/mcp.json):
{
"mcpServers": {
"scanova-mcp": {
"transport": "http",
"url": "https://mcp.scanova.io/mcp",
"headers": {
"Authorization": "YOUR_SCANOVA_API_KEY_HERE"
}
}
}
}
For VS Code (~/.vscode/mcp.json):
{
"mcpServers": {
"scanova-mcp": {
"transport": "http",
"url": "https://mcp.scanova.io/mcp",
"headers": {
"Authorization": "YOUR_SCANOVA_API_KEY_HERE"
}
}
}
}
For Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"scanova-mcp": {
"transport": "http",
"url": "https://mcp.scanova.io/mcp",
"headers": {
"Authorization": "YOUR_SCANOVA_API_KEY_HERE"
}
}
}
}
Tools
82 tools. Type is the tool's MCP annotation: read-only, write (only adds), or destructive (can change or switch off something live - hosts should confirm first).
Documentation
| Tool | Type | Description |
|---|---|---|
probe_docs_mcp |
Read-only | Check connectivity to the Scanova docs MCP server and list its available tools |
query_docs |
Read-only | Bridge to the live Scanova docs MCP server |
Categories & validation
| Tool | Type | Description |
|---|---|---|
get_qr_categories |
Read-only | List available QR code categories (URL, vCard, WiFi, Document, Social Media, etc.) |
get_qr_category_fields |
Read-only | Return the required/optional info JSON field reference for a QR code category (or all categories if none is given) - static reference data, no API call |
validate_qr_info |
Read-only | Validate a category + info JSON payload before calling create_qr_code or update_qr_code - catches malformed JSON, missing required fields, and invalid URLs/emails ahead of time |
QR code design
| Tool | Type | Description |
|---|---|---|
get_qr_design_options |
Read-only | Return the full catalog of available QR design options: all data pattern names, gradient styles, eye shape codes, frame IDs, error correction levels, fonts, and design tips |
preview_qr_design |
Read-only | Preview a QR code design without saving it: the design, scan-safety checks (colour contrast, logo and error correction), a real scan test, and a rendered image |
set_qr_design |
Destructive | Apply a visual design to an existing QR code using human-friendly named parameters |
list_custom_domains |
Read-only | List the account's custom short-link domains (e.g |
QR codes
| Tool | Type | Description |
|---|---|---|
create_qr_code |
Write | Create a new QR code |
open_qr_code_creation_form |
Read-only | Show an interactive form for creating a QR code |
list_qr_codes |
Read-only | List QR codes and/or Pages in the account |
retrieve_qr_code |
Read-only | Get details of a specific QR code |
update_qr_code |
Destructive | Update an existing QR code |
activate_qr_code |
Write | Activate a QR code |
deactivate_qr_code |
Destructive | Deactivate a QR code |
delete_qr_code |
Destructive | Permanently delete a QR code |
download_qr_code |
Read-only | Download QR code image in PNG, JPG, or PDF format |
list_tags |
Read-only | List tags attached to (or assignable against) the account's QR codes |
list_trashed_qr_codes |
Read-only | List QR codes in the trash (deleted but restorable), with when each was deleted and its scan count |
restore_qr_codes |
Write | Restore QR codes from the trash so they work again |
get_qr_health |
Read-only | The account's QR code health check: problems that stop codes working as intended - e.g |
update_qr_tags |
Destructive | Replace a QR code's tags with the ones given (an empty list removes them all) |
list_gs1_recalls |
Read-only | List the account's GS1 batch/lot recall notices: which GTIN and batch, the message shown to people who scan an affected product, and whether each is active |
create_gs1_recall |
Write | Recall a product batch: everyone who scans a GS1 QR code for this GTIN and batch/lot sees the message instead |
update_gs1_recall |
Destructive | Change a recall notice's message, or set is_active false once the recall is over (its history is kept) |
Folders
| Tool | Type | Description |
|---|---|---|
create_folder |
Write | Create a new folder to organize QR codes or pages |
list_folders |
Read-only | List all folders of a given type (qr or page) |
update_folder |
Write | Rename an existing folder |
delete_folder |
Destructive | Delete a folder |
move_qr_codes_to_folder |
Write | Move multiple QR codes into a folder |
unassign_qr_codes_from_folder |
Write | Remove multiple QR codes from a folder (moves them to uncategorized) |
Forms
| Tool | Type | Description |
|---|---|---|
list_forms |
Read-only | List all lead capture forms, optionally filtered by active status |
retrieve_form |
Read-only | Get detailed information about a specific form including its configuration |
create_form |
Write | Create a new lead-capture form from a title and its questions (each with an answer type - short answer, email, multiple choice, rating… - and options for choice questions) |
update_form |
Destructive | Update a form's name or active status |
delete_form |
Destructive | Permanently delete a form |
attach_form_to_qr |
Write | Attach a lead capture form to a QR code |
detach_form_from_qr |
Destructive | Remove the lead capture form from a QR code |
list_form_responses |
Read-only | List the responses a form has collected, newest first: each with its answers, when it came in and which QR code it came from |
get_form_analytics |
Read-only | A form's performance: total responses and skips (with the change against the previous period), responses and skips by date, and responses by source QR code |
get_form_question_analytics |
Read-only | How each question of a form was answered, in the form's order: how many responses answered it (and the rate), and for choice, rating and scale questions how often each answer was given |
list_form_templates |
Read-only | List ready-made form templates (name, description and their blocks), to start a new form from with create_form |
list_form_notifications |
Read-only | List the email alerts set up for new form responses: who they go to, how often, which forms, and whether each is on |
create_form_notification |
Write | Email someone when forms get new responses - for every response, or as a daily, weekly or monthly digest |
update_form_notification |
Destructive | Change a form alert: its recipient, frequency or forms, or switch it on or off |
Lead lists (legacy)
| Tool | Type | Description |
|---|---|---|
list_lead_lists |
Read-only | List all lead lists, optionally filtered by active status |
retrieve_lead_list |
Read-only | Get detailed information about a specific lead list including webhook config |
update_lead_list |
Destructive | Update a lead list's name or active status |
delete_lead_list |
Destructive | Permanently delete a lead list |
attach_lead_list_to_qr |
Write | Attach a lead list to a QR code for lead capture |
detach_lead_list_from_qr |
Destructive | Remove the lead list from a QR code |
Analytics
| Tool | Type | Description |
|---|---|---|
get_account_stats |
Read-only | Retrieve account-level usage counters (total QR codes, scans, users, etc.) |
get_qr_analytics |
Read-only | Retrieve QR code performance metrics broken down by device, geography, date, etc |
get_analytics_overview |
Read-only | The account's scan overview: all-time totals, this month, the last months, and scans by date |
list_analytics_reports |
Read-only | List the scan-analytics reports that have been generated: their scope, dates, status, and a download link once complete |
create_analytics_report |
Write | Generate a scan-analytics report for some QR codes, tags or folders over a date range |
Account & billing
| Tool | Type | Description |
|---|---|---|
get_current_plan |
Read-only | Retrieve the account's active subscription plan - expiry, billing state, and the full quota list it grants |
list_available_plans |
Read-only | List the plans the account can move to, with their features and quotas |
get_downgrade_impact |
Read-only | Before moving to a smaller plan: which of the account's QR codes, users, domains and other resources exceed that plan and would block or be affected by the change |
list_payments |
Read-only | The account's payment history: amount, date and status of each payment |
list_orders |
Read-only | The account's orders (plan purchases, renewals and top-ups) and their status |
list_quota_topups |
Read-only | Extra quota bought on top of the plan (e.g |
Team
| Tool | Type | Description |
|---|---|---|
list_users |
Read-only | List all users in the account |
get_user |
Read-only | Get details of a specific user |
add_user |
Write | Invite a new user to the account by email |
remove_user |
Destructive | Remove a user from the account |
list_user_roles |
Read-only | List all available user roles that can be assigned |
create_custom_role |
Write | Create a custom role/access-level (requires a dedicated plan quota - a 403 means the plan doesn't include it) |
update_user_role |
Destructive | Change the role of an existing user |
resend_user_invitation |
Write | Send a team member's invitation email again, for someone who hasn't accepted yet |
get_activity_feed |
Read-only | What happened in the account: who created, changed or deleted what, and when |
get_activity_summary |
Read-only | Counts of the account's activity over a period, for a quick picture of who did what |
Custom domains
| Tool | Type | Description |
|---|---|---|
get_custom_domain |
Read-only | One custom domain's details: its DNS (TXT/CNAME) verification, SSL status and whether it's the default |
Bulk operations
| Tool | Type | Description |
|---|---|---|
list_bulk_operations |
Read-only | List the account's bulk operations (bulk QR code generation and updates): what each did, its status and results |
get_bulk_operation_stats |
Read-only | Totals across the account's bulk operations |
Media
| Tool | Type | Description |
|---|---|---|
list_media |
Read-only | List files in the account's media library (images, PDFs, videos…), e.g |
Integrations
| Tool | Type | Description |
|---|---|---|
get_integrations_overview |
Read-only | Which integrations the account has connected (webhooks, Zapier, Slack, HubSpot, Google Analytics…) and their state |
list_webhooks |
Read-only | List the account's webhooks: where each sends events, for which forms or QR codes, and whether it's on |
Conversion tracking
| Tool | Type | Description |
|---|---|---|
list_tracking_sites |
Read-only | List the websites set up for conversion tracking (what visitors do after scanning), with their domains |
list_tracking_funnels |
Read-only | List a tracking site's conversion funnels: the steps from scan to conversion |
Landing pages
| Tool | Type | Description |
|---|---|---|
list_page_templates |
Read-only | List landing page templates for QR codes that open a page - Scanova's own, or only the account's saved ones |
list_page_blocks |
Read-only | The blocks a landing page can have for a page category, what each is for and how many a page holds |
get_page_block |
Read-only | One block type's fields, defaults and data schema |
list_page_themes |
Read-only | The themes a page can use, with their main colours |
get_page_theme |
Read-only | One theme's full values (colours, fonts, corners, shadows, spacing, background) |
get_page_template |
Read-only | One template's theme and blocks |
get_page |
Read-only | A code's landing page: blocks in order, theme, unpublished changes, preview and builder links |
get_page_preview |
Read-only | A 1-hour link to the latest draft as visitors would see it; optionally a screenshot |
create_page |
Write | Create a code with a landing page as a draft, from a template or blocks |
edit_page |
Write | Change the draft: add, change, remove or move blocks; theme; theme values; a template's look or layout; name; search details |
publish_page |
Write (destructive) | Make the draft live for everyone scanning the code |
Pages are written in the page builder's block format, so they open at app.scanova.io and can be edited there. Blocks are checked against Scanova's own schemas before saving; refresh them with python scripts/sync_page_blocks.py <qcg-backend> <qcg-frontend-next> when blocks change.
Usage
The server provides the following MCP tools that you can use in your MCP-compatible IDE:
API Endpoints
The deployed server provides these endpoints:
- POST
/mcp- Main MCP endpoint (Streamable HTTP) - GET
/health- Health check endpoint - GET
/- Service information and documentation - GET
/.well-known/oauth-protected-resource- OAuth protected-resource metadata
Protocol
Built on the MCP Python SDK 2.x, which serves
the protocol. The server is dual-era:
- MCP 2026-07-28 (stateless): every request carries its protocol version;
server/discover,
cache hints,resultTypeand request-header validation are supported. - Earlier revisions (2025-11-25 and before): clients that open with
initialize- today's
Cursor, VS Code, Claude Desktop, claude.ai and ChatGPT connectors - keep working unchanged.
Authentication. Send your Scanova API key (or an OAuth access token from Scanova's
authorization server) in Authorization - Bearer <key> or the bare key - or in X-API-Key.
Listing tools and UI resources needs no key; calling a tool without one returns401 with a WWW-Authenticate challenge pointing at the protected-resource metadata.
Tool failures (for example a Scanova API error) come back as tool results with isError: true,
so the assistant can read and explain them.
UI widgets use the MCP Apps
extension (io.modelcontextprotocol/ui), plus openai/outputTemplate for ChatGPT.
Local Development (Optional)
If you want to run the server locally for development:
Installation
Clone the repository:
git clone <repository-url> cd qcg-mcpInstall dependencies:
uv syncRun locally:
# HTTP server mode (same app as production) uv run src/cloud_server.py # Or stdio mode for local MCP testing (uses MCP_ACCESS_TOKEN for the Scanova API) MCP_ACCESS_TOKEN=your-key uv run src/main.py --stdioConfigure for local testing:
{ "mcpServers": { "qcg-mcp": { "transport": "http", "url": "http://localhost:8000/mcp", "headers": { "Authorization": "YOUR_SCANOVA_API_KEY_HERE" } } } }
Docker Deployment (Local)
Build the Docker image:
docker build -t mcpserver:local .Start the container:
docker run -d --name mcpserver -p 8000:8000 mcpserver:localOr with Docker Compose:
docker compose up -dVerify the server is running:
curl http://localhost:8000/healthConfigure your IDE (HTTP transport):
Use the "Configure for local testing" snippet above and point tohttp://localhost:8000/mcp, ensuring theAuthorizationheader contains your Scanova API key.View logs and stop:
# View logs docker logs -f mcpserver # Stop and remove with Compose docker compose down # Or stop/remove single container docker stop mcpserver && docker rm mcpserver
Releases and deploys
Pull request titles follow Conventional Commits
(feat: …, fix: …, docs: …, chore: …; add ! - feat!: … - for a breaking change).
Merging adds the PR to the draft release, versioned by
release-drafter with semantic versioning:feat → minor, fix and the rest → patch, breaking → major.
Publishing the draft release deploys it (.github/workflows/deploy.yml): tests run, the
image is built and tagged with the version, the scanova-mcp ECS Express service is updated to it,
and /health and initialize are checked. A merge to main does not deploy. To redeploy or roll
back, run the Deploy workflow from the Actions tab with that release's tag as the ref.
AWS access is GitHub OIDC → IAM role qcg-mcp-github-deploy (no stored keys): it trusts only this
repository's production environment (release tags v* only) and can only push this image, update
the scanova-mcp service and pass its task roles.
Troubleshooting
Common Issues
"API key is required"
- Ensure your Scanova API key is correctly set in the headers
- Check that the header format matches one of the supported formats
- Get your API key from https://app.scanova.io/
"Invalid token" error
- Verify your API key is valid and active
- Ensure there are no extra spaces or characters in the API key
- Try regenerating your API key from the Scanova dashboard
Connection errors
- Check that the server URL is correct
- Ensure your internet connection is working
- Verify the server is deployed and running
Tool not found
- Restart your IDE after adding the MCP configuration
- Check that the JSON configuration is valid
- Verify the server responds at the
/healthendpoint
scripts/sync_page_blocks.py
"""Refresh src/page_blocks/ from Scanova's own sources.
The page tools build landing pages in the page builder's block format. The
format is owned by two repos, so this copies it here rather than restating it:
- qcg-backend src/qr_manager/json_schema/{v2,categories/components}: the JSON
Schemas a page's blocks must pass to publish (copied as-is to schemas/).
- qcg-frontend-next packages/blocks/src/blocks/landing/*/definition.ts: each
block's label, limit, categories, fields and defaults (dumped to blocks.json
by dump_block_definitions.mjs, which needs `npm i esbuild` next to it).
Usage: python scripts/sync_page_blocks.py <qcg-backend checkout> <qcg-frontend-next checkout>
"""
import json
import shutil
import subprocess
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
OUT = HERE.parent / "src" / "page_blocks"
# Blocks an assistant shouldn't place on an ordinary page: they belong to a
# special page type (intro/document/image pages, cover/main pages), only make
# sense on a GS1 code, or run the customer's own JavaScript.
LEFT_OUT = {
"auto_redirect", "document_viewer", "image_gallery", "cover_page", "main_page", "intro_page",
"gs1_info", "custom_script",
}
def main(backend: Path, frontend: Path) -> None:
src = backend / "src" / "qr_manager" / "json_schema"
schemas = OUT / "schemas"
if schemas.exists():
shutil.rmtree(schemas)
shutil.copytree(src / "v2", schemas / "v2")
shutil.copytree(src / "categories" / "components", schemas / "categories" / "components")
shutil.copytree(backend / "src" / "theme_manager" / "json_schema", schemas / "theme")
dumped = json.loads(subprocess.run(
["node", str(HERE / "dump_block_definitions.mjs"), str(frontend)],
check=True, capture_output=True, text=True,
).stdout)
allowed = json.loads((src / "v2" / "page-types" / "component-page.json").read_text())
allowed = allowed["items"]["allOf"][1]["properties"]["type"]["enum"]
blocks = {}
for block_type in allowed:
d = dumped.get(block_type)
if block_type in LEFT_OUT or not d or "error" in d:
continue
blocks[block_type] = {
"label": d.get("label"),
"description": d.get("description"),
"limit": d.get("limit"),
"removable": d.get("canDelete") is not False,
"categories": d.get("allowedInCategories"),
"fields": d.get("fields") or [],
"defaults": d.get("defaults") or {},
}
(OUT / "blocks.json").write_text(json.dumps(blocks, indent=1, ensure_ascii=False) + "\n")
print(f"{len(blocks)} blocks, schemas from {src}")
if __name__ == "__main__":
main(Path(sys.argv[1]), Path(sys.argv[2]))
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.