Inspiration

"Where's the nearest restroom?" is the wrong question. The nearest one is often closed after 6 PM, has no stall a wheelchair fits in, or has nowhere to change a diaper. A parent with a toddler who says "I need to pee, right now" doesn't have time to find that out at the door.

Korea publishes those answers in one national file: opening hours, accessible stalls, changing tables, emergency bells. I run a parking and restroom map app in Korea built on that file, so I know its problems. The hours are free text, written 2,129 different ways in the October 2026 file. And since February 2025 the file has no coordinates at all.

What it does

Gotta Go is a self-hosted MCP server for Alexa+. You say what you need, and it answers with a restroom you can actually use:

"Alexa, I need to pee! Right now!" "The closest one, Bukchon Culture Center, 40 meters southwest, is closed right now (closed since 6 PM). Go to Gyedong Church, 200 meters north. It closes at 10 PM, in 20 minutes, so go now." "Anything that stays open later?" "Gyedong Church closes at 10 PM. The nearest one that stays open later is Wonseo Park, 200 meters southeast, and it's open 24 hours."

  • Open now, in local time. Hours are read into rules: weekdays, weekly closing days, overnight hours, lunch breaks, closed months.
  • Needs, not just distance. Accessible stall, changing table, children's toilet, emergency bell, free entry.
  • Explains the skip. When it passes over a nearer place, it names it and says why. That one sentence is why you can trust the rest.
  • Closing soon is a risk. A place that closes before you could walk there doesn't count as open, and "closes in 20 minutes" is said out loud.
  • Never guesses. If the record is silent or contradicts itself, the answer is "the record doesn't confirm it", never "yes".
  • A conversation, not one lookup. Say "my mom uses a wheelchair" once and every later search includes it. "Take me to the second one", "anything that stays open later?" and "is there a changing table there?" all refer to the place you were just talking about. The server keeps this per MCP session, so a new session starts clean.
  • Speakable names. Korean names become something an English voice can say, for example Anguk Station (Line 3), with the original on screen.

It works with 5,491 public restrooms in Seoul (details from the national file, locations from Seoul City's open data) and with 258 toilets in Seattle from OpenStreetMap, through the same server.

How I built it

  • MCP server (Node.js, official @modelcontextprotocol/sdk 1.32.1): Streamable HTTP with sessions, protocol revision 2025-11-25. Six tools. Lookups: find_restroom, get_restroom, data_sources. Conversation: remember_needs, other_option ("the second one", "stays open later"), about_restroom ("is it accessible?"). Each MCP session holds its own conversation (remembered needs, last results, which place "it" is), readable as the gotta-go://conversation resource; nothing is written to disk. Tools carry readOnlyHint/idempotentHint annotations; find_restroom and remember_needs declare an outputSchema and return structuredContent with a ready-to-speak speech field. The server instructions hold the voice rules and say which tool handles which follow-up.
  • Alexa+ simulator (web app): the Alexa+ add-on tools are preview-only, so the page plays the Alexa+ side. It is a real MCP client: about 60 lines of hand-written JSON-RPC over Streamable HTTP (initialize, tools/list, tools/call), and a traffic panel shows every message. Voice in through the browser's speech recognition, voice out through speech synthesis, and a screen card with a small radar of where you are, the place it chose, and the places it skipped.
  • Agent: a tool-calling LLM plays Alexa's part: it picks the tool and the needs. I ran it locally (a local open-weight model, 8-bit, on a MacBook) through an OpenAI-compatible endpoint, so there is no cloud cost. Without a model, a small built-in keyword router makes the same tool calls, so judges can run it on any machine. The demo video uses the built-in keyword router to play Alexa's part; the same six MCP tools are called identically when a tool-calling model (I tested a local open-weight model on the same Mac) is the client. The video says which mode it is in, on screen.
  • Data: details come from the Ministry of the Interior and Safety file (data.go.kr dataset 15012892, downloaded 2026-10-09, 53,585 restrooms). Coordinates were dropped from it in February 2025, so for Seoul I match each row's road or parcel address to Seoul City's own restroom points (data.seoul.go.kr OA-22586, KOGL Type 1): 3,610 of 5,609 rows matched, plus 1,881 places that only Seoul's file has. No geocoding API is used, so no geocoder results are stored. Seattle comes from OpenStreetMap through Overpass.
  • Hours parser: reads 49,284 of the 53,585 rows (92.0%) into rules or "open 24 hours"; 0.6% say "not open to the public". The other 7.4% stay "unknown" with a reason ("only a duration is recorded", "the record says not open to the public but also lists hours"). I released it separately as an open-source package, kr-open-hours.
  • Agent Skill: skills/gotta-go/SKILL.md teaches a skill-aware agent how to use the server.
  • Tests: 27 with node --test: the hours parser, real-data scenarios, the conversation (remembered needs, "the second one", "stays open later", silent records stay silent), and end-to-end runs with the official MCP client over Streamable HTTP, including two sessions that must not share memory. I also checked the server with the MCP Inspector CLI.

Challenges I ran into

  • Hours written 2,129 ways. "09:00~18:00", "9시~18시", "0900-1800", "(평일)09:00~18:00+(주말)09:00~18:00", "화-금(09:00~18:00), 토-일(09:00~17:00), 월 휴관", "09:00~18:00 (12:00~13:00 제외)". The hard part was deciding what not to parse. "11시간" (11 hours) has no start time; "운영시간 내" means "during the building's hours". Both stay unknown.
  • Records that contradict themselves. 247 rows say "not open to the public" and list opening hours anyway (204 of them read "월~금 09시~18시", weekdays 9 to 6). Gotta Go reports those as unknown instead of picking one.
  • No coordinates. They were dropped from the national file in February 2025 (the data.go.kr page for dataset 15012892 says so). A geocoding API would work, but its terms say results can't be stored, and the government's road-address location database is only released after an application and a purpose review. Seoul City publishes its own restroom points under an open license, so I matched by address instead. Matching by name looked tempting but put too many places hundreds of meters off, so I dropped it.
  • "It" in a voice conversation. "Is there a changing table there?" only makes sense if the server knows which place "there" is. Keeping that per MCP session, instead of asking the agent to pass ids back, made follow-ups reliable even with a simple client.
  • Time zones. A server in one zone answering for Seoul and Seattle has to evaluate hours in the place's own zone.
  • Making it speakable. A list of three restrooms is useless by voice. The answer had to be one recommendation plus one reason, under about 40 words.

Accomplishments that I'm proud of

  • The "closer, but no" sentence. It turns a search result into advice.
  • Unknown stays unknown, everywhere, and the tests check it.
  • The same server answers for Seoul and Seattle, with very different data.

What I learned

  • In voice, the reason matters more than the list.
  • Open data has two failure modes: wrong and silent. Silent is more common (only 17 of 258 Seattle toilets in OpenStreetMap list hours), and an assistant has to say so out loud.
  • MCP outputSchema plus a speech field is a clean split: the agent gets structure for the screen and a sentence for the voice.
  • An MCP session maps well to one voice conversation. The server can remember what matters to the person without storing anything after the session ends.

Who it is for

Anyone in a city that publishes restroom data: Seoul alone has 5,491 public restrooms in this build, the national file lists 53,585 across Korea, and the same server already answers for Seattle from OpenStreetMap.

What's next

  • All of Korea (53,585 restrooms) and more cities' open data.
  • Routes that avoid steep slopes and stairs, from my walking-route work in Seongnam.
  • Real walking distance instead of straight-line.
  • Running it on a real Alexa+ device once self-hosted MCP servers can be connected. ```

Built With

Share this project:

Updates

Submission history