@gjsify/devtools-mcp 0.8.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/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/dbus-client.js +1 -0
- package/lib/esm/error-map.js +1 -0
- package/lib/esm/generic-tools.js +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/profile.js +0 -0
- package/lib/esm/profiles/storybook.js +1 -0
- package/lib/esm/run-server.js +1 -0
- package/lib/esm/stdio-transport.js +1 -0
- package/lib/esm/tool-result.js +1 -0
- package/lib/types/dbus-client.d.ts +24 -0
- package/lib/types/error-map.d.ts +12 -0
- package/lib/types/error-map.spec.d.ts +2 -0
- package/lib/types/generic-tools.d.ts +3 -0
- package/lib/types/index.d.ts +10 -0
- package/lib/types/profile.d.ts +26 -0
- package/lib/types/profiles/storybook.d.ts +5 -0
- package/lib/types/run-server.d.ts +8 -0
- package/lib/types/stdio-transport.d.ts +33 -0
- package/lib/types/tool-result.d.ts +15 -0
- package/package.json +69 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"@girs/glib-2.0";import t from"@girs/gio-2.0";import{DEVTOOLS_INTERFACE as n,resolveBusAddress as r}from"@gjsify/devtools-protocol";const i=`org.freedesktop.DBus`;var DbusDevtoolsClient=class{constructor(e){this.busNameBase=e,this._bus=t.bus_get_sync(t.BusType.SESSION,null)}resolve(e){return r(this.busNameBase,e)}control(r,i,a,o){let{busName:s,objectPath:c}=this.resolve(r);return new Promise((r,l)=>{this._bus.call(s,c,n,i,a,o?e.VariantType.new(o):null,t.DBusCallFlags.NONE,-1,null,(e,t)=>{try{r(e.call_finish(t))}catch(e){l(e)}})})}async jsonCall(e,t,n=null){let[r]=(await this.control(e,t,n,`(s)`)).recursiveUnpack();return JSON.stringify(JSON.parse(r),null,2)}nameHasOwner(n){return new Promise(r=>{this._bus.call(i,`/org/freedesktop/DBus`,i,`NameHasOwner`,e.Variant.new_tuple([e.Variant.new_string(n)]),e.VariantType.new(`(b)`),t.DBusCallFlags.NONE,-1,null,(e,t)=>{try{r(e.call_finish(t).recursiveUnpack()[0])}catch{r(!1)}})})}listInstances(){return new Promise(n=>{this._bus.call(i,`/org/freedesktop/DBus`,i,`ListNames`,null,e.VariantType.new(`(as)`),t.DBusCallFlags.NONE,-1,null,(e,t)=>{try{let r=e.call_finish(t).recursiveUnpack()[0],i=this.busNameBase;n(r.filter(e=>e===i||e.startsWith(`${i}.`)).map(e=>({instance:e===i?`default`:e.slice(i.length+1),busName:e})))}catch{n([])}})})}};export{DbusDevtoolsClient};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{fail as e}from"./tool-result.js";import{parseDbusErrorMessage as t}from"@gjsify/devtools-protocol";const n=/ServiceUnknown|NameHasNoOwner|was not provided by any|StartServiceByName/;function dbusError(r,i){let a=r instanceof Error?r.message:String(r);if(n.test(a))return e(`No devtools-enabled app on the session bus (${i.busName}, instance "${i.instance}"). Launch the app with GJSIFY_DEVTOOLS=1. (D-Bus error: ${a})`);let o=a.match(/GDBus\.Error:[^\s:]*:\s*([\s\S]+)/)?.[1]?.trim();if(o){let n=t(o);return e(n.code===`internal`?`D-Bus call failed: ${o}`:`${n.code}: ${n.message}`)}return e(`D-Bus call failed: ${a}`)}export{dbusError};
|
|
@@ -0,0 +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),r={instance:n.string().optional().describe(`App instance label; omit for the default app`)};function delay(e){return new Promise(n=>{t.timeout_add(t.PRIORITY_DEFAULT,e,()=>(n(),t.SOURCE_REMOVE))})}function registerGenericTools(i,a=`all`){let{server:o,client:s,ok:c,fail:l,dbusError:u}=i,want=e=>a===`all`||a.includes(e);want(`get_status`)&&o.registerTool(`get_status`,{description:`JSON snapshot of the running app: app id, active window, toplevel count, focused widget, pause state, plus any app-specific status contributed by extensions.`,inputSchema:n.object({...r})},async({instance:e})=>{try{return c(await s.jsonCall(e,`GetStatus`))}catch(t){return u(t,e)}}),want(`screenshot`)&&o.registerTool(`screenshot`,{description:`Capture a PNG screenshot of the active window via the GTK GSK pipeline. Needs a visible window (auto-raises + retries once when the capture comes back empty).`,inputSchema:n.object({scope:n.string().optional(),...r})},async({scope:n,instance:r})=>{try{let grab=async()=>(await s.control(r,`Screenshot`,t.Variant.new_tuple([strv(n??`window`)]),`(ay)`)).get_child_value(0).deepUnpack(),i=await grab();return(!i||i.length===0)&&(await s.control(r,`PresentWindow`,null,null),await delay(350),i=await grab()),!i||i.length===0?l(`Screenshot returned no data — the window is likely occluded or minimized. Bring it to the foreground (present_window) and retry.`):e(t.base64_encode(i))}catch(e){return u(e,r)}}),want(`list_actions`)&&o.registerTool(`list_actions`,{description:`List the app.* and win.* GActions (name, enabled, parameter/state types) that activate_action / change_action_state can drive.`,inputSchema:n.object({...r})},async({instance:e})=>{try{return c(await s.jsonCall(e,`ListActions`))}catch(t){return u(t,e)}}),want(`activate_action`)&&o.registerTool(`activate_action`,{description:`Activate a GAction by scope + name, with an optional parameter (a plain JSON value coerced to the action's declared type).`,inputSchema:n.object({scope:n.enum([`app`,`win`]),name:n.string(),value:n.union([n.string(),n.number(),n.boolean()]).optional(),...r})},async({scope:e,name:n,value:r,instance:i})=>{try{let a=r===void 0?``:JSON.stringify(r);return await s.control(i,`ActivateAction`,t.Variant.new_tuple([strv(e),strv(n),strv(a)]),null),c(`Activated ${e}.${n}`)}catch(e){return u(e,i)}}),want(`change_action_state`)&&o.registerTool(`change_action_state`,{description:`Set a stateful GAction’s state — for idempotent toggles like win.toggle-grid.`,inputSchema:n.object({scope:n.enum([`app`,`win`]),name:n.string(),value:n.union([n.string(),n.number(),n.boolean()]),...r})},async({scope:e,name:n,value:r,instance:i})=>{try{return await s.control(i,`ChangeActionState`,t.Variant.new_tuple([strv(e),strv(n),strv(JSON.stringify(r))]),null),c(`Set state ${e}.${n} = ${JSON.stringify(r)}`)}catch(e){return u(e,i)}}),want(`present_window`)&&o.registerTool(`present_window`,{description:`Raise + focus the app’s active window.`,inputSchema:n.object({...r})},async({instance:e})=>{try{return await s.control(e,`PresentWindow`,null,null),c(`Presented the window.`)}catch(t){return u(t,e)}}),want(`resize_window`)&&o.registerTool(`resize_window`,{description:`Resize the active window to an absolute pixel size (exercises responsive breakpoints). Returns the requested size.`,inputSchema:n.object({width:n.number(),height:n.number(),...r})},async({width:e,height:n,instance:r})=>{try{let[i,a]=(await s.control(r,`ResizeWindow`,t.Variant.new_tuple([t.Variant.new_int32(e),t.Variant.new_int32(n)]),`(ii)`)).recursiveUnpack();return c(`Resized to ${i}×${a}.`)}catch(e){return u(e,r)}}),want(`list_toplevels`)&&o.registerTool(`list_toplevels`,{description:`List the app’s live toplevel windows (path, type, title, mapped, focused).`,inputSchema:n.object({...r})},async({instance:e})=>{try{return c(await s.jsonCall(e,`ListToplevels`))}catch(t){return u(t,e)}}),want(`dump_tree`)&&o.registerTool(`dump_tree`,{description:'Dump the widget tree as JSON from `root` (a widget path like "toplevel:0/child:2", or omit for the active window), bounded by `depth`.',inputSchema:n.object({root:n.string().optional(),depth:n.number().optional(),...r})},async({root:e,depth:n,instance:r})=>{try{let i=t.Variant.new_tuple([strv(e??``),t.Variant.new_int32(n??8)]);return c(await s.jsonCall(r,`DumpTree`,i))}catch(e){return u(e,r)}}),want(`get_property`)&&o.registerTool(`get_property`,{description:`Read a widget’s GObject property by widget path + property name (JSON-safe value).`,inputSchema:n.object({path:n.string(),prop:n.string(),...r})},async({path:e,prop:n,instance:r})=>{try{let i=t.Variant.new_tuple([strv(e),strv(n)]);return c(await s.jsonCall(r,`GetProperty`,i))}catch(e){return u(e,r)}}),want(`get_focused`)&&o.registerTool(`get_focused`,{description:`The currently-focused widget (path, name, type), or null.`,inputSchema:n.object({...r})},async({instance:e})=>{try{return c(await s.jsonCall(e,`GetFocused`))}catch(t){return u(t,e)}}),want(`dump_gsettings`)&&o.registerTool(`dump_gsettings`,{description:`Dump every key + value of an installed GSettings schema (read-only).`,inputSchema:n.object({schema:n.string(),...r})},async({schema:e,instance:n})=>{try{return c(await s.jsonCall(n,`DumpGSettings`,t.Variant.new_tuple([strv(e)])))}catch(e){return u(e,n)}}),want(`dump_css`)&&o.registerTool(`dump_css`,{description:`List the devtools-installed CSS provider names.`,inputSchema:n.object({...r})},async({instance:e})=>{try{return c(await s.jsonCall(e,`DumpCss`))}catch(t){return u(t,e)}}),want(`swap_css`)&&o.registerTool(`swap_css`,{description:`Live-install or replace a named CSS provider (high priority) — re-theme the running app without a restart. Returns applied.`,inputSchema:n.object({name:n.string(),css:n.string(),...r})},async({name:e,css:n,instance:r})=>{try{let[i]=(await s.control(r,`SwapCss`,t.Variant.new_tuple([strv(e),strv(n)]),`(b)`)).recursiveUnpack();return c(i?`Applied CSS provider "${e}".`:`No display — CSS not applied.`)}catch(e){return u(e,r)}}),want(`list_instances`)&&o.registerTool(`list_instances`,{description:`List devtools-enabled app instances on the session bus (default + labelled).`,inputSchema:n.object({})},async()=>{try{return c(JSON.stringify(await s.listInstances(),null,2))}catch(e){return u(e)}})}export{registerGenericTools};
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +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";export*from"@gjsify/devtools-protocol";export{e as DbusDevtoolsClient,o as GjsStdioTransport,i as dbusError,t as fail,n as image,r as ok,a as registerGenericTools,c as registerStorybookTools,s as runDevtoolsMcp,l as storybookProfile};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import e from"@girs/glib-2.0";import{z as t}from"zod";const strv=t=>e.Variant.new_string(t),n={instance:t.string().optional().describe(`Storybook instance label; omit for the default app`)};function registerStorybookTools(r){let{server:i,client:a,ok:o,dbusError:s}=r;i.registerTool(`list_stories`,{description:`List every registered story: title, story variant, category, and controls.`,inputSchema:t.object({...n})},async({instance:e})=>{try{return o(await a.jsonCall(e,`ListStories`))}catch(t){return s(t,e)}}),i.registerTool(`get_current_story`,{description:`The currently-displayed story (title, variant, live args), or null.`,inputSchema:t.object({...n})},async({instance:e})=>{try{return o(await a.jsonCall(e,`GetCurrentStory`))}catch(t){return s(t,e)}}),i.registerTool(`open_story`,{description:`Open a story by its full "Category/Name" title (updates the sidebar selection).`,inputSchema:t.object({title:t.string(),...n})},async({title:t,instance:n})=>{try{let[r]=(await a.control(n,`OpenStory`,e.Variant.new_tuple([strv(t)]),`(b)`)).recursiveUnpack();return o(r?`Opened "${t}".`:`No story titled "${t}".`)}catch(e){return s(e,n)}}),i.registerTool(`set_story_arg`,{description:`Set one arg on the active story (drives the same path as the control panel; the row refreshes).`,inputSchema:t.object({name:t.string(),value:t.union([t.string(),t.number(),t.boolean(),t.null()]),...n})},async({name:t,value:n,instance:r})=>{try{let[i]=(await a.control(r,`SetStoryArg`,e.Variant.new_tuple([strv(t),strv(JSON.stringify(n))]),`(b)`)).recursiveUnpack();return o(i?`Set ${t} = ${JSON.stringify(n)}.`:`No active story to set an arg on.`)}catch(e){return s(e,r)}})}function storybookProfile(e){return{name:`gjsify-storybook-devtools`,version:`0.8.0`,busNameBase:e,registerTools:registerStorybookTools}}export{registerStorybookTools,storybookProfile};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{DbusDevtoolsClient as e}from"./dbus-client.js";import{fail as t,ok as n}from"./tool-result.js";import{dbusError as r}from"./error-map.js";import{registerGenericTools as i}from"./generic-tools.js";import{GjsStdioTransport as a}from"./stdio-transport.js";import o from"@girs/glib-2.0";import{McpServer as s}from"@modelcontextprotocol/sdk/server/mcp.js";async function runDevtoolsMcp(c){let l=new e(c.busNameBase),u=new s({name:c.name,version:c.version}),d={server:u,client:l,ok:n,fail:t,dbusError:(e,t)=>r(e,l.resolve(t))};i(d,c.genericTools??`all`),c.registerTools?.(d);let f=o.MainLoop.new(null,!1),p=new a(()=>f.quit());u.connect(p).then(()=>console.error(`[gjsify-devtools-mcp] ${c.name} on stdio (DBus base: ${c.busNameBase})`),e=>{console.error(`[gjsify-devtools-mcp] failed to connect:`,e),f.quit()}),f.run()}export{runDevtoolsMcp};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"@girs/glib-2.0";import t from"@girs/gio-2.0";import n from"@girs/giounix-2.0";t._promisify(t.DataInputStream.prototype,`read_line_async`,`read_line_finish`);var GjsStdioTransport=class{constructor(e){this.onExit=e,this._in=null,this._out=null,this._cancellable=new t.Cancellable,this._closed=!1,this._encoder=new TextEncoder,this._decoder=new TextDecoder}async start(){if(this._in)throw Error(`GjsStdioTransport already started`);let e=n.InputStream.new(0,!1);this._in=new t.DataInputStream({base_stream:e}),this._out=n.OutputStream.new(1,!1),this._readLoop()}async _readLoop(){for(;!this._closed&&this._in;){let t;try{[t]=await this._in.read_line_async(e.PRIORITY_DEFAULT,this._cancellable)}catch(e){this._closed||this.onerror?.(e instanceof Error?e:Error(String(e)));break}if(t===null)break;let n=this._decoder.decode(t).trim();if(n)try{this.onmessage?.(JSON.parse(n))}catch(e){this.onerror?.(e instanceof Error?e:Error(String(e)))}}this._closed||(this._closed=!0,this.onclose?.()),this.onExit?.()}async send(e){if(!this._out)throw Error(`GjsStdioTransport not started`);this._out.write_all(this._encoder.encode(`${JSON.stringify(e)}\n`),null)}async close(){this._closed||(this._closed=!0,this._cancellable.cancel(),this.onclose?.(),this.onExit?.())}};export{GjsStdioTransport};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";const ok=e=>({content:[{type:`text`,text:e}]}),fail=e=>({content:[{type:`text`,text:e}],isError:!0}),image=(e,t=`image/png`)=>({content:[{type:`image`,data:e,mimeType:t}]});export{fail,image,ok};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import GLib from '@girs/glib-2.0';
|
|
2
|
+
import { type BusAddress } from '@gjsify/devtools-protocol';
|
|
3
|
+
/**
|
|
4
|
+
* Talks the `org.gjsify.Devtools` control plane of a running app over the
|
|
5
|
+
* session bus. One per bridge process; resolves a per-instance bus address
|
|
6
|
+
* from the app's base id + an optional instance label.
|
|
7
|
+
*/
|
|
8
|
+
export declare class DbusDevtoolsClient {
|
|
9
|
+
readonly busNameBase: string;
|
|
10
|
+
private readonly _bus;
|
|
11
|
+
constructor(busNameBase: string);
|
|
12
|
+
resolve(label?: string): BusAddress;
|
|
13
|
+
/** Async call to an instance's Devtools interface; resolves with the reply variant. */
|
|
14
|
+
control(label: string | undefined, method: string, params: GLib.Variant | null, replyType: string | null): Promise<GLib.Variant>;
|
|
15
|
+
/** Call a `() -> (s)` method and return its JSON string (pretty-printed). */
|
|
16
|
+
jsonCall(label: string | undefined, method: string, params?: GLib.Variant | null): Promise<string>;
|
|
17
|
+
/** Whether `busName` currently has an owner (the app is running). */
|
|
18
|
+
nameHasOwner(busName: string): Promise<boolean>;
|
|
19
|
+
/** Enumerate devtools-enabled instances on the bus (base + labelled). */
|
|
20
|
+
listInstances(): Promise<Array<{
|
|
21
|
+
instance: string;
|
|
22
|
+
busName: string;
|
|
23
|
+
}>>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ToolResult } from './tool-result.js';
|
|
2
|
+
/**
|
|
3
|
+
* Map a DBus call failure to a clear MCP tool result. Distinguishes:
|
|
4
|
+
* (1) the app isn't running on the bus, (2) a typed devtools rejection the
|
|
5
|
+
* app threw — its `<code>: message` survives as a GDBus remote error (see
|
|
6
|
+
* `formatDbusErrorMessage`), so the original, actionable message is surfaced,
|
|
7
|
+
* (3) any other DBus failure.
|
|
8
|
+
*/
|
|
9
|
+
export declare function dbusError(error: unknown, resolved: {
|
|
10
|
+
busName: string;
|
|
11
|
+
instance: string;
|
|
12
|
+
}): ToolResult;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { runDevtoolsMcp } from './run-server.js';
|
|
2
|
+
export { DbusDevtoolsClient } from './dbus-client.js';
|
|
3
|
+
export { registerGenericTools } from './generic-tools.js';
|
|
4
|
+
export { dbusError } from './error-map.js';
|
|
5
|
+
export { fail, image, ok } from './tool-result.js';
|
|
6
|
+
export type { ToolResult } from './tool-result.js';
|
|
7
|
+
export { GjsStdioTransport } from './stdio-transport.js';
|
|
8
|
+
export type { DevtoolsToolProfile, GenericToolName, McpToolContext } from './profile.js';
|
|
9
|
+
export { registerStorybookTools, storybookProfile } from './profiles/storybook.js';
|
|
10
|
+
export * from '@gjsify/devtools-protocol';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { DbusDevtoolsClient } from './dbus-client.js';
|
|
3
|
+
import type { ToolResult } from './tool-result.js';
|
|
4
|
+
/** The generic per-app tool names the bridge can register. */
|
|
5
|
+
export type GenericToolName = 'get_status' | 'screenshot' | 'list_actions' | 'activate_action' | 'change_action_state' | 'present_window' | 'resize_window' | 'list_instances' | 'list_toplevels' | 'dump_tree' | 'get_property' | 'get_focused' | 'dump_gsettings' | 'dump_css' | 'swap_css';
|
|
6
|
+
/** Context handed to the generic tool registrations and to a profile's `registerTools`. */
|
|
7
|
+
export interface McpToolContext {
|
|
8
|
+
readonly server: McpServer;
|
|
9
|
+
readonly client: DbusDevtoolsClient;
|
|
10
|
+
readonly ok: (text: string) => ToolResult;
|
|
11
|
+
readonly fail: (text: string) => ToolResult;
|
|
12
|
+
readonly dbusError: (error: unknown, instance?: string) => ToolResult;
|
|
13
|
+
}
|
|
14
|
+
/** Describes an MCP bridge for a specific app (or the generic default). */
|
|
15
|
+
export interface DevtoolsToolProfile {
|
|
16
|
+
/** MCP server name. */
|
|
17
|
+
readonly name: string;
|
|
18
|
+
/** MCP server version. */
|
|
19
|
+
readonly version: string;
|
|
20
|
+
/** The app's base DBus name, e.g. `"org.example.App"`. */
|
|
21
|
+
readonly busNameBase: string;
|
|
22
|
+
/** Which generic tools to register. Default: `'all'`. */
|
|
23
|
+
readonly genericTools?: GenericToolName[] | 'all';
|
|
24
|
+
/** Register app-specific tools on top of the generic ones. */
|
|
25
|
+
readonly registerTools?: (ctx: McpToolContext) => void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DevtoolsToolProfile, McpToolContext } from '../profile.js';
|
|
2
|
+
/** Register the storybook-specific tools (list/open stories, read/set args). */
|
|
3
|
+
export declare function registerStorybookTools(ctx: McpToolContext): void;
|
|
4
|
+
/** A ready-made profile for a storybook app: generic tools + the story tools. */
|
|
5
|
+
export declare function storybookProfile(busNameBase: string): DevtoolsToolProfile;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DevtoolsToolProfile } from './profile.js';
|
|
2
|
+
/**
|
|
3
|
+
* Run an MCP↔devtools bridge for a profile: build the server, register the
|
|
4
|
+
* generic + app-specific tools, connect a GJS stdio transport, and pump the
|
|
5
|
+
* GLib main loop until the client closes stdin. Logs only to stderr so the
|
|
6
|
+
* JSON-RPC stdout stream stays clean.
|
|
7
|
+
*/
|
|
8
|
+
export declare function runDevtoolsMcp(profile: DevtoolsToolProfile): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
2
|
+
import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* MCP stdio transport implemented on GJS Gio streams. The SDK's bundled
|
|
5
|
+
* `StdioServerTransport` reads Node's `process.stdin`, which gjsify does not
|
|
6
|
+
* deliver piped data on — so we read newline-delimited JSON-RPC straight off
|
|
7
|
+
* fd 0 with a `Gio.DataInputStream` and write replies to fd 1. Drive it from
|
|
8
|
+
* a `GLib.MainLoop` (see {@link runDevtoolsMcp}) so the async read loop is
|
|
9
|
+
* pumped.
|
|
10
|
+
*/
|
|
11
|
+
export declare class GjsStdioTransport implements Transport {
|
|
12
|
+
private readonly onExit?;
|
|
13
|
+
onclose?: () => void;
|
|
14
|
+
onerror?: (error: Error) => void;
|
|
15
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
16
|
+
sessionId?: string;
|
|
17
|
+
private _in;
|
|
18
|
+
private _out;
|
|
19
|
+
private readonly _cancellable;
|
|
20
|
+
private _closed;
|
|
21
|
+
private readonly _encoder;
|
|
22
|
+
private readonly _decoder;
|
|
23
|
+
/**
|
|
24
|
+
* @param onExit called when stdin reaches EOF or the transport closes.
|
|
25
|
+
* Separate from {@link onclose} because the MCP `Server` overwrites
|
|
26
|
+
* `onclose` during `connect()`; the owner uses this to quit its loop.
|
|
27
|
+
*/
|
|
28
|
+
constructor(onExit?: (() => void) | undefined);
|
|
29
|
+
start(): Promise<void>;
|
|
30
|
+
private _readLoop;
|
|
31
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ToolResult {
|
|
2
|
+
content: Array<{
|
|
3
|
+
type: 'text';
|
|
4
|
+
text: string;
|
|
5
|
+
} | {
|
|
6
|
+
type: 'image';
|
|
7
|
+
data: string;
|
|
8
|
+
mimeType: string;
|
|
9
|
+
}>;
|
|
10
|
+
isError?: boolean;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export declare const ok: (text: string) => ToolResult;
|
|
14
|
+
export declare const fail: (text: string) => ToolResult;
|
|
15
|
+
export declare const image: (base64: string, mimeType?: string) => ToolResult;
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gjsify/devtools-mcp",
|
|
3
|
+
"version": "0.8.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"module": "lib/esm/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"default": "./lib/esm/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"lib"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"clear": "rm -rf lib tmp tsconfig.tsbuildinfo test.gjs.mjs || exit 0",
|
|
19
|
+
"check": "gjsify tsc --noEmit",
|
|
20
|
+
"build": "gjsify run build:gjsify && gjsify run build:types",
|
|
21
|
+
"build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
|
|
22
|
+
"build:types": "gjsify tsc",
|
|
23
|
+
"build:test": "gjsify run build:test:gjs",
|
|
24
|
+
"build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
|
|
25
|
+
"test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:gjs",
|
|
26
|
+
"test:gjs": "gjsify run test.gjs.mjs"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"gjs",
|
|
30
|
+
"mcp",
|
|
31
|
+
"devtools",
|
|
32
|
+
"debug",
|
|
33
|
+
"dbus",
|
|
34
|
+
"gtk"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@girs/gio-2.0": "2.88.0-4.0.4",
|
|
38
|
+
"@girs/giounix-2.0": "2.0.0-4.0.4",
|
|
39
|
+
"@girs/gjs": "4.0.4",
|
|
40
|
+
"@girs/glib-2.0": "2.88.0-4.0.4",
|
|
41
|
+
"@gjsify/devtools-protocol": "^0.8.0",
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
|
+
"zod": "4.3.6"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@gjsify/cli": "^0.8.0",
|
|
47
|
+
"@gjsify/unit": "^0.8.0",
|
|
48
|
+
"@types/node": "^25.9.2",
|
|
49
|
+
"typescript": "^6.0.3"
|
|
50
|
+
},
|
|
51
|
+
"gjsify": {
|
|
52
|
+
"runtimes": {
|
|
53
|
+
"gjs": "polyfill",
|
|
54
|
+
"node": "none",
|
|
55
|
+
"browser": "none",
|
|
56
|
+
"nativescript": "none"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"license": "MIT",
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "git+https://github.com/gjsify/gjsify.git",
|
|
63
|
+
"directory": "packages/framework/devtools-mcp"
|
|
64
|
+
},
|
|
65
|
+
"bugs": {
|
|
66
|
+
"url": "https://github.com/gjsify/gjsify/issues"
|
|
67
|
+
},
|
|
68
|
+
"homepage": "https://github.com/gjsify/gjsify/tree/main/packages/framework/devtools-mcp#readme"
|
|
69
|
+
}
|