Skip to content

Commit e2095ff

Browse files
authored
Add recommended guards helper (#12)
## Summary - add public `recommendedGuards()` helper for unpackaged + env-var MCP opt-in checks - export the helper and its options type from the root and types entrypoints - update README quickstart/security snippets to use the package helper ## Verification - [x] `pnpm run lint` - [x] `pnpm run typecheck` - [x] `pnpm run test` - [x] `pnpm run build` - [ ] `pnpm test:electron` if Electron/CDP behavior changed Closes #5 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a public helper to opt into MCP when the app is unpackaged and a specified environment variable equals "1". * **Documentation** * Quickstart and guidance updated with example usage and clearer behavior and edge-case notes. * **Tests** * Unit tests added covering packaged vs unpackaged states, env-var opt-in, and error conditions. * **Chores** * Changeset added documenting a minor version bump. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 115d110 commit e2095ff

6 files changed

Lines changed: 110 additions & 9 deletions

File tree

.changeset/recommended-guards.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nebula-agents/electron-mcp": minor
3+
---
4+
5+
Add a public `recommendedGuards()` helper for unpackaged app plus env-var MCP opt-in checks.

README.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ external model, this package is probably not the right fit.
1919

2020
```ts
2121
import { app, BrowserWindow } from "electron";
22-
import { createElectronMcpServer } from "@nebula-agents/electron-mcp";
22+
import {
23+
createElectronMcpServer,
24+
recommendedGuards,
25+
} from "@nebula-agents/electron-mcp";
2326

2427
let mainWindow: BrowserWindow | null = null;
2528

@@ -30,7 +33,7 @@ const mcp = createElectronMcpServer({
3033
app.whenReady().then(async () => {
3134
mainWindow = new BrowserWindow();
3235

33-
if (recommendedGuards()) {
36+
if (recommendedGuards({ app, envVar: "MY_APP_MCP" })) {
3437
await mcp.start();
3538
console.log(`MCP listening at ${mcp.url}`);
3639
}
@@ -39,10 +42,6 @@ app.whenReady().then(async () => {
3942
app.on("before-quit", () => {
4043
void mcp.stop();
4144
});
42-
43-
function recommendedGuards(): boolean {
44-
return !app.isPackaged && process.env.MY_APP_MCP === "1";
45-
}
4645
```
4746

4847
Connect an MCP client to the logged HTTP URL. The default is
@@ -124,11 +123,17 @@ own gate or do not start it.
124123
Recommended production guard:
125124

126125
```ts
127-
function recommendedGuards(): boolean {
128-
return !app.isPackaged && process.env.MY_APP_MCP === "1";
126+
import { recommendedGuards } from "@nebula-agents/electron-mcp";
127+
128+
if (recommendedGuards({ app, envVar: "MY_APP_MCP" })) {
129+
await mcp.start();
129130
}
130131
```
131132

133+
The helper returns `true` only when the app is not packaged and the selected
134+
environment variable is set to `"1"`. It throws if neither `app.isPackaged` nor
135+
`isPackaged` is provided.
136+
132137
## Maintenance
133138

134139
This is open code with no support SLA. Agent Labs reviews issues and PRs on a

src/guards.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export interface RecommendedGuardsOptions {
2+
app?: { readonly isPackaged: boolean };
3+
isPackaged?: boolean;
4+
env?: Record<string, string | undefined>;
5+
envVar?: string;
6+
}
7+
8+
const DEFAULT_ENV_VAR = "ELECTRON_MCP";
9+
10+
export function recommendedGuards({
11+
app,
12+
isPackaged,
13+
env = process.env,
14+
envVar = DEFAULT_ENV_VAR,
15+
}: RecommendedGuardsOptions = {}): boolean {
16+
const packaged = isPackaged ?? app?.isPackaged;
17+
if (packaged === undefined) {
18+
throw new Error(
19+
"[mcp] recommendedGuards requires app.isPackaged or isPackaged",
20+
);
21+
}
22+
return !packaged && env[envVar] === "1";
23+
}

src/index.test.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
66
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
77
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8-
import { createElectronMcpServer } from "./index";
8+
import { createElectronMcpServer, recommendedGuards } from "./index";
99

1010
// Ephemeral-port helper — the harness picks a free port via `port: 0`
1111
// and the handle exposes the resolved URL.
@@ -120,3 +120,68 @@ describe("createElectronMcpServer", () => {
120120
expect(handle.isRunning).toBe(false);
121121
});
122122
});
123+
124+
describe("recommendedGuards", () => {
125+
it("throws a packaged-state error when called without options", () => {
126+
expect(() => recommendedGuards()).toThrow(/isPackaged/i);
127+
});
128+
129+
it("allows startup when the app is unpackaged and the opt-in env var is enabled", () => {
130+
expect(
131+
recommendedGuards({
132+
isPackaged: false,
133+
env: { MY_APP_MCP: "1" },
134+
envVar: "MY_APP_MCP",
135+
}),
136+
).toBe(true);
137+
});
138+
139+
it("blocks startup when the opt-in env var is not enabled", () => {
140+
expect(
141+
recommendedGuards({
142+
isPackaged: false,
143+
env: {},
144+
envVar: "MY_APP_MCP",
145+
}),
146+
).toBe(false);
147+
});
148+
149+
it("blocks startup in packaged builds even when the opt-in env var is enabled", () => {
150+
expect(
151+
recommendedGuards({
152+
isPackaged: true,
153+
env: { MY_APP_MCP: "1" },
154+
envVar: "MY_APP_MCP",
155+
}),
156+
).toBe(false);
157+
});
158+
159+
it("accepts an Electron app-like object for the packaged-state check", () => {
160+
expect(
161+
recommendedGuards({
162+
app: { isPackaged: false },
163+
env: { MY_APP_MCP: "1" },
164+
envVar: "MY_APP_MCP",
165+
}),
166+
).toBe(true);
167+
});
168+
169+
it("blocks startup when an Electron app-like object reports a packaged build", () => {
170+
expect(
171+
recommendedGuards({
172+
app: { isPackaged: true },
173+
env: { MY_APP_MCP: "1" },
174+
envVar: "MY_APP_MCP",
175+
}),
176+
).toBe(false);
177+
});
178+
179+
it("throws when no packaged-state source is provided", () => {
180+
expect(() =>
181+
recommendedGuards({
182+
env: { MY_APP_MCP: "1" },
183+
envVar: "MY_APP_MCP",
184+
}),
185+
).toThrow(/isPackaged/i);
186+
});
187+
});

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
import type { SurfaceGetter, SurfaceMap } from "./surfaces";
88
import type { ToolDef } from "./tool-def";
99

10+
export { type RecommendedGuardsOptions, recommendedGuards } from "./guards";
11+
1012
export interface ElectronMcpServerConfig {
1113
getSurfaces: SurfaceGetter;
1214
port?: number;

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export type {
22
ElectronMcpServerConfig,
33
ElectronMcpServerHandle,
44
McpLogger,
5+
RecommendedGuardsOptions,
56
SurfaceGetter,
67
SurfaceMap,
78
ToolDef,

0 commit comments

Comments
 (0)