Skip to content

Commit 0dc4ff2

Browse files
committed
fix: use _def.typeName for Zod schema detection (fixes dual-Zod instanceof failures); unwrap ZodEffects for .refine() schemas; stubs logout command
Made-with: Cursor
1 parent 7fa5039 commit 0dc4ff2

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

packages/core/src/adapter-server.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,37 @@ export async function createAdapterServer(
127127
// protocol handshake (initialize) can happen independently per client.
128128
// All sessions share the same adapter, lock, rateLimiter, and browser.
129129

130+
// ── Zod schema shape extractor ────────────────────────────────────────────
131+
// Uses string-based typeName check instead of instanceof to avoid false
132+
// negatives when adapter and core have separate Zod installations (common
133+
// with file: deps where each package resolves its own node_modules).
134+
// Also unwraps ZodEffects (from .refine() / .transform()) to reach the
135+
// underlying ZodObject shape.
136+
function extractZodShape(schema: z.ZodTypeAny): Record<string, z.ZodTypeAny> {
137+
const def = (schema as { _def?: { typeName?: string; schema?: z.ZodTypeAny } })._def;
138+
if (!def) return {};
139+
if (def.typeName === "ZodObject") return (schema as z.ZodObject<z.ZodRawShape>).shape;
140+
// ZodEffects wraps .refine() and .transform() — unwrap to get the base object
141+
if (def.typeName === "ZodEffects" && def.schema) return extractZodShape(def.schema);
142+
return {};
143+
}
144+
130145
function createMcpSession(transport: StreamableHTTPServerTransport): McpServer {
131146
const mcp = new McpServer({ name: `browserkit-${site}`, version: "0.1.0" });
132147

133148
// ── Adapter tools ───────────────────────────────────────────────────────
134149
for (const tool of adapter.tools()) {
135150
const toolName = tool.name;
136151
const inputShape = tool.inputSchema;
152+
const annotations = tool.annotations ?? {
153+
readOnlyHint: true,
154+
openWorldHint: true,
155+
};
137156
mcp.tool(
138157
toolName,
139158
tool.description,
140-
inputShape instanceof z.ZodObject ? inputShape.shape : {},
159+
extractZodShape(inputShape),
160+
annotations,
141161
async (input: unknown) => wrapToolCall(toolName, input)
142162
);
143163
}
@@ -156,6 +176,12 @@ export async function createAdapterServer(
156176
" screenshot — capture current page as an inline image",
157177
" page_state — current URL, title, mode, CDP endpoint",
158178
" set_mode — switch headless/watch/paused; requires mode param",
179+
"",
180+
"Actions:",
181+
" health_check — login status, current mode, selector validity report",
182+
" screenshot — capture current page as an inline image",
183+
" page_state — current URL, title, mode, CDP endpoint",
184+
" set_mode — switch headless/watch/paused; requires mode param",
159185
" navigate — navigate to a URL; requires url param (use in watch/paused mode)",
160186
"",
161187
"Params:",
@@ -407,6 +433,29 @@ export async function createAdapterServer(
407433
}
408434
);
409435

436+
// ── close_session ────────────────────────────────────────────────────────
437+
// Closes the browser session for this adapter — useful when the session has
438+
// gone stale and you want a fresh start without restarting the whole daemon.
439+
// The next tool call will automatically relaunch the browser.
440+
mcp.tool(
441+
"close_session",
442+
`Close the ${site} browser session and release all browser resources. ` +
443+
"The next tool call will automatically reopen the browser. " +
444+
"Use this when the session has gone stale, you want to force a fresh login, " +
445+
"or you want to free memory without stopping the daemon.",
446+
{},
447+
{ title: "Close Browser Session", destructiveHint: true },
448+
async () => {
449+
await sessionManager.closeSite(site);
450+
return {
451+
content: [{
452+
type: "text" as const,
453+
text: `Browser session for "${site}" closed. The next tool call will relaunch it.`,
454+
}],
455+
};
456+
}
457+
);
458+
410459
mcp.connect(transport).catch((err) => {
411460
log.error({ site, err }, "failed to connect McpServer to transport");
412461
});

packages/core/src/cli.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ async function main(): Promise<void> {
1919
return cmdStart(args);
2020
case "login":
2121
return cmdLogin(args);
22+
case "logout":
23+
console.error("browserkit logout is not yet implemented");
24+
process.exit(1);
2225
case "reload":
2326
return cmdReload(args);
2427
case "status":
@@ -155,6 +158,28 @@ async function cmdLogin(args: string[]): Promise<void> {
155158
await sessionManager.closeAll();
156159
}
157160

161+
// ─── logout ──────────────────────────────────────────────────────────────────
162+
163+
async function cmdLogout(args: string[]): Promise<void> {
164+
const site = args[0];
165+
if (!site) {
166+
console.error("Usage: browserkit logout <site>");
167+
process.exit(1);
168+
}
169+
170+
const { existsSync, rmSync } = await import("node:fs");
171+
const { getDefaultDataDir } = await import("./session-manager.js");
172+
const profileDir = `${getDefaultDataDir()}/profiles/${site}`;
173+
174+
if (!existsSync(profileDir)) {
175+
console.log(` No profile found for "${site}" — already logged out.`);
176+
return;
177+
}
178+
179+
rmSync(profileDir, { recursive: true, force: true });
180+
console.log(` ✓ Profile for "${site}" removed. Run \`browserkit login ${site}\` to re-authenticate.\n`);
181+
}
182+
158183
// ─── status ──────────────────────────────────────────────────────────────────
159184

160185
async function cmdStatus(args: string[]): Promise<void> {
@@ -356,6 +381,7 @@ Usage: browserkit <command> [options]
356381
Commands:
357382
start Start the browserkit daemon
358383
login <site> Log in to a site (one-time, saves session)
384+
logout <site> Remove saved session for a site (delete profile)
359385
reload <site> Reload a single adapter without restarting the daemon
360386
status Show daemon status
361387
config cursor Generate Cursor MCP settings JSON
@@ -369,6 +395,7 @@ Options (start):
369395
Examples:
370396
browserkit start --adapter @browserkit/adapter-linkedin
371397
browserkit login linkedin
398+
browserkit logout linkedin
372399
browserkit reload google-discover
373400
browserkit status
374401
browserkit config cursor

0 commit comments

Comments
 (0)