GAM Seller MCP Node
Use this MCP server to discover sell-side ad inventory, read coarse availability forecasts, and manage your own buyer-scoped soft commitments under a governed, audit-first pipeline.
Fetch the node's RS256-signed well-known capabilities document (trust anchor, identity, privacy posture).
Discover product families you're entitled to see (formats, channel, sites, firm list prices).
Get a Low/Mid/High availability forecast for a product family and period.
Check availability for a requested impression volume and period, including partial fits and where-else answers (per README).
Create a firm, TTL-bound buying intent at the family's current firm price; rejected if price is stale or mismatched.
Revoke one of your own active intents by ID, idempotent per client_request_id.
All calls are authenticated, default-deny policy-checked, rate-limited, disclosure-schema-checked, and written to a hash-chained audit ledger—no ad-server writes.
Exposes read-only ad inventory discovery from Google Ad Manager, providing tools for well-known capabilities, product families, and bucketized availability forecasts.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@GAM Seller MCP Nodeshow me available product families"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Governed MCP seller control-plane prototype for future GAM integration.
A Model Context Protocol server that exposes sell-side ad inventory to buyer-side AI agents: discovery, firm pricing, and a buyer-scoped soft commitment primitive. No writes to an ad server exist. Forecasts can come live from Google Ad Manager (opt-in, read-only ForecastService snapshot); the catalog is operator config, and without a GAM connection forecast data is synthetic.
What problem does this solve?
Sell-side ad inventory (availability, pricing, product structure) lives inside ad servers that hold commercially sensitive and sometimes personal data. Giving an AI buyer agent direct API access to GAM or a similar system creates three risks:
Risk | Without this project | With this project |
Data over-exposure | Agent can read raw avails, deal IDs, exact floor prices | Only coarse buckets and pre-declared families |
Accidental writes | Agent SDK can create orders, modify line items | No ad-server writes exist; the only write is a buyer's own soft commitment, which can never become a GAM order or an inventory hold |
No accountability | API calls are logged but not auditable | Hash-chained audit ledger; every allow/deny recorded |
Related MCP server: google-ads-mcp
How it works
A buyer agent connects via MCP and gets five tools — three read-only, plus a buyer-scoped commitment primitive (create/revoke) that is the sole write surface:
Buyer agent
│
├── well_known_capabilities ← Signed trust anchor. Check this first.
│ Returns: RS256-signed capability document, node identity, privacy posture.
│
├── discover_products ← What can I buy here, and at what firm price?
│ Returns: product families the buyer is entitled to see (e.g. "Pre-Roll Video"),
│ each with its formats, channel and sites, and its firm list price
│ when the publisher has configured them.
│ Never returns: deal IDs, internal IDs, raw inventory, exact per-impression pricing.
│
├── get_forecast ← How available is this family next quarter?
│ Returns: Low / Mid / High availability bucket.
│ Never returns: exact impression counts, CPM curves, floor prices.
│
├── check_availability ← Can you deliver 2.8M impressions of this family in October?
│ Returns: available / partial (up to ~2.7M, ~1.3M viewable) / unavailable, as of the
│ last forecast — and, when it does not fit, where it would ("November: 3.1M").
│ Volumes come from the forecast rounded down to 2 significant figures
│ (configurable by the publisher).
│ Never returns: other buyers' bookings, floor prices, audience data.
│
├── create_intent ← Commit to a product at its current firm price (with TTL).
│ Records a firm, time-boxed buying intent — rejected if the price is stale or
│ mismatched. NOT a GAM order and NOT an inventory hold; it is the handoff artifact
│ the classic sales rails pick up. Buyer-scoped: you can only ever commit as yourself.
│
└── revoke_intent ← Withdraw one of your own active intents by id.Every call flows through the same pipeline before any domain logic runs:
Buyer request
│
▼
[SEC-GATE-3] Replay detection — deduplicate client_request_id
│
▼
[Auth] RS256 token validation → identity confirmed or AUTH_FAILED
│
▼
[Policy] Surface denylist → entitlement check → scope check (Default-Deny)
│
▼
[Rate limit] N=1 / T=30s per buyer_id
│
▼
[Domain] Catalog / ForecastEngine — synthetic, seeded or live GAM forecast snapshot
│
▼
[Disclosure] Response checked against the tool's strict schema — undeclared field → withheld
│
▼
[Audit] Append-only hash-chained ledger, buyer pseudonymized (HMAC)
│
▼
Response to buyerEach request-path gate rejects on failure. One honest caveat to the diagram above:
client_request_id(the replay-guard deduplication key) is required by default on every authenticated surface since v0.9.0 — a request without it is rejected, so SEC-GATE-3 cannot be bypassed by omission. An operator can explicitly opt out for legacy clients withMCP_REQUIRE_IDEMPOTENCY_KEY=0, which reopens that bypass.
The rate-limit stage covers every authenticated tool — the read surfaces, create_intent,
and revoke_intent — so no authenticated surface bypasses it.
A corrupted or tampered on-disk ledger is detected on startup and the node refuses to serve (fail-closed on load, plus a chain-integrity verify before the first request) rather than resetting to an empty chain.
create_intent runs the same gates and adds one more before it records anything: the buyer's
price_ref must match the family's current firm price, or the request is rejected.
Quick start
Run a full pilot in one command.
scripts/pilot.shbrings the node up on your config with production guards on, mints a buyer token per entitled buyer, and prints how to drive a buyer agent through the whole loop (discover → forecast → commit → revoke) — seedocs/PILOT-QUICKSTART.md. The reference buyer agent lives atexamples/buyer-client-ts/agent.ts; hosting behind TLS is a filled-in-the-blanks recipe indeploy/.
Install in an MCP client (via npx)
Add the server to your MCP client (Claude Desktop, Claude Code, Cursor, …):
{
"mcpServers": {
"gam-seller": {
"command": "npx",
"args": ["-y", "gam-seller-mcp-node"]
}
}
}Or run it directly (stdio transport — the default for MCP clients):
npx -y gam-seller-mcp-nodeDemo mode. With no config of your own, the node boots on a bundled
pilot-publisherexample (illustrative catalog, prices and forecasts) and says so on stderr — it starts instead of failing, so you can try the tools immediately. Because buyer surfaces always require a token (there is no anonymous path, even in demo), the node prints a ready-to-use demo buyer token on startup: copy it and pass it as thetokenargument todiscover_products/get_forecastto see the example families, prices and forecasts.For a real deployment, point
MCP_CONFIG_DIRat a directory holding your owndeployment.json,catalog.json,entitlements.jsonandpricing.json:MCP_CONFIG_DIR=/etc/gam-seller/config npx -y gam-seller-mcp-node
From source
git clone https://github.com/juan-sibbo/gam-seller-mcp-node.git
cd gam-seller-mcp-node
npm install
npm run build
npm run start:http # HTTP transport on 127.0.0.1:3900Run the full buyer-agent walkthrough (scripted demo) — the five native tools driven over a real in-process MCP transport, ending in the governed refusals (fail-closed auth, Default-Deny, fail-closed pricing) and a verified audit chain:
npm run demo # or: npx tsx demo/run-demo.tsWith Docker
docker compose upThe node starts on 127.0.0.1:3900. The well-known document is at
/.well-known/seller-mcp-capabilities. Persistent volumes for keys and audit data are
pre-configured in docker-compose.yml.
Configure for your publisher
Four JSON files drive all publisher-specific behaviour — no code changes needed. Place them
in config/ (from-source) or in the directory named by MCP_CONFIG_DIR (npx/containerised):
deployment.json # DSR contact, controller model, data retention window
catalog.json # product families + per-buyer access grants
entitlements.json # which buyers are entitled to which MCP surfaces
pricing.json # firm list prices per family (fail-closed on expiry)
forecast.json # OPTIONAL — seed availability buckets from real numbers (still synthetic-labeled)
gam.json # OPTIONAL — live GAM forecast (network, service-account key path, family → targeting)Invalid config always fails closed: a malformed file stops the node rather than running
with a silently different access policy. Absent config (no config/ and no MCP_CONFIG_DIR)
drops to the bundled config/examples/pilot-publisher/
example — demo mode, announced on stderr — so the node is never a broken install, only ever a
real deployment or a clearly-labelled demo.
Taking a pilot onto real inventory (short of a live GAM connection) is all configuration —
see docs/PUBLISHER-DEPLOYMENT.md:
Seed the forecast with the publisher's own availability, exported once from a GAM report, via an optional
forecast.json(template:config/examples/pilot-publisher/forecast.sample.json). Buckets become realistic while every result stayssynthetic: true— pre-loaded is not a live read, so no live-GAM claim is made.Connect GAM live with an optional
gam.json(template:config/examples/pilot-publisher/gam.sample.json) and a service account added to the GAM network with a read role that can run forecasts. The node asksForecastService.getAvailabilityForecastfor every configured family × period at boot and every 30 minutes, using prospective line items that are never saved — nothing in GAM is created, modified or reserved. Buyers are answered from that snapshot as Low/Mid/High buckets withsynthetic: false; raw availability never leaves the node. Precedence:gam.json>forecast.json> synthetic.Close the handoff loop so a committed intent reaches the publisher's sales rails, via
MCP_INTENT_HANDOFF=file(a local JSONL drop an operator forwarder tails). The handoff makes no outbound call — forwarding is the operator's process, and a URL value is refused. A handoff record is a notification, never a GAM order or inventory hold.Harden for the road:
MCP_REQUIRE_OPERATOR_CONFIG=1(refuse to boot on demo config),MCP_ANCHOR_SINK=tsa(anchor the audit trail to a third party).
Network egress — declared and bounded, not deny-all. Buyer request handling makes no outbound
network calls. The live GAM forecast (gam.json) calls Google's OAuth token endpoint and the Ad
Manager SOAP API for the configured network, at boot and on the 30-minute refresh cycle — never
inside a buyer request, so buyers cannot generate load on the publisher's GAM. Optional audit anchoring can generate operator-configured egress outside the buyer
request path (at boot and on the periodic anchor cycle): the TSA backend (MCP_ANCHOR_SINK=tsa)
submits the ledger head hash to the configured RFC 3161 authority; the S3 backend
(MCP_ANCHOR_SINK=s3) writes the anchor record to the configured Object Lock bucket; a custom
sink module (MCP_ANCHOR_SINK=<module>) runs operator-supplied code. The default local anchor
backend performs no external network call. Destinations come only from operator configuration —
no buyer input can choose one. This surface is pinned by
tests/egress-surface.test.ts: a new outbound capability fails CI
until it is declared on purpose.
Why not just use the GAM API directly?
Approach | Data exposure | Writability | Auditability | AI-agent friendly |
Raw GAM API | Everything in the account | Full CRUD | Logging only | Poor (SOAP/REST, no MCP) |
OpenRTB bid requests | User-level data, floor prices | Bid-only | None | Poor |
This server | Families + availability checks from the live forecast | Buyer's own soft commitment only (no GAM writes) | Hash-chained ledger | Native MCP |
Current status
Working prototype. The full request pipeline (auth → policy → rate-limit → domain → audit),
the buyer-scoped commitment primitive (create_intent / revoke_intent, with TTL expiry),
the audit ledger, GDPR data-subject-rights toolkit, Docker packaging, HTTP transport,
and a live interop probe (Python buyer agent simulation) are all implemented and tested.
The persistence layer is hardened for restarts (append-only, atomic writes, durable rotation
state, fail-closed load), and the head-hash anchor is append-only with selectable external WORM
backends (RFC 3161 timestamping / S3 Object Lock) — see Known limitations
for the residual (a live write-once destination is an operator infra act).
GAM connection — forecast only: the live adapter
(src/forecast/gam-source.ts) reads availability from GAM's
ForecastService when gam.json is present. Catalog families and their GAM targeting (ad units,
sizes, environment) are still mapped by hand in config, and prices remain static list prices.
Without gam.json the forecast is synthetic (or seeded) and says so (synthetic: true). See the
open issues for the roadmap.
Known limitations — dated status. Closed rows are kept on purpose: a limitations list that changes state over time is both a proof of honesty and a proof of progress.
Limitation | Anchor | Status | Closed by |
Attribution ( |
| Design decision, not a defect — traceability vs. erasability (ADR-4) | — |
Head-hash anchor rewrote its whole file each write ( |
| ✅ Closed 2026-08-23 — append-only JSONL + injectable | |
External WORM anchoring needs the operator to point at a live write-once destination (a TSA URL, or a locked bucket) — the node ships the backends, not the destination |
| Open — deployment boundary (infra act) | — |
|
| Closed in v0.9.0 — required by default on every authenticated surface (fail-closed); | |
No TLS in transit (a reverse proxy is expected to terminate) | — | Open — deployment boundary | — |
|
| ✅ Closed 2026-08-18 — now behind the rate-limit gate like every authenticated surface | |
GDPR DSR CLI ( |
| ✅ Closed 2026-08 — ships as the | |
Ledger loaded fail-open — a corrupt file reset to an empty chain |
| ✅ Closed 2026-08-07 | |
Chain integrity not verified before serving on startup |
| ✅ Closed 2026-08-07 |
Architecture
See docs/ARCHITECTURE.md for the module map and data-flow diagrams.
Key modules:
Module | Role |
| MCP tool definitions + request pipeline |
| Default-Deny engine, entitlement store, surface allowlist/denylist, per-tool disclosure schemas |
| RS256 key management, token issuance/validation, revocation denylist |
| Hash-chained ledger, HMAC pseudonymization, append-only head-hash anchoring with selectable WORM backends ( |
| Firm list price store, expiry-aware (fail-closed on stale prices) |
| Bucket engine + sources: synthetic, seeded, live GAM snapshot |
| Service-account OAuth + minimal Ad Manager SOAP client (read-only) |
| GDPR Art. 15/17/18/20 data-subject-rights toolkit (also shipped as the |
| Product family store, per-buyer access grants |
Security model
Default-Deny. Every request is denied unless an explicit entitlement says otherwise — there is no "allow by default" path in the code.
Two gates: who may call a tool, and what it may return. The policy layer works on surface
labels: each authenticated tool declares one allowed surface when it is registered, and exact
pricing, deal IDs, raw availability numbers, cross-buyer state, real inventory holds (soft-lock)
and any ad-server write are permanently on the denylist. That label check does not look at the
response, so it cannot on its own stop a tool under an allowed label from returning something it
shouldn't. The disclosure gate does: every authenticated tool must declare a strict response
schema (src/policy/disclosure.ts), and a response carrying any undeclared field is withheld and
replaced by a generic INTERNAL_ERROR (counted on mcp_disclosure_rejected_total). A new tool
cannot be registered without that schema, so adding a buyer-visible field means changing it in one
reviewable file. The one permitted write is a buyer's own commitment (create_intent /
revoke_intent), which required an explicit amendment to the surface allowlist and stays
buyer-scoped.
Opaque errors. A denied request, a failed authentication, and a revoked token all return the
same generic AUTH_FAILED code. Internal reasons never reach the buyer.
Audit-first. Every allow/deny is written to the ledger before the response is sent.
Buyer buyer_id values are pseudonymized (HMAC-SHA256) before entering the chain. Note that
buyer_id and request_id, while stored in each audit entry, are not included in the
hash-chain's canonical input (audit/event.ts:50); those fields are not covered by the
chain's tamper-evidence guarantee.
Privacy by construction. Responses carry only inventory-level data (product family, coarse bucket). User-level attributes don't exist in any response path.
See docs/DESIGN-PRINCIPLES.md for the full reasoning.
Regulatory posture
The AEPD (Spain's data protection authority) published guidelines on agentic AI systems in February 2026. The four recommendations most relevant to an ad-inventory node map directly to existing design decisions:
AEPD recommendation | This node |
Protection by design and by default | Default-Deny: every surface denied unless an explicit entitlement grants access |
Record and document agent actions | Append-only hash-chained audit ledger; every allow/deny recorded before the response is sent |
Control what leaves toward third parties, and with what traceability | Buyer-facing disclosure allowlist (SEC-GATE-*): exact pricing, deal IDs and raw availability are permanently blocked from responses. Network egress allowlist: the only outbound connections are the operator-opted audit anchors and the operator-opted GAM forecast refresh (see Network egress above), pinned by CI |
Govern agent memory with purpose and retention rules | DSR toolkit (Arts. 15/17/18/20); configurable retention window enforced on the audit ledger |
This alignment is declared machine-readably in the signed well-known document
(/.well-known/seller-mcp-capabilities) under privacy_posture.regulatory_alignment_declared:
["GDPR", "AEPD-orientaciones-IA-agentica-2026"]. A buyer agent or auditor can verify it
cryptographically without trusting this README.
The node does not make legal determinations — whether a given processing has a legitimate basis, whether consent is valid, whether a particular treatment is permitted. Those judgements belong to the controller (the broadcaster). The node provides the mechanisms; the controller applies the criteria. This boundary is what keeps the node's design stable regardless of how the EU Data Act negotiations resolve.
Machine-readable trust anchor
The /.well-known/seller-mcp-capabilities endpoint returns an RS256-signed JWT. A buyer agent
reads and verifies this document before the first authenticated request. The privacy_posture
block inside it is machine-readable and cryptographically bound to the node's keypair:
Property | Current value | Meaning |
|
| No end-user personal data in any response path |
|
| No audience targeting surfaces |
|
| Node does not consume TC strings (server-to-server, PATH A) |
|
| No device storage access (ePrivacy N/A) |
|
| Declared operating jurisdiction |
|
| Declared alignment |
| from | Contact for data-subject requests |
| from | Publisher's declared controller role |
| from | Hot/archive retention windows in days/months |
Not yet in the well-known document (properties that remain implicit):
Whether the catalog and forecast data are synthetic or live (
data_source)Whether head-hash anchoring uses a local file or cloud Object Lock (
anchor_store)Whether the node is in demo mode or serving a real publisher config (
deployment_mode)
These properties would allow a buyer agent to programmatically distinguish a demo deployment from a production one, and a locally-anchored node from one with external tamper-evidence. They are not present in the current version.
Testing
npm test # full suite (vitest)
python3 sandbox/buyer-agent-probe.py # external Python interop probe (no shared code with server)The test suite includes:
Unit tests for each module (policy, pricing, identity, audit, catalog, forecast, DSR)
Integration tests over real in-memory MCP transports (
tests/server.test.ts)HTTP transport tests over a real ephemeral-port HTTP server (
tests/http.test.ts)End-to-end session tests simulating a full buyer-agent session (
tests/buyer-agent-session.test.ts)External Python probe that exercises the HTTP transport without any shared Node.js code
CI runs on every push via GitHub Actions.
Data protection
Raw buyer_id values never enter the audit ledger — only an HMAC pseudonym. The
src/dsr/toolkit.ts implements export, restriction, and erasure of a
buyer's audit data (GDPR Art. 15/17/18/20). The node stores nothing about end users; the DSR
scope is exactly what it records — B2B buyer organization pseudonyms and their request events.
Distribution note. Operator commands ship in the npm package and the container image as the
gam-seller-admin bin (issue-token, revoke-token, dsr …; gam-seller-dsr remains as an alias
for the DSR commands), so no checkout is needed. They write the node's state and therefore run
only while the node is stopped: a running node owns its state directory (owner lease) and the
command is refused with exit code 3. The scripts/*.ts entries are dev wrappers over the same code.
Roadmap
See the open issues for the full roadmap. Highlights:
GAM inventory mapping — derive family targeting from GAM ad units/placements instead of hand-written
gam.jsonBuyer agent SDKs — Python and TypeScript client libraries for the MCP buyer flow
OpenRTB 3.0 taxonomy — align
family_idscheme with IAB standardsWell-known observability properties — expose
data_source/anchor_store/deployment_modeso a buyer agent can distinguish demo from production programmatically
(The Prometheus /metrics endpoint is already shipped — loopback-only, opt-in.)
Contributing
See CONTRIBUTING.md. Issues tagged
good first issue
are a good starting point.
License
MIT — see LICENSE.
Available Tools
6 toolscheck_availabilityARead-only
Check whether the publisher can deliver a number of impressions of a product family in a period. Returns available, partial (with the volume it can offer) or unavailable, the viewable share when forecast, and — if the volume does not fit — up to 3 alternative periods or families where it does. Figures are forecast estimates, rounded down (2 significant figures by default), not reservations.
| Name | Required | Description | Default |
|---|---|---|---|
| token | No | Buyer bearer JWT (RS256, aud=seller-mcp-node). Identity is derived from token.sub. | |
| period | Yes | Target period (e.g. 2026-10, Q4-2026) | |
| family_id | Yes | Product family ID from discover_products | |
| impressions | Yes | Impressions the buyer wants to deliver in the period | |
| client_request_id | No | Idempotency key for replay detection — required on every call unless the operator opted out; fresh value per call |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint and openWorldHint=false; the description adds substantial context beyond that: the three possible outcomes (available/partial/unavailable), the partial-volume and viewable-share fields, the fallback alternatives, and critically that figures are forecast estimates rounded down, not reservations. That last point prevents an agent from treating the result as a booking.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, front-loaded with the core action and then the return semantics. Every clause earns its place; nothing is redundant with the name or annotations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the full burden of describing the return value, and it does so thoroughly: outcome states, partial volume, viewable share, alternative periods/families, and the estimate-not-reservation caveat.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all five parameters (token, period, family_id, impressions, client_request_id). The description only hints at a rounding default ('2 significant figures by default'), which does not add parameter-level syntax beyond the schema. Baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource with scope: checks whether the publisher can deliver N impressions of a product family in a period. This is clearly distinguishable from siblings like get_forecast (retrieves forecasts) and create_intent (commits a buy).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied (a pre-commit availability probe before create_intent) but never stated explicitly, and no sibling alternative or when-not condition is named. The agent must infer the place in the workflow from the sibling list.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_intentA
Register a firm buying intent over a product family at its current firm price (soft commitment with TTL). Not a GAM order or inventory hold.
| Name | Required | Description | Default |
|---|---|---|---|
| token | No | Buyer bearer JWT (RS256, aud=seller-mcp-node). Identity is derived from token.sub. | |
| period | Yes | Target period (e.g. Q4-2026, 2026-10) | |
| family_id | Yes | Product family ID from discover_products | |
| price_ref | Yes | The firm list price the buyer commits to; must match the family's current firm price | |
| client_request_id | No | Idempotency key for replay detection — required on every call unless the operator opted out; fresh value per call |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnly=false, destructive=false, idempotent=false, and openWorld=false, so the safety profile is covered. Beyond that, the description adds genuinely useful semantics: the commitment is 'soft' and has a TTL, which an agent cannot infer from the annotations. It omits what happens at TTL expiry and does not surface the mandatory client_request_id replay requirement, keeping it below 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences: the core action and its soft-commitment/TTL nature come first, with the disambiguation clause front-loaded immediately after. Every clause earns its place and nothing is redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With a rich 100%-covered input schema, an explicit annotation set, and no output schema, the description supplies the essential conceptual framing (soft commitment, TTL, not an order/hold). It is nearly complete, missing only lifecycle details such as expiry behavior or the interaction with the revoke_intent sibling.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so every parameter (token, period, family_id, price_ref, client_request_id) is already documented in the schema. The description's 'at its current firm price' loosely reinforces price_ref but adds no format, constraint, or edge-case detail beyond what the schema states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Register a firm buying intent over a product family') with the key qualifier 'at its current firm price' and the semantic nature ('soft commitment with TTL'). The final clause 'Not a GAM order or inventory hold' actively distinguishes it from realistic confusions like check_availability and other order-like siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The 'Not a GAM order or inventory hold' exclusion tells the agent when this tool is the wrong choice, which is meaningful routing guidance. It does not, however, explicitly point to a positive alternative (e.g. check_availability for stock, revoke_intent for withdrawal) or state preconditions like needing a family_id from discover_products, so it stops short of a full 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discover_productsBRead-only
Discover coarse product families available to this buyer.
| Name | Required | Description | Default |
|---|---|---|---|
| token | No | Buyer bearer JWT (RS256, aud=seller-mcp-node). Identity is derived from token.sub. | |
| client_request_id | No | Idempotency key for replay detection — required on every call unless the operator opted out; fresh value per call |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds the useful qualifier 'coarse', implying the result is an aggregated/high-level view rather than a full catalog, but it says nothing about pagination, filtering, or that identity comes from the token.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no filler. It is efficient, though 'coarse' carries a lot of unexplained weight for one adjective.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only discovery tool with annotations covering safety and a 100%-covered schema, the remaining gap is the return shape: there is no output schema, yet the description does not characterize what a 'coarse product family' looks like or how results are ordered/limited. Adequate but leaves an agent guessing about output.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and both parameters (token, client_request_id) are documented in the schema itself with detail on RS256/JWT and idempotency. The description adds nothing beyond that, so the baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('discover') and resource ('product families') with a scope qualifier ('available to this buyer'). It is distinguishable from siblings like get_forecast or check_availability, though it does not name what makes it different beyond the resource.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use guidance, no prerequisites, and no pointer to alternatives such as well_known_capabilities, which a sibling for discovery/capability listing. The agent must infer the entry point from the name alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_forecastARead-only
Get a coarse availability forecast (Low/Mid/High) for a product family and period. synthetic tells whether the bucket comes from a live ad-server forecast.
| Name | Required | Description | Default |
|---|---|---|---|
| token | No | Buyer bearer JWT (RS256, aud=seller-mcp-node). Identity is derived from token.sub. | |
| period | Yes | Target period (e.g. Q4-2026, 2026-10) | |
| family_id | Yes | Product family ID from discover_products | |
| client_request_id | No | Idempotency key for replay detection — required on every call unless the operator opted out; fresh value per call |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnlyHint=true, openWorldHint=false), and the description adds real behavioral context beyond them: the result is bucketed rather than numeric, and the `synthetic` flag discloses whether the bucket derives from a live ad-server forecast or a fallback. That provenance disclosure is genuinely useful for trusting the value, though nothing is said about staleness or caching.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences, zero filler, and the scope statement precedes the return-value note. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description does carry part of the return-value burden and does so adequately (bucket scale plus the synthetic provenance flag). The main residual gap is routing guidance against check_availability, which is outside the strict return-value remit.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so family_id, period, token, and client_request_id are all documented in the schema. The description only restates the family/period inputs and does not add format, range, or default semantics beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Get), resource (availability forecast), granularity (coarse Low/Mid/High), and the two dimensions it is keyed on (product family, period). It is clear what the tool does, though it never explicitly contrasts itself with the sibling check_availability, which sounds like the adjacent retrieval tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The word 'coarse' and the Low/Mid/High scale imply this is the cheap planning-grade lookup rather than a precise availability check, which is a usable hint. But there is no explicit when-to-use, when-not-to-use, or named alternative (e.g. versus check_availability), so the agent must infer the routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
revoke_intentADestructiveIdempotent
Revoke one of your own active intents by id. Idempotent per client_request_id.
| Name | Required | Description | Default |
|---|---|---|---|
| token | No | Buyer bearer JWT (RS256, aud=seller-mcp-node). Identity is derived from token.sub. | |
| intent_id | Yes | The intent_id returned by create_intent | |
| client_request_id | No | Idempotency key for replay detection — required on every call unless the operator opted out; fresh value per call |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and idempotentHint=true, so safety and repeat-call behavior are covered structurally. The description adds useful detail beyond that: idempotency is keyed on client_request_id, and the constraint that only the caller's own active intents can be revoked.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, no padding, and the core action plus scope is front-loaded before the idempotency note.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive but idempotent mutation with full schema coverage and complete annotations, the description covers enough: what it does, on what scope, and idempotency semantics. Only a fuller statement of failure behavior (e.g. what happens to an already-revoked or expired intent) is missing, and no output schema exists to defer to.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and all three parameters (token, intent_id, client_request_id) are documented in the schema itself, so the baseline of 3 applies. The description adds no format or syntax detail beyond what the schema already says.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (revoke) and resource (intent) with scope qualifiers ('one of your own active') and the lookup key ('by id'). It is easy to distinguish from create_intent, though it never names a sibling explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the words 'your own active intents', which tells the agent the target must be an existing intent it owns, but there is no explicit when-to-use/when-not guidance or named alternative among the siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
well_known_capabilitiesBRead-only
Return the signed RS256 capability document for this Seller MCP Node.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds genuinely useful context by specifying the document is signed with RS256 (a verifiable trust anchor), but it says nothing about whether the endpoint is public or needs auth, or how the signature should be validated.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence that identifies the resource and its key property (signed RS256). No filler, no redundancy with the title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description carries the return-value burden; it names the artifact but not what the capability document contains or how to consume the signature. For a simple discovery endpoint this is borderline adequate, but an agent gains little actionable detail about the payload.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters and the schema is an empty object, so there is no parameter semantics to explain. Per the baseline for 0-param tools, a 4 is appropriate; the description correctly implies no inputs are needed.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Return') and a well-defined resource ('signed RS256 capability document for this Seller MCP Node'), which is clearly distinct from transactional siblings like create_intent or check_availability. It does not explicitly name or contrast with siblings, but the resource is unique enough that confusion is unlikely.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no indication of when an agent should call this versus discover_products or the other siblings, nor any prerequisites or call ordering. The well-known/trust-anchor nature implies a discovery/bootstrap use, but that must be inferred entirely by the reader.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.11.1- Added
check_availability - Changed
create_intent1 field changed- changed
Input schema / properties / client_request_id / descriptionPrevious value: -"Client-supplied idempotency key for replay detection"New value: +"Idempotency key for replay detection — required on every call unless the operator opted out; fresh value per call"
- Changed
discover_products1 field changed- changed
Input schema / properties / client_request_id / descriptionPrevious value: -"Client-supplied idempotency key for replay detection"New value: +"Idempotency key for replay detection — required on every call unless the operator opted out; fresh value per call"
- Changed
get_forecast1 field changed- changed
Input schema / properties / client_request_id / descriptionPrevious value: -"Client-supplied idempotency key for replay detection"New value: +"Idempotency key for replay detection — required on every call unless the operator opted out; fresh value per call"
- Changed
revoke_intent1 field changed- changed
Input schema / properties / client_request_id / descriptionPrevious value: -"Client-supplied idempotency key for replay detection"New value: +"Idempotency key for replay detection — required on every call unless the operator opted out; fresh value per call"
4 tool updates
v0.8.0- Added
create_intent - Changed
discover_products3 fields changed- removed
Input schema / properties / buyer_idRemoved value: -{ - "description": "Buyer identifier (opaque, B2B)", - "type": "string" -} - changed
Input schema / properties / token / descriptionPrevious value: -"Domain-2 inter-service JWT (RS256)"New value: +"Buyer bearer JWT (RS256, aud=seller-mcp-node). Identity is derived from token.sub." - removed
Input schema / requiredRemoved value: -[ - "buyer_id" -]
- Changed
get_forecast3 fields changed- removed
Input schema / properties / buyer_idRemoved value: -{ - "description": "Buyer identifier (opaque, B2B)", - "type": "string" -} - changed
Input schema / properties / token / descriptionPrevious value: -"Domain-2 inter-service JWT (RS256)"New value: +"Buyer bearer JWT (RS256, aud=seller-mcp-node). Identity is derived from token.sub." - changed
Input schema / requiredPrevious value: -[ - "buyer_id", - "family_id", - "period" -]New value: +[ + "family_id", + "period" +]
- Added
revoke_intent
2 tool updates
v0.2.0- Changed
discover_products1 field changed- removed
Input schema / additionalPropertiesRemoved value: -false
- Changed
get_forecast1 field changed- removed
Input schema / additionalPropertiesRemoved value: -false
2 tool updates
v0.1.1- Added
get_forecast - Added
well_known_capabilities
2 tool updates
- Removed
get_forecast - Removed
well_known_capabilities
3 tool updates
v0.1.0- First observed
discover_products - First observed
get_forecast - First observed
well_known_capabilities
TDQS
Scored across 6 tools
Most tools target distinct operations: capability discovery, product discovery, coarse forecasting, specific availability check, and intent create/revoke. The main overlap is between get_forecast and check_availability, which both deal with availability but differ in granularity (coarse Low/Mid/High vs. specific volume with alternatives); descriptions help distinguish them.
Five of six tools follow a clear verb_noun or verb_noun_phrase pattern (discover_products, get_forecast, check_availability, create_intent, revoke_intent). well_known_capabilities breaks the pattern as a noun phrase rather than a verb-led action, but the convention is otherwise consistent.
Six tools is well-scoped for a seller-side MCP node covering authentication metadata, product discovery, forecasting, availability checking, and intent lifecycle. Each tool earns its place without obvious redundancy or bloat.
The surface covers discovery, forecast, availability, and intent create/revoke, but lacks read operations for intents (e.g., list_intents or get_intent) and any explicit price lookup despite create_intent relying on current firm price. An agent can work around this only if create responses include sufficient identifiers, making it a notable gap.
Maintenance
Related MCP Connectors
Google Ads MCP server — manage campaigns, keywords, and metrics.
Read-only MCP tools for AI agent discovery, structured resources, and NIULAI information.
MCP server for querying and analyzing data from ad platforms, analytics tools, and spreadsheets
Guarded MCP server for agent-readable business truth, provenance, readiness, and discovery.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA Multi-Agent Conversation Protocol Server that provides programmatic access to the Google Authorized Buyers Marketplace API, allowing agents to interact with the digital advertising marketplace through natural language.-

google-ads-mcpofficial
AlicenseAqualityAmaintenanceMCP server that provides tools and resources for interacting with Google Ads API, enabling search, metadata retrieval, and account management through natural language.32,759 PyPI1,001Apache 2.0- AlicenseAqualityBmaintenanceA read-only MCP server that enables AI agents to act as GCP platform engineers, allowing them to investigate incidents, take inventory, and find cost-optimization opportunities in Google Cloud projects without mutating any infrastructure.1622 PyPI2MIT
- AlicenseNot gradedqualityCmaintenanceA security-first MCP server that enables AI clients to read and write Google Ad Manager data through the Ad Manager API, with least-privilege defaults, gated writes, and per-user OAuth support.443 npmMIT