Skip to content

1.9.79: text_ad_create, text_ad_update and text_ad_delete in the MCP connector - #260

Merged
tracking202 merged 8 commits into
1.9.79from
1.9.79-mcp-text-ad-crud
Oct 28, 2026
Merged

tracking202 merged 8 commits into
1.9.79from
1.9.79-mcp-text-ad-crud

Conversation

@tracking202

@tracking202 tracking202 commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Backlog: 5A30 (with the check WKB1)

What changed and why

The MCP catalogue (go-cli/cmd/mcp_catalog.go, served by p202 mcp and by 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.

  • Three tools: text_ad_create, text_ad_update, text_ad_delete, declared as the campaign_* / 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 carry destructiveHint, delete idempotentHint and the dry_run note), an id positional on update and delete (delete also takes ids), no NotStaged/NotHosted. Create's note says a tracker shows an ad through its text_ad_id (tracker_update). text_ad_list keeps 1.9.79: the hosted MCP server's list tools take no all #279's hosted omission of all.
  • Hosted connector: AiConnect::WRITE_AREAS gains text-ads, so an approval with propose access now mints text-ads:stage and write access text-ads:write (TestHostedMCPToolsStayInsideTheAIKeyAreas holds 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 in documentation/cli/10-go-cli.md, docs/cli-agent.md and the changelog.
  • The CLI commands the schemas come from (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. Now create refuses a missing link before any request (no config needed; validation, exit 1, the hint names p202 campaign list / p202 landing-page list and the other kind of ad); update holds 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-type and --landing-page-type are enums 0, 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 optional Check/CheckFields/ExampleFields on crudEntity, used only by text-ad.
  • The API (32cd43c, found by review): TextAdsController refuses an advanced landing page's ad (text_ad_type 1) that names a simple landing page, with a 422 naming landing_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. OwnedReferencesIntegrationTest has the case; docs/openapi.yaml says so.
  • WKB1: landing_page_create already makes an advanced landing page: landing_page_type was a tool argument and LandingPagesController::requiredLinkMayBeNone() takes aff_campaign_id: 0 for type 1. Measured live through p202 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-type is the only change there.
  • Catalogue budget: TestMCPCatalogStaysInBudget is 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.
  • 1.9.79: a hosted call refuses answers with more JSON values than it decodes #281's value guard: the three tools are held to it like every other (the hosted-areas and value tests loop over the catalogue). The one shape of their own is text_ad_update's read of the stored ad before its PUT: TestHostedTextAdUpdateRefusedAtItsReadSendsNoWrite holds an answer past the bound there to the refusal, one GET and no PUT, with the hint that nothing that changes data was sent.
  • Agent evals (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 twin setup-text-ad-002 (an ad for nothing is asked about, not made). Reference-agent branches for both. 001 is listed in searchKnownMisses with why (two tasks; the Text Ads page's text-ad list ranks first, text-ad create fourth).
  • Tests: crud_text_ad_test.go (create refusals, what create sends, update against a stored ad: which requests go out and in what order), TestMCPToolArgv cases for the three tools, mcpWriteToolInput for 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, dropping text-ads from WRITE_AREAS, dropping the type enum (each planted with a mktemp backup checked with cmp, restored and re-run green).
  • Docs: docs/cli.md, docs/cli-agent.md, documentation/cli/10-go-cli.md, docs/openapi.yaml; changelogs.txt and 202-config/changelogs.json under 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 and setupParentDeleteNote beside the text ad tools), mcp_test.go (both sets of TestMCPToolArgv cases; the budget above), docs/cli-agent.md and documentation/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 with seed.sh, CLI built from this branch, every call through p202 mcp call <tool> --input -:

  • text_ad_create for EVAL Campaign B → 201 (text_ad_type 0, aff_campaign_id 2); without a campaign → validation envelope naming --aff-campaign-id; text_ad_type: "2" → refused naming the flag; a 101-character headline → 422 naming text_ad_headline.
  • landing_page_create with landing_page_type 1, aff_campaign_id 0 → advanced page (WKB1); text_ad_create type 1 on it → 201.
  • text_ad_update: headline only → 200 (that it reads nothing first is the unit test's); text_ad_type 1 on a campaign's ad with no page → refused; aff_campaign_id 0 → refused; type and link together → 200.
  • tracker_update text_ad_id on EVAL Campaign B's tracker, read back with tracker_get (text_ad_id set).
  • text_ad_list before and after; text_ad_delete with dry_run: true (preview, nothing removed per text_ad_list), then the delete, then text_ad_list (gone).
  • --read-only refuses text_ad_create; --staged returns a chg_ proposal.
  • Hosted path end to end: signed in, approved Read and change on connect-ai.php, exchanged the code at POST /api/v3/connect/token with its verifier → scope includes text-ads:write; with that key text_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) → 403 requires 'text-ads:write'; a propose key → staged change.
  • Full agent-eval suite with the reference agent on a fresh instance: 53 cases, 0 fail, 0 error, 53 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 --changed on 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):

Scope report
------------
  go         PASS  ran under CI's go1.25.14 via GOTOOLCHAIN (local go is 1.24)
  golangci   PASS  ran under CI's go1.25.14 via GOTOOLCHAIN (local go is 1.24)
  patterns   PASS
  phpcs      PASS
  phpstan    PASS
  schema     PASS
  syntax     PASS
  unit       PASS

Then, after merging 32cd43c (PHP and the spec only; no Go change), --since origin/1.9.79 with the PHP tiers and openapi:

Scope report
------------
  syntax     PASS
  phpstan    PASS
  phpcs      PASS
  unit       PASS
  schema     PASS
  patterns   PASS
  openapi    PASS

OwnedReferencesIntegrationTest against a scratch database (p202_ta_owned): OK, 6 tests, 240 assertions. CI status: not yet observed.

What could not run here

  • CI's MySQL 8.0 (this sandbox has MariaDB 10.11); the PR's Agent Evals and Go CLI workflows will run the cases and Go suites there.
  • No rubric judge: every eval case reports needs_judge here, as in CI without judge_cmd.
  • The hosted relay itself (another repo): the install side of the hosted path was driven end to end, the relay's own tool offering was not.

For the reviewer to check

  1. Where the text ad rule lives. The missing-link refusal is in the Go CLI (checkTextAdLink), so p202 mcp and 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 (OwnedReferencesIntegrationTest pins a text ad with no campaign as accepted, and sync/import copy existing rows that may have none).
  2. The update rule reads the stored ad (one GET) only when the update sets text_ad_type, aff_campaign_id or landing_page_id and the body alone does not decide; an edit of the copy is sent unread and never refused for what the ad already holds.
  3. Budget raised to 88,000 rather than trimming other tools.
  4. Existing hosted keys are not widened. An upgrade step could rewrite stored AI-connection scopes to the new lists, but that would grant text ad writes nobody approved; the person reconnects instead (documented).
  5. --landing-page-type became an enum (0, 1) alongside --text-ad-type; the API still takes any TINYINT for both.

Found outside this item (one line each)

  • The API (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).
  • A 403 scope refusal for a hosted AI key hints "mint one: 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 (/capabilities lists principal.scopes).
  • The PHP CLI's text-ad entity (cli/Application.php) requires only text_ad_name and none of the copy the API requires.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr

…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
Copilot AI balanced review requested due to automatic review settings October 23, 2026 19:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T12:30:38.463845Z 6c40104 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

Code review: CLEAN

For ff2dfa4: 0 issue(s). The review is encrypted for the maintainers in the review-findings artifact of this run.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread go-cli/cmd/crud.go
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Adversarial review: NON-BLOCKING

For 6c40104: 6 finding(s). The report is encrypted for the maintainers in the review-findings artifact of this run.

Rewritten on every push; the check fails on BLOCKING.

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
@chatgpt-codex-connector

Copy link
Copy Markdown

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
@tracking202
tracking202 merged commit 339c262 into 1.9.79 Oct 28, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants