From 5a552c477e54c9cb001a8297e19d2896d78406e5 Mon Sep 17 00:00:00 2001 From: Sean C Davis Date: Tue, 15 Sep 2026 13:55:06 -0400 Subject: [PATCH 1/2] feat: credit MCP-spawned logins to utm_source=mcp via NETLIFY_LOGIN_SOURCE The Netlify MCP server sets NETLIFY_LOGIN_SOURCE=mcp when it spawns netlify login. Only allow-listed values override the default cli source. Co-Authored-By: Claude Fable 5.1 --- docs/index.md | 4 ++++ src/utils/login-url.ts | 9 ++++++++- tests/unit/utils/login-url.test.ts | 20 ++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 4ab2fd42432..85974570127 100644 --- a/docs/index.md +++ b/docs/index.md @@ -229,6 +229,10 @@ Recognized values are `claude`, `codex`, `copilot`, `gemini`, `cursor`, `opencod The CLI also recognizes markers that agent products set on their own, such as `AI_AGENT`, `CODEX_CI`, and `GEMINI_CLI`. `NETLIFY_AGENT` takes precedence over all of them, even when its value isn't recognized. +### Login source + +A program that runs `netlify login` on a user's behalf can set `NETLIFY_LOGIN_SOURCE` so a signup is credited to it rather than to the CLI. The only recognized value is `mcp`, which the Netlify MCP server sets. Any other value is ignored and the login URL keeps `utm_source=cli`. + ### Telemetry When telemetry is enabled, each CLI telemetry event includes the detected agent: diff --git a/src/utils/login-url.ts b/src/utils/login-url.ts index ca642062af5..5f6d7c9928b 100644 --- a/src/utils/login-url.ts +++ b/src/utils/login-url.ts @@ -4,8 +4,15 @@ import { getDrivingAgent } from './agent-detection.js' // a session or run id, or a path, none of which may reach a URL. const SOURCES_WITH_ANNOUNCED_VALUE = new Set(['NETLIFY_AGENT', 'AI_AGENT']) +const LOGIN_SOURCES = new Set(['mcp']) + const sanitizeUtmTerm = (raw: string): string => raw.replace(/[^A-Za-z0-9_.:@-]/g, '').slice(0, 64) +const getUtmSource = (env: NodeJS.ProcessEnv): string => { + const source = env.NETLIFY_LOGIN_SOURCE + return source !== undefined && LOGIN_SOURCES.has(source) ? source : 'cli' +} + const getUtmTerm = (source: string, env: NodeJS.ProcessEnv): string => { const value = SOURCES_WITH_ANNOUNCED_VALUE.has(source) ? env[source] : undefined return sanitizeUtmTerm(value ? `${source}:${value}` : source) @@ -16,7 +23,7 @@ export const buildAuthorizeUrl = (ticketId: string, env: NodeJS.ProcessEnv = pro const params = new URLSearchParams({ response_type: 'ticket', ticket: ticketId, - utm_source: 'cli', + utm_source: getUtmSource(env), utm_campaign: 'integrations', }) diff --git a/tests/unit/utils/login-url.test.ts b/tests/unit/utils/login-url.test.ts index 3aa8e8dac8b..6abb985cd3c 100644 --- a/tests/unit/utils/login-url.test.ts +++ b/tests/unit/utils/login-url.test.ts @@ -10,6 +10,26 @@ test('tags the URL with only the CLI source and campaign when no agent is detect ) }) +test('credits the MCP server as the source when it announces the login', () => { + const params = paramsFor({ NETLIFY_LOGIN_SOURCE: 'mcp' }) + + expect(params.get('utm_source')).toBe('mcp') + expect(params.get('utm_campaign')).toBe('integrations') +}) + +test('keeps the CLI as the source for a login source that is not on the allow-list', () => { + expect(paramsFor({ NETLIFY_LOGIN_SOURCE: 'https://evil.example' }).get('utm_source')).toBe('cli') + expect(paramsFor({ NETLIFY_LOGIN_SOURCE: '' }).get('utm_source')).toBe('cli') +}) + +test('combines the MCP source with the announced agent', () => { + const params = paramsFor({ NETLIFY_LOGIN_SOURCE: 'mcp', NETLIFY_AGENT: 'claude-code' }) + + expect(params.get('utm_source')).toBe('mcp') + expect(params.get('utm_content')).toBe('claude') + expect(params.get('utm_term')).toBe('NETLIFY_AGENT:claude-code') +}) + test('adds the agent name and the deciding variable with its value', () => { const params = paramsFor({ AI_AGENT: 'claude-code_2-1-259_agent' }) From c3bb7bb95352815397c4c5229a4130221168fcca Mon Sep 17 00:00:00 2001 From: Sean C Davis Date: Tue, 15 Sep 2026 14:58:09 -0400 Subject: [PATCH 2/2] docs: use Vale-preferred spellings in the login source section Co-Authored-By: Claude Fable 5.1 --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 85974570127..7ccdb034f3f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -231,7 +231,7 @@ The CLI also recognizes markers that agent products set on their own, such as `A ### Login source -A program that runs `netlify login` on a user's behalf can set `NETLIFY_LOGIN_SOURCE` so a signup is credited to it rather than to the CLI. The only recognized value is `mcp`, which the Netlify MCP server sets. Any other value is ignored and the login URL keeps `utm_source=cli`. +A program that runs `netlify login` on a user’s behalf can set `NETLIFY_LOGIN_SOURCE` so a sign-up is credited to it rather than to the CLI. The only recognized value is `mcp`, which the Netlify MCP server sets. Any other value is ignored and the login URL keeps `utm_source=cli`. ### Telemetry