Inspiration
Most people already know where they will be this week. It is on the calendar: a client meeting on campus, lunch on Fourth Avenue, a pickup on Speedway. The coffee shop two blocks from that 9:30 meeting has no idea, and neither does the taco place next to dinner.
Local deals live in apps people forget to open and in ad products small shops cannot afford. The shopper does the searching, and the shop pays to be found by people who were never coming its way. I wanted to turn that around: start from where someone is already going, and let an agent do the looking.
Who it's for and why it matters
Two people. The shopper with a full week, who would take a good offer next to a place they are already going but will never go looking for one. And the owner of a small local shop, who cannot afford an ad product and gets no benefit from being found by people who were never coming their way. Nearby puts the shop in front of the one person who will be two blocks away at the right hour, and it costs the shop a minute to post the offer.
It also answers a question every payments company now has to answer: how do you let an AI agent take part in a purchase without letting it spend? Nearby's answer is structural. The agent can find, choose and explain. Only a person's tap can pay.
What it does
Nearby reads the week from your calendar, looks at where each appointment puts you, and brings back one or two local offers that fit each stop. Each pick comes with a one-sentence reason. Say yes once and it checks out through PayPal. Nothing is ever bought without that yes.
- Shopper. Open the week and watch the agent work on each stop, live. Open a stop and see it on a map with the places around it and up to two picks. Tap Yes, approve on PayPal's sandbox page, and come back to a receipt with a redemption code. A pick you passed on can be brought back, and a bought pick shows its code again.
- Merchant. Post an offer in about a minute: what it is, the price, the days and hours it is valid. View, edit or remove it afterwards. When a shopper buys, the sale appears on the merchant's dashboard within a few seconds.
- Any other agent. Nearby's offers are open to any AI assistant that speaks MCP. Ask your own assistant what is near your next stop; it finds the offer on Nearby and hands you a link. You review the pick in Nearby and it is still one yes on PayPal. The assistant cannot buy.
The demo needs no account. One tap puts you in the shopper's seat or behind a sample merchant's counter, and you can switch at any time. Every merchant, offer and appointment is made up and labelled as sample data.
How I built it
The agent is Claude. It runs on the server through the Anthropic Messages API with tool use, and it has four tools: list_upcoming_appointments, find_places_near, find_offer_candidates and propose_offer. For each appointment it chooses at most two of the matched offers and writes the reason for each.
The calendar comes in over MCP. The repository includes a small read-only MCP server, nearby-calendar-mcp, with one tool. Claude reads the week through it the way any MCP client would. It uses only each event's time and location.
You can watch it work. My week shows each stop's run as it happens: the real tool calls, in order, then the number of picks. Nothing is animated for effect; a step appears only when that call was made.
Matching is code, not the model. Plain code decides which offers are eligible: within a mile of the appointment, valid on that weekday, and within a window around the appointment's time, so a lunch deal is never offered beside a 9 AM meeting. Each eligible offer gets a score from distance and time fit. Claude chooses among those and explains; it cannot introduce an offer the code did not match. Its answer is checked in code before anything is shown.
PayPal handles the payment, and Claude cannot reach it. Nearby uses the PayPal Orders v2 REST API from the server: create the order, send the shopper to PayPal to approve, capture on return. Claude has no payment tool. Tapping Yes mints a single-use confirmation token, stored only as a hash and valid for ten minutes, and only the purchase route can create a PayPal order, and only with that token. The amount is the offer's price read from the database, never a number from the browser or the model. The app refuses to start unless it is pointed at the PayPal sandbox.
Open to other agents, with the same rule. The app serves a second, public MCP server at /mcp with two read-only tools, find_offers_near and get_offer. They run Nearby's own matching code and no model. Each offer comes back with a checkout link. A person opens it, taps "Review in Nearby", and lands on that stop with the offer as a pick marked "Suggested by your assistant"; from there it is the same single yes and the same PayPal checkout. Nothing the outside assistant writes is shown as Nearby's words, and it never holds a way to pay. A shop posts an offer once, and any agent can bring it a customer.
The map is Google's. Places API (New) finds what is around each appointment, shown on a Google map with Google's attribution.
Stack. TypeScript and Node with Fastify, React with Vite, Tailwind CSS with shadcn/ui components, PostgreSQL, Vitest, deployed as one container on Railway.
I wrote a full product specification first and built from it with Claude Code.
Challenges I ran into
- Deciding what the agent may decide. An agent that can spend money has to be boxed in by something stronger than a prompt. The answer was to give the model no payment tool at all and to put the purchase behind a token that only a person's tap creates.
- A demo that cannot depend on the model. If Claude is slow, returns something invalid, or is switched off, Nearby still shows picks: the top two by score with a plain templated reason, labelled as matched by distance and time. The same goes for Google: without it the map becomes a simple drawn plot.
- Paying exactly once. A shopper can come back from PayPal twice, press Back, cancel, or try again. Each of those had to end in one capture or none, and a second return shows the same receipt.
- Time. A deal's hours, an appointment's time and the shopper's clock all have to agree. Everything runs on Tucson time, and the sample appointments place themselves on the coming week so the demo never goes stale.
Accomplishments that I'm proud of
- The whole loop works live: Claude picks, the shopper says yes, PayPal's sandbox takes the payment, and the sale reaches the merchant.
- The agent explains itself. Every pick has a reason a person can check against the map.
- The agent cannot spend. That is enforced in the structure of the code, not requested in a prompt.
- Any MCP-capable assistant can sell a Nearby offer, and still cannot spend the shopper's money.
- More than 300 automated tests, none of which calls a live service. PayPal, Anthropic and Google each sit behind one adapter.
- It is usable on a phone and readable: text meets WCAG AA contrast and touch targets are at least 44 pixels.
What I learned
- Agentic commerce is mostly a question of boundaries. The useful work was deciding which decisions belong to the model (choose and explain), which to code (eligibility, price, payment), and which to the person (yes).
- An offer layer is the missing piece. There is no public deals feed for an agent to read, so Nearby carries its own: the merchant dashboard. Because the offers live in Nearby's own store, opening them to other agents over MCP was a small step, and it changed what the product is: not only an app, but a place agents can shop.
- Time matters as much as distance. Most of what makes a pick feel right is the time window, not the radius.
What's next for Nearby
- A Google Calendar back end for the MCP server, so a shopper connects a real calendar.
- Sign-in for merchants. In the demo an offer you post stays in your own browser; a real shop's offer should reach every shopper and every agent.
- Payments to each merchant directly, and refunds.
- A look at PayPal's Agent Toolkit for the checkout step.
Built With
- claude
- docker
- fastify
- google-maps
- mcp
- node.js
- paypal
- postgresql
- railway
- react
- tailwindcss
- typescript
- vite