-
Notifications
You must be signed in to change notification settings - Fork 15
feat(mcp-web-fetch): expose domain filters #383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # Web Fetch Domain Filters Implementation Plan | ||
|
|
||
| > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. | ||
|
|
||
| **Goal:** Expose Anthropic-compatible `allowed_domains` and `blocked_domains` parameters on the `web_fetch` MCP tool and route them to the existing engine host filters. | ||
|
|
||
| **Architecture:** Keep enforcement in the existing URL safety and fetch engine path. Extend only the MCP schema and handler adapter so `allowed_domains` maps to `allowHosts` and `blocked_domains` maps to `denyHosts`. | ||
|
|
||
| **Tech Stack:** TypeScript, Vitest, existing `@frontagent/mcp-web-fetch` package. | ||
|
|
||
| --- | ||
|
|
||
| ### Task 1: Add Schema and Handler Coverage | ||
|
|
||
| **Files:** | ||
| - Modify: `packages/mcp-web-fetch/src/tools.security.test.ts` | ||
|
|
||
| - [x] **Step 1: Add schema assertions** | ||
|
|
||
| Import `webFetchSchema` and `vi`, then add tests asserting the schema exposes `allowed_domains` and `blocked_domains` as optional string arrays. | ||
|
|
||
| - [x] **Step 2: Add handler no-network assertions** | ||
|
|
||
| Patch `globalThis.fetch` with a throwing mock for each domain-filter test, call `handleWebFetchTool('web_fetch', ...)`, assert the result fails with allow/block wording, and assert the fetch mock was not called. | ||
|
|
||
| - [x] **Step 3: Run focused test and confirm failure before implementation** | ||
|
|
||
| Run: `pnpm --dir packages/mcp-web-fetch test -- --run src/tools.security.test.ts` | ||
|
|
||
| Expected before implementation: schema/domain-filter tests fail because schema fields are missing and handler does not pass filters to the engine. | ||
|
|
||
| ### Task 2: Map Tool Parameters to Engine Options | ||
|
|
||
| **Files:** | ||
| - Modify: `packages/mcp-web-fetch/src/tools.ts` | ||
|
|
||
| - [x] **Step 1: Extend `webFetchSchema`** | ||
|
|
||
| Add optional `allowed_domains` and `blocked_domains` properties with `type: 'array'`, string `items`, and descriptions noting their mapping to `allowHosts` and `denyHosts`. | ||
|
|
||
| - [x] **Step 2: Extend `handleWebFetchTool`** | ||
|
|
||
| Pass `allowHosts: args.allowed_domains as string[] | undefined` and `denyHosts: args.blocked_domains as string[] | undefined` to `fetchUrl`. | ||
|
|
||
| - [x] **Step 3: Run focused tests** | ||
|
|
||
| Run: `pnpm --dir packages/mcp-web-fetch test -- --run src/tools.security.test.ts` | ||
|
|
||
| Expected after implementation: all tests in `tools.security.test.ts` pass. | ||
|
|
||
| ### Task 3: Verify and Prepare PR | ||
|
|
||
| **Files:** | ||
| - No additional planned code files. | ||
|
|
||
| - [x] **Step 1: Run mcp-web-fetch package tests** | ||
|
|
||
| Run: `pnpm --dir packages/mcp-web-fetch test` | ||
|
|
||
| Expected: package test suite passes. | ||
|
|
||
| - [x] **Step 2: Run required broader gate** | ||
|
|
||
| Run: `pnpm quality:precommit` | ||
|
|
||
| Expected: lint, typecheck, test, and workflow tests pass. | ||
|
|
||
| - [x] **Step 3: Run GitNexus final diff analysis** | ||
|
|
||
| Run GitNexus `detect_changes` for `/tmp/frontagent-issue-380` before committing and include the blast radius summary in the PR body. | ||
|
|
||
| - [ ] **Step 4: Commit, push, and open PR** | ||
|
|
||
| Commit with a focused message, push `feat/web-fetch-domain-filters-380`, and open a PR to `develop` with `Closes #380`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,18 @@ export const webFetchSchema = { | |
| maximum: 5000000, | ||
| description: '响应体最大字节数,超出后截断,默认 2000000,硬上限 5000000', | ||
| }, | ||
| allowed_domains: { | ||
| type: 'array', | ||
| items: { type: 'string' }, | ||
| minItems: 1, | ||
| description: '可选允许域名列表;映射到底层引擎 allowHosts,仅允许抓取这些主机名。', | ||
| }, | ||
|
Comment on lines
+41
to
+46
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 671cbd0: |
||
| blocked_domains: { | ||
| type: 'array', | ||
| items: { type: 'string' }, | ||
| minItems: 1, | ||
| description: '可选阻止域名列表;映射到底层引擎 denyHosts,拒绝抓取这些主机名。', | ||
| }, | ||
| }, | ||
| required: ['url'], | ||
| }, | ||
|
|
@@ -56,10 +68,28 @@ export async function handleWebFetchTool( | |
| try { | ||
| switch (toolName) { | ||
| case 'web_fetch': { | ||
| const allowHosts = args.allowed_domains as string[] | undefined; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 用 |
||
| const denyHosts = args.blocked_domains as string[] | undefined; | ||
|
|
||
| if (Array.isArray(allowHosts) && allowHosts.length === 0) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 空数组被显式拒绝,但 schema 已声明 |
||
| return { | ||
| success: false, | ||
| error: 'allowed_domains must contain at least one domain when provided', | ||
| }; | ||
| } | ||
| if (Array.isArray(denyHosts) && denyHosts.length === 0) { | ||
| return { | ||
| success: false, | ||
| error: 'blocked_domains must contain at least one domain when provided', | ||
| }; | ||
| } | ||
|
|
||
| const data = await fetchUrl(args.url as string, { | ||
| format: args.format as 'text' | 'html' | undefined, | ||
| timeoutMs: args.timeoutMs as number | undefined, | ||
| maxBytes: args.maxBytes as number | undefined, | ||
| allowHosts, | ||
| denyHosts, | ||
| }); | ||
| return { success: true, data }; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/allow/i过于宽泛,可能匹配无关错误文案;收紧为/not in allowlist/i以确保断言的是 allowlist 拒绝。