@briefroom/mcp
Advanced tools
| import { z } from 'zod'; | ||
| import { runCliAsToolResult, } from '../lib/tool-result.js'; | ||
| export const deployHtmlDescription = 'Zip a local directory of HTML/CSS/JS and upload it to briefroom, returning a share URL. Uses BRIEFROOM_TOKEN (or the briefroom CLI login) for authentication. Non-interactive.'; | ||
| export const deployHtmlDescription = 'Zip a local directory of HTML/CSS/JS and upload it to briefroom, returning a share URL. Supports a share-link expiry (also applied to the existing link on redeploy) and password protection (Pro+ plans). Uses BRIEFROOM_TOKEN (or the briefroom CLI login) for authentication. Non-interactive.'; | ||
| // Flag injection 防御: `-` 始まりの値は CLI の argv パーサ (citty/mri) に | ||
@@ -31,3 +31,3 @@ // フラグとして解釈され、`--api-url=http://evil/` を注入して PAT を攻撃者に | ||
| .optional() | ||
| .describe('Share link expiry. Defaults to 7d on the server.'), | ||
| .describe('Share link expiry. Defaults to 7d on the server. When set on a redeploy, it also updates the existing link.'), | ||
| new: z | ||
@@ -37,5 +37,29 @@ .boolean() | ||
| .describe('Ignore briefroom.json and create a brand new room with a unique slug.'), | ||
| password: z | ||
| .string() | ||
| .min(6) | ||
| .max(128) | ||
| .optional() | ||
| .describe('Protect the share link with a password (Pro+ plans only). Passed to the CLI via an environment variable, never as an argv flag, so it is not exposed in the process list.'), | ||
| visibility: z | ||
| .enum(['unlisted', 'password_protected']) | ||
| .optional() | ||
| .describe("Share link visibility. 'unlisted' removes an existing password; 'password_protected' requires the password field."), | ||
| }; | ||
| export const DEPLOY_TIMEOUT_MS = 120_000; | ||
| export async function runDeployHtml(input, cliOpts = {}) { | ||
| // Fix P2-1 (追加防御): password と visibility='unlisted' の同時指定は矛盾 | ||
| // (保護しつつ解除は成立しない)。CLI 側でも弾くが、MCP boundary でも早期 reject して | ||
| // 「password が env で届き visibility=unlisted だけ CLI に渡ってサイレント解除」を防ぐ。 | ||
| if (input.password !== undefined && input.visibility === 'unlisted') { | ||
| return { | ||
| content: [ | ||
| { | ||
| type: 'text', | ||
| text: "Invalid input: 'password' cannot be combined with visibility 'unlisted'. Omit 'password' (with visibility 'unlisted') to remove protection, or omit 'visibility' to set a password.", | ||
| }, | ||
| ], | ||
| isError: true, | ||
| }; | ||
| } | ||
| // 全 flag は path より前に置く (`--` 以降は全て positional 扱いになるため)。 | ||
@@ -48,2 +72,4 @@ // value flag は `--key=value` inline 形式 (中身に `--` があっても flag 化しない)。 | ||
| args.push(`--expires=${input.expires}`); | ||
| if (input.visibility) | ||
| args.push(`--visibility=${input.visibility}`); | ||
| if (input.new) | ||
@@ -53,3 +79,17 @@ args.push('--new'); | ||
| args.push('--', input.path); | ||
| return runCliAsToolResult({ timeoutMs: DEPLOY_TIMEOUT_MS, ...cliOpts, args }, (stdout) => stdout.trim()); | ||
| // password は argv に載せない (= `ps` で他ユーザーに見えるのを防ぐ)。子プロセスの | ||
| // 環境変数 BRIEFROOM_SHARE_PASSWORD 経由で渡し、CLI 側がそれを読む。 | ||
| // | ||
| // Fix P2-2 (hermetic): password の駆動は input.password のみ。親 env に | ||
| // BRIEFROOM_SHARE_PASSWORD が混入していても子へ漏らさないよう、常に子 env を明示構築し、 | ||
| // input.password が無いときは明示的に delete する (= 親 env 由来の意図しない再適用 + | ||
| // argon2 再ハッシュ + 閲覧セッション失効を防ぐ)。 | ||
| const childEnv = { ...(cliOpts.env ?? process.env) }; | ||
| if (input.password !== undefined) { | ||
| childEnv.BRIEFROOM_SHARE_PASSWORD = input.password; | ||
| } | ||
| else { | ||
| delete childEnv.BRIEFROOM_SHARE_PASSWORD; | ||
| } | ||
| return runCliAsToolResult({ timeoutMs: DEPLOY_TIMEOUT_MS, ...cliOpts, args, env: childEnv }, (stdout) => stdout.trim()); | ||
| } |
+2
-2
| { | ||
| "name": "@briefroom/mcp", | ||
| "version": "0.1.0", | ||
| "version": "0.2.0", | ||
| "description": "briefroom stdio MCP server — expose the briefroom CLI to Claude Code, Codex, and other agents.", | ||
@@ -23,3 +23,3 @@ "license": "MIT", | ||
| "zod": "^4.4.3", | ||
| "@briefroom/cli": "^0.1.2" | ||
| "@briefroom/cli": "^0.2.0" | ||
| }, | ||
@@ -26,0 +26,0 @@ "devDependencies": { |
+5
-3
@@ -123,8 +123,10 @@ # @briefroom/mcp | ||
| "room": "demo-a", // optional slug override | ||
| "expires": "7d", // optional — "7d" | "30d" | "never" | ||
| "new": false // optional — start a brand new room | ||
| "expires": "7d", // optional — "7d" | "30d" | "never" (also updates the existing link on redeploy) | ||
| "new": false, // optional — start a brand new room | ||
| "password": "s3cret", // optional — password-protect the link (Pro+ plans; passed to the CLI via env, never argv) | ||
| "visibility": "unlisted" // optional — "unlisted" | "password_protected"; "unlisted" clears an existing password | ||
| } | ||
| ``` | ||
| Returns the raw CLI JSON (`share_url`, `room_id`, `version_number`, …). | ||
| Returns the raw CLI JSON (`share_url`, `room_id`, `version_number`, `visibility`, …). | ||
@@ -131,0 +133,0 @@ ### `get_feedback` |
23181
13.16%349
13.31%176
1.15%6
20%+ Added
- Removed
Updated