
Company News
Socket Joins New OpenJS Program to Fund Node.js Security Work
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.
virwave-breathe
Advanced tools
Calm, animated breathing sessions from the VirWave app, as an MCP server. Runs on your computer and collects nothing.
Calm, animated breathing sessions from the VirWave app, as an MCP server. Ask for a breathing break and you get a session paced to your breath. In Claude Desktop the session appears in the chat itself; elsewhere it opens in your browser. Either way it waits for you to press Begin.
It runs on your computer, makes no network requests, and collects nothing. It works with Claude Desktop, Claude Code, and any other MCP client that can start a local server.
You need Node.js 18 or newer for the npx routes. The Claude Desktop extension uses the Node.js that comes with Claude Desktop.
Claude Desktop, as an extension
virwave-breathe.mcpb from the latest release.The first time a session appears in the chat, Claude asks for permission to show it. Choose "Always allow" and later sessions appear straight away.
Claude Desktop, by hand: add this to claude_desktop_config.json (Settings, Developer, Edit Config), then restart Claude Desktop.
{
"mcpServers": {
"virwave-breathe": { "command": "npx", "args": ["-y", "virwave-breathe"] }
}
}
Claude Code
claude mcp add virwave-breathe -- npx -y virwave-breathe
For any other MCP client, the command is npx -y virwave-breathe (stdio).
Claude on the web, iPhone, and Android, with nothing to install
VirWave Breathe is also hosted, at one address:
https://xswebtvkueusdaeboizp.supabase.co/functions/v1/breathe
In Claude (on a paid plan), open Settings, then Connectors, choose "Add custom connector", give it the name VirWave Breathe and that address, and add it. It needs no sign-in. The same address works in ChatGPT with developer mode on, and in any MCP client that connects to a URL. See "Hosted" below for what is different.
In Claude Desktop the session appears in the chat, as a card you can expand to fill the window. In a client that can't show it in the chat, it opens in your default browser. In Claude Code, Claude publishes it as an Artifact instead. Every way, it opens at rest and only starts when you press Begin.
| Kind | Name | Does |
|---|---|---|
| Tool | start_session | Matches a session to how you are, what you'd like instead, and how long you have (2, 5, 10, or 15 minutes). Moves through stages at one steady pace, with no breath holds |
| Tool | breathe | Creates a session from a pattern and shape you choose, for a number of minutes or breath cycles |
| Tool | list_patterns | Lists the patterns: phases and seconds, what each is good for, its shapes, and whether it holds the breath |
| Resource | virwave://patterns | Same data as list_patterns |
| Resource | virwave://shapes | The seven shapes, a plain line on each, and the patterns each draws |
| Resource | ui://virwave-breathe/session-<fingerprint>.html | The session as it appears in the chat (an MCP Apps view). One fixed page for every session |
| Prompt | breathing-break | "Here's how I feel and how long I have": matches a session (or picks a hold-free pattern) and starts it |
list_patterns only reads. When the session is shown in the chat, breathe and start_session write nothing. Otherwise each saves one new HTML file (the session page) on your computer and, unless you turn it off, opens it in your browser. They never change or delete anything. The tool result always says which happened: "Shown in the chat", "Opened in their browser", or the file path.
With no arguments, breathe plays the app's own default session: In & Out, 4 seconds in and 8 seconds out, with no breath holds, drawn on the circle (DEFAULT_BREATH_CONFIG).
| Setting | Extension option | Environment variable | Default |
|---|---|---|---|
| Show sessions in the chat | "Show sessions in the chat" | VIRWAVE_INLINE: auto, on, or off | auto: in the chat when the client supports it, in the browser otherwise |
| Save a copy of sessions shown in the chat | "Also save the session page to a folder" | VIRWAVE_SAVE_COPY (true turns it on) | Off |
| Open sessions in the browser | "Open sessions in my browser automatically" | VIRWAVE_OPEN_BROWSER (false turns it off) | On |
| Where session pages are saved | "Where session pages are saved" | VIRWAVE_SAVE_DIR | A VirWave Breathe folder in your computer's temporary files |
How the server decides: a client that can show MCP Apps says so when it connects (capabilities.extensions["io.modelcontextprotocol/ui"], as the MCP Apps specification sets out). With VIRWAVE_INLINE=auto the server follows that: such a client gets the session in the chat, with no browser and no file, and every other client gets exactly what version 1.0 gave. off sends every client to the browser. on answers for the chat view even if the client didn't say it supports it. The browser and folder settings apply whenever the session is not shown in the chat; the folder also applies to saved copies.
The temporary folder is the default because it needs no permission prompt, keeps your own folders tidy, and is cleared by the system. When opening is off, or a browser can't be opened, the tool result says so and gives the file path instead. When a caller passes its own out_dir (as Claude Code does, to publish an Artifact), the browser is left alone.
The hosted server has none of these settings: it saves nothing and opens no browser (see "Hosted").
Sessions are for relaxing and resetting. They are not medical care, and nothing here is described as treatment. The server's instructions and the breathing-break prompt follow the app's own rules (src/domain/distressProtocol.ts):
start_session) never hold the breath at all.test/instructions.test.ts fails if any of these lines go missing.
VirWave Breathe, as an extension or through npx, runs entirely on your computer. The hosted server is covered at the end of this section.
npx command make no network requests.VirWave Breathe folder in your temporary files). Your choices are not logged or kept anywhere else. The page, and the view in the chat, load nothing from the internet and store nothing in your browser. The chat view declares an empty list of allowed internet addresses, so the client blocks it from the network as well.VirWave's full privacy policy: https://virwave.com/privacy/. Terms: https://virwave.com/terms/.
The hosted server (see "Hosted") is the same code at an address on the internet, for Claude on the web and phones and for ChatGPT. What changes: your assistant's requests to it (the tool's inputs: pattern, shape, and length, or feeling, goal, and minutes) travel to that server, which builds the session and sends it back, and a session opened from its link is fetched from it. The server keeps no record of them. It has no database, no accounts, and no analytics, and it writes nothing. A session link opens the session page on GitHub Pages with the session in the part of the address after #, which browsers never send to a server. The hosting platform and GitHub keep ordinary request logs (address, path, time, and outcome) for a limited time to run the service, as any web host does, and VirWave reads them only to find faults. Nothing about you is sent anywhere else.
Your conversation with Claude, or with any other assistant you use this with, is covered by that product's own privacy policy, not this one.
MIT, copyright 2026 VirWave OAM, Inc.
The published bundle includes these parts of the VirWave app's code, under the same licence: the breathing engine (src/engine/breathingEngine.ts), the rhythm, timing, breakdown, shape, and session-matching data from src/domain/, and a snapshot of the design tokens the session page uses (src/theme.generated.ts). It also bundles the MCP TypeScript SDK and zod, both MIT, with their dependencies. The chat view follows the MCP Apps specification (MIT); its few messages are written out in src/player/bridge.ts, so the MCP Apps SDK is not bundled and adds nothing to the licence list. The rest of the VirWave app, the VirWave name, and the logo are not covered by this licence.
Support: open an issue, or write to info@virwave.com.
The rest of this page is for people working on the package.
In the app, Breathe opens app/begin.tsx: colour wheel, then how it feels, what you'd like instead, and how long you have. start_session is that flow without the screen. It's a separate tool rather than more inputs on breathe because a match picks its own shapes, rhythm, and length, so pattern, shape, and cycles would only conflict with it.
feeling (flat, faint, nameable, directional, insistent, saturating), goal (grounded, calm, clear, unstuck, settled), and minutes (2, 5, 10, or 15). The descriptions are the app's own labels and notes from src/domain/sessionMatch.ts. The feeling is how clearly something comes through, never which emotion; Claude is told never to name, guess, or ask about one, and to ask at most one plain question when it can't tell.sessionPlan gives the stages and seconds; resolveModality gives each stage's shape and whether it's counted. The page swaps the drawing at each boundary with activeStageAt, instantly, as app/session.tsx swaps the renderer. Phase text and counts follow each stage until the person uses T or #.app/begin.tsx picks it with coreSegment) through shapeSelectionPatch and deriveSessionRuntimeConfig. Every modality the match can reach runs In & Out 4-8, so every matched session is hold-free, whatever the answers: breathe in, a longer breath out. test/match.test.ts and npm run smoke check all 120 combinations for a hold and fail if one appears.matchedCycles, the session ends on the meditation timer at the chosen minutes, and the bar shows one segment per cycle (capped at 8), as the app's bar does. It doesn't show stages.whyLine but doesn't show it on the session screen, so it's only in the tool result for Claude.test/match.test.ts checks all 120 feeling, goal, and length combinations against the app's functions.
| Shape | What you see | Patterns |
|---|---|---|
box | A square whose outline draws itself once per breath cycle | Box Rhythm, In & Out, In, Hold, Out |
triangle | A triangle whose outline draws itself once per breath cycle | In, Hold, Out, 4-7-8, In & Out |
circle | A ring that draws itself once per breath cycle, with no corners to turn | In & Out, In, Hold, Out, Box Rhythm, 4-7-8 |
focus-point | A soft point crosses a line as you breathe in and comes back as you breathe out | In & Out |
infinity | A soft point travels a figure-eight, once around per breath cycle | In & Out |
ripple | Rings spread out from a still centre and fade, once per breath cycle | In & Out |
color-field | Soft colour swells and settles, once per breath cycle | In & Out |
The last four are the app's breath-locked modalities. The app pairs them with In & Out only, so this server does too. Their names and one-line descriptions come straight from src/domain/modalityConfig.ts.
Why the default is the circle. It's the app's own: DEFAULT_BREATH_CONFIG opens every new session on the circle with In & Out 4-8, and the matched Breathing stages run there too. The app gives the circle to everyone, so every pattern can be drawn on it; for patterns with holds, the box or triangle comes first. For In & Out the box remains the best fit after it (a two-phase rhythm turns on a corner), and the triangle would switch phase mid-side, so it ranks last.
Reduced motion. Each shape has the app's still version: the box, triangle, and circle show their full outline, the focus point rests at the middle of its line, the infinity point rests at the crossing, the ripple's rings hold evenly spaced, and the colour field holds at its resting size.
The page is the app's session screen (app/session.tsx), rebuilt for the browser and checked side by side against iPhone 16e screenshots of the app:
sessionTokens.background, navy to teal, corner to corner) under the app's default dim, with the twelve ambient particles spread across it, at the percentage positions CinematicParticles gives them.SessionAura rings (outer and inner breathe in opposite directions, the middle sweep turns once every 20 seconds) and the ShapeAura glow, sized with computeAuraRingSizes and driven by the app's resolveAuraInstruction. The shape sits inside it at layout.shapeRatio.apps.apple.com/app/id6738364276, from eas.json) on Apple devices and Google Play (com.virwave.virwave) elsewhere, chosen from the user agent, App Store without JavaScript. It opens in a new tab and carries no tracking parameters.<title> and a heading only screen readers see. Phase changes are announced, every button has a label and a visible focus ring, and Space begins, pauses, or resumes.The app sizes the screen in points with fixed caps. The page keeps the iPhone proportions and scales them to the window (up to layout.maxContentWidth), so a desktop browser shows the same composition, larger, rather than a shape that outgrows its orb.
Claude Desktop, and any other client that supports MCP Apps, shows the session inside the conversation. It is the same page and the same player, not a second design:
breathe and start_session point at one view, ui://virwave-breathe/session-<fingerprint>.html (the fingerprint changes whenever the view does, because hosts keep the first copy they fetch) (_meta.ui.resourceUri). The view is a single self-contained HTML document with all seven drawings and no session in it.structuredContent. The words in the result are for the assistant; the data is for the view. The view fills itself in and waits for Begin.ui/request-display-mode), and the same button brings the session back to the chat. Full screen uses the saved page's layout, and respects the safe areas the client reports.ui/open-link), which asks you to confirm. Without a client that can open links it stays an ordinary link._meta.ui.csp), which is the tightest policy the specification has, and asks for no camera, microphone, location, or clipboard.examples/basic-host in the ext-apps repository) at chat-column and phone widths, side by side with the app.The hosted server is the same code over Streamable HTTP at one address, for clients that cannot run a program on your computer: Claude on the web and phones, ChatGPT, and any other MCP client that connects to a URL.
https://xswebtvkueusdaeboizp.supabase.co/functions/v1/breathe
What is different from the extension:
out_dir.…/session?pattern=…&shape=…&cycles=…, or …/session?feeling=…&goal=…&minutes=…). The link opens the session page, https://virwave.github.io/virwave-breathe/, with the session carried in the address after #, which never reaches a server. It is the chat view's own document, standing on its own. (A server that can serve HTML on its own domain answers the link with the page directly instead; VirWave's hosting cannot, so it redirects.)POST is the MCP endpoint (plain JSON responses, no sessions). A plain GET returns a short JSON description of the server. GET …/session?… redirects to the session page with that session in the address, or serves the page itself where it can; either may be cached for a day.dist/hosted.js (virwave-breathe/hosted on npm): a fetch-style request handler with the player built in, for Deno and the edge runtimes built on it (Deno.serve(handleRequest)), Cloudflare Workers (export default { fetch: handleRequest }), Bun, or Node. Two optional settings: VIRWAVE_PUBLIC_URL gives the address clients use when a proxy rewrites it, and VIRWAVE_SESSION_PAGE_URL makes session links redirect to a copy of dist/session.html served elsewhere, for a host that cannot serve HTML itself.To host it yourself: npm install virwave-breathe, then import { handleRequest } from 'virwave-breathe/hosted' and serve it, or npm run build here and deploy dist/hosted.js. npm run smoke:hosted checks the bundle in process, and VIRWAVE_SMOKE_HOSTED=<address> npm run smoke:hosted checks a live one.
The sessions are built from the VirWave app's own files, unchanged. In the app's repository the package compiles them in place from the app's src/ folder, which is how the paths below are written. In the public repository, virwave/virwave-breathe, the same files are copies under vendor/virwave/ (so src/engine/breathingEngine.ts is vendor/virwave/engine/breathingEngine.ts). The copies are made by a script and never edited by hand; see vendor/virwave/README.md. The one exception is domain/worlds.ts, which is a short stand-in there: the app's file lists its background themes, and this project only uses the default one. Design tokens are not copied at all: src/theme.generated.ts is a snapshot of the few values the session page reads (npm run generate:theme, app repository only).
src/engine/breathingEngine.ts is bundled unchanged into the page and drives the animation.src/domain/rhythmTemplates.ts and timingPresets.ts: free-tier presets only, gated to FREE_SHAPES.resolveBreakdownFromRhythmAndTiming.DEFAULT_BREATH_CONFIG.timingPresetId, so it follows the app.src/render/shapes/ with the same constants. For the four breath-locked shapes, the maths inside each renderer's useAnimatedProps worklet is now a plain function in src/player/geometry.ts, driven by the same (phaseIndex + progress) / totalPhases and the app's default motion intensity (0.7). The app's renderers weren't changed; test/geometry.test.ts checks positions, radii, opacities, and scale at key points in the cycle.DEFAULT_BREATH_CONFIG. src/theme.generated.ts is a snapshot of the tokens the page uses, written from the app's own theme files, and a check in the app's repository fails when it falls out of date.From the root of virwave/virwave-breathe (or from packages/virwave-mcp in the VirWave app's repository, where the package is developed):
npm ci
npm run build # dist/server.js (MCP SDK and zod bundled in) and dist/player.js
npm test # vitest: catalog, planner, schema, geometry parity, page safety, the chat view, delivery, publishing files
npm run typecheck
npm run smoke # spawns dist/server.js over stdio, as a client with MCP Apps and as one without, and exercises every tool, resource, and prompt
To try a local build, point a client at it: claude mcp add virwave-breathe -- node "$(pwd)/dist/server.js".
npm run smoke always starts the server with VIRWAVE_OPEN_BROWSER=false, so it never launches a browser. Set VIRWAVE_SMOKE_SERVER=/path/to/server.js to run the same checks against an installed copy (the npm tarball, or an unpacked .mcpb).
CI runs the same checks on every push: .github/workflows/ci.yml in the public repository, and .github/workflows/virwave-mcp.yml in the app's repository (where the root tsconfig.json and eslint.config.js skip packages/**, so this package owns its own checks).
The app itself is private. npm run export:public -- <folder> (app repository only) writes the standalone project that becomes virwave/virwave-breathe: this package at the root, plus the few app files it compiles under vendor/virwave/, with the path aliases pointed there. The script copies only the files on its allowlist (scripts/public-files.mjs), removes a short, documented list of internal comments from the copies without touching any code, and refuses to finish if the code of a copy differs from the app's. It also refuses to finish if any word on its never-ship list would become public. After building the exported folder, npm run sweep:public -- <folder> checks the same list against the source, the built dist/*.js, and the tarball npm pack makes. test/export.test.ts fails when the package starts compiling an app file that isn't on the allowlist, so nothing new becomes public without a deliberate change. Changes are made in the app's repository and exported again; the public copy is not edited by hand.
package.json holds the version. build.mjs passes it into the server, and test/package.test.ts fails if manifest.json or server.json disagree with it.
npm run validate:mcpb # checks manifest.json against the MCPB spec
npm run pack:mcpb # builds, then writes dist/virwave-breathe.mcpb (not committed)
npm pack --dry-run # shows exactly what npm would publish
Release from the public repository, not from the app's. A readable bundle keeps some source comments, and only the public copies have had the app's internal comments removed; the code in the two builds is the same.
npm publish runs the build, typecheck, tests, and smoke first (prepublishOnly). Two files run: dist/server.js and dist/player.js. There are no runtime dependencies.manifest.json, icon.png, and .mcpbignore define the .mcpb. It runs the same two files.server.json, with mcpName in package.json to show the npm package belongs to the same name.FAQs
Calm, animated breathing sessions from the VirWave app, as an MCP server. Runs on your computer and collects nothing.
The npm package virwave-breathe receives a total of 0 weekly downloads. As such, virwave-breathe popularity was classified as not popular.
We found that virwave-breathe demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.