Let an LLM operate a computer through Munim without hard-coding its tools, and drive that runtime from a browser page via a Chrome extension.
This repo has two parts:
server/- the Agent Capability Runtime: discovers Munim's MCP tools, normalizes them into capabilities, validates requests, and exposes an HTTP interface.browser_extension/- a Manifest V3 Chrome extension that extracts text from a page, checks it matches the runtime's tool-call format, executes it through the runtime, injects the result back into the page, and (optionally) clicks a send button - all configurable per web page.
User
|
v
LLM ---> Agent Runtime ---> Munim Provider ---> Windows Desktop
(registry, (MCP client,
router) stdio)
The LLM never talks to Munim directly. It talks to the runtime over HTTP; the runtime routes to the provider.
gnosis/
README.md this file
server/ Agent Capability Runtime (TypeScript/Node)
src/
index.ts startup: config -> connect Munim -> discover -> HTTP
config.ts env config (MUNIM_PATH, PORT, CAPABILITY_PREFIX)
agent/runtime.ts routes actions, validates, dispatches to providers
capabilities/ registry, meta-capabilities, JSON-schema validation
providers/ provider interface + Munim MCP provider
server/http.ts Express HTTP interface
tests/ Vitest suite
.env.example example environment variables
package.json
browser_extension/ Manifest V3 Chrome extension
manifest.json
options.html / .js settings UI (per-page config, Start/Stop)
background.js service worker: gate -> /execute -> inject -> auto-click
content.js extract from a source element; inject into a textarea
parser.js parses extracted text as a capability call (the gate)
selector.js pure helpers (selectors, per-page keys, tab picking)
capability_prompt.js the agent-facing capability-discovery prompt
- Node.js 20+ (developed on Node 24)
- A Munim executable (or any MCP stdio command) that exposes the computer-use tools
- Google Chrome (or Chromium) to load the extension
cd server
npm install
cp .env.example .env # then edit .env
npm run dev # tsx src/index.ts (hot reload)
| Variable | Required | Default | Description |
|---|---|---|---|
| MUNIM_PATH | yes | - | Path to the Munim executable (MCP stdio command). |
| PORT | no | 3000 | HTTP port for the runtime interface. |
| CAPABILITY_PREFIX | no | computer | Prefix applied to provider tool names. |
| Script | Description |
|---|---|
| npm run dev | Run the server in development (tsx). |
| npm run build | Compile TypeScript to dist/. |
| npm start | Run the compiled server (node dist/index.js). |
| npm test | Run the Vitest suite once. |
| npm run test:watch | Run Vitest in watch mode. |
| npm run typecheck | Type-check without emitting. |
On startup the server loads config from .env, connects to the Munim MCP server over stdio, discovers tools and registers them as capabilities (<prefix>.<tool>), then starts the HTTP interface on PORT.
Base URL: http://localhost:<PORT> (default http://localhost:3000).
Health check.
{ "status": "ok", "service": "Agent Capability Runtime", "capabilities": <number> }
Lightweight discovery - names and descriptions only.
{ "capabilities": [ { "name": "computer.click", "description": "..." }, ... ] }
Full definition for one capability, including its JSON input schema.
{ "name": "computer.click", "description": "...", "inputSchema": { ... } }
Returns 404 with { "error": true, "code": "CAPABILITY_NOT_FOUND", ... } for unknown names.
Execute a capability. Request body:
{
"capability": "computer.type_text",
"arguments": { "text": "hello" }
}
capability(string, required) - a capability name from discovery.arguments(object, optional) - fields from that capability'sinputSchema.
Success response (200):
{ "ok": true, "capability": "computer.type_text", "result": <provider result> }
Failure response:
{ "ok": false, "error": true, "code": "<CODE>", "message": "...", "details": <optional> }
| Code | HTTP | Meaning |
|---|---|---|
| INVALID_REQUEST | 400 | Body shape is wrong (missing capability). |
| INVALID_ARGUMENTS | 400 | arguments violate the capability's schema. |
| CAPABILITY_NOT_FOUND | 404 | Unknown capability name. |
| PROVIDER_NOT_FOUND | 404 | No provider registered for the capability. |
| EXECUTION_ERROR | 500 | Provider threw during execution. |
The runtime owns two capabilities that need no provider:
capabilities.list- same payload asGET /capabilities.capabilities.describe- args{ "name": "<capability>" }.
Call them through POST /execute like any other capability.
curl -s http://localhost:3000/capabilities
curl -s -X POST http://localhost:3000/execute \
-H "Content-Type: application/json" \
-d '{"capability":"capabilities.list","arguments":{}}'
- Open
chrome://extensionsin Chrome. - Enable Developer mode (top right).
- Click Load unpacked and select the
browser_extension/folder.
The extension turns a web page into an agent loop:
- Extract - watches a source element (by class or id) and captures its text once the DOM settles (streaming-safe debounce).
- Gate -
parser.jsdecides whether the extracted text is a valid tool call, i.e. a JSON object of the form{ "capability": "...", "arguments": { ... } }. Free-form text is ignored and never executed. - Execute - a valid tool call is POSTed to the runtime (
/execute). - Inject - the runtime's response is written into a destination textarea (the container is found by class/id, then the textarea inside it).
- Auto-send (optional) - when enabled, the extension clicks a configured button after a successful extract + inject.
Open the extension's options (click the toolbar icon, or chrome://extensions -> Details -> Extension options).
- Extract from (source) - class or id of the element to read.
- Inject into (destination) - class or id of the container holding a textarea; the extension writes into the textarea inside it.
- Auto-send (optional) - toggle auto, and set the class or id of the button to click.
Click Start to arm the session, Stop to end it.
Settings are saved per web page origin (e.g. https://chat.deepseek.com). The options page shows the current page's origin and loads that page's saved settings. The Saved pages dropdown lists every origin with saved settings, so you can switch between sites.
- Non-
http(s)pages (chrome://,file://,about:) are not eligible and cannot be saved. - Reloading a page keeps its settings; another site uses its own.
The agent must reply with only a JSON object in this shape when calling a tool:
{ "capability": "<name>", "arguments": { ... } }
Rules enforced by the extension's gate:
capabilitymust be a non-empty string under thecomputer.prefix or acapabilities.*meta-capability.argumentsmust be an object (when present).- Anything that is not such a JSON object (normal chat, prose) is ignored.
The full, current list of valid capability names and their argument schemas comes from the server - discover it via GET /capabilities and GET /capabilities/:name.
The server test suite covers the runtime, HTTP interface, capability registry, meta-capabilities, JSON-schema validation, and the extension's pure modules (parser.js, selector.js, capability_prompt.js).
cd server
npm test # run once
npm run test:watch # watch mode
- Provider abstraction (
server/src/providers/provider.ts) - the runtime depends on an interface, not on Munim.MunimProviderimplements it over MCP stdio and normalizes each tool to<prefix>.<tool>. - Capability registry - discovered capabilities are stored with their full input schemas.
- Runtime (
server/src/agent/runtime.ts) - resolves capabilities, handles meta-capabilities internally, validates arguments against the schema (Ajv), then dispatches to the owning provider. - Validation (
server/src/capabilities/validation.ts) - enforces the provider-supplied JSON schema before execution. - Extension gate (
browser_extension/parser.js) - the client-side mirror: it only forwards text that is a well-formed capability call; the server remains the authority on names and arguments.
- The runtime is the execution layer, not an autonomous agent. The model decides what to do; the runtime decides whether it is allowed and how to route it.
- The extension keeps only a subset of capability schemas locally. It accepts any well-formed
computer.*name and lets the server reject unknown ones - so valid tool calls for tools the extension does not know about still reach the server. - See
server/Agent Capability Runtime — MVP Implementation Requirements.mdfor the full design goals.