Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ jobs:
- run: npm run build
- run: npm test
- run: npm run check:package
- run: npm run check:glama
- name: Build the Glama stdio image
if: matrix.node == 24
run: docker build --file Dockerfile.glama --tag cockroach-browser-glama:ci .
- name: Inspect the Glama image over MCP stdio
if: matrix.node == 24
run: npm run check:glama
env:
COCKROACH_BROWSER_GLAMA_IMAGE: cockroach-browser-glama:ci
- run: npm run check:site
- run: npm audit --omit=dev

Expand Down
28 changes: 28 additions & 0 deletions Dockerfile.glama
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# syntax=docker/dockerfile:1.7
# Glama introspects the native stdio MCP process. The root Dockerfile continues
# to package the authenticated browser daemon and is intentionally unchanged.
FROM node:24-bookworm-slim@sha256:3638d9a6fe4030bd716be989438248074489337ba3275657f93595428be4fc03 AS build

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts

COPY tsconfig.json tsconfig.build.json ./
COPY src ./src
RUN npm run build

FROM node:24-bookworm-slim@sha256:3638d9a6fe4030bd716be989438248074489337ba3275657f93595428be4fc03 AS runtime

ENV NODE_ENV=production
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --omit=dev --ignore-scripts \
&& npm cache clean --force

COPY --from=build /app/dist ./dist

USER node

ENTRYPOINT ["node", "dist/cli.js"]
CMD ["mcp"]
4 changes: 4 additions & 0 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ Start the authenticated daemon first. Load its token into the MCP process throug

The MCP surface provides browser_capabilities, browser_health, browser_sessions, browser_snapshot, browser_audit, browser_capture, browser_network, and browser_propose_action. Capture and network tools return bounded read evidence. A proposal returns canonical action material for a governed dispatcher and does not execute it.

## Reproduce the Glama build

Glama must inspect the stdio MCP process, not the authenticated HTTP daemon packaged by the root Dockerfile. Select Dockerfile.glama with the repository root as the build context in Glama's Dockerfile admin page. The root glama.json identifies the authorized maintainer. Schema inspection requires no secret because all eight tools are registered before a daemon client is configured; reproduce it with npm run build followed by npm run check:glama. Calls that read browser sessions still require COCKROACH_BROWSER_TOKEN and, when the daemon is not at the default loopback URL, COCKROACH_BROWSER_URL.

## Keep lifecycle authority outside the model

Session creation, profile import, login, secret resolution, remote binding, and raw action dispatch stay with the host. This prevents a model from expanding its own origins, credentials, browser state, or resource ceilings.
Expand Down
6 changes: 6 additions & 0 deletions glama.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://glama.ai/mcp/schemas/server.json",
"maintainers": [
"AjnasNB"
]
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@
"test:source": "npm test",
"typecheck": "tsc -p tsconfig.json --noEmit",
"check:package": "node scripts/check-package.mjs",
"check:glama": "node scripts/check-glama.mjs",
"check:site": "node scripts/check-site.mjs",
"check": "npm run typecheck && npm run build && npm test && npm run check:package && npm run check:site && npm audit --omit=dev && npm pack --dry-run --ignore-scripts",
"check": "npm run typecheck && npm run build && npm test && npm run check:package && npm run check:glama && npm run check:site && npm audit --omit=dev && npm pack --dry-run --ignore-scripts",
"prepack": "npm run clean && npm run build && npm run check:package",
"prepublishOnly": "npm run check"
},
Expand Down
82 changes: 82 additions & 0 deletions scripts/check-glama.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const packageJson = await json("package.json");
const glama = await json("glama.json");
const dockerfile = await readFile(resolve(root, "Dockerfile.glama"), "utf8");

assert.equal(glama.$schema, "https://glama.ai/mcp/schemas/server.json");
assert.deepEqual(glama.maintainers, ["AjnasNB"]);

for (const contract of [
"COPY package.json package-lock.json ./",
"RUN npm ci --omit=dev --ignore-scripts",
'ENTRYPOINT ["node", "dist/cli.js"]',
'CMD ["mcp"]'
]) {
assert.ok(dockerfile.includes(contract), `Dockerfile.glama is missing: ${contract}`);
}

const expectedTools = [
"browser_audit",
"browser_capabilities",
"browser_capture",
"browser_health",
"browser_network",
"browser_propose_action",
"browser_sessions",
"browser_snapshot"
];
const image = process.env.COCKROACH_BROWSER_GLAMA_IMAGE?.trim();
if (image && !/^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,255}$/.test(image)) {
throw new Error("COCKROACH_BROWSER_GLAMA_IMAGE is not a valid local image reference");
}
const stderr = [];
const transport = new StdioClientTransport({
command: image ? "docker" : process.execPath,
args: image
? ["run", "--rm", "--interactive", image]
: [resolve(root, "dist", "cli.js"), "mcp"],
cwd: root,
stderr: "pipe"
});
transport.stderr?.on("data", (chunk) => stderr.push(String(chunk)));
const client = new Client({ name: "cockroach-browser-glama-check", version: packageJson.version });

try {
await client.connect(transport, { timeout: 10_000 });
assert.deepEqual(client.getServerVersion(), {
name: "cockroach-browser",
version: packageJson.version
});

const result = await client.listTools({}, { timeout: 10_000 });
assert.deepEqual(result.tools.map((tool) => tool.name).sort(), expectedTools);
for (const tool of result.tools) {
assert.ok(tool.title, `${tool.name} must expose a title`);
assert.ok(tool.description, `${tool.name} must expose a description`);
assert.equal(tool.inputSchema.type, "object", `${tool.name} must expose an object input schema`);
}

process.stdout.write(`${JSON.stringify({
ok: true,
mode: image ? "container" : "local-process",
server: client.getServerVersion(),
tools: expectedTools
}, null, 2)}\n`);
} catch (error) {
const serverStderr = stderr.join("").trim();
if (serverStderr) process.stderr.write(`${serverStderr}\n`);
throw error;
} finally {
await client.close();
}

async function json(path) {
return JSON.parse(await readFile(resolve(root, path), "utf8"));
}
5 changes: 5 additions & 0 deletions site/content.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,11 @@ cockroach-browser persistent-profile archive --name support-review`,
body:
"<p>The MCP surface provides <code>browser_capabilities</code>, <code>browser_health</code>, <code>browser_sessions</code>, <code>browser_snapshot</code>, <code>browser_audit</code>, <code>browser_capture</code>, <code>browser_network</code>, and <code>browser_propose_action</code>. Capture and network tools return bounded read evidence. A proposal returns canonical action material for a governed dispatcher and does not execute it.</p>"
},
{
title: "Reproduce the Glama build",
body:
"<p>Glama must inspect the stdio MCP process, not the authenticated HTTP daemon packaged by the root <code>Dockerfile</code>. Select <code>Dockerfile.glama</code> with the repository root as the build context in Glama's Dockerfile admin page. The root <code>glama.json</code> identifies the authorized maintainer. Schema inspection requires no secret because all eight tools are registered before a daemon client is configured; reproduce it with <code>npm run build</code> followed by <code>npm run check:glama</code>. Calls that read browser sessions still require <code>COCKROACH_BROWSER_TOKEN</code> and, when the daemon is not at the default loopback URL, <code>COCKROACH_BROWSER_URL</code>.</p>"
},
{
title: "Keep lifecycle authority outside the model",
body:
Expand Down
10 changes: 7 additions & 3 deletions site/docs/mcp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,21 @@ <h2>Configure the local server</h2>
<span class="section-number">02</span>
<h2>Observation-first tools</h2>
<p>The MCP surface provides <code>browser_capabilities</code>, <code>browser_health</code>, <code>browser_sessions</code>, <code>browser_snapshot</code>, <code>browser_audit</code>, <code>browser_capture</code>, <code>browser_network</code>, and <code>browser_propose_action</code>. Capture and network tools return bounded read evidence. A proposal returns canonical action material for a governed dispatcher and does not execute it.</p>
</section><section class="manual-section" id="keep-lifecycle-authority-outside-the-model">
</section><section class="manual-section" id="reproduce-the-glama-build">
<span class="section-number">03</span>
<h2>Reproduce the Glama build</h2>
<p>Glama must inspect the stdio MCP process, not the authenticated HTTP daemon packaged by the root <code>Dockerfile</code>. Select <code>Dockerfile.glama</code> with the repository root as the build context in Glama's Dockerfile admin page. The root <code>glama.json</code> identifies the authorized maintainer. Schema inspection requires no secret because all eight tools are registered before a daemon client is configured; reproduce it with <code>npm run build</code> followed by <code>npm run check:glama</code>. Calls that read browser sessions still require <code>COCKROACH_BROWSER_TOKEN</code> and, when the daemon is not at the default loopback URL, <code>COCKROACH_BROWSER_URL</code>.</p>
</section><section class="manual-section" id="keep-lifecycle-authority-outside-the-model">
<span class="section-number">04</span>
<h2>Keep lifecycle authority outside the model</h2>
<p>Session creation, profile import, login, secret resolution, remote binding, and raw action dispatch stay with the host. This prevents a model from expanding its own origins, credentials, browser state, or resource ceilings.</p>
</section><section class="manual-section" id="route-consequential-work-through-maqam">
<span class="section-number">04</span>
<span class="section-number">05</span>
<h2>Route consequential work through Maqam</h2>
<p>MCP proposes. Maqam evaluates policy, binds an approval to the exact operation, dispatches through the driver, rejects replay, and records governance evidence.</p>
</section>
</article>
<aside class="page-toc" aria-label="On this page"><h2>On this page</h2><a href="#configure-the-local-server">Configure the local server</a><a href="#observation-first-tools">Observation-first tools</a><a href="#keep-lifecycle-authority-outside-the-model">Keep lifecycle authority outside the model</a><a href="#route-consequential-work-through-maqam">Route consequential work through Maqam</a></aside>
<aside class="page-toc" aria-label="On this page"><h2>On this page</h2><a href="#configure-the-local-server">Configure the local server</a><a href="#observation-first-tools">Observation-first tools</a><a href="#reproduce-the-glama-build">Reproduce the Glama build</a><a href="#keep-lifecycle-authority-outside-the-model">Keep lifecycle authority outside the model</a><a href="#route-consequential-work-through-maqam">Route consequential work through Maqam</a></aside>
</main>
<footer class="footer">
<div class="shell footer-inner">
Expand Down
3 changes: 3 additions & 0 deletions site/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,9 @@ Start the authenticated daemon first. Load its token into the MCP process throug
### Observation-first tools
The MCP surface provides browser_capabilities, browser_health, browser_sessions, browser_snapshot, browser_audit, browser_capture, browser_network, and browser_propose_action. Capture and network tools return bounded read evidence. A proposal returns canonical action material for a governed dispatcher and does not execute it.

### Reproduce the Glama build
Glama must inspect the stdio MCP process, not the authenticated HTTP daemon packaged by the root Dockerfile. Select Dockerfile.glama with the repository root as the build context in Glama's Dockerfile admin page. The root glama.json identifies the authorized maintainer. Schema inspection requires no secret because all eight tools are registered before a daemon client is configured; reproduce it with npm run build followed by npm run check:glama. Calls that read browser sessions still require COCKROACH_BROWSER_TOKEN and, when the daemon is not at the default loopback URL, COCKROACH_BROWSER_URL.

### Keep lifecycle authority outside the model
Session creation, profile import, login, secret resolution, remote binding, and raw action dispatch stay with the host. This prevents a model from expanding its own origins, credentials, browser state, or resource ceilings.

Expand Down
1 change: 1 addition & 0 deletions site/search.json
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@
"keywords": [
"Configure the local server",
"Observation-first tools",
"Reproduce the Glama build",
"Keep lifecycle authority outside the model",
"Route consequential work through Maqam"
]
Expand Down