Skip to content

Register VM service with DTD #8436

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 8, 2025
Merged

Register VM service with DTD #8436

merged 1 commit into from
Aug 8, 2025

Conversation

helin24
Copy link
Member

@helin24 helin24 commented Aug 8, 2025

Addresses #8261

How to test that this works:

  1. Set up an MCP agent - Jake recommended using gemini-cli as the easiest option
  2. With this change in our plugin, start running an app from Android Studio/IJ
  3. Copy the DTD URI (Find Action > Dart: Copy DTD URI to Clipboard)
  4. Start agent and ask for a widget tree of your running app. It should ask for the DTD URI and then provide a widget tree; see below.
helinx-mac:flutter helinx$ gemini

 ███            █████████  ██████████ ██████   ██████ █████ ██████   █████ █████
░░░███         ███░░░░░███░░███░░░░░█░░██████ ██████ ░░███ ░░██████ ░░███ ░░███
  ░░░███      ███     ░░░  ░███  █ ░  ░███░█████░███  ░███  ░███░███ ░███  ░███
    ░░░███   ░███          ░██████    ░███░░███ ░███  ░███  ░███░░███░███  ░███
     ███░    ░███    █████ ░███░░█    ░███ ░░░  ░███  ░███  ░███ ░░██████  ░███
   ███░      ░░███  ░░███  ░███ ░   █ ░███      ░███  ░███  ░███  ░░█████  ░███
 ███░         ░░█████████  ██████████ █████     █████ █████ █████  ░░█████ █████
░░░            ░░░░░░░░░  ░░░░░░░░░░ ░░░░░     ░░░░░ ░░░░░ ░░░░░    ░░░░░ ░░░░░

Tips for getting started:
1. Ask questions, edit files, or run commands.
2. Be specific for the best results.
3. /help for more information.

╭────────────────────────────────────────────────────────╮
│  > can you show me the widget tree of my running app?  │
╰────────────────────────────────────────────────────────╯

✦ I can do that, but I need the Dart Tooling Daemon URI first. Please paste it here. If you're using VS Code or Android Studio, you can find it by searching for the "Copy DTD Uri to clipboard" action.

╭───────────────────────────────────────╮
│  > ws://127.0.0.1:53745/DI-z9OH3XB0=  │
╰───────────────────────────────────────╯

 ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
 │ ✔  connect_dart_tooling_daemon (dart MCP Server) {"uri":"ws://127.0.0.1:53745/DI-z9OH3XB0="}                                                                                                                       │
 │                                                                                                                                                                                                                    │
 │    Connection succeeded                                                                                                                                                                                            │
 ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
 │ ✔  get_widget_tree (dart MCP Server) {}                                                                                                                                                                            │
 │                                                                                                                                                                                                                    │
 │    {"description":"[root]","shouldIndent":true,"widgetRuntimeType":"RootWidget","valueId":"inspector-0","children":[{"description":"MyApp","shouldIndent":true,"widgetRuntimeType":"MyApp","valueId":"inspector-1  │
 │    ","createdByLocalProject":true,"children":[{"description":"MaterialApp","shouldIndent":true,"widgetRuntimeType":"MaterialApp","valueId":"inspector-2","createdByLocalProject":true,"children":[{"description":  │
 │    "MyHomePage","shouldIndent":true,"widgetRuntimeType":"MyHomePage","valueId":"inspector-3","createdByLocalProject":true,"children":[{"description":"Scaffold","shouldIndent":true,"widgetRuntimeType":"Scaffold  │
 │    ","valueId":"inspector-4","createdByLocalProject":true,"children":[{"description":"Center","shouldIndent":true,"widgetRuntimeType":"Center","valueId":"inspector-5","createdByLocalProject":true,"children":[{  │
 │    "description":"Column","shouldIndent":true,"widgetRuntimeType":"Column","valueId":"inspector-8","createdByLocalProject":true,"children":[{"description":"Text","shouldIndent":true,"widgetRuntimeType":"Text",  │
 │    "valueId":"inspector-11","createdByLocalProject":true,"textPreview":"You have pushed the button this many times test                                                                                            │
 │    2:","children":[]},{"description":"Text","shouldIndent":true,"widgetRuntimeType":"Text","valueId":"inspector-12","createdByLocalProject":true,"textPreview":"0","children":[]}]}]},{"description":"AppBar","sh  │
 │    ouldIndent":true,"widgetRuntimeType":"AppBar","valueId":"inspector-6","createdByLocalProject":true,"children":[{"description":"Text","shouldIndent":true,"widgetRuntimeType":"Text","valueId":"inspector-9","c  │
 │    reatedByLocalProject":true,"textPreview":"Flutter Demo Home                                                                                                                                                     │
 │    Page","children":[]}]},{"description":"FloatingActionButton","shouldIndent":true,"widgetRuntimeType":"FloatingActionButton","valueId":"inspector-7","createdByLocalProject":true,"children":[{"description":"I  │
 │    con","shouldIndent":true,"widgetRuntimeType":"Icon","valueId":"inspector-10","createdByLocalProject":true,"children":[]}]}]}]}]}]}]}                                                                            │
 ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦  - [root]
       - MyApp
           - MaterialApp
               - MyHomePage
                   - Scaffold
                       - Center
                           - Column
                               - Text: "You have pushed the button this many times test 2:"
                               - Text: "0"
                       - AppBar
                           - Text: "Flutter Demo Home Page"
                       - FloatingActionButton
                           - Icon

@helin24 helin24 requested review from pq and jwren August 8, 2025 16:31
@helin24
Copy link
Member Author

helin24 commented Aug 8, 2025

CC @jakemac53

@@ -738,6 +746,12 @@ public void onAppStarting(DaemonEvent.AppStarting event) {
@Override
public void onAppDebugPort(@NotNull DaemonEvent.AppDebugPort debugInfo) {
app.setWsUrl(debugInfo.wsUri);
this.appVmServiceUri = debugInfo.wsUri;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we get any sort of identifier that we can use as a map key that we can look up when we get the app stopped event for this particular app?

While the MCP server today only supports one connected app, we do want to have it support multiple in the future, so ideally the IDE plugins wouldn't be assuming just one.

Copy link
Member Author

Choose a reason for hiding this comment

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

IJ can register multiple apps to DTD. There's an app name that's a unique identifier, or the VM service URI is unique too.

This is an example of the params we send with two apps running at the same time:

2025-08-08 09:28:04 io.flutter.run.daemon.FlutterAppDaemonEventListener [INFO   ] Successful request ConnectedApp.registerVmService to DTD with params: {"uri":"ws://127.0.0.1:50489/m1aDg3zyHq4=/ws","name":"752c8ff1-86dd-4c91-abab-825747059057"}  
2025-08-08 09:29:33 io.flutter.run.daemon.FlutterAppDaemonEventListener [INFO   ] Successful request ConnectedApp.registerVmService to DTD with params: {"uri":"ws://127.0.0.1:51041/iUbSRQCLBfg=/ws","name":"65328744-2368-46bf-9e23-70066b219342"}  

The unregisterVmService takes the URI as a param also, so I imagine you would use that as the key?

@helin24 helin24 merged commit 35796f6 into flutter:master Aug 8, 2025
7 checks passed
@helin24 helin24 deleted the mcp branch August 8, 2025 17:14
@pq
Copy link
Collaborator

pq commented Aug 10, 2025

/fyi @DanTup who did the analogous work for VS Code.

Copy link
Collaborator

@pq pq left a comment

Choose a reason for hiding this comment

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

Awesome! 🎉

final JsonObject params = new JsonObject();
params.addProperty("uri", debugInfo.wsUri);
params.addProperty("name", debugInfo.appId);
sendDtdRequest("ConnectedApp.registerVmService", params);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure how we want to test this (And I'd love to get your thoughts), but in anticipation, maybe we should introduce some constants somewhere?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, for now I can add these methods as constants. Maybe a separate file that links to DTD documentation would be helpful.

I haven't thought much about testing; I suppose one part would be testing dtdService.sendRequest in the Dart plugin. And ultimately integration testing in the Dart plugin.

@helin24
Copy link
Member Author

helin24 commented Aug 11, 2025

Some clean up in #8442

@pq
Copy link
Collaborator

pq commented Aug 11, 2025

Awesome. Thanks for following up!

@helin24
Copy link
Member Author

helin24 commented Aug 11, 2025

I added some notes above on how to test this

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.

4 participants