MCPcopy Create free account
hub / github.com/adcontextprotocol/adcp / handleSyncCreatives

Function handleSyncCreatives

server/src/training-agent/task-handlers.ts:2036–2151  ·  view source on GitHub ↗
(args: ToolArgs, ctx: TrainingContext)

Source from the content-addressed store, hash-verified

2034}
2035
2036export async function handleSyncCreatives(args: ToolArgs, ctx: TrainingContext) {
2037 const req = args as unknown as SyncCreativesRequest & ToolArgs & { dry_run?: boolean };
2038 const session = await getSession(sessionKeyFromArgs(req, ctx.mode, ctx.userId, ctx.moduleId));
2039 const isDryRun = req.dry_run === true;
2040
2041 if (!req.creatives?.length) {
2042 return {
2043 errors: [{ code: 'INVALID_REQUEST', message: 'creatives array is required' }] as TaskError[],
2044 };
2045 }
2046
2047 if (!isDryRun && session.creatives.size + req.creatives.length > MAX_CREATIVES_PER_SESSION) {
2048 return {
2049 errors: [{ code: 'LIMIT_EXCEEDED', message: `Session limit reached (max ${MAX_CREATIVES_PER_SESSION} creatives). Start a new session.` }] as TaskError[],
2050 };
2051 }
2052
2053 // Build a set of valid format IDs for validation
2054 const validFormatIds = new Set(getFormats().map(f => f.format_id.id));
2055 const ownAgentUrlCanonical = canonicalizeAgentUrl(getAgentUrl());
2056
2057 const results: SyncCreativeResult[] = [];
2058 for (const creative of req.creatives) {
2059 if (!creative.creative_id) {
2060 return {
2061 errors: [{
2062 code: 'INVALID_REQUEST',
2063 message: 'creative_id is required on each creative. The buyer assigns creative IDs.',
2064 }],
2065 };
2066 }
2067 const creativeId = creative.creative_id;
2068 const formatId = creative.format_id as FormatID;
2069
2070 // Reject clearly-malformed agent_urls before we persist them. Prevents
2071 // javascript:/data: or overlong URLs landing in JSONB via the pointer.
2072 if (formatId?.agent_url !== undefined) {
2073 if (typeof formatId.agent_url !== 'string' || formatId.agent_url.length === 0 || formatId.agent_url.length > MAX_URL_LEN) {
2074 return { errors: [{ code: 'INVALID_REQUEST', message: `format_id.agent_url: must be a non-empty string up to ${MAX_URL_LEN} chars` }] as TaskError[] };
2075 }
2076 if (!/^https?:\/\//i.test(formatId.agent_url)) {
2077 return { errors: [{ code: 'INVALID_REQUEST', message: 'format_id.agent_url: must use http:// or https://' }] as TaskError[] };
2078 }
2079 }
2080
2081 // Validate format_id only when the format is claimed against this agent.
2082 // Cross-agent format references (e.g. creative.adcontextprotocol.org) are
2083 // resolved by the referenced creative agent at render time — the seller
2084 // just stores the pointer. Compare canonical forms so a trailing slash
2085 // or case variant of the local URL still counts as local.
2086 const isLocalFormat = !formatId?.agent_url
2087 || canonicalizeAgentUrl(formatId.agent_url) === ownAgentUrlCanonical;
2088 if (formatId?.id && isLocalFormat && !validFormatIds.has(formatId.id)) {
2089 return {
2090 errors: [{
2091 code: 'INVALID_REQUEST',
2092 message: `Unknown format_id "${formatId.id}". Use list_creative_formats to see available formats.`,
2093 }] as TaskError[],

Callers

nothing calls this directly

Calls 8

getSessionFunction · 0.85
sessionKeyFromArgsFunction · 0.85
getFormatsFunction · 0.85
getAgentUrlFunction · 0.85
hasMethod · 0.80
canonicalizeAgentUrlFunction · 0.70
setMethod · 0.45
getMethod · 0.45

Tested by

no test coverage detected