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:

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.

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:

Sales rep is Chonkers (configurable in store/catalog.jsonrep_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.

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 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:

No install required — just paste the URL.

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.

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.

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:


Quick-start paths by audience

I want to be a negotiable store

  1. Read STORE_SETUP.md
  2. Write a catalog.json for your products (with merchant judgment on floors + levers)
  3. pip install pier39-merchant-server
  4. python -c "from pier39_merchant_server import serve; serve(catalog_path='catalog.json', host='0.0.0.0')"
  5. Deploy on Fly/Render/your infra
  6. Point your domain at it
  7. You're now negotiable by every AI shopper that speaks negotiate.v1

I want to negotiate online prices

  1. Read SHOPPER_SETUP.md
  2. Open Claude Desktop → Settings → Connectors → Add custom connector
  3. Name: Negotiate Agent, URL: https://mcp.pier39.ai/mcp
  4. Restart Claude Desktop
  5. 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

  1. Read PROTOCOL.md
  2. Implement either side in your language of choice
  3. Test against Atlas at https://negotiate.pier39.ai/negotiate.json
  4. 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


License


See also