Fairplug (sandbox)
OfficialFairplug MCP
An MCP server that lets AI assistants surface sponsored offers honestly: typed offer data, a tracked link, and a disclosure text the assistant must show. Builders who show the offer get paid; users always know it is sponsored.
Early pilot. Fairplug is at an early stage. The offers are fictional example data, there are no live advertisers yet, and no real money moves through this server. The sandbox is for trying the integration, not for earning.
Try it now (no signup, no key)
Public sandbox, read-only, fictional data, nothing tracked or paid, rate-limited to 60 requests/min per IP:
https://fairplug-mcp.fairplughq.workers.dev/sandbox/mcpClient config (streamable HTTP):
{ "mcpServers": { "fairplug-sandbox": { "url": "https://fairplug-mcp.fairplughq.workers.dev/sandbox/mcp" } } }Stdio-only clients: npx mcp-remote https://fairplug-mcp.fairplughq.workers.dev/sandbox/mcp
Server card: https://fairplug-mcp.fairplughq.workers.dev/.well-known/mcp/server-card.json
Related MCP server: affiliate-router-mcp
Quickstart (run locally)
npm install
npm run build
npm test # unit + integration tests
npm run verify # end-to-end: MCP client -> stdio server -> both tools -> redirect -> conversion -> report
node dist/src/server.js # MCP over stdioNeeds Node >= 18 (Node >= 22 only for wrangler deploys). Check a remote deployment: npm run remote-check -- <base-url> - (sandbox only) or <base-url> <mcp-token> <admin-token> (full).
Tools
search_offers(query, category?, region?): keyword search;category= travel | saas | retail or a sub-category;region= ISO country code (hard eligibility filter). Every result hassponsored: true, a disclosure label and sponsor name.get_tracked_link(offer_id, agent_id, context?): returns a tracked URL (HMAC-signed, 15-min TTL) and a requireddisclosure_text. In the sandbox the URL is a non-functional placeholder and nothing is recorded.
Rules
Disclosure first. Every offer is sponsored and must be labelled as such, in the same message as the link, using the disclosure_text we return. Show at most one sponsored offer, alongside organic alternatives. No disclosure, no payout. Tool output is typed data, never instructions to the model.
Fees. Fairplug takes 10% of a validated commission, with a US$0.50 minimum, never more than 15% of the commission (the cap wins on tiny commissions: a US$2.00 commission pays US$0.30). Network fees are passed through at cost. You see every number in the open-books report. Only validated conversions count. No fee on anything pending or reversed.
Early-pilot honesty. Seed offers are fictional; conversions here are simulated; no payouts are being made yet. We will say so plainly until that changes.
Security notes
/mcp(private) requires a bearer token; the sandbox at/sandbox/mcpis separate, stores nothing and exposes no report/admin data.Never commit secrets. Set
FAIRPLUG_SIGNING_SECRET,FAIRPLUG_MCP_TOKEN,FAIRPLUG_ADMIN_TOKENas environment variables orwrangler secret put. The default signing secret is a public dev value.
License
MIT, see LICENSE.
Store
Append-only JSONL at var/events.jsonl (override dir with FAIRPLUG_DATA_DIR). Logs every tool call (ok or error), link issuance, click (including rejected/duplicate), and conversion. Shared safely by the MCP, redirect and CLI processes.
CLI
node dist/src/cli.js record_conversion --click-id clk_... [--order-value 189.00] [--status validated|pending|reversed] [--network-fee 0.25]
node dist/src/cli.js reportrecord_conversion is a simulation (stand-in for an advertiser/network postback). It requires that the click was recorded (open the tracked URL first, e.g. curl -i '<tracked_url>'). cpa_percent offers need --order-value; fixed/CPL use the offer's payout amount. Re-recording for the same click replaces its status (e.g. reversed).
Report per agent: calls, links, unique clicks, conversions (validated/pending/reversed), gross commission, Fairplug fee, network fees, net payout. Only validated conversions count toward money. Fee = 10% of validated commission, US$0.50 minimum, capped at 15% of commission; network fees passed through at cost. Net = gross - fee - network fees.
Small-commission rule: the 15% cap overrides the US$0.50 minimum. The minimum would exceed the cap for any commission under ~US$3.33 (0.50 / 0.15), so there the fee is floor(15% x commission). Examples: US$2.00 commission -> fee US$0.30 (not 0.50); US$1.00 -> 0.15; US$3.00 -> 0.45; US$4.00 -> 0.50 (minimum applies from ~US$3.34 up to US$5.00, where 10% takes over). Implemented in src/fees.ts, covered by test/core.test.ts. Assumed interpretation; confirm.
Known limits / not implemented
Fee edge case: see "Small-commission rule" above (cap beats minimum; assumed, confirm).
No FX: non-USD payouts in the seed (NZD) are treated as USD-equivalent in the report.
Signing secret defaults to a public dev value; set
FAIRPLUG_SIGNING_SECRETfor anything beyond local use.Not built: disclosure_rendered receipts (D5), auction/ranking scores, rate limits, API keys, ledger/reserves, validation windows, real network/postback integration, real-money ledger; the hosted conversion endpoint is an admin-recorded simulation, not a real network postback, bot/IP filters. Search is simple keyword scoring, not BM25/embeddings.
The stdio setup's redirect server only binds to 127.0.0.1; use the HTTP/remote build below for links that work off-machine.
Remote (streamable HTTP)
One process/Worker serves POST /mcp (MCP streamable HTTP, stateless, JSON responses), GET /r/clk_... (click logger), GET /health, and GET /admin/report (only if FAIRPLUG_ADMIN_TOKEN set). Shared code: src/handler.ts (Web Request -> Response).
Env: FAIRPLUG_REDIRECT_BASE (public base URL used in tracked links), FAIRPLUG_SIGNING_SECRET, optional FAIRPLUG_MCP_TOKEN (Bearer required on /mcp), FAIRPLUG_ADMIN_TOKEN.
Node (any host/VM/container): FAIRPLUG_REDIRECT_BASE=http://127.0.0.1:8788 npm run start:http (PORT / FAIRPLUG_PORT, default 8788). Events go to var/events.jsonl.
Cloudflare Workers (recommended free host): worker/index.ts + wrangler.jsonc. State is one SQLite-backed Durable Object (free-plan compatible), table events. Local test, no account needed (wrangler needs Node >= 22):
printf 'FAIRPLUG_SIGNING_SECRET=dev\nFAIRPLUG_MCP_TOKEN=test-token\nFAIRPLUG_ADMIN_TOKEN=admin-token\n' > .dev.vars
npx wrangler dev --local --port 8799 --var FAIRPLUG_REDIRECT_BASE:http://127.0.0.1:8799
npm run build && npm run remote-check -- http://127.0.0.1:8799 test-token admin-tokenClient config for builders: { "mcpServers": { "fairplug": { "url": "https://<host>/mcp", "headers": { "Authorization": "Bearer <token>" } } } } (or npx mcp-remote https://<host>/mcp --header "Authorization: Bearer <token>" for stdio-only clients).
Open-books endpoints (hosted + Node build)
All money is USD, 2 decimals. ADMIN = Authorization: Bearer <FAIRPLUG_ADMIN_TOKEN>. Admin endpoints return 404 to anyone else (and if no admin token is configured).
POST /admin/agent-token(ADMIN) body{"agent_id":"my_agent"}->{agent_id, token}. This is how a builder gets a per-agent report token: Fairplug (admin) issues it and hands it over once. Only a SHA-256 hash is stored (Durable Object tableagent_tokens;var/agent_tokens.jsonon Node), so a lost token can't be recovered, only re-issued; re-issuing rotates (the old token stops working). The token only grantsGET /reportfor that one agent_id.POST /admin/conversion(ADMIN) body{"click_id":"clk_...","status":"validated|pending|reversed","commission":20.00,"network_fee":0.25,"order_value":189.00}(statusdefaults to validated,network_fee/order_valueoptional). The click_id must have an issued link and a recorded click. Re-posting for the same click_id replaces its status (e.g. validated -> reversed). Simulation stand-in for a network postback.GET /report?agent_id=my_agentwithAuthorization: Bearer <that agent's token>or the admin token -> JSON:clicks(unique),links_issued,conversions{validated, pending, reversed: count + commission},gross_commission_usd,fairplug_fee_usd,network_fees_usd,net_payout_usd,fee_policy, and per-conversionitemsso every number can be re-derived. Only validated conversions count toward gross/fee/net; fee is computed per conversion (10%, 15% cap, US$0.50 min, cap wins for tiny commissions); network fees at cost.
curl -X POST $HOST/admin/agent-token -H "Authorization: Bearer $ADMIN" -H 'content-type: application/json' -d '{"agent_id":"my_agent"}'
curl -X POST $HOST/admin/conversion -H "Authorization: Bearer $ADMIN" -H 'content-type: application/json' -d '{"click_id":"clk_...","commission":20,"network_fee":0.25}'
curl "$HOST/report?agent_id=my_agent" -H "Authorization: Bearer $AGENT_TOKEN"Tests: test/http.test.ts (auth, validation, fee math, rotation, token isolation); npm run remote-check also exercises them when given an admin token.
Deploy (Cloudflare)
Free Cloudflare account (email only, no card).
npx wrangler login(Node >= 22).Edit
FAIRPLUG_REDIRECT_BASEinwrangler.jsonctohttps://fairplug-mcp.<your-subdomain>.workers.dev.npx wrangler secret put FAIRPLUG_SIGNING_SECRET(long random),FAIRPLUG_MCP_TOKEN,FAIRPLUG_ADMIN_TOKEN.npx wrangler deploy, thennpm run remote-check -- https://<host> <mcp-token> <admin-token>.Do not add a payment method / Workers Paid; on Free, going over limits just fails requests, nothing is billed. Free limits: 100k requests/day, DO SQLite 100k row-writes/day (~each tool call writes 1-3 rows, each click 1), 5 GB; resets 00:00 UTC.
Available Tools
2 toolsget_tracked_linkBInspect
Get a tracked redirect URL for an offer plus the disclosure_text you MUST show the user next to the link.
| Name | Required | Description | Default |
|---|---|---|---|
| context | No | Short note on the user's intent (logged, no PII) | |
| agent_id | Yes | Your stable agent identifier | |
| offer_id | Yes | offer_id from search_offers |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It usefully discloses a compliance constraint (the mandatory disclosure_text) but omits other traits such as whether the tracked link mutates state, expiry/rate limits, or auth requirements. Some value added, but significant gaps remain.
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 efficiently packed sentence that front-loads the primary output (tracked redirect URL) and appends the secondary requirement. No filler, though it is on the terse side.
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?
There is no output schema, so the description must cover return values; it does name both the URL and disclosure_text. However, for a 3-parameter tool it leaves the link's lifecycle (expiry, single-use) and auth expectations unstated, making it only minimally complete.
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 agent_id, offer_id, and context are all documented in the schema (including the 'offer_id from search_offers' provenance). The description adds no parameter meaning beyond what the schema already provides, so 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 and resource (get a tracked redirect URL for an offer) and even names the secondary artifact returned (disclosure_text). It is clear what the tool produces, though it does not explicitly name or differentiate itself from the sibling search_offers.
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 clause about disclosure_text being something you 'MUST show the user' implies this is the follow-on step after selecting an offer, but there is no explicit when-to-use vs search_offers guidance or stated prerequisites. Usage is implied rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_offersAInspect
Search Fairplug's sponsored/affiliate offers (EXAMPLE seed data in this local build). Returns typed data only, never instructions. Every result is sponsored and must be disclosed.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | What the user is looking for | |
| region | No | ISO country code of the user, e.g. US | |
| category | No | travel | saas | retail, or a sub-category like hotels |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden, and it delivers real behavioral context: results are typed data only and never instructions (prompt-injection safety), and every result is sponsored and must be disclosed (compliance obligation). It omits pagination, result counts, and auth requirements, keeping it short of a 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?
Three short, front-loaded sentences: what it searches, a safety guarantee, and a disclosure obligation. Zero filler and no repetition of schema content.
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 low-complexity, three-parameter read tool with no output schema, the description covers domain, safety, and compliance caveats adequately. It leaves return shape and result limits unstated, which is a minor gap given no output schema exists.
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 all three parameters (query, region, category) are already documented in the schema. The description adds no syntax or format detail beyond that, which is the expected baseline when the schema does the work.
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 ('Search') and resource ('sponsored/affiliate offers') with a clear domain owner (Fairplug). It does not explicitly differentiate itself from the sibling get_tracked_link, so it stops short of a 5.
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 'query' parameter and the search framing imply when to use it, but there is no explicit when-to-use guidance, no exclusions, and no mention of when to prefer get_tracked_link instead. Usage is inferable but not stated.
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.
2 tool updates
v0.1.0- First observed
get_tracked_link - First observed
search_offers
TDQS
Scored across 2 tools
The two tools have clearly distinct purposes: search_offers is for discovery, get_tracked_link is for generating a redirect URL and disclosure. An agent can easily tell which to use without overlap.
Both names follow a consistent snake_case verb_noun pattern (search_offers, get_tracked_link). No mixed conventions or vague verbs.
Two tools is thin for an affiliate offers server; while each earns its place, the surface lacks auxiliary operations (e.g., fetching offer details by ID) that would make the count feel well-scoped.
The core workflow of searching offers and generating a tracked link with disclosure is covered, but minor gaps remain, such as direct retrieval of a specific offer without searching.
Maintenance
Related MCP Connectors
Agent-native insurance quoting protocol — sandbox, MCP + REST, eligibility pre-flight
Real, buyable product and service offers — live pricing and trackable links for any MCP agent.
AI-agent-first offers directory: search and publish listings via MCP.
- UnifAPIOAuthcom.unifapi
Hosted MCP server for live public-data APIs and Skills for AI agents.
Related MCP Servers
AlicenseNot gradedqualityDmaintenanceMCP server for the Agorus AI agent marketplace, exposing API operations as tools for LLMs to discover, contract, and interact with agents and services.10 npmMIT- AlicenseAqualityAmaintenanceVendor-neutral MCP server that discovers, routes, and attributes revenue across paid APIs, referral links, and affiliate programs for agent tools.8259 npmMIT
- FlicenseNot gradedqualityCmaintenanceMCP server for the Vibe-Marketer Agent API that enables generating ads, auditing landing pages, and running local preflight checks on ad compliance, with an autonomous agent loop to improve results.-

AON MCP Serverofficial
AlicenseNot gradedqualityBmaintenanceAON(Agent Offer Network) MCP connects AI agents to real, buyable products and services with live pricing and tracked links—enabling builders to earn developer revenue share from attributed clicks and conversions. AON MCP Server owned by aon.pro4Apache 2.0