From 1c432fae773bb5b6e6cd646efc9f04f8549afc21 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Tue, 20 Jan 2026 10:11:11 -0500 Subject: [PATCH] feat: Add debug logging to AdCP tools and clarify probe messaging - Add `debug` parameter to all 10 AdCP tool schemas for protocol-level visibility - Include debug_logs in tool output when debug mode enabled - Remove redundant `call_adcp_agent` tool (individual tools provide better validation) - Fix `probe_adcp_agent` messaging to clarify connectivity vs protocol compliance Co-Authored-By: Claude Opus 4.5 --- .changeset/honest-maps-decide.md | 10 ++ server/src/addie/mcp/adcp-tools.ts | 116 ++++++++++++++++++----- server/src/addie/mcp/member-tools.ts | 136 +-------------------------- server/src/addie/tool-sets.ts | 1 - 4 files changed, 106 insertions(+), 157 deletions(-) create mode 100644 .changeset/honest-maps-decide.md diff --git a/.changeset/honest-maps-decide.md b/.changeset/honest-maps-decide.md new file mode 100644 index 0000000000..03781f4130 --- /dev/null +++ b/.changeset/honest-maps-decide.md @@ -0,0 +1,10 @@ +--- +"adcontextprotocol": patch +--- + +Add debug logging support to Addie's AdCP tools and clarify probe vs test behavior. + +- Add `debug` parameter to all 10 AdCP tool schemas (get_products, create_media_buy, etc.) +- Include debug_logs in tool output when debug mode is enabled +- Remove redundant `call_adcp_agent` tool (individual tools provide better schema validation) +- Fix `probe_adcp_agent` messaging to clarify it only checks connectivity, not protocol compliance diff --git a/server/src/addie/mcp/adcp-tools.ts b/server/src/addie/mcp/adcp-tools.ts index add820548f..7c39263885 100644 --- a/server/src/addie/mcp/adcp-tools.ts +++ b/server/src/addie/mcp/adcp-tools.ts @@ -2,8 +2,8 @@ * AdCP Protocol Tools * * Standard MCP tools that match the AdCP protocol specification. - * These expose the same functionality as call_adcp_agent but with - * proper schemas that match the protocol, enabling skills to work. + * Each tool has a typed schema that helps Claude understand the parameters. + * Use debug=true to see protocol-level details (requests, responses, schema validation). */ import { logger } from '../../logger.js'; @@ -72,6 +72,10 @@ export const ADCP_MEDIA_BUY_TOOLS: AddieTool[] = [ }, }, }, + debug: { + type: 'boolean', + description: 'Enable debug logging to see protocol-level details (requests, responses, schema validation)', + }, }, required: ['agent_url', 'brief'], }, @@ -135,6 +139,10 @@ export const ADCP_MEDIA_BUY_TOOLS: AddieTool[] = [ type: 'string', description: 'ISO 8601 datetime when campaign ends', }, + debug: { + type: 'boolean', + description: 'Enable debug logging to see protocol-level details', + }, }, required: ['agent_url', 'buyer_ref', 'brand_manifest', 'packages', 'start_time', 'end_time'], }, @@ -185,6 +193,10 @@ export const ADCP_MEDIA_BUY_TOOLS: AddieTool[] = [ type: 'boolean', description: 'Preview changes without applying', }, + debug: { + type: 'boolean', + description: 'Enable debug logging to see protocol-level details', + }, }, required: ['agent_url', 'creatives'], }, @@ -207,6 +219,10 @@ export const ADCP_MEDIA_BUY_TOOLS: AddieTool[] = [ items: { type: 'string' }, description: 'Filter to specific format categories (video, display, audio, etc.)', }, + debug: { + type: 'boolean', + description: 'Enable debug logging to see protocol-level details', + }, }, required: ['agent_url'], }, @@ -224,6 +240,10 @@ export const ADCP_MEDIA_BUY_TOOLS: AddieTool[] = [ type: 'string', description: 'The sales agent URL (must be HTTPS)', }, + debug: { + type: 'boolean', + description: 'Enable debug logging to see protocol-level details', + }, }, required: ['agent_url'], }, @@ -257,6 +277,10 @@ export const ADCP_MEDIA_BUY_TOOLS: AddieTool[] = [ end: { type: 'string', description: 'ISO date (YYYY-MM-DD)' }, }, }, + debug: { + type: 'boolean', + description: 'Enable debug logging to see protocol-level details', + }, }, required: ['agent_url', 'media_buy_id'], }, @@ -298,6 +322,10 @@ export const ADCP_CREATIVE_TOOLS: AddieTool[] = [ type: 'object', description: 'Source manifest - minimal for generation, complete for transformation', }, + debug: { + type: 'boolean', + description: 'Enable debug logging to see protocol-level details', + }, }, required: ['agent_url', 'target_format_id'], }, @@ -341,6 +369,10 @@ export const ADCP_CREATIVE_TOOLS: AddieTool[] = [ enum: ['url', 'html'], description: 'Output format (default: url)', }, + debug: { + type: 'boolean', + description: 'Enable debug logging to see protocol-level details', + }, }, required: ['agent_url', 'request_type'], }, @@ -407,6 +439,10 @@ export const ADCP_SIGNALS_TOOLS: AddieTool[] = [ type: 'number', description: 'Limit number of results', }, + debug: { + type: 'boolean', + description: 'Enable debug logging to see protocol-level details', + }, }, required: ['agent_url', 'signal_spec', 'deliver_to'], }, @@ -442,6 +478,10 @@ export const ADCP_SIGNALS_TOOLS: AddieTool[] = [ required: ['type'], }, }, + debug: { + type: 'boolean', + description: 'Enable debug logging to see protocol-level details', + }, }, required: ['agent_url', 'signal_agent_segment_id', 'deployments'], }, @@ -522,7 +562,8 @@ export function createAdcpToolHandlers( async function executeTask( agentUrl: string, task: string, - params: Record + params: Record, + debug: boolean = false ): Promise { const validationError = validateAgentUrl(agentUrl); if (validationError) { @@ -531,30 +572,45 @@ export function createAdcpToolHandlers( const authToken = await getAuthToken(agentUrl); - logger.info({ agentUrl, task, hasAuth: !!authToken }, `AdCP: executing ${task}`); + logger.info({ agentUrl, task, hasAuth: !!authToken, debug }, `AdCP: executing ${task}`); try { const { AdCPClient } = await import('@adcp/client'); - const multiClient = new AdCPClient([ - { - id: 'target', - name: 'target', - agent_uri: agentUrl, - protocol: 'mcp', - ...(authToken && { auth_token: authToken }), - }, - ]); + const multiClient = new AdCPClient( + [ + { + id: 'target', + name: 'target', + agent_uri: agentUrl, + protocol: 'mcp', + ...(authToken && { auth_token: authToken }), + }, + ], + { debug } + ); const client = multiClient.agent('target'); - const result = await client.executeTask(task, params); + const result = await client.executeTask(task, params, undefined, { debug }); if (!result.success) { - return `**Task failed:** \`${task}\`\n\n**Error:**\n\`\`\`json\n${JSON.stringify(result.error, null, 2)}\n\`\`\``; + let output = `**Task failed:** \`${task}\`\n\n**Error:**\n\`\`\`json\n${JSON.stringify(result.error, null, 2)}\n\`\`\``; + + // Include debug logs on failure (always useful for debugging) + if (result.debug_logs && result.debug_logs.length > 0) { + output += `\n\n**Debug Logs:**\n\`\`\`json\n${JSON.stringify(result.debug_logs, null, 2)}\n\`\`\``; + } + + return output; } let output = `**Task:** \`${task}\`\n**Status:** Success\n\n`; output += `**Response:**\n\`\`\`json\n${JSON.stringify(result.data, null, 2)}\n\`\`\``; + // Include debug logs if debug mode is enabled + if (debug && result.debug_logs && result.debug_logs.length > 0) { + output += `\n\n**Debug Logs:**\n\`\`\`json\n${JSON.stringify(result.debug_logs, null, 2)}\n\`\`\``; + } + return output; } catch (error) { logger.error({ error, agentUrl, task }, `AdCP: ${task} failed`); @@ -565,17 +621,19 @@ export function createAdcpToolHandlers( // Media Buy handlers handlers.set('get_products', async (input: Record) => { const agentUrl = input.agent_url as string; + const debug = input.debug as boolean | undefined; const params: Record = { brief: input.brief, }; if (input.brand_manifest) params.brand_manifest = input.brand_manifest; if (input.filters) params.filters = input.filters; - return executeTask(agentUrl, 'get_products', params); + return executeTask(agentUrl, 'get_products', params, debug); }); handlers.set('create_media_buy', async (input: Record) => { const agentUrl = input.agent_url as string; + const debug = input.debug as boolean | undefined; const params: Record = { buyer_ref: input.buyer_ref, brand_manifest: input.brand_manifest, @@ -584,58 +642,64 @@ export function createAdcpToolHandlers( end_time: input.end_time, }; - return executeTask(agentUrl, 'create_media_buy', params); + return executeTask(agentUrl, 'create_media_buy', params, debug); }); handlers.set('sync_creatives', async (input: Record) => { const agentUrl = input.agent_url as string; + const debug = input.debug as boolean | undefined; const params: Record = { creatives: input.creatives, }; if (input.assignments) params.assignments = input.assignments; if (input.dry_run !== undefined) params.dry_run = input.dry_run; - return executeTask(agentUrl, 'sync_creatives', params); + return executeTask(agentUrl, 'sync_creatives', params, debug); }); handlers.set('list_creative_formats', async (input: Record) => { const agentUrl = input.agent_url as string; + const debug = input.debug as boolean | undefined; const params: Record = {}; if (input.format_types) params.format_types = input.format_types; - return executeTask(agentUrl, 'list_creative_formats', params); + return executeTask(agentUrl, 'list_creative_formats', params, debug); }); handlers.set('list_authorized_properties', async (input: Record) => { const agentUrl = input.agent_url as string; - return executeTask(agentUrl, 'list_authorized_properties', {}); + const debug = input.debug as boolean | undefined; + return executeTask(agentUrl, 'list_authorized_properties', {}, debug); }); handlers.set('get_media_buy_delivery', async (input: Record) => { const agentUrl = input.agent_url as string; + const debug = input.debug as boolean | undefined; const params: Record = { media_buy_id: input.media_buy_id, }; if (input.granularity) params.granularity = input.granularity; if (input.date_range) params.date_range = input.date_range; - return executeTask(agentUrl, 'get_media_buy_delivery', params); + return executeTask(agentUrl, 'get_media_buy_delivery', params, debug); }); // Creative handlers handlers.set('build_creative', async (input: Record) => { const agentUrl = input.agent_url as string; + const debug = input.debug as boolean | undefined; const params: Record = { target_format_id: input.target_format_id, }; if (input.message) params.message = input.message; if (input.creative_manifest) params.creative_manifest = input.creative_manifest; - return executeTask(agentUrl, 'build_creative', params); + return executeTask(agentUrl, 'build_creative', params, debug); }); handlers.set('preview_creative', async (input: Record) => { const agentUrl = input.agent_url as string; + const debug = input.debug as boolean | undefined; const params: Record = { request_type: input.request_type, }; @@ -644,12 +708,13 @@ export function createAdcpToolHandlers( if (input.requests) params.requests = input.requests; if (input.output_format) params.output_format = input.output_format; - return executeTask(agentUrl, 'preview_creative', params); + return executeTask(agentUrl, 'preview_creative', params, debug); }); // Signals handlers handlers.set('get_signals', async (input: Record) => { const agentUrl = input.agent_url as string; + const debug = input.debug as boolean | undefined; const params: Record = { signal_spec: input.signal_spec, deliver_to: input.deliver_to, @@ -657,17 +722,18 @@ export function createAdcpToolHandlers( if (input.filters) params.filters = input.filters; if (input.max_results) params.max_results = input.max_results; - return executeTask(agentUrl, 'get_signals', params); + return executeTask(agentUrl, 'get_signals', params, debug); }); handlers.set('activate_signal', async (input: Record) => { const agentUrl = input.agent_url as string; + const debug = input.debug as boolean | undefined; const params: Record = { signal_agent_segment_id: input.signal_agent_segment_id, deployments: input.deployments, }; - return executeTask(agentUrl, 'activate_signal', params); + return executeTask(agentUrl, 'activate_signal', params, debug); }); return handlers; diff --git a/server/src/addie/mcp/member-tools.ts b/server/src/addie/mcp/member-tools.ts index e357506d37..92d7f45030 100644 --- a/server/src/addie/mcp/member-tools.ts +++ b/server/src/addie/mcp/member-tools.ts @@ -503,8 +503,8 @@ export const MEMBER_TOOLS: AddieTool[] = [ { name: 'probe_adcp_agent', description: - 'Check if an AdCP agent is online and discover its capabilities. Always validates connectivity first, then retrieves available tools and operations. Use this as the primary tool for investigating agents - it combines health checking with capability discovery.', - usage_hints: 'use for "is this agent working?", "what can this agent do?", "check my agent", "probe agent", "test connectivity"', + 'Check if an AdCP agent is online and list its advertised capabilities. This only verifies connectivity (the agent responds to HTTP requests) - it does NOT verify the agent implements the protocol correctly. Use test_adcp_agent to verify actual protocol compliance.', + usage_hints: 'use for "is this agent online?", "check connectivity", "what tools does this agent advertise?". For compliance testing, use test_adcp_agent instead.', input_schema: { type: 'object', properties: { @@ -551,51 +551,6 @@ export const MEMBER_TOOLS: AddieTool[] = [ required: ['agent_url'], }, }, - { - name: 'call_adcp_agent', - description: - 'Execute an AdCP protocol task on an agent. This is a low-level proxy that forwards requests directly to the agent. Use adcp-media-buy, adcp-signals, or adcp-creative skills for detailed parameter schemas.', - usage_hints: 'use for "call get_products", "execute create_media_buy", "run sync_creatives", "get_signals", "build_creative", "interact with the agent directly", "use the full AdCP spec". For testing workflows, prefer test_adcp_agent instead.', - input_schema: { - type: 'object', - properties: { - agent_url: { - type: 'string', - description: 'The agent URL (must be HTTPS, e.g., "https://sales.example.com")', - }, - task: { - type: 'string', - enum: [ - // Media Buy tasks - 'get_products', - 'list_authorized_properties', - 'list_creative_formats', - 'create_media_buy', - 'update_media_buy', - 'sync_creatives', - 'list_creatives', - 'get_media_buy_delivery', - // Signals tasks - 'get_signals', - 'activate_signal', - // Creative tasks - 'build_creative', - 'preview_creative', - ], - description: 'The AdCP task to execute', - }, - params: { - type: 'object', - description: 'Task parameters - structure depends on the task. See protocol skills for schemas.', - }, - auth_token: { - type: 'string', - description: 'Optional auth token. If not provided, will use saved token for this agent.', - }, - }, - required: ['agent_url', 'task'], - }, - }, // ============================================ // AGENT CONTEXT MANAGEMENT // ============================================ @@ -1968,11 +1923,11 @@ export function createMemberToolHandlers( // Summary response += `\n---\n`; if (isHealthy && (agent?.capabilities?.tools?.length ?? 0) > 0) { - response += `✅ Agent is healthy and ready to use. Try \`test_adcp_agent\` to run a full workflow test.`; + response += `✅ Agent is **online** and responding. Run \`test_adcp_agent\` to verify protocol compliance.`; } else if (isHealthy) { - response += `✅ Agent is responding but not in the registry. You can still use \`call_adcp_agent\` to execute tasks directly.`; + response += `✅ Agent is **online** but not in the registry. Try calling it with \`get_products\` or run \`test_adcp_agent\` to verify it works correctly.`; } else { - response += `❌ Agent is not responding. Check the URL and ensure the agent is running.`; + response += `❌ Agent is **not responding**. Check the URL and ensure the agent is running.`; } return response; @@ -2173,87 +2128,6 @@ export function createMemberToolHandlers( } }); - // ============================================ - // ADCP AGENT PROXY - // ============================================ - handlers.set('call_adcp_agent', async (input) => { - const agentUrl = input.agent_url as string; - const task = input.task as string; - const params = (input.params as Record) || {}; - let authToken = input.auth_token as string | undefined; - - // Validate URL to prevent SSRF attacks - try { - const url = new URL(agentUrl); - - // Must be HTTPS - if (url.protocol !== 'https:') { - return '**Error:** Agent URL must use HTTPS protocol.'; - } - - // Block internal/private networks - const hostname = url.hostname.toLowerCase(); - if ( - hostname === 'localhost' || - hostname === '127.0.0.1' || - hostname === '::1' || - hostname.endsWith('.local') || - hostname.endsWith('.internal') || - hostname.startsWith('10.') || - hostname.startsWith('192.168.') || - hostname.match(/^172\.(1[6-9]|2\d|3[01])\./) || - hostname === '169.254.169.254' // AWS metadata - ) { - return '**Error:** Agent URL cannot point to internal or private networks.'; - } - } catch { - return '**Error:** Invalid agent URL format.'; - } - - // Look up saved auth token if not provided - if (!authToken && memberContext?.organization?.workos_organization_id) { - const savedContext = await agentContextDb.getByOrgAndUrl( - memberContext.organization.workos_organization_id, - agentUrl - ); - if (savedContext?.id) { - authToken = (await agentContextDb.getAuthToken(savedContext.id)) ?? undefined; - } - } - - logger.info({ agentUrl, task, hasAuth: !!authToken }, 'Addie: call_adcp_agent'); - - try { - const { AdCPClient } = await import('@adcp/client'); - const multiClient = new AdCPClient([ - { - id: 'target', - name: 'target', - agent_uri: agentUrl, - protocol: 'mcp', - ...(authToken && { auth_token: authToken }), - }, - ]); - const client = multiClient.agent('target'); - - const result = await client.executeTask(task, params); - - if (!result.success) { - // Return errors in a structured way - return `**Task failed:** \`${task}\`\n\n**Error:**\n\`\`\`json\n${JSON.stringify(result.error, null, 2)}\n\`\`\``; - } - - // Format successful response - let output = `**Task:** \`${task}\`\n**Status:** Success\n\n`; - output += `**Response:**\n\`\`\`json\n${JSON.stringify(result.data, null, 2)}\n\`\`\``; - - return output; - } catch (error) { - logger.error({ error, agentUrl, task }, 'Addie: call_adcp_agent failed'); - return `**Task failed:** \`${task}\`\n\n**Error:** ${error instanceof Error ? error.message : 'Unknown error'}`; - } - }); - // ============================================ // GITHUB ISSUE DRAFTING // ============================================ diff --git a/server/src/addie/tool-sets.ts b/server/src/addie/tool-sets.ts index 9a8b60418d..25a8cf45da 100644 --- a/server/src/addie/tool-sets.ts +++ b/server/src/addie/tool-sets.ts @@ -124,7 +124,6 @@ export const TOOL_SETS: Record = { 'get_signals', 'activate_signal', // Agent management - 'call_adcp_agent', 'save_agent', 'list_saved_agents', 'remove_saved_agent',