DevCompanion Extended is a local MCP server for developing Equicord plugins against the Discord runtime. It gives coding agents the missing half of plugin development: live webpack source, React ownership, Flux stores/actions, patch proof, reload control, and post-reload health checks.
The intended loop is: orient in the current Discord session, locate the runtime seam, prove the patch, edit the repository, reload, and verify the result.
| Tool | Use it for |
|---|---|
discord |
Runtime orientation, platform/build context, enums, endpoints, and REST |
component |
Visible UI location, React owner traces, props, DOM, and patch seams |
module |
Landmark resolution, compact dossiers, source, exports, and dependency graphs |
store / flux |
Store discovery, safe getters, action producers, handlers, and dependencies |
intl |
Resolve Discord message hashes and stable #{intl::KEY} anchors |
patch |
Rank targets, prove replacements, inspect plugin patch health, and verify live output |
plugin |
Inspect plugin state/settings and intentionally change them |
buildOutput / reloadDiscord |
Build diagnostics, renderer reloads, and readiness verification |
interaction / trace / intercept |
Bounded runtime observation when static inspection is insufficient |
batch / resource |
Combine safe reads and retrieve large evidence without bloating tool responses |
evaluateCode |
Last-resort renderer evaluation when no dedicated tool answers the question |
All 16 tools return structured MCP content as well as text for older clients.
Tooling
The server currently advertises 16 tools and 139 action-based subtools. Tool names stay stable; the action is passed through the tool's action argument unless the tool is a single operation.
Reads the development build log, captured renderer console, native-server readiness, and cached patch health.
| Action | What it does |
|---|---|
status |
Return build-log metadata, native-server status, and renderer console-capture status without reading the full log. |
recent |
Read a bounded window of the most recent build-output lines. |
errors |
Return error-level build lines and captured renderer errors. |
search |
Search build output with a literal query or regular expression. |
runtimeReady |
Wait until the renderer and native DevCompanion boundary are ready for live calls. |
reloadHealth |
Perform the authoritative post-reload check, including new build/console errors and cached patch-health totals. |
clear |
Clear the captured build output; this mutates diagnostic state. |
Locates visible Discord UI and bridges DOM elements to React fibers, owning webpack modules, CSS, stores, and patch seams.
| Action | What it does |
|---|---|
inspect |
Inspect a selected element, its React component chain, CSS hints, and likely stores. |
trace |
Trace an element through DOM, React ownership, module candidates, patch hints, Intl candidates, CSS, and stores; compact by default. |
locate |
Rank visible UI candidates from selectors, text, component names, classes, bounds, or captured interaction targets. |
find |
Find components through the component index or the same visible-element ranking pipeline used by locate. |
rebuildIndex |
Clear and rebuild the cached component-name, Mana component, story, and UI-barrel indexes. |
tree |
Return a bounded React render tree rooted at the selected element. |
seam |
Rank viable patch boundaries; with replace, synthesize and prove up to three complete definitions without editing files. |
clone |
Generate a pasteable React approximation of a visible element or active modal. |
query |
Run a CSS selector and return bounded DOM matches; properties provides a strict attribute/property projection. |
styles |
Read selected computed CSS properties or the properties that differ from a default element. |
modify |
Temporarily change inline styles, classes, or attributes for live UI experiments; changes disappear on rerender. |
text |
Read bounded text from an element and its immediate children. |
path |
Build a DOM path and a shorter candidate selector for an element. |
fiber |
Traverse summarized React fibers upward or downward from an element. |
props |
Read React props and matching DOM properties; use properties to request only named values. |
hooks |
Inspect the nearest function component's bounded hook list and supported hook values. |
contexts |
Inspect React context providers above an element, including bounded keys and samples. |
forceUpdate |
Force the nearest updatable class or function component to rerender. |
state |
Read the nearest class state or function-component state-hook values. |
owner |
Follow React debug-owner/parent relationships and summarize owning components. |
root |
Identify the React root and summarize its root fiber for a selected element. |
domTree |
Return a bounded DOM subtree with tags, IDs, classes, leaf text, and child truncation metadata. |
Searches, resolves, inspects, and relates Discord webpack modules without requiring agents to guess minified identifiers.
| Action | What it does |
|---|---|
search |
Search module factories by literal text, regular expression, or AND-style patterns. |
find |
Use runtime finders such as props, code, display name, class name, export name, or pattern. |
resolve |
Resolve a semantic landmark such as an Intl key/hash, CSS suffix, store, action type, enum-like symbol, or source literal to defining modules. |
inspect |
Return a compact module dossier by default, or full source/runtime evidence on explicit request. |
extract |
Read a bounded source range from one module factory. |
exports |
Load and summarize a module's runtime exports; runtime values require explicit opt-in. |
publicExports |
Statically recognize public export names from factory source without loading the module. |
context |
Find a query inside a module and return bounded source context around each match. |
diff |
Compare two module factories and report their source differences. |
deps |
Return a module's direct webpack dependencies. |
whereUsed |
Return modules that directly import the requested module. |
path |
Find a bounded dependency path from one module to another. |
neighborhood |
Return a bounded bidirectional dependency subgraph around a module. |
functionAt |
Recover the enclosing function around a source offset or matching source landmark. |
structure |
Summarize factory structure such as functions, strings, calls, exports, and source signals. |
size |
Report source byte/character size for one module. |
ids |
List known webpack module IDs in bounded pages. |
patchedList |
List modules currently touched by registered plugin patches. |
findFactory |
Inspect or search raw webpack factory functions without executing their exports. |
stats |
Report webpack module, cache, analysis-index, and lazy-loader statistics. |
loadLazy |
Invoke bounded lazy-module loaders and invalidate generation-bound analysis caches. |
watch |
Start watching webpack module execution/loading for matching modules. |
watchGet |
Read captures from an active module watch. |
watchStop |
Stop and clean up a module watch. |
suggest |
Rank patch anchors. strategy: "quick" preserves the fast legacy result; strategy: "exhaustive" adds semantic ownership, uniqueness proof, durability history, risks, and registered-factory coverage. |
annotate |
Replace or label raw Intl hashes in module source with resolved message keys. |
css |
Search/index Discord CSS-module class maps and their owning webpack modules. |
components |
Search/index exported React components and component-like modules. |
Discovers Flux stores, their public surface, subscriptions, and intentionally requested state.
| Action | What it does |
|---|---|
find |
Search stores by name, methods, getters, or properties. |
list |
List registered Flux store names, with optional filtering. |
state |
Read a named property/getter or invoke a specifically requested store method. |
call |
Invoke a named store method with explicit arguments. |
subscriptions |
List Flux action types handled by a store. |
methods |
Walk the store prototype chain and describe available methods/getters. |
snapshot |
Read a bounded snapshot of all safe zero-argument getters. |
guide |
Recommend likely safe getters and exact follow-up calls for a store task. |
inspect |
Return store identity, module ownership, methods, subscriptions, and optional snapshot metadata. |
Maps Discord's Flux dispatcher statically and can perform bounded observation or explicit dispatch.
| Action | What it does |
|---|---|
events |
List known dispatcher action types, optionally filtered. |
types |
Alias the known Flux action-type inventory for type-oriented discovery. |
dispatch |
Dispatch an explicit Flux action payload into Discord; this mutates renderer state. |
listeners |
List stores, ordered handlers, and subscriptions for one event. |
observe |
Capture a bounded synchronous window of matching Flux actions and summarize payload keys. |
graph |
Show one store's dispatcher dependency node, dependencies, dependents, and handled actions. |
producers |
Find source modules that produce a named action type. |
chain |
Return the ordered store-handler execution chain for an action type. |
Turns Discord message hashes, keys, and text into readable, stable #{intl::KEY} patch anchors.
| Action | What it does |
|---|---|
resolve |
Resolve a hash, key, or message query and return the best stable anchor. |
search |
Rank Intl entries by message text, key, or hash. |
scan |
Scan supplied text or a module factory for Intl hashes and resolve them in place. |
discover |
Search runtime messages and source evidence for unknown hash-to-key mappings. |
test |
Validate candidate keys against runtime hashes and add successful mappings to memory. |
cache |
Report cache validation/collision statistics, clear the runtime cache, or flush the sorted validated map to disk through mode. |
Targets, validates, benchmarks, diagnoses, and live-verifies Equicord/Vencord patches against the current Discord bundle.
| Action | What it does |
|---|---|
target |
Rank stable find anchors and candidate modules from source, Intl, CSS, component, or module evidence. |
verify |
Validate find, match, and optional replace behavior without returning full previews. |
preview |
Validate a patch and return bounded before/after replacement previews. |
unique |
Check whether a prospective find string is unique and suggest stronger surrounding anchors when it is not. |
analyze |
Audit registered plugin patches for missing, ambiguous, risky, or stale matches. |
plugin |
Report one plugin's live patch health, module matches, optional replacement proof, and repo source mapping. |
lint |
Lint patch definitions for brittle finders, regex hazards, syntax risks, and unsupported patterns. |
finds |
Inventory and evaluate registered patch find values across plugins. |
benchmark |
Benchmark one replacement regex against matching module source. |
compare |
Compare two match/replacement strategies for correctness and performance. |
slowscan |
Scan registered patches for slow or pathological match behavior. |
conflicts |
Find modules patched by multiple plugins and summarize overlap severity. |
diff |
Apply a proposed replacement in memory and return the resulting source difference. |
broken |
Find broken runtime patches and classify why their find or replacement no longer matches. |
prove |
Prove uniqueness, match, changed replacement, syntax, conflicts, and performance; adaptive by default, exhaustive on request. |
source |
Map runtime evidence to plugin, patch, replacement, property, method, and exact line/column ranges when TypeScript is available. |
verifyLive |
Produce a privacy-safe receipt from required DOM/store/Flux contracts; an allowlisted click runs only with executeTrigger: true. |
repair |
Propose bounded, source-mapped repairs and promote only complete proof passes. Never edits files. |
Provides runtime orientation plus focused Discord platform, API, constants, design-system, and telemetry helpers.
| Action | What it does |
|---|---|
context |
Return the current user, selected channel, and selected guild snapshot when that private runtime context is explicitly needed. |
orient |
Return compact renderer readiness, module/store/plugin counts, build status, patch-health state, and recommended next calls. |
api |
Make an explicit Discord REST request through RestAPI; non-GET methods mutate remote state and cannot be batched. |
snowflake |
Decode a Discord snowflake into timestamp, age, worker, process, and increment fields. |
endpoints |
List or search Discord endpoint constants and preview callable route builders. |
common |
List or filter exports available from Equicord's @webpack/common surface. |
enum |
Find runtime enum-like exports containing a named string or numeric member. |
memory |
Report Chromium JavaScript heap usage and limits. |
performance |
Report navigation/resource timing plus bounded DevCompanion per-action latency, size, cache, and resource-fallback telemetry. |
gateway |
Inspect the active Discord gateway connection, heartbeat, sequence, and uptime state. |
waitForIpc |
Poll until the current Discord user/runtime is available or the timeout expires. |
constants |
Browse or filter Discord constant categories and bounded value samples. |
experiments |
Summarize or search registered experiments, assignments, and override descriptors. |
platform |
Report desktop/web platform flags and safe Discord environment/build endpoints. |
tokens |
Browse Discord design tokens, semantic colors, themes, shadows, radii, and spacing. |
icons |
List/filter available IconUtils helper functions for Discord asset URLs. |
buildInfo |
Return Discord build number, version hash, release channel, platform, OS, and native build number. |
Inspects and intentionally changes live Equicord/Vencord plugin state and settings.
| Action | What it does |
|---|---|
list |
List plugins, enablement, authors, patch counts, and optional patch details. |
enable |
Enable a plugin immediately when safe, or persist the state and report that a restart is required for patched plugins. |
disable |
Disable a plugin immediately when safe, or persist the state and report that a restart is required for patched plugins. |
toggle |
Switch a plugin between enabled and disabled using the same lifecycle/restart rules. |
settings |
Read modern or legacy setting definitions, JSON-typed values, restart metadata, and redacted sensitive fields. |
setSetting |
Persist one setting value and report its previous value plus restart requirement; sensitive values remain redacted. |
| Action | What it does |
|---|---|
| Single operation | Execute JavaScript in the Discord renderer and serialize the final expression. This is a powerful mutation-capable escape hatch for questions no dedicated tool can answer. |
Provides longer-lived Flux action and store-change capture sessions than the bounded flux.observe helper.
| Action | What it does |
|---|---|
events |
List currently known Flux events with optional filtering. |
handlers |
List dispatcher handlers registered for one event. |
storeEvents |
List action types handled by a named store. |
start |
Start a bounded Flux trace with optional event/filter selection. |
get |
Read events captured by the active trace without stopping it. |
stop |
Stop the active Flux trace and return its final captures. |
store |
Start watching selected state from a store and record changes caused by Flux activity. |
Temporarily wraps a runtime exported function to capture calls and returns, including dotted export paths.
| Action | What it does |
|---|---|
set |
Install a bounded interceptor on a module export/function path. |
get |
Read captured arguments, return values, errors, and call counts from an active interceptor. |
stop |
Restore the original function and return final interceptor results. |
| Action | What it does |
|---|---|
| Single operation | Reload the Discord renderer; the next DevCompanion request automatically waits for runtime readiness. Native-code changes still require a full application restart. |
| Action | What it does |
|---|---|
run |
Execute multiple policy-classified read-only DevCompanion calls in one round trip. Unknown or mutating tool/action combinations are rejected before execution. |
Retrieves complete evidence that exceeded an action's inline response budget.
| Action | What it does |
|---|---|
list |
List live resources with MIME type, stored/original UTF-8 bytes, truncation, access time, and expiry. |
read |
Read a whole resource or an explicit UTF-8 byte range while updating its LRU access time. |
clear |
Remove all stored resources. |
Connects the next real UI interaction to its DOM target, React ownership, and associated Flux actions.
| Action | What it does |
|---|---|
start |
Begin capturing the next click/interaction and related Flux activity. |
get |
Read the current interaction target and captures without stopping the session. |
stop |
Stop capture, clean up listeners/interceptors, and return final results. |
once |
Start capture, wait for one interaction or timeout, then return and clean up in a single call. |
discord.orient, component.locate, component.seam, module.inspect, and patch.prove return decision-sized results by default. Pass detail: "full" when the compact result points to evidence you actually need. Explicit component.props, component.hooks, store.state, store.snapshot, module.exports, and evaluateCode remain the escape hatches for intentionally requested runtime values. Pass properties to component.props or component.query for a strict projection instead of serializing unrelated values.
Compatibility text is a concise summary; use structuredContent for the result object. Outputs that exceed their action-specific budget become a 1 KiB preview plus a resourceId, stored/original byte counts, expiry, truncation state, and exact resource.read arguments. discord.performance includes per-action DevCompanion p50/p95 latency, result sizes, cache hits, and resource fallbacks for the current renderer session, with its renderer-only timing scope reported explicitly.
Use module.suggest with strategy: "exhaustive" when a patch needs a durable anchor rather than a fast shortlist. The scanner derives candidates from validated Intl keys, stores, Flux action types, public exports, stable methods/properties, enum members, CSS source keys, and meaningful literals. It rejects minified names, module/chunk IDs, rendered hashes, assets, URLs, build numbers, import noise, and locale-definition bundles. Unique semantic anchors win; bounded two-anchor expressions are considered only when a strong single anchor is not unique.
Ranking is proof-ordered: correct target uniqueness, cross-build survival, verified functional success, semantic strength, risk count, then compactness. Compact results contain the best five candidates and stay within 3 KiB. Full evidence is resource-backed when necessary. Scans cover registered factories without silently loading lazy chunks; coverage and the explicit module.loadLazy follow-up keep incomplete runtime coverage from becoming a success verdict.
Durability history is enabled by default at Discord's userData/devcompanionExtended/anchor-history.json. Version 2 migrates version 1 records and adds bounded proof, repair, functional-pass, and deterministic-failure counters. It still stores only SHA-256 anchor/fingerprint digests, build hashes, kinds, outcomes, and timestamps—never selectors, source, runtime values, messages, or UI data. The atomically replaced file remains bounded to eight builds, 4,096 records, and 1 MiB.
Use patch.repair with pluginName plus patchIndex, or with an explicit find, match, and replace. It recovers likely targets from surviving match/semantic evidence, generates bounded candidates, and runs every proposal through the complete patch.prove pipeline. It never edits repository files and never labels an unproved candidate verified. patch.plugin separately reports APPLIED, NOT_APPLIED, CONSUMED_NO_CHANGE, FIND_AMBIGUOUS, or FIND_NO_MATCH; registration is not functional proof. After applying a proposed definition, reload, require buildOutput.reloadHealth, inspect patch.plugin, and finish with selector-backed patch.verifyLive.
patch.prove and patch.repair use benchmarkMode: "adaptive" by default: static failures skip timing, while valid replacements get a cold shot, bounded warmup, calibrated samples, and confirmation rounds inside a 40 ms budget. An unmeasurable valid replacement is BENCHMARK_INCONCLUSIVE, never PASS. Use benchmarkMode: "exhaustive", or explicit iterations/rounds, for the compatible 10,000-iteration, three-round path. patch.benchmark remains exhaustive.
For a visible seam, pass the exact replacement template to component.seam as replace; optional pluginName adds conflict and repository context. DCE returns only definitions that pass the canonical proof pipeline. Use patch.source with patchIndex and replacementIndex for exact TypeScript-AST property ranges. Computed/function values are marked unsupported and lexical fallback is precision: "coarse"; runtime-only modules return unmappedRuntimeModule instead of a guessed file.
patch.verifyLive accepts up to eight typed checks: DOM present/absent/attributeEquals, store getter-style truthy/falsy/equals/changed/unchanged, and Flux minimum counts. All checks are required unless required: false. testSelector remains a compatibility shorthand for one required DOM-present check; testTrigger is only a hint. A declarative click executes only when executeTrigger: true, which makes the call a write and therefore non-batchable. Receipts expose counts, types, digests/comparisons, timing, and verdicts—never DOM text, store values, Flux payloads, IDs, or message data. Only VERIFIED receipts teach functional anchor success.
Prior art: Sadan's scored unique-find concept helped motivate this problem space. DevCompanion Extended's semantic scanner, proof ordering, bounded durability history, and repair pipeline are an independent implementation and do not reuse AGPL source.
- Call
discord.orient. - Locate visible text or a component with compact
component.locate; request full detail only when its selector/owner summary is insufficient. - Follow ownership with
component.trace, then askcomponent.seamfor viable insertion points. - When the intended replacement is known, pass it to
component.seamto synthesize and prove definitions; otherwise usemodule.suggestorpatch.target. - Apply only a complete proof pass.
- Resolve a store landmark with
module.resolve, then read its compactmodule.inspectdossier. - Use
store.guidebefore guessing getter names. - Trace producers and ordered handlers with
flux.producers,flux.chain, orflux.graph. - Use
interaction.onceonly when the next click is the clearest way to identify the action.
- Prove the replacement with adaptive compact
patch.prove; use exhaustive mode ordetail: "full"only when needed. - Edit and build the plugin.
- Call
reloadDiscordfor renderer-only changes. - Require
buildOutput.reloadHealthto report ready. - Require
patch.pluginregistration evidence, then finish with typedpatch.verifyLivechecks. Execute a click only with explicit opt-in. A successful build or registration alone is not functional proof.
Install the folder as an Equicord/Vencord user plugin, rebuild, then enable devcompanionExtended in Discord. The default MCP endpoint is http://127.0.0.1:8487; the ipcPort plugin setting changes it and requires a full Discord restart.
For one trusted repository, add this to <project>/.codex/config.toml. For every trusted Codex project, put the same block in ~/.codex/config.toml instead.
[mcp_servers.devcompanion]
url = "http://127.0.0.1:PORT"Start a fresh Codex task after changing MCP configuration.
Make sure not to forget to make the PORT the same as your ipcPort setting config!
Configure a Streamable HTTP server named devcompanion at http://127.0.0.1:8487 according to your agent's docs.
The distributable skill is in skills/equicord-plugin-dev. Copy that directory into your agent's skill directory, then invoke $equicord-plugin-dev for Equicord plugin tasks. The package includes progressive references for patches, UI, Flux, stores, settings, commands, menus, hooks, and TypeScript patterns, plus MCP dependency metadata for DevCompanion.
batch accepts read-only calls only. Dispatches, REST writes, reloads, settings changes, plugin toggles, watches, and other mutations are rejected. Unknown tools/actions are also rejected instead of being assumed safe.
Oversized results become short previews with a resourceId. Read them with resource.read. Resources expire after ten minutes and are bounded by count, per-entry bytes, and aggregate bytes; truncation and original-byte metadata are explicit.