utm-helper-mcp
MCP server for generating consistent UTM-tagged links based on an authoritative, version-controlled UTM specification.
README
UTM Helper MCP server
An Arcade MCP server that helps teams produce consistent UTM-tagged links by reading an authoritative, version-controlled UTM spec and exposing it through MCP tools.
This repo is both the spec source of truth (GUIDE.md,
utm-spec.yaml) and the server that serves it. See the
PRD for the full design.
Status: in progress.
get_guidelines(),validate_url(), andlist_campaigns()are implemented.build_urlandadd_campaignland in later issues.
Tools
| Tool | Description |
|---|---|
get_guidelines() |
Returns the human-readable UTM guide (GUIDE.md) fetched from the configured Git spec source. |
validate_url(url) |
The mandatory last hop: normalizes a URL's UTM parameters against the spec and validates them, returning the normalized URL, a changelog of fixups, and any soft nudges. |
list_campaigns() |
Returns the known recurring campaigns, read fresh from the campaign registry Google Sheet (via a shared service credential). Validates the Sheet's column schema on every read. |
validate_url — normalize + validate (the last hop)
validate_url reads the structured spec (utm-spec.yaml) from the configured
Git source and applies it end-to-end to a URL:
-
Normalize every UTM value deterministically (lowercase, hyphen-separated, no spaces; the structural
_inutm_campaignis preserved). Each change is reported in achangelog. -
Validate, with per-field behavior asymmetry straight from the spec:
Param On unknown value utm_source(open enum)Emits the normalized link + a nudge to add the source to the spec repo. utm_medium(closed enum)Hard refuse (raises): shows the valid set + closest suggestion; no link is emitted. utm_campaign(sheet)Must match the YYYY-qN_kebab-slugtemplate; a malformed name hard-errors. Registry membership is checked once the campaign registry lands.utm_content/utm_term(free)Always shape-normalized; never refused.
A missing required parameter (utm_source, utm_medium, utm_campaign)
also hard-refuses. Non-UTM query params are passed through untouched.
On success it returns { "url", "changelog": [{param, from, to}, ...], "nudges": [...] }.
Hard refusals and spec-source failures surface as loud tool errors — it never
falls back to a built-in spec.
Setup (admin)
Requires Python ≥ 3.12 and uv.
uv sync --extra dev # install runtime + dev deps into .venv
Configuration — pointers only, via an Arcade secret
Configuration only says where to look for the spec; it never encodes behavior. All behavior (enums, shape rules, casing) lives in the spec repo and changes by reviewed PR.
The single pointer is provided as the Arcade tool secret
UTM_SPEC_SOURCE_URL — it is not hard-coded in the server. The tool reads it
from its injected Context at call time (context.get_secret(...)).
| Secret | Meaning |
|---|---|
UTM_SPEC_SOURCE_URL |
Raw base URL of the Git spec source. The server appends file names (e.g. GUIDE.md), so it must serve raw file contents. Works with GitHub raw, GitLab raw, or any self-hosted mirror. A trailing / is added if omitted. |
UTM_CAMPAIGN_SHEET_ID |
The campaign registry Google Sheet ID (the token in the Sheet URL between /d/ and /edit). Required for list_campaigns(); no default registry exists, so a missing ID fails loud. |
UTM_CAMPAIGN_SHEET_RANGE |
A1 tab/range to read. Optional; defaults to Campaigns!A:D. |
UTM_CAMPAIGN_SA_JSON |
The full Google service-account key JSON. The shared server-side credential used to read the Sheet — so end users need no direct Sheet access. Required for list_campaigns(). |
Campaign Sheet setup (admin)
The utm_campaign registry lives in a Google Sheet, read with a shared
service-account credential so end users need no direct Sheet/Google access:
- Create a Google Cloud service account and download its key JSON → set as
UTM_CAMPAIGN_SA_JSON. - Enable the Google Sheets API for that project.
- Share the registry Sheet (Viewer) with the service account's email.
- The Sheet's first row must be exactly:
campaign | description | added_by | added_at.
The schema is validated on every read; a mismatched header or malformed row fails loud with a row-numbered, actionable error (see Failure behavior).
Set it where it belongs for your environment:
- Production: configure it on the Arcade dashboard,
or with
arcade secret set UTM_SPEC_SOURCE_URL <url>. - Local dev: put it in
.env(copy.env.example) — Arcade discovers.envand injects the secret into the toolContext.
First run / no config: if the secret is unset, the tool uses the opinionated
default seed shipped in this repo
(https://raw.githubusercontent.com/ArcadeAI/utm-helper-mcp/main/) and logs a
warning. This is the only case where a default is used — see Failure
behavior below.
Run
# stdio (Claude Desktop, CLI clients)
uv run src/utm_server/server.py
# http (Cursor, VS Code)
uv run src/utm_server/server.py http # serves on http://127.0.0.1:8000/mcp/
Caching
Successful spec reads are cached in memory for 5 minutes
(SPEC_CACHE_TTL_SECONDS in sources.py). The
spec is slow-changing (reviewed-PR cadence), so this trades a little staleness
for far fewer network round-trips. The cache is per-process and per-file; a
restart clears it. The TTL is a code constant, not configuration, to keep config
strictly pointers-only.
The campaign Sheet is the opposite: it churns constantly, so list_campaigns()
reads it fresh on every call (no result cache) — newly added campaigns appear
immediately. Only the CampaignRegistry object (HTTP client + minted token) is
reused across calls.
Failure behavior — fail loud, never silently default
If the spec source is unreachable, returns a non-2xx status, or the file is missing, the tool raises a specific error naming the URL and failure and telling the user to contact the admin. It never falls back to a built-in or stale guide — a misconfigured deploy must look broken, not quietly serve the wrong content. The single announced exception is the first-run default seed source above (which is still fetched over the network like any other source).
The campaign Sheet is human-editable, so list_campaigns() defensively
validates its schema on every read: the header row must be exactly
campaign | description | added_by | added_at, and each data row is checked. On
a mismatch (wrong headers, extra columns, a row missing its campaign name) it
raises a specific, row-numbered error ("…headers don't match… fix the header
row (row 1)…", "…row 7 is missing a campaign name…") and returns no campaigns —
it never silently guesses what a column means.
Development
uv run pytest # tests
uv run ruff check . # lint
uv run mypy src # type-check
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。