vinted-seller-mcp

vinted-seller-mcp

An MCP server that helps sellers create Vinted listings from photos, researching comparable listings, estimating prices, and filling the listing form. It requires explicit confirmation before publishing, deleting, or sending messages.

Category
Visit Server

README

vinted-seller-mcp

Photos in, a ready-to-publish Vinted listing out. An MCP server and CLI that researches comparable listings, estimates a defensible price, finds the right category and brand, writes the listing, fills Vinted's real form — and stops, until you say publish.

Works with Hermes, Claude Desktop, Claude Code, and any MCP client. One stdio server behind all of them.

photos + what you can see
        ↓
search_similar_items   →  real comparables, filtered
        ↓
estimate_price         →  median, range, confidence
        ↓
find_category / find_brand
        ↓
prepare_listing        →  a local draft; nothing sent to Vinted
        ↓
        ⏸  you review it
        ↓
validate_listing       →  fills the real form, STOPS before publishing
        ↓
        ⏸  you approve
        ↓
publish_listing

Install

git clone https://github.com/rachid598/mcpvin.git
cd mcpvin
npm install
npx playwright install chromium
npm run build
./scripts/install-hermes.sh
vinted-seller login --country fr
vinted-seller status --country fr

Requires Node.js 20+. npm install pulls Playwright's Chromium (~150 MB) on first run.

For the vinted-seller command to be on your PATH, either npm link in the checkout or npm install -g .. Otherwise call node dist/cli/index.js — the install script prints the absolute path.


Manual browser authentication

This is the recommended way to sign in.

vinted-seller login-manual --country fr

It opens your own Chrome/Chromium — the one already installed on the machine — on a profile dedicated to this tool, and waits. You sign in. You close the window. That is the whole flow.

/usr/bin/chromium
is now open at https://www.vinted.fr/ using a profile dedicated to this tool:
  /home/you/.vinted-seller-mcp/profile-fr

Sign in yourself. Your password, CAPTCHA, Google login and 2FA stay between you and Vinted -
this program never sees them, and never touches your personal browser profile.

When you are signed in, CLOSE THE BROWSER WINDOW. This command is waiting for that.

Why this exists. On a real machine, the Chromium started by Playwright was shown "Ta session a été bloquée" by Vinted straight away — while ordinary Chromium, same PC, same IP, worked fine. The response is not to disguise the automated browser (this project does not do that, ever) but to take the automation out of the sign-in altogether.

What it does and does not do:

  • Your own browser, launched directly. Playwright is not loaded at all on this path.
  • A dedicated profile, ~/.vinted-seller-mcp/profile-fr/. Your personal Chrome profile is never opened, read, or copied from — your other sessions, history and extensions are untouched.
  • The login is entirely yours. Nothing here asks for, types, stores or transmits your password, and nothing touches a CAPTCHA or 2FA.
  • No anti-bot technique of any kind. The only flags passed are the profile directory and two that suppress first-run noise.

Pick the browser yourself if auto-detection guesses wrong:

VINTED_CHROME_PATH=/usr/bin/google-chrome vinted-seller login-manual --country fr

Check the profile at any time — purely local, no browser, no Vinted request:

vinted-seller profile --country fr
Country: fr
Profile path: /home/you/.vinted-seller-mcp/profile-fr
Profile exists: yes
Initialised: yes

The profile remembers its browser

A Chromium profile is not portable between browser builds. Two things break:

  • Chromium records Last Version and refuses to open a profile written by a newer build.
  • On Linux, cookies are encrypted with a key from whichever password store that build picked (gnome-libsecret, kwallet, or the "basic" fallback).

So a profile created by the system's Chrome 151 is unreadable to Playwright's bundled Chromium 141 — which is exactly how a perfectly good session came back as expired on the first field test.

login-manual therefore records the browser it used, in profile-fr.browser.json next to the profile, and the session layer reopens the profile with that same binary. No environment variable needed anywhere — including in the MCP server Hermes spawns.

status shows which browser it picked:

Country: fr
Profile: OK
Session: authenticated
Browser: /opt/google/chrome/google-chrome (profile metadata (Google Chrome 151.0.7922.137))

Resolution order: recorded browser → VINTED_CHROME_PATH → detected system browser → Playwright's bundled Chromium.

The older Playwright login

vinted-seller login --country fr

Still available, and still never handles your password. But it drives Playwright's Chromium, which Vinted's anti-automation has been observed to block immediately. If you see "Ta session a été bloquée", switch to login-manual. This mode is kept until field testing settles which one works.


Checking the session

vinted-seller status --country fr

Unlike profile, this one does open the stored profile with Playwright and ask Vinted who you are.

Your session lives in a Chromium profile at ~/.vinted-seller-mcp/profile-fr/, and the browser keeps Vinted's own tokens fresh from there.

Country: fr
Profile: OK
Session: authenticated
Username: yourname
User ID: 123456

If it lapses:

Country: fr
Profile: OK
Session: expired
Session expired. Reauthentication required: `vinted-seller login --country fr`.

Hermes

Two independent layers, so Hermes never depends on anything proprietary:

1. The MCP server — the actual tools, over stdio:

hermes mcp add vinted-seller --command node --args /ABSOLUTE/PATH/mcpvin/dist/mcp/stdio.js
hermes mcp list
hermes mcp test vinted-seller

2. The skill — integrations/hermes/vinted-seller/SKILL.md, which teaches Hermes how to use those tools well: identify honestly, price with evidence, never publish without asking.

./scripts/install-hermes.sh does both. It is idempotent — re-run it after every git pull, or use ./scripts/update-hermes.sh, which pulls, rebuilds and re-registers in one step. ./scripts/uninstall-hermes.sh removes both (add --purge-data to delete your session and drafts too).

If the hermes CLI is not on your PATH, the script builds everything anyway and prints the exact registration command and JSON config to use by hand.

On another machine

git clone https://github.com/rachid598/mcpvin.git
cd mcpvin
./scripts/install-hermes.sh
vinted-seller login --country fr

Then ask Hermes: "Here are photos of a jacket — prepare me a Vinted listing."


The tools

27 tools, each with a Zod schema, documented parameters and structured errors.

Session whoami · session_status
Research search_items · get_item · search_similar_items · get_similar_items · estimate_price
Taxonomy find_category · find_brand
Drafting prepare_listing · get_listing_draft · list_drafts · update_listing_draft · add_draft_photos · delete_draft
Publishing validate_listing · publish_listing
Shop my_listings · seller_stats · edit_listing · update_price · deactivate_listing · reactivate_listing · delete_listing
Messages conversations · read_messages · send_message

vinted-seller tools lists them. npm run mcp:inspect runs a real MCP handshake and prints what a client discovers.

The three that need permission

publish_listing, delete_listing and send_message all require confirm: true and are annotated destructiveHint. Without it they fail with CONFIRMATION_REQUIRED and a hint telling the assistant to ask you first. This is enforced in the tool schema, not just the prompt — a model that has drifted still cannot publish by accident.

validate_listing fills Vinted's real form completely and never clicks publish. That is the safe end-to-end check.


The CLI

vinted-seller login-manual --country fr      # RECOMMENDED: your own browser, no Playwright
vinted-seller profile  --country fr          # where the profile is; local only
vinted-seller login    --country fr          # Playwright Chromium (may be blocked by Vinted)
vinted-seller status   --country fr          # is the session alive?
vinted-seller whoami   --country fr

vinted-seller search   "nike air max 90"     # catalogue search
vinted-seller similar  --brand Nike --model "Air Max 90" --size 42
vinted-seller price    --brand Nike --model "Air Max 90" --condition very_good
vinted-seller category "baskets homme"
vinted-seller brand    "Nike"

vinted-seller prepare  --brand Nike --model "Air Max 90" --size 42 \
                       --color noir --condition very_good \
                       --defect "usure sur le talon droit" \
                       --photo ./1.jpg --photo ./2.jpg --photo ./3.jpg
vinted-seller drafts                          # list local drafts
vinted-seller draft    <draftId>
vinted-seller validate <draftId> --screenshot # fill the form, stop before publishing

vinted-seller mcp                             # MCP server on stdio
vinted-seller serve-http --port 8787          # MCP over Streamable HTTP

--json on any command for machine-readable output.


Pricing

estimate_price returns the full distribution, three price points, and a confidence level:

{
  "count": 12, "currency": "EUR",
  "minimum": 45, "lowerPercentile": 55, "median": 62,
  "average": 63.5, "upperPercentile": 70, "maximum": 85,
  "quickSalePrice": 53, "recommendedPrice": 62, "optimisticPrice": 72,
  "confidence": "high",
  "rationale": "Based on 12 comparable listings on Vinted. 2 outliers (1 EUR, 900 EUR) were excluded."
}

It refuses to look precise when it isn't. Below three usable comparables the confidence is insufficient_data and the rationale says so — a median of two listings is not a market price.

Comparables are found by trying several phrasings (Vinted's search is literal), then filtering out duplicates, accessories, broken/for-parts listings, mismatched storage capacities and wrong sizes. Every exclusion comes back in rejected with its reason, so a price can be defended.


Drafts

~/.vinted-seller-mcp/
├── profile-fr/              # your Chromium profile — never leaves this machine
└── drafts/
    └── 01M05Z76YAAF8G9F64RHQ07M1D/
        ├── listing.json     # fields, comparables, estimate, validation, history
        └── photos/
            ├── 01.jpg
            ├── 02.jpg
            └── 03.jpg

Statuses: draft → validated → published, or failed.

Photos are copied into the draft — your originals are never moved, modified or deleted. Plain files, so you can read, diff, back up or hand-edit any of it.


Configuration

Everything is optional; see .env.example. There are no credentials in it.

Variable Default
VINTED_SELLER_HOME ~/.vinted-seller-mcp profiles, drafts, photos
VINTED_COUNTRY fr default storefront
VINTED_RATE_LIMIT_PER_SEC 2 per country
VINTED_CACHE_TTL_MS 60000 GET cache; 0 disables
VINTED_HEADLESS true login always shows a window
VINTED_LOG_LEVEL info JSON logs on stderr
VINTED_MCP_TOKEN — bearer token for serve-http

23 storefronts: fr be de at es it nl pt lu ie fi gr sk lt hr uk pl cz hu ro se dk us.


First real test on the Hermes PC

The first run against real Vinted is the one that matters. Do the steps in order and stop at the first one that fails — each depends on the previous.

  1. install the Hermes MCP integration
  2. login-manual — sign in with your own browser
  3. close Chromium once signed in
  4. status — does the saved profile still yield a session?
  5. whoami
  6. search
  7. comparables and pricing
  8. validate --headed only if 4-7 worked
  9. never publish during testing

Step 4 is the real unknown: it is the first time Playwright reopens the profile you created by hand. If Vinted blocks that, stop and say so — do not work around it.

# 1. install
git clone https://github.com/rachid598/mcpvin.git
cd mcpvin
npm install
npx playwright install chromium
npm run build

# 2. sign in with YOUR OWN browser - it opens, you log in, you close it
node dist/cli/index.js login-manual --country fr

# 3. confirm the session took (this one uses Playwright on the saved profile)
node dist/cli/index.js status --country fr

Expected:

Country: fr
Profile: OK
Session: authenticated
Username: yourname
User ID: 123456
# 4. check the catalogue is reachable
node dist/cli/index.js search "nike air max 90" --country fr --limit 5

# 5. build a draft from real market data, with real photos
node dist/cli/index.js prepare \
  --brand Nike --model "Air Max 90" --size 42 --color Noir \
  --condition very_good --category baskets \
  --photo ./photo1.jpg --photo ./photo2.jpg --photo ./photo3.jpg \
  --country fr

# 6. fill the REAL form with a visible browser - this does NOT publish
node dist/cli/index.js validate <draft-id> --headed --screenshot

Step 6 fills Vinted's actual new-listing form and stops. Watch the window: photos upload, the category dropdown opens, the brand/size/condition/colour block appears, the price goes in. Then read the report:

valid: true
ready_to_publish: true
publish button: enabled
photos uploaded: 3/3
filled: photos (3), title, description, category (Baskets), brand (Nike), size (42), …
missing: -
The form was NOT submitted.

Do not click Upload yourself, and do not run publish_listing. Close the window; nothing was created.

If a field failed, the report names it, the selectors that were tried, and the page URL:

VINTED_DEBUG_BROWSER=1 node dist/cli/index.js validate <draft-id> --headed

adds a screenshot and a structural dump of the page's interactive elements under ~/.vinted-seller-mcp/debug/ — enough to fix src/browser/selectors.ts. It never writes page HTML, cookies or tokens.


Known environment gotchas (Linux)

Snap Chromium cannot write to dot-directories. It is confined and refuses ~/.vinted-seller-mcp/, so login-manual opens no window at all. Use the .deb Chrome or Chromium instead, or point VINTED_CHROME_PATH at one.

Playwright's bundled Chromium fails to start on Ubuntu 24.04+ with FATAL: No usable sandbox! — kernel.apparmor_restrict_unprivileged_userns=1 blocks the user-namespace sandbox, and the bundled chrome-sandbox is not setuid. The system Chrome .deb has a setuid chrome-sandbox and an AppArmor profile, so it works. Since the profile now records the system browser, this resolves itself after login-manual. The launch error says all of this if you hit it.

VINTED_NO_SANDBOX=1 also works but genuinely reduces isolation — prefer the system browser.


If Playwright stays blocked

Vinted may refuse the automated browser even after a manual sign-in. That would take away the browser half of this tool — not the useful half.

Everything below runs over the HTTP client and the local draft store, and does not need Playwright to drive a form:

Still works Tool
Comparable search search_items, search_similar_items, get_item
Price estimation estimate_price
Category and brand ids find_category, find_brand
Title and description prepare_listing
Drafts and photos prepare_listing, update_listing_draft, add_draft_photos, get_listing_draft

So the assistant can still do the work that takes you the longest — research the market, justify a price, write the listing, organise the photos — and hand you a finished draft:

vinted-seller draft <draft-id>              # the full record
vinted-seller draft <draft-id> --json       # machine-readable

The draft directory holds the copied photos ready to upload:

~/.vinted-seller-mcp/drafts/<draft-id>/
├── listing.json
└── photos/01.jpg 02.jpg 03.jpg

You then create the listing in Vinted yourself, copying title, description, price, category, brand, size, condition and colour across, and picking the photos from photos/. Only the final form-filling is manual.

Only validate_listing, publish_listing, edit_listing's browser fallback and status/whoami need Playwright. If browser automation turns out to be incompatible with Vinted's protections, that is a real answer and the architecture will be adapted — not worked around.


Development

npm run lint
npm run typecheck
npm run build
npm test          # 124 tests
npm run check     # all four

The browser suite drives the real Playwright code against a local replica of Vinted's listing form (tests/fixtures/listing-form.html) that reproduces what breaks naive automation: a hidden file input, custom dropdowns with a search box, and the brand/size/condition/colour block that only mounts after a category is chosen. The MCP suite speaks the wire protocol to the built server exactly as Hermes does.

Tests never touch the real Vinted. Live tests are opt-in with VINTED_LIVE_TESTS=1 and never publish, delete or send a message.

See docs/DECISIONS.md for why the project is shaped this way, docs/REFERENCES.md for what was learned from prior work and under which licences, and WORKLOG.md for current state and known limitations.


Security and good behaviour

  • Your Vinted password is never requested, stored or handled. Sign-in is manual, in a window you control.
  • CAPTCHAs and 2FA are never circumvented.
  • Cookies, tokens and session material are redacted from logs and never returned by a tool.
  • Nothing is published, deleted or sent to another person without an explicit confirmation for that specific action.
  • Requests are rate-limited to 2/second per storefront by default.
  • .gitignore excludes browser profiles, drafts, photos, .env and databases.

This project drives the same endpoints and the same web form your own browser uses, on your own account. Vinted publishes no public API; using this is subject to their Terms of Service and that call is yours.


Publishing this to its own repository

This tree currently lives on a branch of another repository. To give it the standalone vinted-seller-mcp repo it is named for, from a machine with an authenticated gh:

gh auth status                       # confirm you are signed in
gh repo create vinted-seller-mcp --private --source=. --remote=vinted --push

Or without gh: create an empty private repository on GitHub, then

git remote add vinted git@github.com:rachid598/vinted-seller-mcp.git
git push -u vinted HEAD:main

Nothing sensitive is tracked — see Security — so the history is safe to move as is.


Licence

MIT.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured