Repository navigation
1.9.79: text_ad_create, text_ad_update and text_ad_delete in the MCP connector - #260
Conversation
…connector The MCP catalogue (p202 mcp, and the hosted relay that runs this CLI as js/wasm) offered text_ad_list only, so a demo account's text ads had to be typed into Setup > Text Ads through a browser. The three write tools are declared as the campaign_* and landing_page_* ones are: kinds create, update and delete (so update and delete carry destructiveHint, delete idempotentHint and the dry_run note), an id positional for update and delete, and a note saying a tracker shows an ad through its text_ad_id. AiConnect::WRITE_AREAS gains text-ads, so a hosted connection approved with propose or write access is minted text-ads:stage or text-ads:write, and the approval page says it changes text ads. A connection approved before the install ran 1.9.79 keeps the scope it was given and answers 403 for the three until it is connected again; nothing widens a stored key. The tools' schemas come from p202 text-ad create/update/delete, which now hold an ad to Setup > Text Ads' rule: a campaign's ad (--text-ad-type 0, the default) names its campaign and an advanced landing page's ad (--text-ad-type 1) its page. The API stores either link as 0 whatever the type and answered 201 for an ad Get Links never offers. create refuses a missing link before any request; update holds the ad as it will be (the flags over the stored ad, read only when a flag sets the type or a link, and refusing rather than guessing when the answer lacks a field). --text-ad-type and --landing-page-type take 0 or 1, as the pages do; the field help names the 100-character limits and where each id comes from. WKB1: landing_page_create already makes an advanced landing page (landing_page_type 1 with aff_campaign_id 0, which the API takes for type 1); measured live through `p202 mcp call`. Nothing was missing. The catalogue budget (TestMCPCatalogStaysInBudget) goes from 80,000 to 84,000 characters: the three tools are about 3,400 and take Setup > Text Ads' fields and nothing else. Agent-eval cases setup-text-ad-001 (write an ad for a campaign and put it on its tracker) and its negative twin 002 (an ad for nothing is asked about, not made), with reference-agent branches; 001 is listed in searchKnownMisses (two tasks; the Text Ads page's list ranks first). Backlog: 5A30, WKB1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff2dfa4e71
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
202-config/changelogs.json conflicted with #258's two lines; both sides' entries are kept, the base's first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…e page p202 text-ad create (and the MCP text_ad_create and text_ad_update tools, which run it) refused a type 1 ad with no landing page, but took the id of a simple one: the check read only that the id was not 0, and the API checks that a linked page is the caller's, not its type. Setup > Text Ads offers only advanced pages for such an ad, and Get Links offers an ad on the advanced page it names, so the ad was stored with a 201 and never offered (found by review). TextAdsController now refuses it with a 422 naming landing_page_id, on a create and on an update that sets the type or the page, including a type change that leaves the ad on the page it already names, whosever it is. An ad stored on a simple page before this saves when re-sent as it is, as the other link checks do. The rule is the API's, so the PHP CLI, sync and a direct request are held to it as well. Measured live through p202 text-ad create/update and p202 mcp call text_ad_update: refused, the stored ad unchanged; the advanced page's ad is made. Planting the check's removal and the re-send exemption's each fails OwnedReferencesIntegrationTest's new case. Backlog: 5A30 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
1.9.79 gained #262. The only conflict was 202-config/changelogs.json, where both sides added 1.9.79 lines: 1.9.79's lines are kept and this branch's follow them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
#280's traffic source and category update and delete tools sit beside the text ad writes in the catalogue; text_ad_list takes #279's hosted omission of all. The catalogue budget goes to 88,000 characters: #280's tools and the text ad writes together come to about 87,800. The docs keep #281's hosted read bounds, then the text ad reconnect note; the changelogs keep 1.9.79's lines and put the text ad lines after them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…lue bound When an update sets the type or a link, text_ad_update reads the stored ad before it writes. Under #281's bound an answer past it there stops the call before the PUT, and the refusal says nothing that changes data was sent; the test holds the call to that one GET. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
The branch on the remote carried 32cd43c (TextAdsController refuses an advanced landing page's ad on a simple page) and two earlier merges of 1.9.79, which this checkout did not have. The changelogs keep 1.9.79's current lines, with the text ad lines after them in 32cd43c's wording. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Brings in 1.9.79's new commits. The conflicts were in 202-config/changelogs.json, changelogs.txt, where both sides added lines: the incoming lines are kept as they are and this branch's follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Backlog: 5A30 (with the check WKB1)
What changed and why
The MCP catalogue (
go-cli/cmd/mcp_catalog.go, served byp202 mcpand by the hosted relay that runs this CLI as js/wasm) offeredtext_ad_listonly, so a demo account's text ads had to be typed into Setup › Text Ads through a browser.text_ad_create,text_ad_update,text_ad_delete, declared as thecampaign_*/landing_page_*writes are (and as 1.9.79: update and delete tools for traffic sources, their accounts and categories in the MCP connector #280's traffic source and category writes, which now sit beside them): kinds create / update / delete (so update and delete carrydestructiveHint, deleteidempotentHintand thedry_runnote), anidpositional on update and delete (delete also takesids), noNotStaged/NotHosted. Create's note says a tracker shows an ad through itstext_ad_id(tracker_update).text_ad_listkeeps 1.9.79: the hosted MCP server's list tools take no all #279's hosted omission ofall.AiConnect::WRITE_AREASgainstext-ads, so an approval with propose access now mintstext-ads:stageand write accesstext-ads:write(TestHostedMCPToolsStayInsideTheAIKeyAreasholds every hosted tool to those lists; it fails without this). The approval page (202-account/connect-ai.php) names text ads in all three access descriptions. A key minted before the install ran 1.9.79 keeps the scope it was given and answers 403 for the three tools until the person connects again; nothing widens a stored key (the person approved a list that did not include text ads). Documented indocumentation/cli/10-go-cli.md,docs/cli-agent.mdand the changelog.p202 text-ad create/update/delete,go-cli/cmd/crud.go): every field was already a flag of the right JSON type (string, the API validates). What was missing was Setup › Text Ads' rule: a campaign's ad (--text-ad-type 0, the default) names its campaign and an advanced landing page's ad (--text-ad-type 1) its page. Nowcreaterefuses a missing link before any request (no config needed;validation, exit 1, the hint namesp202 campaign list/p202 landing-page listand the other kind of ad);updateholds the ad as it will be (the flags given over the stored ad, which it GETs only when a flag sets the type or a link; an answer missing the field is an error, never read as "none").--text-ad-typeand--landing-page-typeare enums0, 1(as the pages offer); the field help names the 100-character limits, the type each link belongs to and where ids come from; the create example includes--aff-campaign-id. Mechanism: an optionalCheck/CheckFields/ExampleFieldsoncrudEntity, used only by text-ad.TextAdsControllerrefuses an advanced landing page's ad (text_ad_type 1) that names a simple landing page, with a 422 naminglanding_page_id, on a create and on an update that sets the type or the page; an ad stored that way before saves when re-sent as it is.OwnedReferencesIntegrationTesthas the case;docs/openapi.yamlsays so.landing_page_createalready makes an advanced landing page:landing_page_typewas a tool argument andLandingPagesController::requiredLinkMayBeNone()takesaff_campaign_id: 0for type 1. Measured live throughp202 mcp call landing_page_create(201,landing_page_type: 1,aff_campaign_id: 0; a simple page with campaign 0 is still refused 422). Nothing was missing; the enum on--landing-page-typeis the only change there.TestMCPCatalogStaysInBudgetis 88,000 characters. 1.9.79 had raised it to 85,000 for 1.9.79: update and delete tools for traffic sources, their accounts and categories in the MCP connector #280's tools; with the text ad writes (~3,400, Setup › Text Ads' fields and nothing else) the list is 87,814.text_ad_update's read of the stored ad before its PUT:TestHostedTextAdUpdateRefusedAtItsReadSendsNoWriteholds an answer past the bound there to the refusal, one GET and no PUT, with the hint that nothing that changes data was sent.tests/fixtures/agent-eval/cases/setup-text-ads.json):setup-text-ad-001(write an ad for EVAL Campaign B and put it on its existing tracker; checks the ad's type, campaign and copy and that the tracker carries it) and its negative twinsetup-text-ad-002(an ad for nothing is asked about, not made). Reference-agent branches for both. 001 is listed insearchKnownMisseswith why (two tasks; the Text Ads page'stext-ad listranks first,text-ad createfourth).crud_text_ad_test.go(create refusals, what create sends, update against a stored ad: which requests go out and in what order),TestMCPToolArgvcases for the three tools,mcpWriteToolInputfor the hosted-areas test, the value-guard test above. Every plant was caught by a named test: removing the create check, the update check, the missing-field refusal, reading type 1 as 0, droppingtext-adsfromWRITE_AREAS, dropping the type enum (each planted with amktempbackup checked withcmp, restored and re-run green).docs/cli.md,docs/cli-agent.md,documentation/cli/10-go-cli.md,docs/openapi.yaml;changelogs.txtand202-config/changelogs.jsonunder 1.9.79, after 1.9.79's own lines.Merged with 1.9.79 (#270, #279, #280, #281, #282)
Conflicts resolved in
mcp_catalog.go(#280's update/delete tools andsetupParentDeleteNotebeside the text ad tools),mcp_test.go(both sets ofTestMCPToolArgvcases; the budget above),docs/cli-agent.mdanddocumentation/cli/10-go-cli.md(#281's hosted read bounds, then the text ad reconnect note) and the changelogs (1.9.79's current lines kept, the text ad lines after them).Live pass (local instance, MariaDB 10.11, PHP 8.3.6, before the merge)
Installed with
install-instance.sh, seeded withseed.sh, CLI built from this branch, every call throughp202 mcp call <tool> --input -:text_ad_createfor EVAL Campaign B → 201 (text_ad_type 0,aff_campaign_id 2); without a campaign →validationenvelope naming--aff-campaign-id;text_ad_type: "2"→ refused naming the flag; a 101-character headline → 422 namingtext_ad_headline.landing_page_createwithlanding_page_type 1, aff_campaign_id 0→ advanced page (WKB1);text_ad_createtype 1 on it → 201.text_ad_update: headline only → 200 (that it reads nothing first is the unit test's);text_ad_type 1on a campaign's ad with no page → refused;aff_campaign_id 0→ refused; type and link together → 200.tracker_update text_ad_idon EVAL Campaign B's tracker, read back withtracker_get(text_ad_idset).text_ad_listbefore and after;text_ad_deletewithdry_run: true(preview, nothing removed pertext_ad_list), then the delete, thentext_ad_list(gone).--read-onlyrefusestext_ad_create;--stagedreturns achg_proposal.connect-ai.php, exchanged the code atPOST /api/v3/connect/tokenwith its verifier → scope includestext-ads:write; with that keytext_ad_create,text_ad_update,text_ad_delete(dry run, then delete) all worked. A key with 1.9.78's write scope (text-ads:read) → 403requires 'text-ads:write'; a propose key → staged change.needs_judge(no judge here; the deterministic halves pass).The simple-page refusal (32cd43c) was measured live by its own commit; I did not repeat the live pass after merging 1.9.79.
Verification (after the merge)
.claude/skills/p202-verify/scripts/verify.sh --since origin/1.9.79 --changedon the merge of 1.9.79 (Go and PHP; PHP 8.3.6, full vendor/, schema tier against a scratch MariaDB 10.11 database; unit 6104 tests, 4 skipped):Then, after merging 32cd43c (PHP and the spec only; no Go change),
--since origin/1.9.79with the PHP tiers and openapi:OwnedReferencesIntegrationTestagainst a scratch database (p202_ta_owned): OK, 6 tests, 240 assertions. CI status: not yet observed.What could not run here
needs_judgehere, as in CI withoutjudge_cmd.For the reviewer to check
checkTextAdLink), sop202 mcpand the hosted relay get it; the API now refuses a type 1 ad on a simple page, but still stores an ad whose type's link is 0 (OwnedReferencesIntegrationTestpins a text ad with no campaign as accepted, and sync/import copy existing rows that may have none).text_ad_type,aff_campaign_idorlanding_page_idand the body alone does not decide; an edit of the copy is sent unread and never refused for what the ad already holds.--landing-page-typebecame an enum (0, 1) alongside--text-ad-type; the API still takes any TINYINT for both.Found outside this item (one line each)
TextAdsController) still stores a text ad whose type's link is 0 and answers 201 (REST, the PHP CLI and sync are not held to the Go CLI's refusal).p202 user apikey create ... --scope write", which is the wrong remedy for a hosted connection (reconnect); the relay could also offer only the tools a key's scope covers (/capabilitieslistsprincipal.scopes).text-adentity (cli/Application.php) requires onlytext_ad_nameand none of the copy the API requires.🤖 Generated with Claude Code
https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr