Skip to content

feat(core): Expose __DC_CONTROLLERS__ for programmatic store access via MCP#3753

Open
ntucker wants to merge 2 commits intomasterfrom
devtools
Open

feat(core): Expose __DC_CONTROLLERS__ for programmatic store access via MCP#3753
ntucker wants to merge 2 commits intomasterfrom
devtools

Conversation

@ntucker
Copy link
Collaborator

@ntucker ntucker commented Mar 1, 2026

Motivation

AI coding assistants using Chrome DevTools MCP or Expo MCP have no programmatic way to inspect or interact with the data-client store. The Redux DevTools Extension integration is one-way (app → extension) with no API to read state back from page context via evaluate_script.

Solution

DevToolsManager now registers each Controller on globalThis.__DC_CONTROLLERS__ (a Map keyed by the devtools connection name) during init() in dev mode.

  • Browser: Chrome DevTools MCP can call __DC_CONTROLLERS__.values().next().value.getState() via evaluate_script
  • React Native: Uses globalThis instead of window, so it works with Hermes debugger and Expo MCP
  • Multiple providers: Each DataProvider registers independently by name
  • Cleanup: Controllers are removed from the map in cleanup() to prevent leaks
  • Same trust boundary: Fully gated behind process.env.NODE_ENV !== 'production', matching the existing Redux DevTools integration

Also includes docs update for DevToolsManager and blog entry.

TODO

  • Create a Cursor skill for devtools debugging in React (using __DC_CONTROLLERS__ with Chrome DevTools MCP)

Made with Cursor


Note

Low Risk
Dev-only change that registers controllers on globalThis during DevToolsManager.init() and removes them on cleanup(), with minimal impact on production behavior.

Overview
Dev-only controller exposure for tooling. DevToolsManager now registers its Controller into globalThis.__DC_CONTROLLERS__ (a Map keyed by the devtools connection name) during init(), and removes it during cleanup(), enabling programmatic store access in browser/RN/Node debugging tools.

Docs and release notes are updated to describe accessing controllers via __DC_CONTROLLERS__, and a new Cursor skill reference (devtools-debugging.md) documents Chrome DevTools MCP workflows (including optional action logging). Types are updated to include the new devtoolsName field, and a changeset bumps @data-client/core/react/vue as patch releases.

Written by Cursor Bugbot for commit db5b610. This will update automatically on new commits. Configure here.

@changeset-bot
Copy link

changeset-bot bot commented Mar 1, 2026

🦋 Changeset detected

Latest commit: db5b610

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@data-client/core Patch
@data-client/react Patch
@data-client/vue Patch
example-benchmark Patch
test-bundlesize Patch
coinbase-lite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

Size Change: +60 B (+0.07%)

Total Size: 80.5 kB

Filename Size Change
examples/test-bundlesize/dist/rdcClient.js 10.3 kB +60 B (+0.59%)
ℹ️ View Unchanged
Filename Size
examples/test-bundlesize/dist/App.js 3.18 kB
examples/test-bundlesize/dist/polyfill.js 307 B
examples/test-bundlesize/dist/rdcEndpoint.js 6.3 kB
examples/test-bundlesize/dist/react.js 59.7 kB
examples/test-bundlesize/dist/webpack-runtime.js 726 B

compressed-size-action

@codecov
Copy link

codecov bot commented Mar 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.05%. Comparing base (5783267) to head (db5b610).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3753   +/-   ##
=======================================
  Coverage   98.05%   98.05%           
=======================================
  Files         151      151           
  Lines        2821     2821           
  Branches      553      553           
=======================================
  Hits         2766     2766           
  Misses         11       11           
  Partials       44       44           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Details
Benchmark suite Current: db5b610 Previous: 7d374d6 Ratio
normalizeLong 459 ops/sec (±1.03%) 448 ops/sec (±1.40%) 0.98
normalizeLong Values 421 ops/sec (±0.25%) 406 ops/sec (±0.51%) 0.96
denormalizeLong 293 ops/sec (±2.86%) 291 ops/sec (±2.97%) 0.99
denormalizeLong Values 270 ops/sec (±2.08%) 264 ops/sec (±1.98%) 0.98
denormalizeLong donotcache 1057 ops/sec (±0.18%) 1051 ops/sec (±0.16%) 0.99
denormalizeLong Values donotcache 778 ops/sec (±0.17%) 756 ops/sec (±0.20%) 0.97
denormalizeShort donotcache 500x 1581 ops/sec (±0.13%) 1609 ops/sec (±0.21%) 1.02
denormalizeShort 500x 858 ops/sec (±2.07%) 839 ops/sec (±2.27%) 0.98
denormalizeShort 500x withCache 5974 ops/sec (±0.17%) 6394 ops/sec (±0.32%) 1.07
queryShort 500x withCache 2780 ops/sec (±0.22%) 2761 ops/sec (±0.45%) 0.99
buildQueryKey All 53808 ops/sec (±0.46%) 53474 ops/sec (±0.61%) 0.99
query All withCache 6474 ops/sec (±0.33%) 6489 ops/sec (±0.17%) 1.00
denormalizeLong with mixin Entity 282 ops/sec (±2.01%) 276 ops/sec (±2.41%) 0.98
denormalizeLong withCache 8166 ops/sec (±0.18%) 7251 ops/sec (±0.43%) 0.89
denormalizeLong Values withCache 5150 ops/sec (±0.17%) 5073 ops/sec (±0.16%) 0.99
denormalizeLong All withCache 6256 ops/sec (±0.17%) 6235 ops/sec (±0.10%) 1.00
denormalizeLong Query-sorted withCache 6448 ops/sec (±0.21%) 6485 ops/sec (±0.12%) 1.01
denormalizeLongAndShort withEntityCacheOnly 1799 ops/sec (±0.29%) 1725 ops/sec (±0.22%) 0.96
getResponse 4609 ops/sec (±0.59%) 4574 ops/sec (±0.48%) 0.99
getResponse (null) 10416446 ops/sec (±0.89%) 10403427 ops/sec (±0.98%) 1.00
getResponse (clear cache) 267 ops/sec (±2.10%) 261 ops/sec (±2.21%) 0.98
getSmallResponse 3384 ops/sec (±0.17%) 3294 ops/sec (±0.16%) 0.97
getSmallInferredResponse 2496 ops/sec (±0.39%) 2518 ops/sec (±0.37%) 1.01
getResponse Collection 4570 ops/sec (±0.42%) 4516 ops/sec (±0.38%) 0.99
get Collection 4563 ops/sec (±0.19%) 4572 ops/sec (±0.28%) 1.00
get Query-sorted 5232 ops/sec (±0.33%) 5198 ops/sec (±0.11%) 0.99
setLong 465 ops/sec (±0.23%) 449 ops/sec (±0.24%) 0.97
setLongWithMerge 256 ops/sec (±0.31%) 257 ops/sec (±0.26%) 1.00
setLongWithSimpleMerge 270 ops/sec (±0.23%) 268 ops/sec (±0.34%) 0.99
setSmallResponse 500x 943 ops/sec (±0.23%) 952 ops/sec (±0.08%) 1.01

This comment was automatically generated by workflow using github-action-benchmark.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

navigate_page → initScript:

window.__DC_ACTION_LOG__ = [];
const _origDispatch = Object.getOwnPropertyDescriptor(Object.getPrototypeOf({}), 'dispatch');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code in skill documentation example

Low Severity

The _origDispatch variable is assigned the result of Object.getOwnPropertyDescriptor(Object.getPrototypeOf({}), 'dispatch'), which always returns undefined since Object.prototype has no dispatch property. The variable is never referenced again in the code block. This appears to be leftover from an earlier approach and is dead code in a skill file that AI coding assistants will follow as a template.

Fix in Cursor Fix in Web

if (process.env.NODE_ENV !== 'production') {
(globalThis as any).__DC_CONTROLLERS__?.delete(this.devtoolsName);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleanup removes another provider's active controller entry

Low Severity

When multiple DataProviders use the default DevToolsManager (no custom name config), they share the same devtoolsName from DEFAULT_CONFIG.name. The cleanup() unconditionally deletes the map entry by name without checking if the entry still belongs to this instance. If Provider A unmounts, it removes Provider B's still-active controller from __DC_CONTROLLERS__, even though Provider B registered after A. The delete call needs an identity check like map.get(name) === this.controller before removing. The PR docs claim multiple providers are "fully supported," but the default naming makes this collision the common case.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant