Skip to content

Commit 76adee4

Browse files
committed
feat: Expose __DC_CONTROLLERS__ for MCP integration into devtools
1 parent 5783267 commit 76adee4

File tree

4 files changed

+51
-6
lines changed

4 files changed

+51
-6
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@data-client/core': patch
3+
'@data-client/react': patch
4+
'@data-client/vue': patch
5+
---
6+
7+
Add `globalThis.__DC_CONTROLLERS__` Map in dev mode for programmatic store access from browser DevTools MCP, React Native debuggers, and other development tooling.
8+
9+
Each [DataProvider](/docs/api/DataProvider) registers its [Controller](/docs/api/Controller) keyed by the devtools connection name, supporting multiple providers on the same page.

docs/core/api/DevToolsManager.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,30 @@ const managers = getDefaultManagers({
107107
});
108108
```
109109

110+
## Programmatic store access {#controllers}
111+
112+
In development mode, `DevToolsManager` registers each [Controller](/docs/api/Controller) on
113+
`globalThis.__DC_CONTROLLERS__` — a `Map` keyed by the devtools connection name. This works
114+
in browsers, React Native, and Node.
115+
116+
```js title="Browser DevTools console"
117+
// List all registered providers
118+
__DC_CONTROLLERS__.keys();
119+
120+
// Get state from the first provider
121+
__DC_CONTROLLERS__.values().next().value.getState();
122+
123+
// Get state by name
124+
__DC_CONTROLLERS__.get('Data Client: My App').getState();
125+
```
126+
127+
This is useful for AI coding assistants using the [Chrome DevTools MCP](https://developer.chrome.com/blog/chrome-devtools-mcp)
128+
or [Expo MCP](https://docs.expo.dev/eas/ai/mcp/) to programmatically inspect and interact
129+
with the store. Each [DataProvider](/docs/api/DataProvider) registers independently, so
130+
multiple providers on the same page are fully supported.
131+
132+
Controllers are removed from the map when `cleanup()` is called.
133+
110134
## More info
111135

112136
Using this Manager allows in browser [debugging and store inspection](../getting-started/debugging.md).

packages/core/src/manager/DevtoolsManager.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,21 @@ export default class DevToolsManager implements Manager {
9494
protected actions: [ActionTypes, State<unknown>][] = [];
9595
declare protected controller: Controller;
9696
declare skipLogging?: (action: ActionTypes) => boolean;
97+
declare protected devtoolsName: string;
9798
maxBufferLength = 100;
9899

99100
constructor(
100101
config?: DevToolsConfig,
101102
skipLogging?: (action: ActionTypes) => boolean,
102103
) {
103104
/* istanbul ignore next */
105+
const options = { ...DEFAULT_CONFIG, ...config };
106+
this.devtoolsName =
107+
options.name ?? `Data Client: ${globalThis.document?.title}`;
104108
this.devTools =
105109
typeof window !== 'undefined' &&
106110
(window as any).__REDUX_DEVTOOLS_EXTENSION__ &&
107-
(window as any).__REDUX_DEVTOOLS_EXTENSION__.connect({
108-
...DEFAULT_CONFIG,
109-
...config,
110-
});
111+
(window as any).__REDUX_DEVTOOLS_EXTENSION__.connect(options);
111112
// we cut it in half so we should double so we don't lose
112113
if (config?.maxAge) this.maxBufferLength = config.maxAge * 2;
113114
if (skipLogging) this.skipLogging = skipLogging;
@@ -118,8 +119,8 @@ export default class DevToolsManager implements Manager {
118119
/* istanbul ignore next */
119120
if (process.env.NODE_ENV !== 'production') {
120121
this.prototype.middleware = function (controller) {
121-
if (!this.devTools) return next => action => next(action);
122122
this.controller = controller;
123+
if (!this.devTools) return next => action => next(action);
123124
const reducer = createReducer(controller as any);
124125
let state = controller.getState();
125126
return next => action => {
@@ -158,6 +159,12 @@ export default class DevToolsManager implements Manager {
158159

159160
/** Called when initial state is ready */
160161
init(state: State<any>) {
162+
if (process.env.NODE_ENV !== 'production') {
163+
((globalThis as any).__DC_CONTROLLERS__ ??= new Map()).set(
164+
this.devtoolsName,
165+
this.controller,
166+
);
167+
}
161168
if (process.env.NODE_ENV !== 'production' && this.devTools) {
162169
this.devTools.init(state);
163170
this.devTools.subscribe((msg: any) => {
@@ -186,5 +193,9 @@ export default class DevToolsManager implements Manager {
186193
}
187194

188195
/** Ensures all subscriptions are cleaned up. */
189-
cleanup() {}
196+
cleanup() {
197+
if (process.env.NODE_ENV !== 'production') {
198+
(globalThis as any).__DC_CONTROLLERS__?.delete(this.devtoolsName);
199+
}
200+
}
190201
}

website/blog/2026-01-19-v0.16-release-announcement.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { parallelFetchFixtures } from '@site/src/fixtures/post-comments';
1919
**Performance:**
2020

2121
**Other Improvements:**
22+
- [DevToolsManager](/docs/api/DevToolsManager#controllers) exposes `globalThis.__DC_CONTROLLERS__` in dev mode for programmatic store access from [Chrome DevTools MCP](https://developer.chrome.com/blog/chrome-devtools-mcp) and [Expo MCP](https://docs.expo.dev/eas/ai/mcp/)
2223
- Remove misleading 'Uncaught Suspense' warning during Next.js SSR
2324
- Fix `sideEffect: false` type being lost with `method: 'POST'` in [RestEndpoint](/rest/api/RestEndpoint)
2425

0 commit comments

Comments
 (0)