@gjsify/devtools-mcp 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/lib/esm/index.js +1 -1
- package/lib/esm/profiles/browser.js +1 -1
- package/lib/esm/profiles/cdp.js +1 -0
- package/lib/esm/profiles/nativescript-types.js +0 -0
- package/lib/esm/profiles/nativescript.js +1 -0
- package/lib/esm/transports/ns-cdp-client.js +1 -0
- package/lib/types/index.d.ts +5 -0
- package/lib/types/profiles/cdp.d.ts +4 -0
- package/lib/types/profiles/cdp.spec.d.ts +2 -0
- package/lib/types/profiles/nativescript-types.d.ts +12 -0
- package/lib/types/profiles/nativescript.d.ts +37 -0
- package/lib/types/transports/ns-cdp-client.d.ts +44 -0
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ A `DevtoolsToolProfile` maps the generic + app-specific DBus methods to MCP tool
|
|
|
34
34
|
- **generic** — `get_status`, `screenshot`, `list_actions`, `activate_action`, `resize_window`, `present_window`, and the introspection tools.
|
|
35
35
|
- **storybook** — `list_stories`, `get_current_story`, `open_story`, `set_story_arg` (+ generics; screenshot via the generic `screenshot`).
|
|
36
36
|
- **browser** — the [`@gjsify/devtools-browser`](../devtools-browser) web-debugging tools: `navigate`, `page_screenshot`, `eval_js`, `inspect_element`, `dom_tree`, `get_network`, … (+ generics).
|
|
37
|
+
- **cdp** — the [`@gjsify/devtools-cdp`](../devtools-cdp) WebKit-remote-inspector tools: `cdp_send` (any `Domain.command`), `cdp_connect` / `cdp_discover_targets` / `cdp_drain_events`, plus ~13 curated typed tools (`cdp_runtime_evaluate`, `cdp_dom_get_document`, `cdp_css_get_computed_style_for_node`, …) generated from the protocol spec (+ generics).
|
|
37
38
|
|
|
38
39
|
## Library API
|
|
39
40
|
|
package/lib/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DbusDevtoolsClient as e}from"./dbus-client.js";import{fail as t,image as n,ok as r}from"./tool-result.js";import{dbusError as i}from"./error-map.js";import{registerGenericTools as a}from"./generic-tools.js";import{GjsStdioTransport as o}from"./stdio-transport.js";import{runDevtoolsMcp as s}from"./run-server.js";import{registerStorybookTools as c,storybookProfile as l}from"./profiles/storybook.js";import{browserProfile as u,registerBrowserTools as d}from"./profiles/browser.js";export*from"@gjsify/devtools-protocol";export{e as DbusDevtoolsClient,o as GjsStdioTransport,u as browserProfile,i as dbusError,t as fail,n as image,r as ok,d as registerBrowserTools,a as registerGenericTools,c as registerStorybookTools,s as runDevtoolsMcp,l as storybookProfile};
|
|
1
|
+
import{DbusDevtoolsClient as e}from"./dbus-client.js";import{fail as t,image as n,ok as r}from"./tool-result.js";import{dbusError as i}from"./error-map.js";import{registerGenericTools as a}from"./generic-tools.js";import{GjsStdioTransport as o}from"./stdio-transport.js";import{runDevtoolsMcp as s}from"./run-server.js";import{registerStorybookTools as c,storybookProfile as l}from"./profiles/storybook.js";import{browserProfile as u,registerBrowserTools as d}from"./profiles/browser.js";import{cdpProfile as f,registerCdpTools as p}from"./profiles/cdp.js";import{NsCdpClient as m}from"./transports/ns-cdp-client.js";import{nativescriptProfile as h,registerNsGenericTools as g,runNativescriptMcp as _}from"./profiles/nativescript.js";export*from"@gjsify/devtools-protocol";export{e as DbusDevtoolsClient,o as GjsStdioTransport,m as NsCdpClient,u as browserProfile,f as cdpProfile,i as dbusError,t as fail,n as image,h as nativescriptProfile,r as ok,d as registerBrowserTools,p as registerCdpTools,a as registerGenericTools,g as registerNsGenericTools,c as registerStorybookTools,s as runDevtoolsMcp,_ as runNativescriptMcp,l as storybookProfile};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"../_virtual/_rolldown/runtime.js";import{image as e}from"../tool-result.js";import t from"@girs/glib-2.0";import{z as n}from"zod";const strv=e=>t.Variant.new_string(e),intv=e=>t.Variant.new_int32(e),r={instance:n.string().optional().describe(`Browser instance label; omit for the default app`)};function registerBrowserTools(i){let{server:a,client:o,ok:s,dbusError:c}=i;a.registerTool(`navigate`,{description:"Navigate to a URL (a `page:*` built-in page or `https://…`). Updates the history stack + URL bar.",inputSchema:n.object({url:n.string(),...r})},async({url:e,instance:n})=>{try{return await o.control(n,`BrowserNavigate`,t.Variant.new_tuple([strv(e)]),null),s(`Navigated to ${e}`)}catch(e){return c(e,n)}});for(let[e,t,i]of[[`back`,`BrowserBack`,`Go back one entry in browser history.`],[`forward`,`BrowserForward`,`Go forward one entry in browser history.`],[`reload`,`BrowserReload`,`Reload the current page.`]])a.registerTool(e,{description:i,inputSchema:n.object({...r})},async({instance:e})=>{try{return await o.control(e,t,null,null),s(i)}catch(t){return c(t,e)}});a.registerTool(`get_page_info`,{description:`Current page state: uri, appUrl, title, canGoBack, canGoForward, lastLoadError.`,inputSchema:n.object({...r})},async({instance:e})=>{try{return s(await o.jsonCall(e,`BrowserGetPageInfo`))}catch(t){return c(t,e)}}),a.registerTool(`page_screenshot`,{description:"PNG of the rendered WEB CONTENT via the WebKit compositor (use this, not the generic `screenshot`, for the page — WebKit composites out-of-process so the GSK shot is blank). region=full (whole scrollable document) | visible (viewport).",inputSchema:n.object({region:n.enum([`full`,`visible`]).optional().default(`full`),...r})},async({region:n,instance:r})=>{try{let a=(await o.control(r,`BrowserScreenshot`,t.Variant.new_tuple([strv(n)]),`(ay)`)).get_child_value(0).deepUnpack();return!a||a.length===0?i.fail(`page_screenshot returned no data — is a page loaded and the window realized?`):e(t.base64_encode(a))}catch(e){return c(e,r)}}),a.registerTool(`set_viewport`,{description:`Set the WebView content-region size in pixels (responsive testing); returns the applied size.`,inputSchema:n.object({width:n.number().int(),height:n.number().int(),...r})},async({width:e,height:n,instance:r})=>{try{let[i,a]=(await o.control(r,`BrowserSetViewport`,t.Variant.new_tuple([intv(e),intv(n)]),`(ii)`)).recursiveUnpack();return s(`Viewport set to ${i}x${a}`)}catch(e){return c(e,r)}}),a.registerTool(`eval_js`,{description:"Evaluate a JavaScript EXPRESSION in the page and return its value (JSON round-trip). Wrap multi-statement code in an IIFE, e.g. `(() => { const a = 1; return a + 1; })()`. Page errors surface as an error.",inputSchema:n.object({script:n.string(),...r})},async({script:e,instance:n})=>{try{let[r]=(await o.control(n,`BrowserEvalJs`,t.Variant.new_tuple([strv(e)]),`(s)`)).recursiveUnpack();return s(r)}catch(e){return c(e,n)}}),a.registerTool(`get_links`,{description:"Every `<a href>` on the page as {href, text, title}.",inputSchema:n.object({...r})},async({instance:e})=>{try{return s(await o.jsonCall(e,`BrowserGetLinks`))}catch(t){return c(t,e)}}),a.registerTool(`follow_link`,{description:"Click the first element matching a CSS selector (or an `<a>` by exact visible text), then wait for the resulting navigation. Returns {clicked, navigated, uri}.",inputSchema:n.object({selectorOrText:n.string(),...r})},async({selectorOrText:e,instance:n})=>{try{let[r]=(await o.control(n,`BrowserFollowLink`,t.Variant.new_tuple([strv(e)]),`(s)`)).recursiveUnpack();return s(r)}catch(e){return c(e,n)}}),a.registerTool(`query_dom`,{description:`Metadata for elements matching a CSS selector ({tagName, id, className, text, href?, visible}).`,inputSchema:n.object({selector:n.string(),...r})},async({selector:e,instance:n})=>{try{let[r]=(await o.control(n,`BrowserQueryDom`,t.Variant.new_tuple([strv(e)]),`(s)`)).recursiveUnpack();return s(r)}catch(e){return c(e,n)}}),a.registerTool(`wait_for_load`,{description:`Wait for the next page load to finish (ms; default 30000). Returns whether it loaded in time.`,inputSchema:n.object({timeout:n.number().int().optional().default(3e4),...r})},async({timeout:e,instance:n})=>{try{let[r]=(await o.control(n,`BrowserWaitForLoad`,t.Variant.new_tuple([intv(e)]),`(b)`)).recursiveUnpack();return s(r?`Page loaded.`:`Timed out waiting for the next load.`)}catch(e){return c(e,n)}}),a.registerTool(`get_console`,{description:`Buffered console.* output captured from the page ({level, args}).`,inputSchema:n.object({...r})},async({instance:e})=>{try{return s(await o.jsonCall(e,`BrowserGetConsole`))}catch(t){return c(t,e)}}),a.registerTool(`inspect_element`,{description:`Inspect the first element matching a CSS selector — tag/id/class + attributes + bounding rect + box model (margin/border/padding/content) + a curated set of computed styles. The Elements + Computed + Box Model panels in one call.`,inputSchema:n.object({selector:n.string(),...r})},async({selector:e,instance:n})=>{try{let[r]=(await o.control(n,`BrowserInspectElement`,t.Variant.new_tuple([strv(e)]),`(s)`)).recursiveUnpack();return s(r)}catch(e){return c(e,n)}}),a.registerTool(`dom_tree`,{description:`The DOM/Elements tree from a selector (default: document root) down to max_depth, capped per node.`,inputSchema:n.object({selector:n.string().optional().default(``),maxDepth:n.number().int().optional().default(3),...r})},async({selector:e,maxDepth:n,instance:r})=>{try{let[i]=(await o.control(r,`BrowserDomTree`,t.Variant.new_tuple([strv(e),intv(n)]),`(s)`)).recursiveUnpack();return s(i)}catch(e){return c(e,r)}}),a.registerTool(`get_network`,{description:`Page network activity via the Resource Timing API — the navigation entry + each resource.`,inputSchema:n.object({...r})},async({instance:e})=>{try{return s(await o.jsonCall(e,`BrowserGetNetwork`))}catch(t){return c(t,e)}}),a.registerTool(`get_accessibility`,{description:`Approximate accessibility tree from a selector (default: body) — role + accessible name + aria-* per node, down to max_depth.`,inputSchema:n.object({selector:n.string().optional().default(``),maxDepth:n.number().int().optional().default(4),...r})},async({selector:e,maxDepth:n,instance:r})=>{try{let[i]=(await o.control(r,`BrowserGetAccessibility`,t.Variant.new_tuple([strv(e),intv(n)]),`(s)`)).recursiveUnpack();return s(i)}catch(e){return c(e,r)}});for(let[e,t,i]of[[`open_inspector`,`BrowserOpenInspector`,`Open the WebKit Web Inspector panel on the browser window.`],[`close_inspector`,`BrowserCloseInspector`,`Close the WebKit Web Inspector panel.`]])a.registerTool(e,{description:i,inputSchema:n.object({...r})},async({instance:e})=>{try{return await o.control(e,t,null,null),s(i)}catch(t){return c(t,e)}})}function browserProfile(e){return{name:`gjsify-browser-devtools`,version:`0.10.0`,busNameBase:e,registerTools:registerBrowserTools}}export{browserProfile,registerBrowserTools};
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{image as e}from"../tool-result.js";import t from"@girs/glib-2.0";import{z as n}from"zod";const strv=e=>t.Variant.new_string(e),intv=e=>t.Variant.new_int32(e),r={instance:n.string().optional().describe(`Browser instance label; omit for the default app`)};function registerBrowserTools(i){let{server:a,client:o,ok:s,dbusError:c}=i;a.registerTool(`navigate`,{description:"Navigate to a URL (a `page:*` built-in page or `https://…`). Updates the history stack + URL bar.",inputSchema:n.object({url:n.string(),...r})},async({url:e,instance:n})=>{try{return await o.control(n,`BrowserNavigate`,t.Variant.new_tuple([strv(e)]),null),s(`Navigated to ${e}`)}catch(e){return c(e,n)}});for(let[e,t,i]of[[`back`,`BrowserBack`,`Go back one entry in browser history.`],[`forward`,`BrowserForward`,`Go forward one entry in browser history.`],[`reload`,`BrowserReload`,`Reload the current page.`]])a.registerTool(e,{description:i,inputSchema:n.object({...r})},async({instance:e})=>{try{return await o.control(e,t,null,null),s(i)}catch(t){return c(t,e)}});a.registerTool(`get_page_info`,{description:`Current page state: uri, appUrl, title, canGoBack, canGoForward, lastLoadError.`,inputSchema:n.object({...r})},async({instance:e})=>{try{return s(await o.jsonCall(e,`BrowserGetPageInfo`))}catch(t){return c(t,e)}}),a.registerTool(`page_screenshot`,{description:"PNG of the rendered WEB CONTENT via the WebKit compositor (use this, not the generic `screenshot`, for the page — WebKit composites out-of-process so the GSK shot is blank). region=full (whole scrollable document) | visible (viewport).",inputSchema:n.object({region:n.enum([`full`,`visible`]).optional().default(`full`),...r})},async({region:n,instance:r})=>{try{let a=(await o.control(r,`BrowserScreenshot`,t.Variant.new_tuple([strv(n)]),`(ay)`)).get_child_value(0).deepUnpack();return!a||a.length===0?i.fail(`page_screenshot returned no data — is a page loaded and the window realized?`):e(t.base64_encode(a))}catch(e){return c(e,r)}}),a.registerTool(`set_viewport`,{description:`Set the WebView content-region size in pixels (responsive testing); returns the applied size.`,inputSchema:n.object({width:n.number().int(),height:n.number().int(),...r})},async({width:e,height:n,instance:r})=>{try{let[i,a]=(await o.control(r,`BrowserSetViewport`,t.Variant.new_tuple([intv(e),intv(n)]),`(ii)`)).recursiveUnpack();return s(`Viewport set to ${i}x${a}`)}catch(e){return c(e,r)}}),a.registerTool(`eval_js`,{description:"Evaluate a JavaScript EXPRESSION in the page and return its value (JSON round-trip). Wrap multi-statement code in an IIFE, e.g. `(() => { const a = 1; return a + 1; })()`. Page errors surface as an error.",inputSchema:n.object({script:n.string(),...r})},async({script:e,instance:n})=>{try{let[r]=(await o.control(n,`BrowserEvalJs`,t.Variant.new_tuple([strv(e)]),`(s)`)).recursiveUnpack();return s(r)}catch(e){return c(e,n)}});let storybookEval=async(e,n)=>{let r=`(function(){var s=window.__storybook;if(!s)return ${JSON.stringify(`(not a storybook page: window.__storybook is undefined)`)};return ${n};})()`,[i]=(await o.control(e,`BrowserEvalJs`,t.Variant.new_tuple([strv(r)]),`(s)`)).recursiveUnpack();return i};a.registerTool(`list_stories`,{description:`Storybook: list the stories on a @gjsify/adwaita-storybook page (title, story, category, controls).`,inputSchema:n.object({...r})},async({instance:e})=>{try{return s(await storybookEval(e,`JSON.stringify(s.listStories())`))}catch(t){return c(t,e)}}),a.registerTool(`open_story`,{description:`Storybook: open a story by its "Category/Name" title; returns the now-current story.`,inputSchema:n.object({title:n.string(),...r})},async({title:e,instance:t})=>{try{return s(await storybookEval(t,`JSON.stringify({opened:s.openStory(${JSON.stringify(e)}),current:s.getCurrentStory()})`))}catch(e){return c(e,t)}}),a.registerTool(`get_current_story`,{description:`Storybook: the active story as {title, story, args}.`,inputSchema:n.object({...r})},async({instance:e})=>{try{return s(await storybookEval(e,`JSON.stringify(s.getCurrentStory())`))}catch(t){return c(t,e)}}),a.registerTool(`set_story_arg`,{description:`Storybook: set one control arg on the active story (drives the live preview); value is string|number|boolean.`,inputSchema:n.object({name:n.string(),value:n.union([n.string(),n.number(),n.boolean()]),...r})},async({name:e,value:t,instance:n})=>{try{return s(await storybookEval(n,`JSON.stringify(s.setArg(${JSON.stringify(e)}, ${JSON.stringify(t)}))`))}catch(e){return c(e,n)}}),a.registerTool(`get_links`,{description:"Every `<a href>` on the page as {href, text, title}.",inputSchema:n.object({...r})},async({instance:e})=>{try{return s(await o.jsonCall(e,`BrowserGetLinks`))}catch(t){return c(t,e)}}),a.registerTool(`follow_link`,{description:"Click the first element matching a CSS selector (or an `<a>` by exact visible text), then wait for the resulting navigation. Returns {clicked, navigated, uri}.",inputSchema:n.object({selectorOrText:n.string(),...r})},async({selectorOrText:e,instance:n})=>{try{let[r]=(await o.control(n,`BrowserFollowLink`,t.Variant.new_tuple([strv(e)]),`(s)`)).recursiveUnpack();return s(r)}catch(e){return c(e,n)}}),a.registerTool(`query_dom`,{description:`Metadata for elements matching a CSS selector ({tagName, id, className, text, href?, visible}).`,inputSchema:n.object({selector:n.string(),...r})},async({selector:e,instance:n})=>{try{let[r]=(await o.control(n,`BrowserQueryDom`,t.Variant.new_tuple([strv(e)]),`(s)`)).recursiveUnpack();return s(r)}catch(e){return c(e,n)}}),a.registerTool(`wait_for_load`,{description:`Wait for the next page load to finish (ms; default 30000). Returns whether it loaded in time.`,inputSchema:n.object({timeout:n.number().int().optional().default(3e4),...r})},async({timeout:e,instance:n})=>{try{let[r]=(await o.control(n,`BrowserWaitForLoad`,t.Variant.new_tuple([intv(e)]),`(b)`)).recursiveUnpack();return s(r?`Page loaded.`:`Timed out waiting for the next load.`)}catch(e){return c(e,n)}}),a.registerTool(`get_console`,{description:`Buffered console.* output captured from the page ({level, args}).`,inputSchema:n.object({...r})},async({instance:e})=>{try{return s(await o.jsonCall(e,`BrowserGetConsole`))}catch(t){return c(t,e)}}),a.registerTool(`inspect_element`,{description:`Inspect the first element matching a CSS selector — tag/id/class + attributes + bounding rect + box model (margin/border/padding/content) + a curated set of computed styles. The Elements + Computed + Box Model panels in one call.`,inputSchema:n.object({selector:n.string(),...r})},async({selector:e,instance:n})=>{try{let[r]=(await o.control(n,`BrowserInspectElement`,t.Variant.new_tuple([strv(e)]),`(s)`)).recursiveUnpack();return s(r)}catch(e){return c(e,n)}}),a.registerTool(`dom_tree`,{description:`The DOM/Elements tree from a selector (default: document root) down to max_depth, capped per node.`,inputSchema:n.object({selector:n.string().optional().default(``),maxDepth:n.number().int().optional().default(3),...r})},async({selector:e,maxDepth:n,instance:r})=>{try{let[i]=(await o.control(r,`BrowserDomTree`,t.Variant.new_tuple([strv(e),intv(n)]),`(s)`)).recursiveUnpack();return s(i)}catch(e){return c(e,r)}}),a.registerTool(`get_network`,{description:`Page network activity via the Resource Timing API — the navigation entry + each resource.`,inputSchema:n.object({...r})},async({instance:e})=>{try{return s(await o.jsonCall(e,`BrowserGetNetwork`))}catch(t){return c(t,e)}}),a.registerTool(`get_accessibility`,{description:`Approximate accessibility tree from a selector (default: body) — role + accessible name + aria-* per node, down to max_depth.`,inputSchema:n.object({selector:n.string().optional().default(``),maxDepth:n.number().int().optional().default(4),...r})},async({selector:e,maxDepth:n,instance:r})=>{try{let[i]=(await o.control(r,`BrowserGetAccessibility`,t.Variant.new_tuple([strv(e),intv(n)]),`(s)`)).recursiveUnpack();return s(i)}catch(e){return c(e,r)}});for(let[e,t,i]of[[`open_inspector`,`BrowserOpenInspector`,`Open the WebKit Web Inspector panel on the browser window.`],[`close_inspector`,`BrowserCloseInspector`,`Close the WebKit Web Inspector panel.`]])a.registerTool(e,{description:i,inputSchema:n.object({...r})},async({instance:e})=>{try{return await o.control(e,t,null,null),s(i)}catch(t){return c(t,e)}})}function browserProfile(e){return{name:`gjsify-browser-devtools`,version:`0.10.0`,busNameBase:e,registerTools:registerBrowserTools}}export{browserProfile,registerBrowserTools};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import e from"@girs/glib-2.0";import{z as t}from"zod";import{PROTOCOL_SPEC as n,generateCdpTools as r}from"@gjsify/devtools-cdp";const strv=t=>e.Variant.new_string(t),i={instance:t.string().optional().describe(`App instance label; omit for the default app`)},a=new Set([`Runtime.evaluate`,`Runtime.callFunctionOn`,`Runtime.getProperties`,`Runtime.getPreview`,`DOM.getDocument`,`DOM.querySelector`,`DOM.querySelectorAll`,`DOM.getOuterHTML`,`DOM.setAttributeValue`,`CSS.getComputedStyleForNode`,`CSS.getMatchedStylesForNode`,`Debugger.setBreakpoint`,`Debugger.setPauseOnExceptions`]);function zodForParam(e,n){if(n&&n.length>0)return t.enum(n);switch(e){case`string`:return t.string();case`number`:return t.number();case`boolean`:return t.boolean();case`array`:return t.array(t.unknown());case`object`:return t.record(t.string(),t.unknown());default:return t.unknown()}}async function cdpSend(t,n,r,i){let[a]=(await t.client.control(n,`CdpSend`,e.Variant.new_tuple([strv(r),strv(JSON.stringify(i))]),`(s)`)).recursiveUnpack();return a}function registerCdpTools(o){let{server:s,client:c,ok:l,dbusError:u}=o;s.registerTool(`cdp_send`,{description:'Send any WebKit Remote Inspector command `Domain.command` (e.g. "Page.reload", "Network.enable") with optional params, and return its result JSON. The universal fallback for commands without a curated tool. Requires a prior cdp_connect.',inputSchema:t.object({method:t.string().describe(`Protocol method, e.g. "Runtime.evaluate".`),params:t.record(t.string(),t.unknown()).optional(),...i})},async({method:e,params:t,instance:n})=>{try{return l(await cdpSend(o,n,e,t??{}))}catch(e){return u(e,n)}}),s.registerTool(`cdp_discover_targets`,{description:`List the inspectable targets (web-page / service-worker / …) on the inspector server.`,inputSchema:t.object({...i})},async({instance:e})=>{try{return l(await c.jsonCall(e,`CdpDiscoverTargets`))}catch(t){return u(t,e)}}),s.registerTool(`cdp_connect`,{description:`Connect to an inspector target (defaults to the first web-page) and auto-enable the Inspector / Runtime / DOM / Console domains. Call before cdp_send / the curated tools.`,inputSchema:t.object({target:t.record(t.string(),t.unknown()).optional().describe(`Optional target selector, e.g. {targetId} or {wsUrl}; omit for the first web-page.`),...i})},async({target:t,instance:n})=>{try{let[r]=(await c.control(n,`CdpConnect`,e.Variant.new_tuple([strv(t?JSON.stringify(t):``)]),`(b)`)).recursiveUnpack();return r?l(`Connected to inspector target.`):o.fail(`No inspectable target found.`)}catch(e){return u(e,n)}}),s.registerTool(`cdp_drain_events`,{description:`Return + clear the buffered protocol events (console messages, DOM mutations, …).`,inputSchema:t.object({...i})},async({instance:e})=>{try{return l(await c.jsonCall(e,`CdpDrainEvents`))}catch(t){return u(t,e)}});let d=r(n,{include:(e,t)=>a.has(`${e}.${t}`)});for(let e of d){let n={};for(let t of e.parameters){let e=zodForParam(t.jsType,t.enum),r=t.description?e.describe(t.description):e;n[t.name]=t.optional?r.optional():r}s.registerTool(e.name,{description:e.description??e.method,inputSchema:t.object({...n,...i})},async t=>{let n=t.instance;try{let r={};for(let n of e.parameters)t[n.name]!==void 0&&(r[n.name]=t[n.name]);return l(await cdpSend(o,n,e.method,r))}catch(e){return u(e,n)}})}}function cdpProfile(e){return{name:`gjsify-cdp-devtools`,version:`0.11.0`,busNameBase:e,registerTools:registerCdpTools}}export{cdpProfile,registerCdpTools};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{fail as e,image as t,ok as n}from"../tool-result.js";import{GjsStdioTransport as r}from"../stdio-transport.js";import{NsCdpClient as i}from"../transports/ns-cdp-client.js";import a from"@girs/gio-2.0";import o from"@girs/glib-2.0";import{z as s}from"zod";import{McpServer as c}from"@modelcontextprotocol/sdk/server/mcp.js";async function call(e,t,n={}){let r={method:t,params:n},i=await e.request(r);if(!i.ok)throw Error(`${i.error.code}: ${i.error.message}`);return i.result}function okJson(e){return n(JSON.stringify(e,null,2))}function registerNsGenericTools(e){let{server:n,client:r}=e;n.registerTool(`get_status`,{description:`JSON snapshot of the running NativeScript app: app id, platform, instance, root view, pause state.`,inputSchema:s.object({})},async()=>{try{return okJson(await call(r,`GetStatus`))}catch(t){return e.fail(messageOf(t))}}),n.registerTool(`list_toplevels`,{description:"List the app's toplevel(s) — NativeScript has a single root view (`toplevel:0`).",inputSchema:s.object({})},async()=>{try{return okJson(await call(r,`ListToplevels`))}catch(t){return e.fail(messageOf(t))}}),n.registerTool(`dump_tree`,{description:'Dump the native view tree as JSON from `root` (a view path like "toplevel:0/child:2", or omit for the root view), bounded by `depth`. Each node: path, type (NativeScript component), name, cssClasses, visible, optional text.',inputSchema:s.object({root:s.string().optional(),depth:s.number().optional()})},async({root:t,depth:n})=>{try{return okJson(await call(r,`DumpTree`,{root:t??``,depth:n??8}))}catch(t){return e.fail(messageOf(t))}}),n.registerTool(`get_property`,{description:`Read a view's property by view path + property name (JSON-safe value).`,inputSchema:s.object({path:s.string(),prop:s.string()})},async({path:t,prop:n})=>{try{return okJson(await call(r,`GetProperty`,{path:t,prop:n}))}catch(t){return e.fail(messageOf(t))}}),n.registerTool(`screenshot`,{description:`Capture a PNG screenshot of the root native view, rendered in-process by the app (no adb, no screen-record permission). Needs the app foregrounded + laid out.`,inputSchema:s.object({})},async()=>{try{let n=await call(r,`Screenshot`);return!n||!n.data?e.fail(`Screenshot returned no data — the root view is not laid out yet (bring the app to the foreground and retry), or device_screenshot as a fallback.`):t(n.data)}catch(t){return e.fail(messageOf(t))}}),registerDeviceScreenshot(e)}function registerDeviceScreenshot(e){e.server.registerTool(`device_screenshot`,{description:"PNG of the WHOLE Android device/emulator screen via `adb exec-out screencap -p` (host-side; works without the in-app devtools agent). Pass `serial` to target a specific device.",inputSchema:s.object({serial:s.string().optional()})},async({serial:n})=>{try{let r=await adbScreencap(n);return r?t(r):e.fail("adb screencap returned no data — is a device connected (`adb devices`)?")}catch(t){return e.fail(`adb screencap failed: ${messageOf(t)}`)}})}function adbScreencap(e){let t=[`adb`,...e?[`-s`,e]:[],`exec-out`,`screencap`,`-p`],n=a.Subprocess.new(t,a.SubprocessFlags.STDOUT_PIPE|a.SubprocessFlags.STDERR_SILENCE);return new Promise((e,t)=>{n.communicate_async(null,null,(r,i)=>{try{let[,t]=n.communicate_finish(i),r=t?.get_data();if(!r||r.length===0)return e(null);e(o.base64_encode(r))}catch(e){t(e)}})})}function messageOf(e){return e instanceof Error?e.message:String(e)}function nativescriptProfile(e){return{name:`gjsify-nativescript-devtools`,version:`0.11.0`,connect:e}}async function runNativescriptMcp(t){let a=new i(t.connect),s=new c({name:t.name,version:t.version}),l={server:s,client:a,ok:n,fail:e};registerNsGenericTools(l),t.registerTools?.(l);let u=o.MainLoop.new(null,!1),d=new r(()=>u.quit());a.connect().then(()=>console.error(`[gjsify-devtools-mcp] ${t.name} connected to the NativeScript inspector`),e=>console.error(`[gjsify-devtools-mcp] NS inspector not reachable yet (tools will retry): ${messageOf(e)}`)),s.connect(d).then(()=>console.error(`[gjsify-devtools-mcp] ${t.name} on stdio (NativeScript CDP)`),e=>{console.error(`[gjsify-devtools-mcp] failed to connect:`,e),u.quit()}),u.run()}export{nativescriptProfile,registerNsGenericTools,runNativescriptMcp};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import e from"@girs/gio-2.0";import t from"@girs/glib-2.0";import n from"@girs/soup-3.0";e._promisify(n.Session.prototype,`send_and_read_async`,`send_and_read_finish`),e._promisify(n.Session.prototype,`websocket_connect_async`,`websocket_connect_finish`);var NsCdpClient=class{constructor(e={}){this._session=new n.Session,this._connection=null,this._nextId=1,this._pending=new Map,this._decoder=new TextDecoder,this._encoder=new TextEncoder,this._host=e.host??`127.0.0.1`,this._port=e.port??9222,this._explicitUrl=e.url}async connect(){if(this._connection)return;let e=this._explicitUrl??await this._discoverDebuggerUrl(),r=t.Uri.parse(e,t.UriFlags.NONE),i=new n.Message({method:`GET`,uri:r}),a=await this._session.websocket_connect_async(i,null,null,t.PRIORITY_DEFAULT,null);a.max_incoming_payload_size=100*1024*1024,a.connect(`message`,(e,t,r)=>{t===n.WebsocketDataType.TEXT&&this._onMessage(this._decoder.decode(r.toArray()))}),a.connect(`closed`,()=>this._onClosed()),this._connection=a,await this._call(`Runtime.enable`,{})}async request(e){let t=JSON.stringify(e),n=`globalThis.__adwDevtools.dispatch(${JSON.stringify(t)})`,r=await this._call(`Runtime.evaluate`,{expression:n,awaitPromise:!0,returnByValue:!0});if(r.exceptionDetails)throw Error(`CDP Runtime.evaluate threw: ${r.exceptionDetails.text??`unknown error`}`);let i=r.result?.value;if(typeof i!=`string`)throw Error(`NativeScript devtools agent returned no response — is installDevtools() called with GJSIFY_DEVTOOLS=1?`);return JSON.parse(i)}async close(){this._connection&&=(this._connection.close(n.WebsocketCloseCode.NORMAL,null),null);for(let e of this._pending.values())e.reject(Error(`CDP connection closed`));this._pending.clear()}async _discoverDebuggerUrl(){let e=`http://${this._host}:${this._port}/json`,r=new n.Message({method:`GET`,uri:t.Uri.parse(e,t.UriFlags.NONE)}),i;try{i=await this._session.send_and_read_async(r,t.PRIORITY_DEFAULT,null)}catch(t){throw Error(`Cannot reach the NativeScript inspector at ${e} — run \`ns debug android\` (or forward the port with \`adb forward tcp:${this._port} …\`). (${t instanceof Error?t.message:String(t)})`)}let a=r.get_status();if(a!==n.Status.OK)throw Error(`Inspector discovery failed: ${e} returned HTTP ${a}`);let o=this._decoder.decode(i.toArray()),s;try{s=JSON.parse(o)}catch{throw Error(`Inspector discovery returned non-JSON from ${e}`)}let c=s.find(e=>typeof e.webSocketDebuggerUrl==`string`)?.webSocketDebuggerUrl;if(!c)throw Error(`No webSocketDebuggerUrl in the inspector target list at ${e}`);return c}_call(e,r){if(!this._connection)return Promise.reject(Error(`CDP connection not open`));let i=this._nextId++,a=JSON.stringify({id:i,method:e,params:r});return new Promise((e,r)=>{this._pending.set(i,{resolve:e,reject:r}),this._connection?.send_message(n.WebsocketDataType.TEXT,new t.Bytes(this._encoder.encode(a)))})}_onMessage(e){let t;try{t=JSON.parse(e)}catch{return}if(typeof t.id!=`number`)return;let n=this._pending.get(t.id);n&&(this._pending.delete(t.id),t.error?n.reject(Error(`CDP error ${t.error.code??``}: ${t.error.message??`unknown`}`.trim())):n.resolve(t.result))}_onClosed(){this._connection=null;for(let e of this._pending.values())e.reject(Error(`CDP connection closed by the device`));this._pending.clear()}};export{NsCdpClient};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -8,4 +8,9 @@ export { GjsStdioTransport } from './stdio-transport.js';
|
|
|
8
8
|
export type { DevtoolsToolProfile, GenericToolName, McpToolContext } from './profile.js';
|
|
9
9
|
export { registerStorybookTools, storybookProfile } from './profiles/storybook.js';
|
|
10
10
|
export { registerBrowserTools, browserProfile } from './profiles/browser.js';
|
|
11
|
+
export { registerCdpTools, cdpProfile } from './profiles/cdp.js';
|
|
12
|
+
export { NsCdpClient } from './transports/ns-cdp-client.js';
|
|
13
|
+
export type { NsCdpConnectOptions } from './transports/ns-cdp-client.js';
|
|
14
|
+
export { nativescriptProfile, registerNsGenericTools, runNativescriptMcp, } from './profiles/nativescript.js';
|
|
15
|
+
export type { NativescriptToolProfile, NsMcpToolContext } from './profiles/nativescript.js';
|
|
11
16
|
export * from '@gjsify/devtools-protocol';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DevtoolsToolProfile, McpToolContext } from '../profile.js';
|
|
2
|
+
export declare function registerCdpTools(ctx: McpToolContext): void;
|
|
3
|
+
/** Tool profile for a @gjsify/devtools-cdp-enabled app. */
|
|
4
|
+
export declare function cdpProfile(busNameBase: string): DevtoolsToolProfile;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `Screenshot` result the NativeScript agent returns. The PNG is
|
|
3
|
+
* base64-encoded (a `Uint8Array` cannot survive the CDP `returnByValue` JSON
|
|
4
|
+
* round-trip), so the host decodes `data` into an MCP image result.
|
|
5
|
+
*
|
|
6
|
+
* MUST match `ScreenshotResult` in `@gjsify/devtools-nativescript/handlers`.
|
|
7
|
+
*/
|
|
8
|
+
export interface ScreenshotResult {
|
|
9
|
+
format: 'png-base64';
|
|
10
|
+
/** base64-encoded PNG, or `null` when the root view is not laid out yet. */
|
|
11
|
+
data: string | null;
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type ToolResult } from '../tool-result.js';
|
|
3
|
+
import { NsCdpClient, type NsCdpConnectOptions } from '../transports/ns-cdp-client.js';
|
|
4
|
+
/** Profile descriptor for a NativeScript devtools target. */
|
|
5
|
+
export interface NativescriptToolProfile {
|
|
6
|
+
/** MCP server name. */
|
|
7
|
+
readonly name: string;
|
|
8
|
+
/** MCP server version. */
|
|
9
|
+
readonly version: string;
|
|
10
|
+
/** CDP connection (explicit ws url, or inspector host:port to discover). */
|
|
11
|
+
readonly connect?: NsCdpConnectOptions;
|
|
12
|
+
/** Register app-specific tools on top of the generic ones. */
|
|
13
|
+
readonly registerTools?: (ctx: NsMcpToolContext) => void;
|
|
14
|
+
}
|
|
15
|
+
/** Context handed to NS tool registrations. */
|
|
16
|
+
export interface NsMcpToolContext {
|
|
17
|
+
readonly server: McpServer;
|
|
18
|
+
readonly client: NsCdpClient;
|
|
19
|
+
readonly ok: (text: string) => ToolResult;
|
|
20
|
+
readonly fail: (text: string) => ToolResult;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Register the generic NS tools (the toolkit-neutral subset of the GTK generic
|
|
24
|
+
* tools that maps onto a NativeScript view tree): get_status, list_toplevels,
|
|
25
|
+
* dump_tree, get_property, screenshot.
|
|
26
|
+
*/
|
|
27
|
+
export declare function registerNsGenericTools(ctx: NsMcpToolContext): void;
|
|
28
|
+
/** A ready-made profile for a NativeScript app: generic NS tools (+ optional extras). */
|
|
29
|
+
export declare function nativescriptProfile(connect?: NsCdpConnectOptions): NativescriptToolProfile;
|
|
30
|
+
/**
|
|
31
|
+
* Run an MCP↔NativeScript-devtools bridge: build the server, register the
|
|
32
|
+
* generic (+ app-specific) tools, connect the CDP transport to the device, and
|
|
33
|
+
* pump the GLib main loop until the client closes stdin. Parallel to
|
|
34
|
+
* `runDevtoolsMcp` (the DBus path) but over the V8 inspector transport. Logs
|
|
35
|
+
* only to stderr so the JSON-RPC stdout stream stays clean.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runNativescriptMcp(profile: NativescriptToolProfile): Promise<void>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { DevtoolsRequest, DevtoolsResponse } from '@gjsify/devtools-protocol';
|
|
2
|
+
/** Connection target — an explicit ws url, or an inspector host:port to discover from. */
|
|
3
|
+
export interface NsCdpConnectOptions {
|
|
4
|
+
/** Explicit `ws://…` CDP debugger url; skips HTTP discovery when set. */
|
|
5
|
+
url?: string;
|
|
6
|
+
/** Inspector host for `/json` discovery. Default `127.0.0.1`. */
|
|
7
|
+
host?: string;
|
|
8
|
+
/** Inspector port for `/json` discovery. Default `9222` (NS V8 `--debug-brk`/`ns debug`). */
|
|
9
|
+
port?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Host-side transport that dials a running NativeScript app's in-app devtools
|
|
13
|
+
* agent (`globalThis.__adwDevtools`) over the V8 inspector CDP socket. The MCP
|
|
14
|
+
* bridge talks the SAME `@gjsify/devtools-protocol` contract over it as it does
|
|
15
|
+
* over DBus for GTK apps, so the generic tools are identical across runtimes.
|
|
16
|
+
*/
|
|
17
|
+
export declare class NsCdpClient {
|
|
18
|
+
private readonly _session;
|
|
19
|
+
private _connection;
|
|
20
|
+
private _nextId;
|
|
21
|
+
private readonly _pending;
|
|
22
|
+
private readonly _host;
|
|
23
|
+
private readonly _port;
|
|
24
|
+
private readonly _explicitUrl;
|
|
25
|
+
private readonly _decoder;
|
|
26
|
+
private readonly _encoder;
|
|
27
|
+
constructor(options?: NsCdpConnectOptions);
|
|
28
|
+
/** Open the CDP WebSocket (discovering the debugger url first if needed). */
|
|
29
|
+
connect(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Send a {@link DevtoolsRequest} to the in-app agent and resolve with its
|
|
32
|
+
* {@link DevtoolsResponse}. The agent's `dispatch` is invoked over CDP
|
|
33
|
+
* `Runtime.evaluate` and its JSON-string return is parsed back.
|
|
34
|
+
*/
|
|
35
|
+
request(req: DevtoolsRequest): Promise<DevtoolsResponse>;
|
|
36
|
+
/** Close the CDP socket and reject any in-flight requests. */
|
|
37
|
+
close(): Promise<void>;
|
|
38
|
+
/** GET `http://host:port/json` and return the first target's debugger url. */
|
|
39
|
+
private _discoverDebuggerUrl;
|
|
40
|
+
/** Issue a raw CDP method call and resolve with its `result` object. */
|
|
41
|
+
private _call;
|
|
42
|
+
private _onMessage;
|
|
43
|
+
private _onClosed;
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/devtools-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "MCP↔devtools bridge for GJS apps — exposes a running app's org.gjsify.Devtools control plane (DBus) to an AI agent as MCP tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -38,13 +38,15 @@
|
|
|
38
38
|
"@girs/giounix-2.0": "2.0.0-4.0.4",
|
|
39
39
|
"@girs/gjs": "4.0.4",
|
|
40
40
|
"@girs/glib-2.0": "2.88.0-4.0.4",
|
|
41
|
-
"@
|
|
41
|
+
"@girs/soup-3.0": "3.6.6-4.0.4",
|
|
42
|
+
"@gjsify/devtools-cdp": "^0.13.0",
|
|
43
|
+
"@gjsify/devtools-protocol": "^0.13.0",
|
|
42
44
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
45
|
"zod": "^4.4.3"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
|
-
"@gjsify/cli": "^0.
|
|
47
|
-
"@gjsify/unit": "^0.
|
|
48
|
+
"@gjsify/cli": "^0.13.0",
|
|
49
|
+
"@gjsify/unit": "^0.13.0",
|
|
48
50
|
"@types/node": "^25.9.2",
|
|
49
51
|
"typescript": "^6.0.3"
|
|
50
52
|
},
|