Ultra Bridge: Translating JAWS Keyboard Commands to NVDA with Sanity Context A developer built Ultra Bridge, an agent that translates keyboard commands between the JAWS and NVDA screen readers by querying a Sanity dataset through Sanity Context MCP. The system models actions separately from commands so that equivalence questions become a single GROQ query, and it surfaces a per-command verification level (tested-by-author, cross-checked, first-party, third-party) so users know whether to trust an answer. Against the seed data, one query returns six letter keys that exist in both readers but perform different actions, including R (region in JAWS, radio button in NVDA) and Q (main content in JAWS, block quote in NVDA). This is a submission for the Sanity Challenge, Path One: Ship an Agent That Queries Real Content https://dev.to/challenges/sanity-2026-09-16 Ultra Bridge is an agent for screen reader users. You ask it things like "What is the NVDA equivalent of JAWS Insert+F7?" or "Which keys do different jobs in JAWS and NVDA?", and it answers from a Sanity dataset through Sanity Context MCP. Every answer says how well the underlying command is verified, so you know whether to trust it or check it yourself first. I built it because I rely on a screen reader myself, and moving between JAWS and NVDA means relearning keys that look the same but are not. That gap costs real time and real confidence, and it is exactly the kind of problem structured content is good at closing. Ultra Bridge is part of the Ultra suite of accessible software I am building. The reason it needs structured content: a keyword search can find a page that mentions both screen readers, but it cannot tell you which keys collide, which ones translate cleanly, or which ones simply do not exist yet on the other side. In Ultra Bridge an action "show a list of all links" is separate from a command the keys one screen reader uses for that action . Equivalents are found through the shared action, so a relationship question becomes one query instead of a guess. This query lists every single letter key that exists in both screen readers but does a different job: php type=="command" && screenReader- slug.current=="jaws" && context=="browse-mode" { keys, "jawsAction": action- title, "nvdaMeansSomethingElse": type=="command" && screenReader- slug.current=="nvda" && keys==^.keys && action. ref = ^.action. ref {"action": action- title} } count nvdaMeansSomethingElse 0 Against the seed data it returns six keys. R is "region" in JAWS and "radio button" in NVDA. Q is "main content" in JAWS and "block quote" in NVDA. A, O, S and D collide too. That is exactly the kind of thing that costs a person time when they switch, and exactly the kind of question a keyword search cannot answer. https://ultra-bridge-xm71.vercel.app/ https://ultra-bridge-xm71.vercel.app/ The page has example questions you can press. Open "How this was found" under any answer to see the exact GROQ query the agent ran to get there — nothing is hidden between the question and the data. https://github.com/demirajvazi10-max/Ultra-Bridge https://github.com/demirajvazi10-max/Ultra-Bridge Schema. Seven document types: screenReader , source , action , command , concept , difference and guide . The dataset currently has 229 documents: 60 actions, 127 commands, 13 concepts, 12 differences, 7 guides and 8 sources. Trust as data, not as an afterthought. Every command has a verification field tested-by-author , cross-checked , first-party , third-party , recalled and references to source documents. The system prompt tells the agent to say the verification level out loud whenever it uses a command, and to point to input help Insert+1 in JAWS, NVDA+1 in NVDA whenever a command is not yet confirmed. When two sources disagree, the disagreement is written on the command instead of hidden. Right now 11 commands are tested-by-author, 58 cross-checked, 44 first-party and 14 third-party — zero are still sitting at the weakest "recalled" level, because I keep a running checklist of what still needs a hands-on pass on a real screen reader. Sanity Context, two modes. /initial-context over HTTP, puts it in its system prompt, and uses groq query and schema explorer for anything that names a specific command, key or action. sanity context create , imports create , build --watch . The two initial context tools have the same name, so the agent drops both and inlines their content directly into the system prompt instead. If the Knowledge Base endpoint is unavailable, the agent falls back to the structured dataset only rather than failing the whole request. Engineering for free-tier reality. The public demo runs on free model tiers, which cap tokens per minute hard enough that a naive integration falls over under real traffic. So the agent only exposes the two tools it actually needs groq query , knowledge base read , compacts every tool result before it goes back to the model stripping fields the model does not need and hard-capping length , and chains four model providers Google, Groq, Cerebras, Anthropic so a rate limit on one provider fails over to the next instead of failing the request. The public page also rate-limits per visitor. What I learned. match is tokenised text matching, so I use equality == for keys that contain + , or the match would silently split "Insert+F7" into pieces. groq-js . It let me test the agent, the prompt queries and the whole interface offline, and it ships in the repo so anyone can try the project without accounts. Accessibility. I am blind and use JAWS day to day, and I tested this myself before anyone else touched it. The interface has a skip link, real headings for every question and answer, one polite live region that announces the full answer once when it is complete, a "read last answer again" button, and stable focus in the question box. An end-to-end test drives it in Chromium and runs axe-core in light and dark colour schemes, so accessibility is checked automatically, not just by hand. Project ID: u2f7mbu5 Dataset: production public