This project is a monorepo containing the Brave Search MCP Server and its underlying dependencies. It is managed using Turbo and pnpm workspaces.
The primary goal is to provide a Model Context Protocol (MCP) Server that integrates with the Brave Search API for AI assistants.
apps/brave-search-mcp: The MCP Server application.- Exposes tools for Web, Image, News, Video, and Local search.
- Consumes the
brave-searchpackage.
packages/brave-search: A typed SDK/wrapper for the Brave Search API.- Standalone library used by the MCP server.
- Monorepo Tools: Turbo, pnpm workspaces
- Language: TypeScript / Node.js
- Frameworks:
@modelcontextprotocol/sdk - Utilities:
axios,zod
- Node.js (LTS)
- pnpm (
npm install -g pnpm) - Brave Search API Key
From the root:
pnpm installBuild all apps and packages using Turbo:
pnpm buildYou can run tasks from the root, which will propagate to the workspaces via Turbo.
pnpm build: Build all packages/apps.pnpm lint: Lint all packages/apps.pnpm check: Typecheck and lint all.pnpm run clean: Clean dist folders.
To run the server locally:
cd apps/brave-search-mcp
export BRAVE_API_KEY=your_api_key_here
node dist/index.js- Entry Point:
src/index.ts - Core:
src/server.ts(BraveMcpServer) - Tools:
src/tools/*.ts(BaseTool implementation)
- Entry Point:
src/braveSearch.ts - Types:
src/types.ts
When creating UI widgets for MCP tools, use the Dual-Resource Strategy to support both MCP-APP hosts (Claude Desktop, MCPJam) and ChatGPT from a single server instance.
-
Separate bundles required -
vite-plugin-singlefilebundles everything, so ext-apps SDK initialization runs even withReact.lazy(). Create two entry points:mcp-app.html- includes ext-apps SDK for MCP-APP hostschatgpt-app.html- standalone, no ext-apps imports (useswindow.openai)
-
Register both resources with combined metadata:
registerAppTool(server, toolName, {
_meta: {
// MCP-APP (ext-apps) looks for this
ui: { resourceUri: 'ui://tool/mcp-app.html' },
// ChatGPT looks for this
'openai/outputTemplate': 'ui://tool/chatgpt-widget.html',
},
}, handler);-
MIME types:
- MCP-APP:
text/html+mcpappoutput(viaRESOURCE_MIME_TYPE) - ChatGPT:
text/html+skybridge
- MCP-APP:
-
ChatGPT widget specifics:
- Data: Poll
window.openai.toolOutput(null at mount, populated later) - Links: Use
openExternal({ href: url })notopenExternal(url) - Types: Create
openai.d.tsforwindow.openaiAPI
- Data: Poll
-
Build scripts in
package.json:
"build:ui": "INPUT=mcp-app.html vite build --config ui/vite.config.ts",
"build:ui:chatgpt": "INPUT=chatgpt-app.html vite build --config ui/vite.config.ts"node dist/index.js --ui --httpSingle flag serves both hosts - each picks the resource matching its capabilities.
- Workspaces: Internal dependencies are referenced via
workspace:*(e.g.,"brave-search": "workspace:*"). - Code Style: ESLint with
@antfu/eslint-config. - TypeScript: Strict mode enabled.