{"id":50433199,"url":"https://github.com/teatak/mcp-server-browser","last_synced_at":"2026-05-31T15:30:34.658Z","repository":{"id":357319403,"uuid":"1236376764","full_name":"teatak/mcp-server-browser","owner":"teatak","description":"MCP server that runs in the browser. Register tools and prompts on a page; expose them to a local MCP client over WebSocket.","archived":false,"fork":false,"pushed_at":"2026-05-12T08:11:38.000Z","size":23,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-12T10:07:30.844Z","etag":null,"topics":["agent","browser","mcp","model-context-protocol","typescript","websocket"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/teatak.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-12T07:35:56.000Z","updated_at":"2026-05-12T08:11:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/teatak/mcp-server-browser","commit_stats":null,"previous_names":["teatak/mcp-server-browser"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/teatak/mcp-server-browser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teatak%2Fmcp-server-browser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teatak%2Fmcp-server-browser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teatak%2Fmcp-server-browser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teatak%2Fmcp-server-browser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teatak","download_url":"https://codeload.github.com/teatak/mcp-server-browser/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teatak%2Fmcp-server-browser/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33737692,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent","browser","mcp","model-context-protocol","typescript","websocket"],"created_at":"2026-05-31T15:30:33.856Z","updated_at":"2026-05-31T15:30:34.647Z","avatar_url":"https://github.com/teatak.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @teatak/mcp-server-browser\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/architecture.png\" alt=\"An MCP server that runs in the browser — the browser acts as the MCP server, the agent as the MCP client; the opposite of playwright-mcp / browserbase / chrome-devtools-mcp.\" width=\"820\"\u003e\n\u003c/p\u003e\n\nAn [MCP (Model Context Protocol)][mcp] server that runs **in the browser**.\n\nRegister tools and prompts on a web page; expose them to a local MCP client\n(such as an agent daemon or sidecar process) over WebSocket. The browser\nacts as the **MCP server** — your tool handlers run client-side and the\nagent calls into them.\n\n[mcp]: https://modelcontextprotocol.io\n\n## Why a \"browser-side server\"?\n\nIn the usual MCP topology, servers run as local processes and expose\nfilesystem / database / API tools. This package flips that: the browser\nexposes capabilities to the agent. Useful when you want the agent to:\n\n- Drive a UI you're rendering (a canvas, a chart, a form).\n- Call into APIs that are only reachable from the user's browser session\n  (authenticated SaaS, page-scoped APIs).\n- Get human-in-the-loop confirmation through DOM affordances.\n\nAt the wire level the browser dials a WebSocket to the agent; at the MCP\nprotocol level the browser is the server (handles `tools/list`,\n`tools/call`, `prompts/list`, etc.).\n\n## How this differs from browser-automation MCP servers\n\nIf you've seen packages like [`@playwright/mcp`][playwright-mcp],\n[`BrowserMCP/mcp`][browser-mcp], [`browserbase/mcp-server-browserbase`][browserbase],\nor [`chrome-devtools-mcp`][chrome-devtools-mcp], those go in the **opposite\ndirection** from this one.\n\n|                            | Browser-automation MCP servers          | `@teatak/mcp-server-browser`                                         |\n| -------------------------- | --------------------------------------- | -------------------------------------------------------------------- |\n| Where the MCP server runs  | A local Node process (or cloud)         | The browser page itself                                              |\n| Who defines the tools      | The package author (fixed set)          | You — the page registers its own tools                               |\n| Browser's role             | Target of automation (driven by agent)  | Active producer of capabilities                                      |\n| Typical tools              | `navigate`, `click`, `screenshot`, …    | Anything your page can do — UI rendering, page-scoped APIs, etc.     |\n| Bridge                     | Chrome extension / CDP / Playwright     | `new WebSocket(...)` from the page                                   |\n\nShort version: those packages give **an agent a browser**. This package\nlets **your browser app give an agent custom tools**.\n\nThe two patterns compose — you can use Playwright MCP to let an agent drive\na page **and** have the same page expose its own MCP server (via this\npackage) for higher-level domain operations.\n\n[playwright-mcp]: https://github.com/microsoft/playwright-mcp\n[browser-mcp]: https://github.com/BrowserMCP/mcp\n[browserbase]: https://github.com/browserbase/mcp-server-browserbase\n[chrome-devtools-mcp]: https://github.com/ChromeDevTools/chrome-devtools-mcp\n\n## Install\n\n```sh\nnpm install @teatak/mcp-server-browser\n```\n\n## Quick start\n\n```ts\nimport { createServer } from \"@teatak/mcp-server-browser\";\n\nconst server = createServer({\n  endpoint: \"ws://127.0.0.1:9669/mcp/ws\",\n  serverInfo: { name: \"my-page\", version: \"1.0.0\" },\n});\n\nserver.registerTool({\n  name: \"demo.echo\",\n  description: \"Echo back whatever the caller passed.\",\n  inputSchema: {\n    type: \"object\",\n    properties: { text: { type: \"string\" } },\n    required: [\"text\"],\n  },\n  handler: async ({ text }) =\u003e ({ ok: true, text }),\n});\n\nserver.connect();\n```\n\n### Tool metadata\n\nSince `0.0.2`, tool definitions may include MCP's `_meta` extension\nobject. It is passed through unchanged in `tools/list`, so clients can carry\nprivate namespaced metadata without adding non-standard top-level fields.\n\n```ts\nserver.registerTool({\n  name: \"demo.echo\",\n  description: \"Echo back whatever the caller passed.\",\n  inputSchema: { type: \"object\", properties: {} },\n  _meta: {\n    \"example.com/tier\": \"lite\",\n  },\n  handler: async () =\u003e ({ ok: true }),\n});\n```\n\n## The other side — a minimal Go agent\n\nThe snippet above is only half the picture. Here's the matching MCP\nclient side — a Go program that accepts the WebSocket from the browser\nand drives it over plain JSON-RPC 2.0. No external MCP library required;\nthe only dependency is `gorilla/websocket`.\n\n```go\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/websocket\"\n)\n\ntype rpcMessage struct {\n\tJSONRPC string          `json:\"jsonrpc\"`\n\tID      json.RawMessage `json:\"id,omitempty\"`\n\tMethod  string          `json:\"method,omitempty\"`\n\tParams  json.RawMessage `json:\"params,omitempty\"`\n\tResult  json.RawMessage `json:\"result,omitempty\"`\n\tError   *struct {\n\t\tCode    int    `json:\"code\"`\n\t\tMessage string `json:\"message\"`\n\t} `json:\"error,omitempty\"`\n}\n\n// Single-flight roundtrip — sends one request and reads the next frame as\n// its response. For concurrent calls, track pending requests by `id` in a\n// sync.Map and dispatch from a dedicated read loop.\nfunc roundtrip(conn *websocket.Conn, id int, method string, params any) (json.RawMessage, error) {\n\tp, _ := json.Marshal(params)\n\tidRaw, _ := json.Marshal(id)\n\tif err := conn.WriteJSON(rpcMessage{\n\t\tJSONRPC: \"2.0\", ID: idRaw, Method: method, Params: p,\n\t}); err != nil {\n\t\treturn nil, err\n\t}\n\tvar resp rpcMessage\n\tif err := conn.ReadJSON(\u0026resp); err != nil {\n\t\treturn nil, err\n\t}\n\tif resp.Error != nil {\n\t\treturn nil, fmt.Errorf(\"rpc %d: %s\", resp.Error.Code, resp.Error.Message)\n\t}\n\treturn resp.Result, nil\n}\n\nvar upgrader = websocket.Upgrader{\n\t// Tighten in production: pin Origin and validate a session token.\n\tCheckOrigin: func(r *http.Request) bool { return true },\n}\n\nfunc main() {\n\thttp.HandleFunc(\"/mcp/ws\", func(w http.ResponseWriter, r *http.Request) {\n\t\tconn, err := upgrader.Upgrade(w, r, nil)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tdefer conn.Close()\n\n\t\t// 1. Handshake.\n\t\tif _, err := roundtrip(conn, 1, \"initialize\", map[string]any{\n\t\t\t\"protocolVersion\": \"2025-03-26\",\n\t\t\t\"clientInfo\":      map[string]any{\"name\": \"demo-agent\", \"version\": \"0.1\"},\n\t\t\t\"capabilities\":    map[string]any{},\n\t\t}); err != nil {\n\t\t\tlog.Printf(\"initialize: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\t// 2. Discover what the page exposes.\n\t\ttools, err := roundtrip(conn, 2, \"tools/list\", struct{}{})\n\t\tif err != nil {\n\t\t\tlog.Printf(\"tools/list: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tlog.Printf(\"browser exposes: %s\", tools)\n\n\t\t// 3. Invoke one.\n\t\tresult, err := roundtrip(conn, 3, \"tools/call\", map[string]any{\n\t\t\t\"name\":      \"demo.echo\",\n\t\t\t\"arguments\": map[string]any{\"text\": \"hello from go\"},\n\t\t})\n\t\tif err != nil {\n\t\t\tlog.Printf(\"tools/call: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tlog.Printf(\"result: %s\", result)\n\t})\n\n\tlog.Println(\"listening on ws://127.0.0.1:9669/mcp/ws\")\n\tlog.Fatal(http.ListenAndServe(\"127.0.0.1:9669\", nil))\n}\n```\n\nRun this next to the Quick start snippet above: the page dials in, gets\n`initialize`d, and has its `demo.echo` tool called once. From here a\nreal agent typically grows a pending-request map keyed by `id` for\nconcurrent calls, a hub holding multiple browser sessions (one per tab),\nand a `notifications/tools/list_changed` handler so the tool set can be\nhot-reloaded as the page registers new tools.\n\n## Authentication\n\nThis package is **unopinionated about auth**. The browser's WebSocket\nconstructor only exposes two knobs (`url` and `protocols`); any auth scheme\nultimately rides on one of those. Instead of baking in a specific mechanism,\nthe library exposes a `createSocket` factory and lets you decide.\n\nThe factory is called on every (re)connect — perfect for short-lived tokens.\n\n### No auth (default)\n\n```ts\ncreateServer({\n  endpoint: \"ws://127.0.0.1:9669/mcp/ws\",\n  serverInfo: { name: \"demo\", version: \"1.0.0\" },\n});\n```\n\n### Bearer token in URL\n\n```ts\ncreateServer({\n  endpoint: \"ws://127.0.0.1:9669/mcp/ws\",\n  serverInfo: { name: \"demo\", version: \"1.0.0\" },\n  createSocket: ({ endpoint }) =\u003e\n    new WebSocket(`${endpoint}?token=${encodeURIComponent(TOKEN)}`),\n});\n```\n\n### Bearer token in `Sec-WebSocket-Protocol`\n\nAvoids tokens leaking into logs / browser history.\n\n```ts\ncreateServer({\n  endpoint: \"ws://127.0.0.1:9669/mcp/ws\",\n  serverInfo: { name: \"demo\", version: \"1.0.0\" },\n  createSocket: ({ endpoint }) =\u003e\n    new WebSocket(endpoint, [\"mcp.v1\", `bearer.${TOKEN}`]),\n});\n```\n\nThe MCP client side should validate the subprotocol on upgrade and echo the\nchosen one back.\n\n### Fresh token per connection\n\n```ts\ncreateServer({\n  endpoint: \"ws://127.0.0.1:9669/mcp/ws\",\n  serverInfo: { name: \"demo\", version: \"1.0.0\" },\n  createSocket: async ({ endpoint, attempt }) =\u003e {\n    const token = await fetch(\"/mcp/session-token\").then((r) =\u003e r.text());\n    return new WebSocket(endpoint, [`bearer.${token}`]);\n  },\n});\n```\n\n`attempt` is `0` on the first connect and increments on each reconnect, in\ncase you want to short-circuit retries after some bound.\n\n### A note on threat model\n\nLocalhost WebSocket endpoints are **not** protected by the browser's\nsame-origin policy — any tab on the user's machine can dial `ws://127.0.0.1`.\nFor real deployments the MCP client side should pair token validation with\nan `Origin` header allowlist.\n\n## Entry points\n\n| Import path                                      | What's there                                          |\n| ------------------------------------------------ | ----------------------------------------------------- |\n| `@teatak/mcp-server-browser`                     | High-level `createServer` API (recommended).          |\n| `@teatak/mcp-server-browser/transport`           | Raw `WsTransport` class for bespoke MCP servers.      |\n| `@teatak/mcp-server-browser/spec`                | Wire-level JSON-RPC / MCP types and constants.        |\n\n## Prompts\n\nIn addition to tools, this package supports a lightweight `prompts` capability\n— a chunk of guidance text that the MCP client should append to its LLM\nsystem instruction. Compared to MCP's standard prompts, this variant is\ndeliberately simpler: no `arguments`, no `prompts/get` round-trip — content\nis delivered inline in `prompts/list`.\n\n```ts\nserver.registerPrompt({\n  name: \"ui-render-table.usage\",\n  description: \"Constraints for the ui_render_table tool.\",\n  content: `When calling ui_render_table, only pass rows from real data. Never invent values.`,\n});\n```\n\n## Status\n\nPre-1.0. API may evolve. Tested against MCP protocol version `2025-03-26`.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteatak%2Fmcp-server-browser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteatak%2Fmcp-server-browser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteatak%2Fmcp-server-browser/lists"}