Pier39 Negotiation Ecosystem — Overview
A comprehensive snapshot of everything that's been built. Read this first to understand what's in the ecosystem, who each piece is for, and where each artifact lives.
TL;DR — what exists
A complete two-sided ecosystem for AI-driven shopping negotiation:
| Piece | What it is | Where it lives | Status |
|---|---|---|---|
| negotiate.v1 | Open HTTP/JSON protocol for AI agents to negotiate against compliant storefronts | PROTOCOL.md |
✅ Live |
| pier39-merchant | Claude Agent Skill teaching the seller-side tactics | pier39-skills |
✅ Live |
| pier39-shopper | Claude Agent Skill teaching the buyer-side tactics | pier39-skills |
✅ Live |
| Atlas Premium Appliance | Reference storefront — live working demo | negotiate.pier39.ai/store |
✅ Live |
| pier39-merchant-server | Pip-installable Python library — drops a complete negotiate.v1 backend into any store | PyPI · GitHub | ✅ Live |
| negotiate-mcp (PyPI) | MCP server for Claude shopper agents (stdio mode) | PyPI · GitHub | ✅ Live |
| negotiate-mcp (hosted) | Same MCP server, hosted as remote/HTTP for one-click custom-connector install | mcp.pier39.ai/mcp |
✅ Live |
| MCP Server Registry entry | Canonical home in Anthropic's MCP server registry | registry.modelcontextprotocol.io (search "negotiate-mcp") | ✅ Live |
| negotiate-directory | Public registry of negotiate.v1 stores — what find_stores searches |
github.com/sanjana-pier39/negotiate-directory |
✅ Live |
| STORE_SETUP.md | Onboarding doc for store owners (sellers) | STORE_SETUP.md |
✅ Live |
| SHOPPER_SETUP.md | Onboarding doc for shoppers (buyers) | SHOPPER_SETUP.md |
✅ Live |
Strategic vision
The product is the seller-side agent. Stores install pier39-merchant. Any AI shopper (Claude, ChatGPT, custom bots) can negotiate at any negotiate.v1-compliant store. The store keeps margin discipline; the shopper gets a fair deal; the protocol does the matchmaking.
Why an open protocol instead of a closed product:
- Lock-in fails when adoption matters more than capture. An open protocol can be implemented by anyone — that grows the surface faster than a proprietary API.
- Shoppers using ChatGPT, Cursor, or custom agents are also customers — they need the same protocol that Claude users get. Open protocol covers all of them.
- Stores would never adopt a closed seller-agent that locks them to one shopper-side ecosystem. Open protocol makes adoption a no-brainer for them.
Three audiences, three artifacts:
| Audience | What they install | Where to start |
|---|---|---|
| Store owners | pier39-merchant-server (pip) |
STORE_SETUP.md |
| Shoppers (Claude users) | negotiate-mcp connector |
SHOPPER_SETUP.md |
| Protocol implementers | Read the spec, build whatever | PROTOCOL.md |
Architecture
Three layers, deliberately decoupled:
┌─────────────────────────────────────────────────────────────┐
│ LAYER 1 — Skills (the brain) │
│ │
│ pier39-shopper pier39-merchant │
│ • Anchor with comps • Hold the floor │
│ • Decreasing demands • Cheap-first concessions │
│ • Walk gracefully • Decreasing-concession pattern │
└─────────────────────────────────────────────────────────────┘
▲
│ (loaded as system prompt)
▼
┌─────────────────────────────────────────────────────────────┐
│ LAYER 2 — Implementation libraries (the body) │
│ │
│ pier39-merchant-server negotiate-mcp │
│ • Drops in a chat backend • 5 MCP tools │
│ • Serves /negotiate.json • discover_store │
│ • Rate-limited, CORS-ready • start_negotiation │
│ • You bring catalog.json • send_message │
└─────────────────────────────────────────────────────────────┘
▲
│ (HTTP/JSON)
▼
┌─────────────────────────────────────────────────────────────┐
│ LAYER 3 — Hosted services (the canonical reference) │
│ │
│ negotiate.pier39.ai mcp.pier39.ai │
│ • Atlas Premium Appliance • Remote MCP for shoppers │
│ • 4 Dyson products • Plug into Claude UI │
│ • Real chat with Chonkers • One-paste install │
└─────────────────────────────────────────────────────────────┘
Each layer can be swapped without breaking the others. A store could use a different skill, a different model, or a non-Python backend — as long as the protocol surface in PROTOCOL.md stays compliant, every shopper still works against them.
The protocol — negotiate.v1
Full spec: PROTOCOL.md. Quick summary:
Discovery
Every compliant store serves a JSON descriptor:
GET /negotiate.json
GET /.well-known/negotiate.json (mirror)
Response includes negotiate_protocol: "negotiate.v1", store info, products with IDs and list prices, endpoint URL templates, and limits.
Chat surface
Three GET endpoints + two POST equivalents. AI agents drive the negotiation purely through GETs (no POST or sandbox needed):
GET /api/store/chat/start?product_id=<id> → {session_id, greeting, next}
GET /api/store/chat/<sid>/say?message=<urlencoded> → {message, closed, next}
GET /api/store/chat/<sid> → {history}
Each response includes a next URL — the next URL to fetch to continue the negotiation. Hypermedia-driven, so the shopper agent doesn't need to memorize URL patterns.
Hidden state
floor, levers, and inventory_note for each product live ONLY in the merchant agent's private system prompt. They're stripped from every public GET response. The shopper agent never sees them.
Why GET-based
Most AI shopper tools (Claude.ai's web browsing, custom GPTs with actions) only POST when they have explicit Action manifests. GET works universally. Browser-based widgets that want JSON POSTs can use the POST equivalents — both are CORS-permissive.
The artifacts in detail
1. The skills
pier39-merchant and pier39-shopper are Claude Agent Skills — folder format with SKILL.md + references/. They teach Claude how to negotiate well; they don't include any implementation code, deployment logic, or storefront UI.
- Repo: github.com/sanjana-pier39/pier39-skills
- License: Apache 2.0
- Install:
git clone+ copy folders to~/.claude/skills/
Each skill defines decision-tree-style tactics for its side of the negotiation. The merchant skill teaches floor-holding and concession ladders; the shopper skill teaches anchoring and walk-away discipline.
2. Atlas Premium Appliance — the reference storefront
Live at negotiate.pier39.ai/store. Fictional Dyson outlet with four products:
- Dyson V15 Detect Absolute (vacuum)
- Dyson Airwrap Complete Long
- Dyson Pure Hot+Cool HP07
- Dyson Supersonic Special Edition
Sales rep is Chonkers (configurable in store/catalog.json → rep_name). Click "Chat with Chonkers" on any product page to negotiate as a human, OR have an AI shopper hit the API endpoints directly.
The storefront also hosts a separate password-gated negotiation viewer at the apex (negotiate.pier39.ai) where two AI agents play out scripted negotiations — that's a separate demo for showing the skill in action without needing two AI clients.
Source: this entire pier39-negotiation-demo/ folder is the storefront's source.
3. pier39-merchant-server — the library for stores
Pip-installable Python library that gives any store a complete negotiate.v1-compliant backend in 6 lines:
from pier39_merchant_server import serve
serve(catalog_path="catalog.json")
That serves all the routes: /negotiate.json, /llms.txt, /api/store/catalog, /api/store/chat/* (both GET and POST equivalents), /api/health. Plus per-IP rate limiting, per-session message caps, permissive CORS, OPTIONS preflight.
The store brings:
- A catalog.json (products with public fields + private floor/levers)
- An ANTHROPIC_API_KEY env var
- Hosting (anywhere that runs Python — Fly, Render, their existing infra)
That's it. Setup time: ~30 minutes for a developer, including the conceptual work of designing floors and levers.
- PyPI: pypi.org/project/pier39-merchant-server
- GitHub: github.com/sanjana-pier39/pier39-merchant-server
- License: MIT
- Onboarding doc: STORE_SETUP.md
4. negotiate-mcp — the MCP server for shoppers
Model Context Protocol server that exposes 6 native tools to any MCP-aware client (Claude Desktop, Cowork, Claude Code, Cursor, Cline, etc.):
find_stores(query, category)— search the public store directorydiscover_store(domain)— probe a domain for negotiate.v1list_products(domain)— get the catalogstart_negotiation(domain, product_id)— open a chat sessionsend_message(next_url, message)— send one shopper turnread_history(history_url)— read running chat history
find_stores is what closes the discovery loop: a shopper says "find me a store that sells Dyson vacuums" and Claude returns matches from the negotiate-directory registry without the shopper ever needing to know a URL.
Two install modes:
Stdio (local): pip install negotiate-mcp or uvx negotiate-mcp, then add to Claude Desktop's claude_desktop_config.json:
{
"mcpServers": {
"negotiate-agent": {
"command": "uvx",
"args": ["negotiate-mcp"]
}
}
}
Remote (hosted): hosted at https://mcp.pier39.ai/mcp. Add to Claude Desktop via the Settings → Connectors → Add custom connector UI:
- Name:
Negotiate Agent - Remote MCP server URL:
https://mcp.pier39.ai/mcp
No install required — just paste the URL.
- PyPI: pypi.org/project/negotiate-mcp
- GitHub: github.com/sanjana-pier39/negotiate-mcp
- MCP Registry:
io.github.sanjana-pier39/negotiate-mcp - Hosted endpoint:
https://mcp.pier39.ai/mcp - License: MIT
- Onboarding doc: SHOPPER_SETUP.md
5. The hosted MCP at mcp.pier39.ai
The same negotiate-mcp package, but configured with MCP_TRANSPORT=streamable-http and deployed as a Fly app. This is what powers the "Add custom connector" install path — shoppers paste a URL instead of installing software locally.
- Deployed via
Dockerfile+fly.tomlinmcp-connector/ - Env vars:
MCP_TRANSPORT=streamable-http,HOST=0.0.0.0,PORT=8000 - No secrets needed (the MCP makes outbound calls to public storefronts; doesn't hold any API keys)
- Includes a runtime patch that disables MCP's DNS-rebinding protection (which assumes localhost-only deployment)
The deploy story is in mcp-connector/Dockerfile + fly.toml. Documented in mcp-connector/PUBLISH.md.
6. negotiate-directory — the public store registry
A single JSON file in a public GitHub repo listing every known negotiate.v1-compliant storefront. The MCP's find_stores tool fetches this file (cached 5 min per process) so AI shoppers can search for stores by query or category.
- Repo: github.com/sanjana-pier39/negotiate-directory
- Live URL:
https://raw.githubusercontent.com/sanjana-pier39/negotiate-directory/main/registry.json - License: CC0 for the data; MIT for the tooling/docs
- How stores get listed: PR to add a single JSON object inside the
storesarray. Maintainers verify the store's/negotiate.jsonis reachable and merge.
Each entry includes name, domain, tagline, categories, sample product names, and the date added. The tag set converges over time; standard categories today: appliances, electronics, furniture, fashion, fitness, books, home, office, etc.
This single file is the matchmaking layer between stores and shoppers. Without it, shoppers need to know URLs by hand. With it, they ask the AI agent and get answers.
7. MCP Server Registry entry
Anthropic's official MCP Server Registry has a canonical listing for io.github.sanjana-pier39/negotiate-mcp published via the mcp-publisher CLI. The registry pulls metadata from the package's server.json manifest and verifies ownership via the mcp-name: io.github.sanjana-pier39/negotiate-mcp marker in the PyPI README.
This entry feeds:
- The MCP Inspector and CLI tools' discovery
- Smithery's auto-import (when published to Smithery)
- Future Claude Desktop in-app browse/install (when Anthropic ships that surface)
Quick-start paths by audience
I want to be a negotiable store
- Read STORE_SETUP.md
- Write a
catalog.jsonfor your products (with merchant judgment on floors + levers) pip install pier39-merchant-serverpython -c "from pier39_merchant_server import serve; serve(catalog_path='catalog.json', host='0.0.0.0')"- Deploy on Fly/Render/your infra
- Point your domain at it
- You're now negotiable by every AI shopper that speaks negotiate.v1
I want to negotiate online prices
- Read SHOPPER_SETUP.md
- Open Claude Desktop → Settings → Connectors → Add custom connector
- Name:
Negotiate Agent, URL:https://mcp.pier39.ai/mcp - Restart Claude Desktop
- In any chat: "Negotiate for the Dyson HP07 at negotiate.pier39.ai. Try to get it under $500."
I just want to see it work
Visit negotiate.pier39.ai/store → click any product → click "Chat with Chonkers" → negotiate as a human.
I want to build a custom shopper or merchant in another language
- Read PROTOCOL.md
- Implement either side in your language of choice
- Test against Atlas at
https://negotiate.pier39.ai/negotiate.json - Done
Distribution surface — where each artifact lives
| Channel | What's there | URL |
|---|---|---|
| PyPI | pier39-merchant-server, negotiate-mcp |
pypi.org/project/{name} |
| GitHub | All source repos | github.com/sanjana-pier39/{repo} |
| MCP Server Registry | negotiate-mcp listing |
registry.modelcontextprotocol.io |
| Fly.io | Atlas storefront, hosted MCP | negotiate.pier39.ai, mcp.pier39.ai |
| Smithery | (Pending submission) | smithery.ai |
| Custom GPT | (Pending — for ChatGPT users) | chatgpt.com (eventual Custom GPT) |
| Anthropic Connectors marketplace | (Pending application) | Claude Desktop Connectors UI |
Major design decisions
Open protocol over proprietary API. Adoption matters more than capture. An open protocol grows the ecosystem; a closed product blocks the chatGPT half of the market.
GET-based negotiation. Most AI fetchers are GET-only. POST equivalents exist for browser widgets, but the canonical surface is GET so shopper agents always work.
Hypermedia (next URLs in responses). The shopper agent never has to construct URLs from templates — each response tells it where to go next. This makes shopper-side implementations trivial.
Hidden state in system prompt only. Floor and levers never appear in any public response. Even if a malicious shopper tries to enumerate the catalog API, they only see public data. The merchant agent enforces the floor through its prompt, not through post-hoc validation.
Stdio + HTTP MCP modes from one codebase. The same negotiate-mcp package runs as a local stdio server (Claude Desktop's classic install) AND as a remote HTTP server (Anthropic's "Add custom connector" UI, Smithery, etc.). One env var (MCP_TRANSPORT) flips between them.
Library-first for stores. A store integrator shouldn't have to read the protocol spec; they should be able to pip install and pass a catalog. The protocol is for implementers building in other languages or for understanding what's happening under the hood.
Sonnet 4.6 by default for the merchant. Cheap enough (~$0.05–$0.15 per negotiation) that public-facing demos are affordable, smart enough that the merchant tactics land. Opus is overkill; Haiku occasionally fumbles.
Notable hard-won lessons
Anthropic's web_fetch is intermittent. Claude.ai's web browsing tool sometimes refuses URLs after repeated requests, or rejects ones flagged by safety classifiers. The solution: don't depend on it for the canonical install path. The MCP connector and the hosted MCP are reliable substitutes.
FastMCP's DNS rebinding protection breaks hosted deploys. The TransportSecurityMiddleware in mcp.server.transport_security defaults to allowlisting only localhost, which rejects any request through Fly/Cloudflare/etc. The fix in negotiate_mcp/server.py monkey-patches _validate_host and _validate_origin to always return True; safe because Fly's edge already handles host routing.
MCP registry ≠ MCP servers list. The community-maintained list at github.com/modelcontextprotocol/servers is no longer accepting PRs. The new canonical home is the MCP Server Registry at registry.modelcontextprotocol.io, with PyPI README marker for ownership verification.
Claude Desktop's "Add Custom Connector" only accepts remote URLs. Stdio servers must be added via JSON config file editing. To get into the UI install path, you need a hosted (HTTP) version of your MCP. Hence the dual-mode negotiate-mcp package.
Roadmap (what's intentionally not built yet)
| Item | Why not yet | Estimated effort |
|---|---|---|
| Custom GPT for ChatGPT users | Pending — closes the ChatGPT audience gap | 1–2 hours |
| Smithery listing | Pending — gets us into the most-trafficked MCP marketplace | 15 minutes (form submission) |
| Anthropic Connectors marketplace listing | Long pipeline; need adoption traction first | Submit anytime, review takes weeks |
| JS/TypeScript merchant server | Doubles the addressable store population (Next.js, Shopify apps, etc.) | 1–2 days |
| Eval suite for the merchant skill | Need this to make credibility claims to real stores | 2–3 days |
| Multi-product / bundle negotiations | The protocol is single-product per session today | Spec change + implementation |
| Persistent session storage | In-memory today; sessions die on restart | Add Redis backend, ~1 day |
| One real third-party store | Validation that the protocol works for someone who didn't build it | ~1 day per integration |
File map (this repo)
pier39-negotiation-demo/
├── OVERVIEW.md ← you are here
├── README.md Atlas storefront repo overview
├── PROTOCOL.md Formal negotiate.v1 spec
├── STORE_SETUP.md Onboarding doc for store owners
├── SHOPPER_SETUP.md Onboarding doc for shoppers
│
├── server.py Atlas backend (negotiate viewer + storefront + chat)
├── run.py CLI runner for two-agent negotiation transcripts
├── viewer-template.html Editorial-style negotiation viewer
├── index.html Generated viewer with embedded transcripts
│
├── store/ Atlas storefront templates + catalog
│ ├── catalog.json 4 Dyson products (public + hidden fields)
│ ├── storefront.html /store catalog page
│ └── product.html /store/p/<id> with embedded chat widget
│
├── scenarios/ Seed scenarios for the negotiation viewer
│ ├── aeron.json Original tight-ZOPA chair scenario
│ ├── marriott-suite.json Hotel-perks scenario (close-friendly)
│ ├── bose-bundle.json Bundle-attach scenario (close-friendly)
│ ├── dyson-bundle.json Cross-product bundle (close-friendly)
│ ├── mercedes-cpo.json High-ticket TCO reframe (close-friendly)
│ └── …
│
├── transcripts/ Saved negotiation runs (input → viewer)
│
├── pier39-merchant-server/ ← The store library (separate repo when published)
│ ├── pier39_merchant_server/
│ │ ├── __init__.py
│ │ └── app.py MerchantApp + serve() + cli()
│ ├── examples/tiny_store/ Working example: catalog + serve.py
│ ├── pyproject.toml
│ ├── README.md
│ ├── PUBLISH.md
│ ├── LICENSE MIT
│ ├── MANIFEST.in
│ └── .gitignore
│
├── negotiate-directory/ ← Public store registry (separate repo when published)
│ ├── registry.json The actual list of stores
│ ├── README.md Schema + how AI shoppers consume it
│ ├── CONTRIBUTING.md How stores submit themselves
│ ├── LICENSE Dual: MIT (tooling) + CC0 (data)
│ └── .gitignore
│
└── mcp-connector/ ← The shopper MCP (separate repo when published)
├── negotiate_mcp/
│ ├── __init__.py
│ ├── __main__.py
│ └── server.py 5 tools + stdio/HTTP transport switch
├── pyproject.toml
├── README.md
├── PUBLISH.md
├── LICENSE MIT
├── server.json MCP Registry manifest
├── claude_desktop_config.example.json
├── Dockerfile For hosted-mode deploy
├── fly.toml Fly app config
└── .dockerignore
Status as of last working session
- Live and discoverable: Atlas storefront, pier39-merchant-server, negotiate-mcp (PyPI + GitHub + Registry + hosted)
- Pending submission: Smithery, Custom GPT, Anthropic Connectors marketplace
- Working end-to-end: stdio install, hosted-MCP install, real human chat at Atlas, two-Claude negotiations from the viewer
- Known issues:
- Claude.ai web browsing is intermittently flaky on
pier39.ai(Anthropic-side fetcher behavior, not our infra) - Atlas's Fly app uses
auto_stop_machines=stopso cold starts add 5–30s latency on the first request; considermin_machines_running=1($2–5/mo) if cold starts hurt the demo
License
- Skills: Apache 2.0
- Library, MCP connector, protocol, docs: MIT
See also
- PROTOCOL.md — formal negotiate.v1 spec
- STORE_SETUP.md — onboarding for store owners
- SHOPPER_SETUP.md — onboarding for shoppers
- pier39-merchant-server/README.md — library reference
- mcp-connector/README.md — MCP connector reference
- pier39-skills — the underlying Claude Agent Skills
- Atlas live demo — try negotiating with Chonkers