Skip to main content
Glama

Google Tag Manager MCP Server

by ambit1977
index.js66.2 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { GTMClient } from './gtm-client.js'; /** * Google Tag Manager MCP サーバー */ class GTMCPServer { constructor() { this.server = new Server( { name: 'gtm-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.gtmClient = new GTMClient(); this.setupHandlers(); } setupHandlers() { // ツール一覧を取得 this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'get_auth_url', description: 'OAuth2認証URLを取得します。このURLにアクセスして認証を完了してください。', inputSchema: { type: 'object', properties: {}, }, }, { name: 'authenticate', description: '認証コードを使用して認証を完了します。get_auth_urlで取得したURLにアクセスし、リダイレクト先のURLから認証コードを取得して使用してください。', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'OAuth2認証コード(リダイレクト先のURLの「code=」の後の値)', }, }, required: ['code'], }, }, { name: 'check_auth_status', description: '現在の認証状態を確認します', inputSchema: { type: 'object', properties: {}, }, }, { name: 'reset_auth', description: '保存された認証情報をリセットします', inputSchema: { type: 'object', properties: {}, }, }, { name: 'list_accounts', description: 'Google Tag Managerのアカウント一覧を取得します', inputSchema: { type: 'object', properties: {}, }, }, { name: 'list_containers', description: '指定されたアカウントのコンテナ一覧を取得します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, }, required: ['accountId'], }, }, { name: 'get_container', description: '指定されたコンテナの詳細を取得します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, }, required: ['accountId', 'containerId'], }, }, { name: 'create_container', description: '新しいコンテナを作成します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, name: { type: 'string', description: 'コンテナ名', }, usageContext: { type: 'array', items: { type: 'string', enum: ['web', 'android', 'ios', 'amp'], }, description: '使用コンテキスト(例: ["web"])', }, }, required: ['accountId', 'name', 'usageContext'], }, }, { name: 'list_workspaces', description: '指定されたコンテナのワークスペース一覧を取得します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, }, required: ['accountId', 'containerId'], }, }, { name: 'get_workspace', description: '指定されたワークスペースの詳細を取得します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, }, required: ['accountId', 'containerId', 'workspaceId'], }, }, { name: 'list_tags', description: '指定されたワークスペースのタグ一覧を取得します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, }, required: ['accountId', 'containerId', 'workspaceId'], }, }, { name: 'get_tag', description: '指定されたタグの詳細を取得します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, tagId: { type: 'string', description: 'タグID', }, }, required: ['accountId', 'containerId', 'workspaceId', 'tagId'], }, }, { name: 'create_tag', description: '新しいタグを作成します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, name: { type: 'string', description: 'タグ名', }, type: { type: 'string', description: 'タグタイプ(例: "googtag"=GA4設定, "gaawe"=GA4イベント, "awct"=Google広告コンバージョン, "html"=カスタムHTML, "img"=カスタム画像, "fbq"=Facebookピクセル, "ua"=Universal Analyticsなど)', }, parameter: { type: 'array', description: 'タグのパラメータ配列', items: { type: 'object', properties: { type: { type: 'string', description: 'パラメータタイプ', }, key: { type: 'string', description: 'パラメータキー', }, value: { type: 'string', description: 'パラメータ値', }, }, }, }, firingTriggerId: { type: 'array', items: { type: 'string', }, description: '発火トリガーIDの配列', }, }, required: ['accountId', 'containerId', 'workspaceId', 'name', 'type'], }, }, { name: 'update_tag', description: '既存のタグを更新します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, tagId: { type: 'string', description: 'タグID', }, name: { type: 'string', description: 'タグ名', }, type: { type: 'string', description: 'タグタイプ', }, parameter: { type: 'array', description: 'タグのパラメータ配列', }, firingTriggerId: { type: 'array', items: { type: 'string', }, description: '発火トリガーIDの配列', }, }, required: ['accountId', 'containerId', 'workspaceId', 'tagId'], }, }, { name: 'delete_tag', description: 'タグを削除します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, tagId: { type: 'string', description: 'タグID', }, }, required: ['accountId', 'containerId', 'workspaceId', 'tagId'], }, }, { name: 'list_triggers', description: '指定されたワークスペースのトリガー一覧を取得します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, }, required: ['accountId', 'containerId', 'workspaceId'], }, }, { name: 'get_trigger', description: '指定されたトリガーの詳細を取得します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, triggerId: { type: 'string', description: 'トリガーID', }, }, required: ['accountId', 'containerId', 'workspaceId', 'triggerId'], }, }, { name: 'update_trigger', description: '既存のトリガーを更新します。filter、autoEventFilter、waitForTagsなどのすべての設定を更新できます。', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, triggerId: { type: 'string', description: 'トリガーID', }, name: { type: 'string', description: 'トリガー名', }, type: { type: 'string', description: 'トリガータイプ', }, customEventFilter: { type: 'array', description: 'カスタムイベントフィルタ(CUSTOM_EVENTタイプ用)', }, filter: { type: 'array', description: 'フィルタ(linkClick、clickなどのタイプ用)', }, autoEventFilter: { type: 'array', description: '自動イベントフィルタ(linkClickタイプ用)', }, waitForTags: { type: 'boolean', description: 'タグの待機を有効化(linkClickタイプ用)', }, checkValidation: { type: 'boolean', description: 'バリデーションチェック(linkClickタイプ用)', }, waitForTagsTimeout: { type: 'number', description: 'タグ待機タイムアウト(ミリ秒、linkClickタイプ用)', }, // formSubmission用 formId: { type: 'string', description: 'フォームID(formSubmissionタイプ用)', }, formClasses: { type: 'string', description: 'フォームクラス(formSubmissionタイプ用)', }, // scrollDepth用 verticalThreshold: { type: 'number', description: '垂直スクロール閾値(パーセント、scrollDepthタイプ用)', }, horizontalThreshold: { type: 'number', description: '水平スクロール閾値(パーセント、scrollDepthタイプ用)', }, // elementVisibility用 selector: { type: 'string', description: 'CSSセレクタ(elementVisibilityタイプ用)', }, visiblePercentageThreshold: { type: 'number', description: '表示割合閾値(パーセント、elementVisibilityタイプ用)', }, continuousTimeMinMilliseconds: { type: 'number', description: '連続表示時間(ミリ秒、elementVisibilityタイプ用)', }, // youtubeVideo用 videoId: { type: 'string', description: 'YouTube動画ID(youtubeVideoタイプ用)', }, enableTriggerOnVideoStart: { type: 'boolean', description: '動画開始時に発火(youtubeVideoタイプ用)', }, enableTriggerOnVideoProgress: { type: 'boolean', description: '動画再生中に発火(youtubeVideoタイプ用)', }, enableTriggerOnVideoComplete: { type: 'boolean', description: '動画完了時に発火(youtubeVideoタイプ用)', }, enableTriggerOnVideoPause: { type: 'boolean', description: '動画一時停止時に発火(youtubeVideoタイプ用)', }, enableTriggerOnVideoSeek: { type: 'boolean', description: '動画シーク時に発火(youtubeVideoタイプ用)', }, // timer用 interval: { type: 'number', description: 'インターバル(ミリ秒、timerタイプ用)', }, limit: { type: 'number', description: '発火回数の上限(timerタイプ用)', }, startTimerOn: { type: 'string', description: 'タイマー開始タイミング(timerタイプ用、例: "windowLoad", "domReady")', }, }, required: ['accountId', 'containerId', 'workspaceId', 'triggerId'], }, }, { name: 'delete_trigger', description: 'トリガーを削除します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, triggerId: { type: 'string', description: 'トリガーID', }, }, required: ['accountId', 'containerId', 'workspaceId', 'triggerId'], }, }, { name: 'create_trigger', description: '新しいトリガーを作成します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, name: { type: 'string', description: 'トリガー名', }, type: { type: 'string', description: 'トリガータイプ(例: "PAGEVIEW", "CLICK", "CUSTOM_EVENT", "linkClick"など)', }, customEventFilter: { type: 'array', description: 'カスタムイベントフィルタ(CUSTOM_EVENTタイプ用)', }, filter: { type: 'array', description: 'フィルタ(linkClick、clickなどのタイプ用)', }, autoEventFilter: { type: 'array', description: '自動イベントフィルタ(linkClickタイプ用)', }, waitForTags: { type: 'boolean', description: 'タグの待機を有効化(linkClickタイプ用)', }, checkValidation: { type: 'boolean', description: 'バリデーションチェック(linkClickタイプ用)', }, waitForTagsTimeout: { type: 'number', description: 'タグ待機タイムアウト(ミリ秒、linkClickタイプ用)', }, // formSubmission用 formId: { type: 'string', description: 'フォームID(formSubmissionタイプ用)', }, formClasses: { type: 'string', description: 'フォームクラス(formSubmissionタイプ用)', }, // scrollDepth用 verticalThreshold: { type: 'number', description: '垂直スクロール閾値(パーセント、scrollDepthタイプ用)', }, horizontalThreshold: { type: 'number', description: '水平スクロール閾値(パーセント、scrollDepthタイプ用)', }, // elementVisibility用 selector: { type: 'string', description: 'CSSセレクタ(elementVisibilityタイプ用)', }, visiblePercentageThreshold: { type: 'number', description: '表示割合閾値(パーセント、elementVisibilityタイプ用)', }, continuousTimeMinMilliseconds: { type: 'number', description: '連続表示時間(ミリ秒、elementVisibilityタイプ用)', }, // youtubeVideo用 videoId: { type: 'string', description: 'YouTube動画ID(youtubeVideoタイプ用)', }, enableTriggerOnVideoStart: { type: 'boolean', description: '動画開始時に発火(youtubeVideoタイプ用)', }, enableTriggerOnVideoProgress: { type: 'boolean', description: '動画再生中に発火(youtubeVideoタイプ用)', }, enableTriggerOnVideoComplete: { type: 'boolean', description: '動画完了時に発火(youtubeVideoタイプ用)', }, enableTriggerOnVideoPause: { type: 'boolean', description: '動画一時停止時に発火(youtubeVideoタイプ用)', }, enableTriggerOnVideoSeek: { type: 'boolean', description: '動画シーク時に発火(youtubeVideoタイプ用)', }, // timer用 interval: { type: 'number', description: 'インターバル(ミリ秒、timerタイプ用)', }, limit: { type: 'number', description: '発火回数の上限(timerタイプ用)', }, startTimerOn: { type: 'string', description: 'タイマー開始タイミング(timerタイプ用、例: "windowLoad", "domReady")', }, }, required: ['accountId', 'containerId', 'workspaceId', 'name', 'type'], }, }, { name: 'list_variables', description: '指定されたワークスペースの変数一覧を取得します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, }, required: ['accountId', 'containerId', 'workspaceId'], }, }, { name: 'get_variable', description: '指定された変数の詳細を取得します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, variableId: { type: 'string', description: '変数ID', }, }, required: ['accountId', 'containerId', 'workspaceId', 'variableId'], }, }, { name: 'create_variable', description: '新しい変数を作成します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, name: { type: 'string', description: '変数名', }, type: { type: 'string', description: '変数タイプ(例: "c"=定数, "v"=データレイヤー, "j"=JavaScript, "d"=DOM要素, "k"=Cookie, "u"=URL, "ae"=自動イベント, "b"=組み込み変数など)', }, parameter: { type: 'array', description: '変数のパラメータ配列。タイプに応じて必要なパラメータを設定してください。', }, }, required: ['accountId', 'containerId', 'workspaceId', 'name', 'type'], }, }, { name: 'update_variable', description: '既存の変数を更新します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, variableId: { type: 'string', description: '変数ID', }, name: { type: 'string', description: '変数名', }, type: { type: 'string', description: '変数タイプ', }, parameter: { type: 'array', description: '変数のパラメータ配列', }, }, required: ['accountId', 'containerId', 'workspaceId', 'variableId'], }, }, { name: 'delete_variable', description: '変数を削除します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, variableId: { type: 'string', description: '変数ID', }, }, required: ['accountId', 'containerId', 'workspaceId', 'variableId'], }, }, { name: 'create_version', description: 'ワークスペースの変更をバージョンとして作成(公開準備)します', inputSchema: { type: 'object', properties: { accountId: { type: 'string', description: 'アカウントID', }, containerId: { type: 'string', description: 'コンテナID', }, workspaceId: { type: 'string', description: 'ワークスペースID', }, name: { type: 'string', description: 'バージョン名(オプション)', }, notes: { type: 'string', description: 'バージョンノート(オプション)', }, }, required: ['accountId', 'containerId', 'workspaceId'], }, }, ], })); // ツール呼び出しを処理 this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'get_auth_url': { const oauth2Auth = this.gtmClient.getOAuth2Auth(); const authUrl = oauth2Auth.getAuthUrl(); return { content: [ { type: 'text', text: JSON.stringify({ authUrl: authUrl, instructions: 'このURLにアクセスして認証を完了してください。認証後、リダイレクト先のURLから「code=」の後の認証コードをコピーし、authenticateツールで使用してください。' }, null, 2), }, ], }; } case 'authenticate': { const oauth2Auth = this.gtmClient.getOAuth2Auth(); const result = await oauth2Auth.authenticateWithCode(args.code); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'check_auth_status': { const oauth2Auth = this.gtmClient.getOAuth2Auth(); const isAuthenticated = oauth2Auth.isAuthenticated(); return { content: [ { type: 'text', text: JSON.stringify({ authenticated: isAuthenticated, message: isAuthenticated ? '認証済みです' : '未認証です。get_auth_urlツールで認証URLを取得してください。' }, null, 2), }, ], }; } case 'reset_auth': { const oauth2Auth = this.gtmClient.getOAuth2Auth(); const result = await oauth2Auth.resetAuth(); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'list_accounts': return { content: [ { type: 'text', text: JSON.stringify(await this.gtmClient.listAccounts(), null, 2), }, ], }; case 'list_containers': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.listContainers(args.accountId), null, 2 ), }, ], }; case 'get_container': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.getContainer(args.accountId, args.containerId), null, 2 ), }, ], }; case 'create_container': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.createContainer(args.accountId, { name: args.name, usageContext: args.usageContext, }), null, 2 ), }, ], }; case 'list_workspaces': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.listWorkspaces(args.accountId, args.containerId), null, 2 ), }, ], }; case 'get_workspace': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.getWorkspace( args.accountId, args.containerId, args.workspaceId ), null, 2 ), }, ], }; case 'list_tags': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.listTags( args.accountId, args.containerId, args.workspaceId ), null, 2 ), }, ], }; case 'get_tag': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.getTag( args.accountId, args.containerId, args.workspaceId, args.tagId ), null, 2 ), }, ], }; case 'create_tag': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.createTag( args.accountId, args.containerId, args.workspaceId, { name: args.name, type: args.type, parameter: args.parameter || [], firingTriggerId: args.firingTriggerId || [], } ), null, 2 ), }, ], }; case 'update_tag': { // 既存のタグデータを取得 const existingTag = await this.gtmClient.getTag( args.accountId, args.containerId, args.workspaceId, args.tagId ); // 更新データを構築(指定されたパラメータで上書き) const tagData = { name: args.name !== undefined ? args.name : existingTag.name, type: args.type !== undefined ? args.type : existingTag.type, parameter: args.parameter !== undefined ? args.parameter : existingTag.parameter || [], firingTriggerId: args.firingTriggerId !== undefined ? args.firingTriggerId : existingTag.firingTriggerId || [], }; // その他のプロパティを保持 if (existingTag.tagFiringOption) { tagData.tagFiringOption = existingTag.tagFiringOption; } if (existingTag.consentSettings) { tagData.consentSettings = existingTag.consentSettings; } if (existingTag.monitoringMetadata) { tagData.monitoringMetadata = existingTag.monitoringMetadata; } // fingerprintを保持(更新に必要) if (existingTag.fingerprint) { tagData.fingerprint = existingTag.fingerprint; } return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.updateTag( args.accountId, args.containerId, args.workspaceId, args.tagId, tagData ), null, 2 ), }, ], }; } case 'delete_tag': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.deleteTag( args.accountId, args.containerId, args.workspaceId, args.tagId ), null, 2 ), }, ], }; case 'list_triggers': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.listTriggers( args.accountId, args.containerId, args.workspaceId ), null, 2 ), }, ], }; case 'create_trigger': { const triggerData = { name: args.name, type: args.type, }; // カスタムイベントフィルタ(CUSTOM_EVENTタイプ用) if (args.customEventFilter) { triggerData.customEventFilter = args.customEventFilter; } // フィルタ(linkClick、clickなどのタイプ用) if (args.filter) { triggerData.filter = args.filter; } // 自動イベントフィルタ(linkClickタイプ用) if (args.autoEventFilter) { triggerData.autoEventFilter = args.autoEventFilter; } // タグ待機設定(linkClickタイプ用) if (args.waitForTags !== undefined) { triggerData.waitForTags = { type: 'boolean', value: args.waitForTags }; } // バリデーションチェック(linkClickタイプ用) if (args.checkValidation !== undefined) { triggerData.checkValidation = { type: 'boolean', value: args.checkValidation }; } // タグ待機タイムアウト(linkClickタイプ用) if (args.waitForTagsTimeout !== undefined) { triggerData.waitForTagsTimeout = { type: 'template', value: String(args.waitForTagsTimeout) }; } // formSubmission用 if (args.type === 'formSubmission') { triggerData.filter = []; if (args.formId) { triggerData.filter.push({ type: 'contains', parameter: [ { type: 'template', key: 'arg0', value: '{{Form ID}}' }, { type: 'template', key: 'arg1', value: args.formId } ] }); } if (args.formClasses) { triggerData.filter.push({ type: 'contains', parameter: [ { type: 'template', key: 'arg0', value: '{{Form Classes}}' }, { type: 'template', key: 'arg1', value: args.formClasses } ] }); } } // scrollDepth用 if (args.type === 'scrollDepth') { triggerData.parameter = [ { type: 'template', key: 'verticalThresholdUnits', value: 'PERCENT' }, { type: 'template', key: 'verticalThreshold', value: String(args.verticalThreshold || 25) } ]; if (args.horizontalThreshold !== undefined) { triggerData.parameter.push( { type: 'template', key: 'horizontalThresholdUnits', value: 'PERCENT' }, { type: 'template', key: 'horizontalThreshold', value: String(args.horizontalThreshold) } ); } } // elementVisibility用 if (args.type === 'visible' || args.type === 'elementVisibility') { triggerData.type = 'visible'; triggerData.parameter = [ { type: 'template', key: 'selector', value: args.selector || '' }, { type: 'template', key: 'visiblePercentageThreshold', value: String(args.visiblePercentageThreshold || 50) }, { type: 'template', key: 'continuousTimeMinMilliseconds', value: String(args.continuousTimeMinMilliseconds || 0) } ]; } // youtubeVideo用 if (args.type === 'youtubeVideo') { triggerData.parameter = [ { type: 'template', key: 'enableTriggerOnVideoStart', value: String(args.enableTriggerOnVideoStart || false) }, { type: 'template', key: 'enableTriggerOnVideoProgress', value: String(args.enableTriggerOnVideoProgress || false) }, { type: 'template', key: 'enableTriggerOnVideoComplete', value: String(args.enableTriggerOnVideoComplete || false) }, { type: 'template', key: 'enableTriggerOnVideoPause', value: String(args.enableTriggerOnVideoPause || false) }, { type: 'template', key: 'enableTriggerOnVideoSeek', value: String(args.enableTriggerOnVideoSeek || false) } ]; if (args.videoId) { triggerData.filter = [ { type: 'equals', parameter: [ { type: 'template', key: 'arg0', value: '{{Video ID}}' }, { type: 'template', key: 'arg1', value: args.videoId } ] } ]; } } // timer用 if (args.type === 'timer') { triggerData.parameter = [ { type: 'template', key: 'interval', value: String(args.interval || 1000) }, { type: 'template', key: 'limit', value: String(args.limit || 1) }, { type: 'template', key: 'startTimerOn', value: args.startTimerOn || 'windowLoad' } ]; } return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.createTrigger( args.accountId, args.containerId, args.workspaceId, triggerData ), null, 2 ), }, ], }; } case 'get_trigger': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.getTrigger( args.accountId, args.containerId, args.workspaceId, args.triggerId ), null, 2 ), }, ], }; case 'update_trigger': { // 既存のトリガーデータを取得 const existingTrigger = await this.gtmClient.getTrigger( args.accountId, args.containerId, args.workspaceId, args.triggerId ); // 更新データを構築(指定されたパラメータで上書き) const triggerData = { name: args.name !== undefined ? args.name : existingTrigger.name, type: args.type !== undefined ? args.type : existingTrigger.type, }; // カスタムイベントフィルタ if (args.customEventFilter !== undefined) { triggerData.customEventFilter = args.customEventFilter; } else if (existingTrigger.customEventFilter) { triggerData.customEventFilter = existingTrigger.customEventFilter; } // フィルタ if (args.filter !== undefined) { triggerData.filter = args.filter; } else if (existingTrigger.filter) { triggerData.filter = existingTrigger.filter; } // 自動イベントフィルタ if (args.autoEventFilter !== undefined) { triggerData.autoEventFilter = args.autoEventFilter; } else if (existingTrigger.autoEventFilter) { triggerData.autoEventFilter = existingTrigger.autoEventFilter; } // タグ待機設定 if (args.waitForTags !== undefined) { triggerData.waitForTags = { type: 'boolean', value: args.waitForTags }; } else if (existingTrigger.waitForTags) { triggerData.waitForTags = existingTrigger.waitForTags; } // バリデーションチェック if (args.checkValidation !== undefined) { triggerData.checkValidation = { type: 'boolean', value: args.checkValidation }; } else if (existingTrigger.checkValidation) { triggerData.checkValidation = existingTrigger.checkValidation; } // タグ待機タイムアウト if (args.waitForTagsTimeout !== undefined) { triggerData.waitForTagsTimeout = { type: 'template', value: String(args.waitForTagsTimeout) }; } else if (existingTrigger.waitForTagsTimeout) { triggerData.waitForTagsTimeout = existingTrigger.waitForTagsTimeout; } // formSubmission用の処理 if (triggerData.type === 'formSubmission') { if (args.formId !== undefined || args.formClasses !== undefined) { triggerData.filter = []; if (args.formId !== undefined) { triggerData.filter.push({ type: 'contains', parameter: [ { type: 'template', key: 'arg0', value: '{{Form ID}}' }, { type: 'template', key: 'arg1', value: args.formId } ] }); } else if (existingTrigger.filter) { // 既存のForm IDフィルタを保持 const formIdFilter = existingTrigger.filter.find(f => f.parameter?.some(p => p.value === '{{Form ID}}') ); if (formIdFilter) { triggerData.filter.push(formIdFilter); } } if (args.formClasses !== undefined) { triggerData.filter.push({ type: 'contains', parameter: [ { type: 'template', key: 'arg0', value: '{{Form Classes}}' }, { type: 'template', key: 'arg1', value: args.formClasses } ] }); } else if (existingTrigger.filter) { // 既存のForm Classesフィルタを保持 const formClassesFilter = existingTrigger.filter.find(f => f.parameter?.some(p => p.value === '{{Form Classes}}') ); if (formClassesFilter) { triggerData.filter.push(formClassesFilter); } } } else if (existingTrigger.filter) { triggerData.filter = existingTrigger.filter; } } // scrollDepth用の処理 if (triggerData.type === 'scrollDepth') { if (args.verticalThreshold !== undefined || args.horizontalThreshold !== undefined) { triggerData.parameter = [ { type: 'template', key: 'verticalThresholdUnits', value: 'PERCENT' }, { type: 'template', key: 'verticalThreshold', value: String(args.verticalThreshold !== undefined ? args.verticalThreshold : (existingTrigger.parameter?.find(p => p.key === 'verticalThreshold')?.value || 25)) } ]; if (args.horizontalThreshold !== undefined) { triggerData.parameter.push( { type: 'template', key: 'horizontalThresholdUnits', value: 'PERCENT' }, { type: 'template', key: 'horizontalThreshold', value: String(args.horizontalThreshold) } ); } else if (existingTrigger.parameter) { const horizontalThreshold = existingTrigger.parameter.find(p => p.key === 'horizontalThreshold'); if (horizontalThreshold) { triggerData.parameter.push( { type: 'template', key: 'horizontalThresholdUnits', value: 'PERCENT' }, horizontalThreshold ); } } } else if (existingTrigger.parameter) { triggerData.parameter = existingTrigger.parameter; } } // elementVisibility用の処理 if (triggerData.type === 'visible' || triggerData.type === 'elementVisibility') { triggerData.type = 'visible'; if (args.selector !== undefined || args.visiblePercentageThreshold !== undefined || args.continuousTimeMinMilliseconds !== undefined) { triggerData.parameter = [ { type: 'template', key: 'selector', value: args.selector !== undefined ? args.selector : (existingTrigger.parameter?.find(p => p.key === 'selector')?.value || '') }, { type: 'template', key: 'visiblePercentageThreshold', value: String(args.visiblePercentageThreshold !== undefined ? args.visiblePercentageThreshold : (existingTrigger.parameter?.find(p => p.key === 'visiblePercentageThreshold')?.value || 50)) }, { type: 'template', key: 'continuousTimeMinMilliseconds', value: String(args.continuousTimeMinMilliseconds !== undefined ? args.continuousTimeMinMilliseconds : (existingTrigger.parameter?.find(p => p.key === 'continuousTimeMinMilliseconds')?.value || 0)) } ]; } else if (existingTrigger.parameter) { triggerData.parameter = existingTrigger.parameter; } } // youtubeVideo用の処理 if (triggerData.type === 'youtubeVideo') { if (args.enableTriggerOnVideoStart !== undefined || args.enableTriggerOnVideoProgress !== undefined || args.enableTriggerOnVideoComplete !== undefined || args.enableTriggerOnVideoPause !== undefined || args.enableTriggerOnVideoSeek !== undefined || args.videoId !== undefined) { triggerData.parameter = [ { type: 'template', key: 'enableTriggerOnVideoStart', value: String(args.enableTriggerOnVideoStart !== undefined ? args.enableTriggerOnVideoStart : (existingTrigger.parameter?.find(p => p.key === 'enableTriggerOnVideoStart')?.value || false)) }, { type: 'template', key: 'enableTriggerOnVideoProgress', value: String(args.enableTriggerOnVideoProgress !== undefined ? args.enableTriggerOnVideoProgress : (existingTrigger.parameter?.find(p => p.key === 'enableTriggerOnVideoProgress')?.value || false)) }, { type: 'template', key: 'enableTriggerOnVideoComplete', value: String(args.enableTriggerOnVideoComplete !== undefined ? args.enableTriggerOnVideoComplete : (existingTrigger.parameter?.find(p => p.key === 'enableTriggerOnVideoComplete')?.value || false)) }, { type: 'template', key: 'enableTriggerOnVideoPause', value: String(args.enableTriggerOnVideoPause !== undefined ? args.enableTriggerOnVideoPause : (existingTrigger.parameter?.find(p => p.key === 'enableTriggerOnVideoPause')?.value || false)) }, { type: 'template', key: 'enableTriggerOnVideoSeek', value: String(args.enableTriggerOnVideoSeek !== undefined ? args.enableTriggerOnVideoSeek : (existingTrigger.parameter?.find(p => p.key === 'enableTriggerOnVideoSeek')?.value || false)) } ]; if (args.videoId !== undefined) { triggerData.filter = [ { type: 'equals', parameter: [ { type: 'template', key: 'arg0', value: '{{Video ID}}' }, { type: 'template', key: 'arg1', value: args.videoId } ] } ]; } else if (existingTrigger.filter) { triggerData.filter = existingTrigger.filter; } } else { if (existingTrigger.parameter) { triggerData.parameter = existingTrigger.parameter; } if (existingTrigger.filter) { triggerData.filter = existingTrigger.filter; } } } // timer用の処理 if (triggerData.type === 'timer') { if (args.interval !== undefined || args.limit !== undefined || args.startTimerOn !== undefined) { triggerData.parameter = [ { type: 'template', key: 'interval', value: String(args.interval !== undefined ? args.interval : (existingTrigger.parameter?.find(p => p.key === 'interval')?.value || 1000)) }, { type: 'template', key: 'limit', value: String(args.limit !== undefined ? args.limit : (existingTrigger.parameter?.find(p => p.key === 'limit')?.value || 1)) }, { type: 'template', key: 'startTimerOn', value: args.startTimerOn !== undefined ? args.startTimerOn : (existingTrigger.parameter?.find(p => p.key === 'startTimerOn')?.value || 'windowLoad') } ]; } else if (existingTrigger.parameter) { triggerData.parameter = existingTrigger.parameter; } } // その他のタイプのパラメータを保持 if (!triggerData.parameter && existingTrigger.parameter) { triggerData.parameter = existingTrigger.parameter; } // fingerprintを保持(更新に必要) if (existingTrigger.fingerprint) { triggerData.fingerprint = existingTrigger.fingerprint; } return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.updateTrigger( args.accountId, args.containerId, args.workspaceId, args.triggerId, triggerData ), null, 2 ), }, ], }; } case 'delete_trigger': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.deleteTrigger( args.accountId, args.containerId, args.workspaceId, args.triggerId ), null, 2 ), }, ], }; case 'list_variables': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.listVariables( args.accountId, args.containerId, args.workspaceId ), null, 2 ), }, ], }; case 'get_variable': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.getVariable( args.accountId, args.containerId, args.workspaceId, args.variableId ), null, 2 ), }, ], }; case 'create_variable': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.createVariable( args.accountId, args.containerId, args.workspaceId, { name: args.name, type: args.type, parameter: args.parameter || [], } ), null, 2 ), }, ], }; case 'update_variable': { // 既存の変数データを取得 const existingVariable = await this.gtmClient.getVariable( args.accountId, args.containerId, args.workspaceId, args.variableId ); // 更新データを構築 const variableData = { name: args.name !== undefined ? args.name : existingVariable.name, type: args.type !== undefined ? args.type : existingVariable.type, parameter: args.parameter !== undefined ? args.parameter : existingVariable.parameter || [], }; // fingerprintを保持(更新に必要) if (existingVariable.fingerprint) { variableData.fingerprint = existingVariable.fingerprint; } return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.updateVariable( args.accountId, args.containerId, args.workspaceId, args.variableId, variableData ), null, 2 ), }, ], }; } case 'delete_variable': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.deleteVariable( args.accountId, args.containerId, args.workspaceId, args.variableId ), null, 2 ), }, ], }; case 'create_version': return { content: [ { type: 'text', text: JSON.stringify( await this.gtmClient.createVersion( args.accountId, args.containerId, args.workspaceId, { name: args.name, notes: args.notes, } ), null, 2 ), }, ], }; default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `Error: ${error.message}\n${error.stack || ''}`, }, ], isError: true, }; } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Google Tag Manager MCP Server running on stdio'); } } const server = new GTMCPServer(); server.run().catch(console.error);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ambit1977/GTM-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server