FineRx MCP server
This server lets an AI assistant give US pharmacy chain prices observed with the FineRx discount card, hand over the card, and navigate foreign-to-US medicine equivalents.
compare_prices: card price of a package at each pharmacy chain with observation dates, nearest stores, and the savings card.
search_drugs / get_drug: resolve drug names to slugs, get strengths/forms/pack sizes, pricing stats, and the card.
find_nearby_pharmacies: stores within 30 miles per chain, optionally with dated card prices for a drug.
get_savings_card: show the free card (codes, instructions, sentence, image/link/email options) with optional drug price.
email_savings_card: send the card to an address only after explicit consent (address is masked).
find_us_equivalent / foreign_brands_for_drug: reviewed foreign brand ↔ US drug mapping with vetted guidance.
get_prescription_options: routes to filling a prescription, including with no prescription or a costly brand.
get_dataset_info: coverage, freshness, and attribution/disclaimer terms.
open_price_finder: open an interactive in-card search with dated from-prices and foreign brands.
Renders results as interactive UI on supporting hosts (ChatGPT, MCP Apps) via
ui://finerx/v2/app.html, and ships prompts/resources.
Uses OpenStreetMap-sourced coordinates to return nearby pharmacy locations in the pharmacy lookup tools.
Provides observed FineRx card prices at Walmart pharmacy locations, including per-state price data for Walmart, within the pharmacy price comparison tools.
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., "@FineRx MCP server What's the US equivalent of Nurofen?"
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.
FineRx MCP server
Give an AI assistant what US pharmacy chains were seen charging with the free FineRx discount card — per chain, each price with the date it was observed, near a ZIP code or nationally — the pharmacies nearby, the card itself, and the reviewed map of foreign medicine brands to their US equivalent (pages and card text in 12 languages), through the Model Context Protocol.
Source: github.com/andrewfinerx/finerx-mcp (public mirror of this package, MIT; issues welcome).
finerx-mcp is a thin client over the FineRx public REST API
(/api/public/v1). It has no direct database access and inherits the public
API's authentication and rate limits, so it adds zero extra attack surface.
What changed in 2.1
A search inside the card:
open_price_finder(query?)opens the FineRx search (fullscreen where the host allows it); the card suggests medicines as the person types (app-onlyui_suggest), each with where its card prices start and the date, and foreign brands that matched.A place as text: inside the card the person can type a ZIP, a city or a street address. Only the app-only
ui_prices/ui_nearbytake it (where); it goes once into the body of the API request that places it and is never logged, stored or echoed — the answer names only the ZIP area / city.find_us_equivalentandget_prescription_optionsare drawn as views too (equivalent,rx); every 2.0 field of their results is still there. A foreign brand picked in the search opens the same view (app-onlyui_equivalent). The three new views use their own template uri,ui://finerx/v2.1/app.html, so a host holding the 2.0 HTML never shows them as plain text; the 2.0 tools keepui://finerx/v2/app.html.email_savings_cardsends a per-person key (an HMAC of the host's anonymous user id), so the API caps card emails per person instead of per server.UI strings for the new views in all 12 languages.
Related MCP server: Biomedical APIs MCP Server
What changed in 2.2
nextin every answer — up to three calls that fit after it, with the arguments already filled in from the answer (the resolved slug, the priced package, a ZIP the person gave);asknames the one thing to get from the person first. An assistant takes the next call from there instead of composing it.compare_basket(drugs, zip?)— several medicines at once: one row per pharmacy chain with the sum of its card prices, only where every priced medicine was seen there. A sum adds observed prices; it is not a quote.compare_tooncompare_prices— an amount the person named (a copay, what they pay now, a price they want to hear about): the answer counts the chains seen below it. A card price replaces insurance for that fill.get_transfer_steps(chain?, drug?, zip?)— how to move a prescription to the chain the person picked: fixed steps, the chain's dated card price, its nearest stores.find_us_equivalents(brands, country?)— the list a person brought from another country, one reviewed entry per medicine.get_drugdraws a card (strengths × pack sizes with dated "from" prices), and the card view carries a QR of the card page for a person at a computer.New views use the template
ui://finerx/v2.2/app.html.
What changed in 2.0
Prices are card prices by pharmacy chain with their observation date (Walmart per state), from one call per question; no other savings program's prices are read or named.
compare_pricestakes a drug name instead of an NDC (ndc+quantitystill work), and a ZIP is optional.The card travels with every answer: each result carries a
cardblock (codes, the dated price with the card, the card sentence, the small print) and ends its text with them.One UI bundle,
ui://finerx/v2/app.html, draws prices, nearby pharmacies and the card; inside it the person changes dose, quantity or ZIP through two app-only tools the model does not see.fromPrice→fromCardPrice,packageCountis gone,get_savings_cardreturns thefinerx.view/2envelope.
Tools
Tool | What it does |
| Card price of one package at each pharmacy chain, with dates; nearest store of each chain; chains priced but without a store nearby; the card. Place: |
| Stores within 30 miles per chain family; with |
| The free card: codes, how to use it, what to say at the counter, links — plus a PNG for hosts that draw no UI |
| Email the card to an address the person gave, after they said yes |
| Name → slug, with |
| Strengths × forms × pack sizes that have a card price, the default package and its prices by chain |
| A medicine from another country → what it is in the US, the vetted sentence to say, its card price |
| What a US drug is called abroad (the reverse lookup) |
| What to do with no prescription yet, and where the drug's card prices start (for controlled / age-restricted medicines: card prices and the card only) |
| Opens the search inside the card: matches for |
| Two to six medicines at once: one row per chain with each card price and — only where every priced medicine was seen there — their sum with the dates |
| How to move a prescription to the chain the person picked: fixed steps, that chain's dated card price, its nearest stores (controlled medicines: the card only) |
| Two to six medicines from another country → one reviewed entry each; a US product only for the same active ingredient |
| Card-price coverage: chain families, banners, newest observation, stores on the map |
| App-only ( |
Every price carries its observedAt date and is never scaled to another pack
size. The server also ships instructions (the card rule: answer with each
price and its date, then the card sentence and codes, then the small print; no
promises of a price or a saving; find_us_equivalent first for a medicine from
another country; no medical advice), the resources finerx://card,
finerx://how-it-works and the UI bundle, and three prompts,
price_and_card(drug), prescription_help(drug?) and
us_equivalent(brand, country?).
US equivalents (the thing nobody else does)
An immigrant does not search "atorvastatin" — they search Но-шпа, Nurofen,
Dolo-Neurobion, 999 Ganmaoling. FineRx holds a reviewed corpus mapping those
brands to their US status, and find_us_equivalent is how an assistant reaches
it instead of answering from memory.
find_us_equivalent(brand="Но-шпа")
# → usClass "rx_alternative", inn "drotaverine hydrochloride",
# guidance "No-Spa (drotaverine hydrochloride) is not sold in the US as the same
# product; the closest US options need a prescription. Ask a doctor or
# pharmacist which one fits."
# otherMatches [{"brand": "No-Spa", "countries": ["Poland"], ...}]
find_us_equivalent(brand="Nurofen", country="Turkey")
# → usClass "same_inn", usGeneric "ibuprofen", usBrands ["Advil", "Motrin"],
# usDrug {slug, fromCardPrice {amount, package, observedAt}}, the card, and the guidance sentence
# that names ibuprofen as the thing to ask the pharmacist for.Three classes, and the difference between them is the whole honesty of the
feature: same_inn means the same active ingredient is sold here (not the
same product — strength, form and excipients differ, which is what
guidanceDisclaimer says); rx_alternative means it is not sold here and
the closest US options need a prescription, so the answer is "ask a clinician",
never a named swap; no_equivalent means nothing here matches, and the
components breakdown states each ingredient's own US status rather than
inventing a substitute.
guidance is written and reviewed server-side. Quote it; do not paraphrase,
translate or extend it — a sentence composed by the model is a medical claim
nobody reviewed. foreign_brands_for_drug(slug) is the reverse ("what is
lisinopril called in Mexico?"), and search_drugs carries a foreignBrands
list so a client that only calls search still finds the corpus.
Handing over the card
The card is free, is not insurance, needs no signup, and is credited at the pharmacy counter by its group code — so an assistant can hand it over completely, with no click-through.
Every result (except
get_dataset_info) carriesstructuredContent.card—codes,priceWithCard(amount, chain,observedAt) or null,law(the approved card sentence in the person's language),fine(the small print),chainsCount,siteUrl,printUrl,actions— and its text ends with the codes, the sentence and the small print. If the API is unreachable, the codes still come back (last good answer, then built-in constants).get_savings_cardadds the steps, the sentence to say to the pharmacist and the links; hosts that draw no UI also get a PNG of the card (never ChatGPT, where an image counts against the Free plan's image quota).email_savings_cardsends one card-only message. It refuses withoutconsent=trueand never calls the API in that case. The result masks the address (j***@example.com); FineRx does not store it.Every URL carries
src=<channel>(chatgptormcpby default), so the surface that sent someone is visible in FineRx's own analytics — nothing about the person is.
Renders as an app
On a host that supports UI components — ChatGPT (Apps SDK) and any host that
implements the standard MCP Apps extension (Claude) — compare_prices,
find_nearby_pharmacies, get_savings_card, open_price_finder,
find_us_equivalent and get_prescription_options are drawn by one bundle,
ui://finerx/v2/app.html (mime text/html;profile=mcp-app), picked by
structuredContent.view of the finerx.view/2 envelope. Tool _meta names it
under both ui.resourceUri and openai/outputTemplate; the resource _meta
carries ui.domain / openai/widgetDomain, an empty CSP and finerx/build.
What the person picks inside the card (a medicine, dose, pack size, ZIP or
city — never an address) is sent to the model as context by the bundle
(ui/update-model-context). UI strings arrive in _meta["finerx/labels"] in
the person's language. The
bundle is built from widget-src/ (Vite + Preact, single file, no external
resources) — see widget-src/README.md.
Prerequisites
A FineRx developer API key (format
frx_live_...). Request one by emailing partners@finerxfinder.com — see https://finerxfinder.com/developers.uvinstalled (providesuvx).
Configuration
The server reads these environment variables:
Variable | Required | Default | Notes |
| yes | — | Your |
| no |
| Point at another host for local testing |
| no |
| PNG |
| no |
|
|
| no |
| Bind address for the HTTP transport |
| no |
| HTTP path of the endpoint |
| no |
|
|
| no | random per process | HMAC salt for the per-person rate limit (ChatGPT |
| no |
| Keep off: other programs' prices are not served in 2.0 |
Install & run
Run directly with uvx (no manual install needed):
FINERX_API_KEY=frx_live_xxxxxxxx uvx finerx-mcpThe server speaks MCP over stdio by default.
Remote (HTTP) mode
The same tools can be served over Streamable HTTP — the transport that connector catalogs (ChatGPT Apps, Claude connectors, Gemini, Grok) consume, so a user adds FineRx by URL with no local install. Set the transport (and, for a hosted deployment, the bind host/port):
FINERX_API_KEY=frx_live_xxxx \
FINERX_MCP_TRANSPORT=streamable-http \
FINERX_MCP_HOST=0.0.0.0 FINERX_MCP_PORT=9000 \
uvx finerx-mcp
# → endpoint at http://<host>:9000/mcpDefaults stay stdio, so existing Claude Desktop/Code/Cursor configs are
unaffected. The HTTP endpoint calls the same public REST API with the server's
FINERX_API_KEY, so hosting it exposes no data beyond the already-public API.
HTTP mode is stateless by default. Keyless one-shot connector calls — a
catalog probe, a single tool call from a chat — open a Streamable-HTTP session
and never DELETE it, so in session mode the transport map only grows (measured
on the hosted endpoint: 3568 sessions over 7 days, ~25 MB of swap a day, until
the container restarts). Stateless mode builds a transport per request and drops
it. It is safe here because no tool keeps per-session state: every call is a
fresh request against the public API, and the card-image cache is process-level,
not per-session. Set FINERX_MCP_STATELESS=0 if you need SSE resumability;
stdio ignores the setting entirely.
Claude Desktop
Add to your claude_desktop_config.json
(macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"finerx": {
"command": "uvx",
"args": ["finerx-mcp"],
"env": {
"FINERX_API_KEY": "frx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}Restart Claude Desktop; the FineRx tools appear in the tools menu.
Claude Code
claude mcp add finerx --env FINERX_API_KEY=frx_live_xxxx -- uvx finerx-mcpOr add it to .mcp.json in your project:
{
"mcpServers": {
"finerx": {
"command": "uvx",
"args": ["finerx-mcp"],
"env": { "FINERX_API_KEY": "frx_live_xxxxxxxx" }
}
}
}Local development
Run against a local FineRx stack (e.g. the dev proxy on :8080):
export FINERX_API_KEY=frx_live_... # a key you created via the CLI
export FINERX_API_BASE=http://localhost:8080/api/public/v1
uv run --project packages/finerx-mcp finerx-mcpA smoke test that drives one tool call end-to-end lives at
scripts/smoke_mcp.py in the FineRx repo.
Run the unit tests (the API is mocked with respx — no key, no network):
cd packages/finerx-mcp && uv run --extra dev pytest -qTerms
Data is provided under the FineRx public API terms: attribution required ("Prices via FineRx"), prices are observed estimates with their dates and may have changed — the pharmacy sets the final price — the card is not insurance, and nothing here is medical advice. Pharmacy location coordinates are © OpenStreetMap contributors (ODbL); place names (the city and state of a ZIP code) come from GeoNames (CC BY 4.0).
Available Tools
10 toolscompare_pricesARead-onlyIdempotent
Compare FineRx prices for one package (an 11-digit NDC + quantity).
Use when you know the exact package. Returns the offer matrix — for each
pharmacy chain x savings program, the observed price and the date it was
observed (observedAt) — plus savingsCard (the free discount card, its
image, and the card price for this package when known). The cheapest offer is
flagged isLowest. Get an ndc from get_drug's packages or the search flow.
Do not call a price the lowest unless the offer says isLowest, and always give
the observation date. Offer the card whenever you quote a price here.
Inputs: ndc (required), quantity (required), locale, channel. Returns: object
email_savings_cardA
Email the free FineRx discount card to an address the person gave you.
Use ONLY after the person has given an email address AND confirmed they want the
card sent there — ask for both first, then set consent to true. The message
contains the card and nothing else; FineRx does not store the address.
Returns {"sent": true, "to": } or {"sent": false, "error": ...}. On an error, say what happened and offer the image or the link instead.
Do not call this without an explicit yes, do not guess or reuse an address, do not retry a refusal, and do not read the full address back to the person.
Inputs: email (required), locale, consent. Returns: object
find_nearby_pharmaciesARead-onlyIdempotent
Find the nearest pharmacy locations to a US ZIP code, per chain.
zip is a 5-digit US ZIP. Optional chains is a comma-separated list of
chain codes (e.g. "walgreens,publix"); default is all chains. limit caps
locations per chain. Coordinates are OpenStreetMap-sourced (ODbL).
Inputs: zip (required), limit, chains. Returns: object
find_us_equivalentARead-onlyIdempotent
Find out what a medicine from another country is in the US, and what it costs.
Use when the person names a medicine that is not a US product — a brand from
home (No-Spa, Nurofen, Dolo-Neurobion, 999 Ganmaoling), a name in another
script (Но-шпа, 泰诺), or an ingredient the US calls something else
(paracetamol) — or asks "what is this called in the US?". Optional country
picks between brands of the same name sold in different places; locale is
a language code (leave it out and the host's own locale is used).
Returns the reviewed entry: usClass (same_inn = the same active
ingredient is sold here, rx_alternative = it is not and the closest US
options need a prescription, no_equivalent = nothing here matches),
guidance (ONE vetted sentence, written to be quoted word for word),
inn, usGeneric, usBrands, rxStatus, notes, components,
usDrug (slug, lowest observed price and the date it was observed),
savingsCard when there is a US drug to fill, pageUrl, and
otherMatches when the same brand name exists in more than one country.
found is false when nothing matched.
Next: quote guidance as written, then say guidanceDisclaimer. With a
usDrug, call compare_prices (or get_drug) for its slug, give the price
WITH its observation date, and offer the free card.
Do not call the US drug the same product, do not suggest swapping one for the
other, and for rx_alternative or no_equivalent do not name a
substitute at all — send the person to a pharmacist or a doctor. Do not
translate or reword guidance; it is reviewed text.
Inputs: brand (required), locale, channel, country. Returns: object
foreign_brands_for_drugARead-onlyIdempotent
Find what a US drug is called abroad — the reverse of find_us_equivalent.
Use when the person has the US name and wants the home-country one ("what is
lisinopril called in Mexico?"), or when naming a foreign brand would let them
recognise the medicine you are describing. slug comes from search_drugs.
Returns the reviewed brands that resolve to this drug: brand,
brandScript (the home-country spelling), countries, inn and
usClass.
Next: name the brands from the person's own country first. An rx_alternative
entry is a DIFFERENT medicine US clinicians use for the same complaint — say
so if you mention it. Brands with no US equivalent never appear here.
Do not present any of these as interchangeable with the US drug, and do not tell anyone to buy or import one.
Inputs: slug (required). Returns: object
get_dataset_infoARead-onlyIdempotent
Get FineRx dataset coverage, freshness, and the attribution/disclaimer terms.
Returns dataset counts (drugs, products, prices, chains, savings programs, pharmacy locations), the latest observation date, and the terms every consumer must honor (attribution + disclaimer).
Inputs: no named parameters. Returns: object
get_drugARead-onlyIdempotent
Get a FineRx drug's details: coverage stats, variants, and the savings card.
Use when you have a slug from search_drugs. Returns overall stats (product/
package/chain/vendor counts, fromPrice, latest observation date), the list of
variants (strength x form) each with a fromPrice, and savingsCard — the
free discount card with its codes, image, and (when known) the card price with
its observedAt date. locale is "en" or "es"; channel tags the links so
we can see which assistant sent someone. Do not quote a price without its
observation date, and offer the card whenever you quote one.
Inputs: slug (required), locale, channel. Returns: object
get_prescription_optionsARead-onlyIdempotent
Explain the routes to filling a prescription, including having none yet.
Use when the person says they have no prescription, asks how to get one, or says
the brand costs too much. Optional drug is a slug from search_drugs.
Returns three sections — havePrescription (steps), noPrescription (named telehealth / clinic options, each with the disclosure to read out when FineRx earns anything), brandCostly (options) — plus a Medicaid note.
Read each option WITH its disclosure. Do not recommend a particular clinician, do not say what to take or at what dose, and do not frame any of this as medical advice.
Inputs: drug, locale. Returns: object
get_savings_cardARead-onlyIdempotent
Get the free FineRx discount card: its codes, how to use it, how to save it.
Use when you have just quoted a FineRx price, when someone asks how to pay less
for a prescription, or when they ask for the card by name. Optional drug is
a slug from search_drugs and adds that drug's card price when we have one;
locale is a language code (leave it out and the host's own locale is used,
else English); channel tags the links with who sent the person.
Returns the card as JSON (the three codes to read at the pharmacy counter, what the card is and is not, the steps to use it, the sentence to say to the pharmacist, the ways to save it — image, print, link, email — an optional observed price with its date, an FAQ, the legal lines, and the UI labels) plus an image of the card. On a host that supports MCP Apps the same data is drawn as an interactive card in the conversation; that is a convenience, not a substitute, so STILL write the three codes in your own answer for the hosts and the people who cannot see it.
Offer ONE way to save it: show the image, send the link, or email it. Do not present the card as insurance, do not promise a price, and do not call a price the lowest unless price.isLowest is true — otherwise repeat price.note as written. The card is free and needs no signup.
Inputs: drug, locale, channel.
search_drugsARead-onlyIdempotent
Search FineRx for drugs by name or localized alias.
Returns candidate drugs with their slug (use it with get_drug), kind (generic/brand), and lowest observed public price (fromPrice). Use this first to resolve a drug name to a slug.
foreignBrands carries any home-country medicine brand that matched the
same text (Нурофен, No-Spa, Dolo-Neurobion) with its active ingredient and
the US generic it maps to. When results is empty and foreignBrands is
not, the person named a medicine from another country: call
find_us_equivalent for the reviewed answer rather than guessing at one.
Inputs: query (required), limit. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 10 tools
Most tools have clearly distinct purposes: search/get/compare for drugs, find_us_equivalent vs foreign_brands_for_drug are cleanly separated by direction, and get_savings_card vs email_savings_card are distinct (retrieve vs send). The only mild overlap is get_drug vs compare_prices (both return prices), but their inputs and outputs differ enough (slug vs NDC) that an agent can choose correctly.
The set mostly follows a verb_noun pattern: search_drugs, get_drug, compare_prices, find_nearby_pharmacies, get_dataset_info, get_savings_card, email_savings_card, get_prescription_options, find_us_equivalent, foreign_brands_for_drug. Minor deviations: find_us_equivalent and foreign_brands_for_drug use a different verb style (find/foreign_brands_for) than the get_/search_/compare_/email_ pattern, but they are still readable and predictable.
10 tools is well within the ideal 3-15 range and each tool covers a distinct aspect of the domain: search, details, price comparison, pharmacy locations, dataset info, savings card, email, prescription options, and international equivalence (both directions). No tool feels redundant or extraneous.
The domain is consumer prescription savings, and the surface covers the full journey: resolving a drug name (search_drugs), getting details (get_drug), comparing prices (compare_prices), finding pharmacies (find_nearby_pharmacies), getting/sending the savings card (get_savings_card, email_savings_card), handling no-prescription or brand-costly situations (get_prescription_options), and international drug mapping in both directions (find_us_equivalent, foreign_brands_for_drug). Dataset info and attribution are also covered. No obvious dead ends.
Maintenance
Related MCP Connectors
Live US drug acquisition costs (CMS NADAC) for AI assistants. Free, no auth, weekly data.
US Rx prices by pharmacy near you + free discount card; foreign-brand equivalents; 12 languages
Pay-per-use tool marketplace for AI agents. Search, price-check, and call APIs via MCP.
Live Rx cash prices at nearby pharmacies in 8 US states + the free LowerScript card. Not insurance.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables AI assistants to search for real-time prescription drug prices, find local pharmacies, and retrieve discount coupons through the GoodRx service. It uses Playwright browser automation to provide drug comparisons and checkout details like BIN and PCN codes.513 npmMIT
- FlicenseBqualityDmaintenanceEnables AI agents to query free biomedical and pharmaceutical APIs for clinical trials, drug data, molecular structures, adverse events, and research literature.14-
- AlicenseNot gradedqualityCmaintenanceUnifies five public US-government drug datasets into a single sourced page per GLP-1/cardiometabolic drug, providing cost, shortage, label, and alternatives. The MCP server exposes the same Drug contract as the UI for agent-native access.71,212,500 npmApache 2.0
- AlicenseNot gradedqualityAmaintenanceEnables AI assistants to query authoritative sources like FDA, WHO, PubMed, RxNorm, and Google Scholar for drug information, health statistics, clinical guidelines, trials, and medical literature. It runs locally with no API keys, so data stays private while results come back with evidence grading and resilient fallbacks.83 npmISC