API and MCP
Status: built 2026-10-24 for EDGEquest (EDGEwave #5 and #7); hand test pending (Tests). Other EDGE tools reach EDGEwave in two ways: its API, for VS Code extensions, and its MCP server, for assistants. Both use the same code and the same files.
The API
activate() returns it. Copy the interfaces into your extension; never import EDGEwave’s code (EDGEwave does the same with REDcode’s). The source of truth is src/api.ts.
const ext = vscode.extensions.getExtension("edgemodders.edgewave");
const api = ext && (ext.isActive ? ext.exports : await ext.activate());
if (api?.apiVersion === 1) { ... }
| Member | |
|---|---|
apiVersion |
1 |
version |
EDGEwave’s version |
voiceLines({ quest, lines, voices?, archiveDir?, project? }) |
Opens Voice Lines on these lines, adding them to the quest’s panel when it is open. One line opens the panel on it. Resolves with voice.json when the panel closes. lines as in the contract. project: the project’s folder, else REDcode’s open project, else the workspace folder with source/ |
voiceStatus(quest, project?) |
The quest’s voice.json, with voices, or undefined when nothing is voiced yet |
soundEvents({ query?, music?, quest?, limit?, project? }) |
The project’s own named sounds, then the game’s sound events, matching a query. See Sound events |
playEvent({ name, project?, show?, inGame? }) |
Plays an event’s sound effect recordings in EDGEwave’s Hear panel: { ok: true, where: "archive", variations }; a project sound plays its WAV (where: "project"). A music cue, or an event with no recordings, plays in the running game through REDnode (where: "game", playingId), or gives { ok: false, reason } when the game is not running with REDnode 0.43 or later. inGame: true plays any event in the game, a project’s own after Pack and Install too; see In the running game. With show: false (#8, added 2026-10-26), see Playing with no tab |
stopSound() |
Stops the sound playEvent is playing with show: false, and the one it started in the game, if any. Also the command EDGEwave: Stop Sound |
customSound({ project?, wav, name, kind? }) |
A sound as a new event a quest can post: { event, file, type, note }, or { reason }. See A new event |
Playing with no tab
playEvent({ name, show: false }) plays at once, for a picker’s ▶. No tab opens or comes forward.
| What plays | One recording, picked at random as the game does: played in the result names it (the sound’s number, or a project sound’s file). The Hear panel (show left out) still has all of them |
| Again | A second call stops the first sound and plays the new one. stopSound() stops it |
| How | A hidden Windows player in EDGEwave’s extension host (a PowerShell with System.Media.SoundPlayer), not a webview, so no click is needed first. It starts when soundEvents is first called, or on the first play (about a second); later sounds start at once |
| Where it cannot | Not on Windows: { ok: false, reason }; call again without show: false for the Hear panel. A recording REDcode does not give as WAV, a music cue, or an event with no recordings: a reason, as with the panel |
Unverified: that the hidden player is heard on every Windows setup. It plays through Windows’ default output device, whatever VS Code’s own sound uses.
Sound events
The list is the game’s own: base\sound\event\eventsmetadata.json (an audioAudioEventArray). In game 2.31 it names every event: 23,634 entries, 23,530 names. Each has its Wwise id (FNV-1 32 of the lower-case name; true for every one), its min and max length, whether it loops, and tags. REDbridge’s older list (sound.events, from WolvenKit for game 1.6) names 14,476 of 20,569 groups.
EDGEwave reads the file through REDcode’s bridge (cr2w.toJson) the first time it is asked. It keeps a small index (3.3 MB) in its own folder, %LOCALAPPDATA%\EDGEwave\events.json, where the MCP server reads it too. It builds it again when the game’s audio_1_general.archive changes. The index is built from the player’s game and never shipped or committed.
| Query field | |
|---|---|
query |
Words that must all be in the name, or be one of its tags. A decimal number finds an event by its hash |
music |
true: only music cues (a mus_ name, or a music_quests_events, quest_music_start or quest_music_stop tag; 1,741 in 2.31). false: no music |
quest |
Only events tagged for quests: the music tags and sfx_quest_events (790 in 2.31). Quests post other events too |
limit |
50 by default, up to 1,000 |
Each result: name, hash, loops, min, max (seconds), tags, music, quest, and sounds (the variations REDcode’s index has, for sound effects).
The project’s own sounds come first (added 2026-10-24): every REDmod custom sound in the project’s source/resources/info.json (project, else REDcode’s open project), made with Add to Project’s A new sound for this project or customSound. Each has project: { type, file }, the tags project and its type, and its WAV’s length as min and max. One under a game event’s name is marked replaces: true and stands in that event’s place, with the event’s tags, so a picker shows what the game will play. The query matches them as it does the game’s.
Hearing an event: sound effects play from the archive through REDcode, in the Hear panel: the raw recordings, without the game’s effects. Music cues live in Wwise’s sound banks, which EDGEwave reads only from A4: they play in the running game, with the game’s own mix (below).
In the running game
Added 2026-10-27, with REDnode 0.43.0’s editor channel (REDnode #17); src/rednodeEditor.ts.
| How | While the game runs with REDnode, REDnode writes %LOCALAPPDATA%\REDnode\editor.json with a local port and a token made new each run. EDGEwave reads it and posts sound.play { name, on: "player" }: the game plays the event on V, as a quest would, with its mix and effects |
| When | playEvent on a music cue or an event with no recordings; playEvent({ name, inGame: true }) for any event; Game Assets’ right-click Play in the Game |
| Again | The next sound EDGEwave plays in the game stops the one before. stopSound() and EDGEwave: Stop Sound stop it |
| No game | { ok: false, reason }: start the game with REDnode 0.43 or later. Also when editor.json is left from a game that ended (its process is gone), the connection is refused, or the player turned the channel off. While the game is still loading: wait, then try again |
| Not checked by the game | REDnode’s channel plays nothing for a name the game does not know, and does not say so |
Unverified until the hand test: that every music cue starts this way (a _START cue may need its _STOP to end; Stop Sound stops what EDGEwave started).
A new event
customSound makes a WAV into a REDmod custom sound under a new event name, edgewave_<name>. It writes the WAV to source/customSounds/<event>.wav (48 kHz, 16-bit, its channels kept) and adds the entry to source/resources/info.json, as Add to Project’s REDmod route does.
kind |
REDmod type | |
|---|---|---|
sfx (default) |
mod_sfx_low_occlusion |
A long attenuation, not muffled: the wiki’s type for a quest sound |
music |
mod_sfx_2d |
No position. REDmod has no music type, so it follows the effects slider, not the music one, and no music state |
Unverified: that a quest phase’s questAudioEventNodeType or a scene’s scnAudioEvent plays a new event name. REDmod can add new events (the wiki), and scripts post them. EDGEquest’s test quest will check it (#7). The game reads custom sounds when it starts, and REDcode packs them only with redcode.pack.redmod on.
The MCP server
edgewave-mcp ships inside the extension. On start EDGEwave copies it to %LOCALAPPDATA%\EDGEwave\mcp\edgewave-mcp.cjs, a path that stays the same across updates. VS Code 1.101 and later see it through their own MCP support, with the ElevenLabs key from secret storage. EDGEwave: Connect an Assistant (MCP)… registers it with Claude Code (claude mcp add --scope user edgewave), or copies the command or a JSON entry for other clients. It asks before it gives the client the ElevenLabs key, which the client keeps in plain text.
| Tool | Writes | |
|---|---|---|
voice_lines |
Yes | { project, quest, lines, voices?, redo?, model?, archiveDir? }: voices the lines that need it (or all with redo) and saves voice.json. Returns each line’s status and files, the characters sent (what ElevenLabs counts), the model and the rate. No ask-first flag: the owner allowed assistants to voice lines (2026-10-23). It spends ElevenLabs credits |
voice_status |
No | { project, quest, lines? }: with lines, made, stale or none for each, and the voices. Without, the whole voice.json |
voice_voices |
No | { search? }: the ElevenLabs account’s voices, with labels and a preview URL |
sound_events |
No | { query?, music?, quest?, limit?, project? }: as soundEvents, from the index EDGEwave built; with project, its own sounds first. Before VS Code has built the index, it says so |
sound_custom |
Yes | { project, wav, name, kind? }: as customSound |
It needs no VS Code to run. Settings (edgewave.voiceLines.model, edgewave.elevenlabs.sampleRate, edgewave.elevenlabs.baseUrl, redcode.pack.redmod) come from VS Code’s user settings file. The key comes from ELEVENLABS_API_KEY. Logs go to stderr.