# Worked example: Acme Co (fictional)

Acme Co sells a subscription product online and runs a wholesale arm. Several people already connected tools to AI clients on their own, on three laptops; the marketing ops lead asks for one read-only, tested setup and a hand-off document.

These connections already existed, so this was remediation, not a first pilot: each owner fixed and tested one server at a time.

Files:

- [../assets/example-acme-stack.md](../assets/example-acme-stack.md): the `stack.md` the agent reads first.
- [../assets/example-acme-client-configs.json](../assets/example-acme-client-configs.json): six client config files from three laptops, as found.
- [../assets/example-acme-client-configs-after.json](../assets/example-acme-client-configs-after.json): the same files after the fixes.
- [../assets/example-acme-tools-baseline.json](../assets/example-acme-tools-baseline.json) and [../assets/example-acme-tools-current.json](../assets/example-acme-tools-current.json): HubSpot tool snapshots in Claude Code from 2026-10-20 and 2026-11-01.
- [../assets/example-acme-inventory.md](../assets/example-acme-inventory.md): the filled inventory, one block per server and client.

## Jobs and known answers

- Weekly wholesale pipeline review (HubSpot, wholesale ops lead in Claude Code and in the Team workspace of Claude Desktop and claude.ai): deal 1001.
- Weekly paid media review (Google Ads in Codex, Meta Ads in Claude Code): closed-week spend, clicks and impressions.
- Monthly flow review (Klaviyo, lifecycle manager in the ChatGPT Business workspace): the Welcome flow's delivered count for September.
- Trial-to-first-order funnel (Mixpanel EU project, product analyst in Cursor): the saved funnel report.
- Weekly traffic check (GA4, ops lead in Claude Code): sessions for one closed day.
- Weekly SEO review (Search Console bulk export, SEO lead in Claude Code): weekly web clicks.

`stack.md` records the Team workspace, the ChatGPT Business workspace and Claude Code on the company subscription as approved for customer data (decisions.md, 2026-09-15), so HubSpot and Klaviyo may go there.

## Scan what is already there

From the skill folder:

```bash
python3 -I scripts/mcp_inventory.py --fixture assets/example-acme-client-configs.json \
  --expect-region mixpanel=eu --as-of 2026-11-01
```

`--as-of` pins the date only to reproduce this example; live runs leave it out. The run ends with `Summary: 12 FAIL, 9 WARN, 3 INFO, 8 PASS` and exit code 1. Identical output on Python 3.9 (built-in TOML reader) and 3.11 or later (`tomllib`). Selected lines, shortened:

```text
FAIL S01 hubspot (claude-code project ~/acme-marketing/.mcp.json): crm-bridge.example is not a HubSpot endpoint, and HubSpot publishes https://mcp.hubspot.com. ...
FAIL S06 klaviyo (claude-code project ~/acme-marketing/.mcp.json): read-only=true is not set on the URL: ...
FAIL S06 ga4 (claude-code project ~/acme-marketing/.mcp.json): started with --allow-write: ...
FAIL S12 ga4 (claude-code project ~/acme-marketing/.mcp.json): GOOGLE_APPLICATION_CREDENTIALS points inside the project (./secrets/adc.json, ...
FAIL S07 mixpanel (cursor global ~/.cursor/mcp.json): mcp.mixpanel.com is the US endpoint, but stack.md expects EU. Use https://mcp-eu.mixpanel.com/mcp.
FAIL S09 bigquery (vscode workspace ~/acme-marketing/.vscode/mcp.json): .vscode/mcp.json uses mcpServers; VS Code reads servers, so this server never loads. ...
FAIL S03 meta-ads (claude-desktop local ...): env META_ACCESS_TOKEN has an inline value (value not printed). ...
FAIL S06 search-console (claude-desktop local ...): env GSC_ENABLE_WRITES=0 is not set: the server defaults to 1, ...
WARN S10 klaviyo (claude-code local, project ~/.claude.json [~/acme-marketing]): defined in 2 scopes: ... The local entry wins for this user only ...
INFO S06 bigquery-acme-ro (claude-code user ~/.claude.json): no config-level read-only switch; read-only depends on an IAM deny policy ...
FAIL S06 google-ads-acme-prod-ro (codex user ~/.codex/config.toml): env GOOGLE_ADS_MCP_TOOLS_CONFIG is not set: the bundled tools config turns on create_asset, ...
```

All twelve FAILs: S01 hubspot; S06 klaviyo (project); S06 and S12 ga4; S07 mixpanel; S09 bigquery (VS Code); S01, S03 and S05 meta-ads; S05 and S06 search-console; S06 google-ads. The nine WARNs: S06 (user-generated content) and S11 klaviyo; S05 and S11 ga4; S13 mixpanel; S11 bigquery (VS Code); S01 search-console; S05 google-ads; S10 klaviyo.

## Decisions per server

- HubSpot: the bridge host carries HubSpot's name but is not HubSpot's (lookalike rule). Route: the official remote server through the Team workspace connector, inherited by Claude Code. Identity: the wholesale ops lead's View-only user with Create off. Layer: source permission plus the Blocked write-and-delete category in the Team workspace and glob deny rules in Claude Code, because View users can still create notes and tasks. Tested as two connections, one per client. The bridge was removed and its token revoked by the Super Admin.
- Klaviyo: the shared project entry had no `read-only=true` and loaded for every teammate; one person's personal read-only entry shadowed it, so only that person was protected. Route: a custom MCP server in the ChatGPT Business workspace with `read-only=true&disable-tools-with-user-generated-content=true`, because the listed app ignores parameters. The shared entry and the personal entry were both removed, so one definition serves the job.
- GA4: Google's local server was started with `--allow-write` and pointed at a credential file inside the repository. Route: the same server in user scope, pinned, without the flag, using application default credentials minted with `analytics.readonly`. The committed file was rotated by its owner.
- Mixpanel: the EU project sat behind the US endpoint through an unpinned proxy. Route: a native `url` entry for `https://mcp-eu.mixpanel.com/mcp`. Layer: a project role without write access, the same in production and the test project; Mixpanel has no read-only switch.
- BigQuery: the VS Code entry used `mcpServers` and never loaded, and the direct Claude Code entry could not sign in, because Google Cloud remote MCP accepts only registered web callbacks. Both were removed. The cloud admin created a Web application OAuth client with the redirect `https://claude.ai/api/mcp/auth_callback`, the Team workspace owner added it as a custom connector, and Claude Code inherits that connector with tools named `mcp__claude_ai_bigquery-acme-ro__*`. The cloud admin also applied the IAM deny policy from the draft.
- Meta Ads: a community package at `@latest` with an inline token, for a vendor that publishes `https://mcp.facebook.com/ads`. Route: the official server with a grant of `ads_mcp_management` and `ads_read` only. The token was rotated.
- Search Console: a community server at `@latest` with writes on. Route: the bulk export already running since 2026-06-01, read through BigQuery MCP with the `search_type = 'web'` filter. The community login was revoked with `auth logout`.
- Google Ads: Google's server, already in Codex with approval mode writes, was running its bundled config with every tool on. A tools config with `labels: false` and `assets: false`, named by `GOOGLE_ADS_MCP_TOOLS_CONFIG`, took the write tools out. The git source was pinned to a reviewed commit and an `enabled_tools` allowlist of the three prefixed read tools added; the `adwords` credential was recorded as write-capable.

Every change to a client file went through the client's own command (`claude mcp remove`, `claude mcp add`) or, for Cursor, VS Code, Claude Desktop and the Codex `enabled_tools` key, as a block the owner pasted. The agent never opened a config file.

The corrected files scan clean:

```bash
python3 -I scripts/mcp_inventory.py --fixture assets/example-acme-client-configs-after.json \
  --expect-region mixpanel=eu --as-of 2026-11-01
```

`Summary: 0 FAIL, 0 WARN, 2 INFO, 6 PASS`, exit code 0. The two INFO lines (Meta Ads, Mixpanel) name source-side layers that only T2 and T4 can prove. HubSpot, BigQuery and Klaviyo live in cloud workspaces, so they are not in these files.

## Test log excerpt

```csv
server,client,test,date,expected,observed,result,evidence,tester_role,notes
hubspot-acme-prod-ro,Claude Code,T4,2026-10-29,write tools unavailable to the agent,manage_crm_objects not available to the agent (deny rule),PASS,the agent's tool list and the rule in /permissions,wholesale ops lead,no write attempted; retail-team deal returned nothing
hubspot-acme-prod-ro,Claude Desktop (Team workspace),T4,2026-10-29,write tools unavailable to the agent,manage_crm_objects not available to the agent (Blocked category),PASS,the connector's tool permissions screen,wholesale ops lead,no write attempted; retail-team deal returned nothing
klaviyo-acme-prod-ro,ChatGPT,T3,2026-10-27,8000 delivered (Welcome flow September 2026),8000,PASS,get_flow_report and the flow report screen,lifecycle manager,ran from the agent's numbered test script; America/New_York
ga4-acme-prod-ro,Claude Code,T1,2026-10-28,property 123456789,property 123456789 plus 2 others,WARN,get_account_summaries,ops lead,because the account reaches two extra properties; removal requested
google-ads-acme-prod-ro,Codex,T3,2026-10-29,2345.67 USD for 2026-10-19 to 2026-10-25,cost_micros 2345670000,PASS,GAQL search and the Google Ads UI,paid media lead,divided by 1000000
bigquery-acme-ro,Claude Code,T4,2026-10-30,execute_sql and cancel_job denied,denied by the deny policy,PASS,execute_sql insert into a disposable table; cancel_job on a made-up job ID; INFORMATION_SCHEMA.JOBS,SEO lead,both still listed; snapshot marks them denied_by iam
```

## Drift run

HubSpot's tool list in Claude Code grew between the 2026-10-20 baseline and 2026-11-01:

```bash
python3 -I scripts/mcp_tools_diff.py assets/example-acme-tools-current.json \
  --baseline assets/example-acme-tools-baseline.json --mode ro
```

```text
FAIL D01 manage_aeo_prompts: new write tool on a read-only connection. ...
FAIL D01 manage_aeo_recommendations: new write tool on a read-only connection. ...
WARN D02 discover_hubspot_schema: new read tool; review what it returns before the next run.
WARN D02 get_aeo_metrics: ...
WARN D02 get_organization_details: ...
WARN D02 search_conversations: ... It may return text written by outsiders (U): keep send-out tools out of this profile.
WARN D02 search_intent_signals: ...
Summary: 2 FAIL, 5 WARN, 0 INFO
```

The per-tool deny list in Claude Code named each write tool, so the two new ones were not covered. The team replaced it with one glob rule on `manage_*`; in the Team workspace the two tools fell into the Blocked write-and-delete category on their own. The next snapshot marked them blocked and became the baseline. The same snapshot without a baseline (`mcp_tools_diff.py assets/example-acme-tools-current.json`) gives `2 FAIL` under D04 for the same two tools, and a later snapshot in which a blocked tool loses its marker fails D06.

## Hand-off

```bash
python3 -I scripts/mcp_inventory_check.py assets/example-acme-inventory.md --as-of 2026-11-01
```

`Summary: 0 FAIL, 0 WARN` with 8 blocks, all connected: seven servers, HubSpot in two clients. Run on 2026-12-10, the same file fails C07 and C08: results older than 30 days need a retest.

The agent proposed, and wrote only after approval: the inventory as `martech-context/mcp-inventory.md`, eight Agent access lines in `stack.md` (replacing "via a bridge a contractor set up"), one `decisions.md` entry per connection, the tool snapshots under `martech-context/mcp-tools/`, and a line under Optional files present in `martech-context/README.md` naming both.

## What the agent did not do

- No write, send, publish, delete or spend call ran outside the T4 sandbox tests. The only write attempts were the harmless denied tests in the Mixpanel test project, the Meta test ad account and a disposable BigQuery table, each named by the user and approved call by call. Each was denied; the source's own records (Mixpanel audit log, BigQuery `INFORMATION_SCHEMA.JOBS`) showed no change. HubSpot's write tools were not available to the agent, so no HubSpot write was attempted.
- No admin setting changed. Drafts went to the HubSpot Super Admin and Team workspace owner (connector data permissions, Blocked category, bridge removal), the ChatGPT workspace admin (Klaviyo plugin), the Klaviyo owner (leftover app authorisations), the cloud admin and Team workspace owner (BigQuery OAuth client, connector and deny policy) and the GA4 admin (extra property access).
- No config file was opened in the conversation and no secret value was printed; the scanner reported `META_ACCESS_TOKEN` by name only.
