{"slug": "fetching-data-from-an-mcp-server-in-your-frontend", "title": "Fetching Data from an MCP Server in Your Frontend", "summary": "A developer outlined two approaches for pulling data from Model Context Protocol (MCP) servers into a frontend web app: running the official TypeScript SDK directly in the browser over Streamable HTTP, or routing calls through a thin backend proxy that holds MCP credentials and enforces per-user authorization and a tool allowlist. The writeup includes working Express and React code showing JSON-RPC 2.0 tools/call and resources/read requests, error handling, and passing through Server-Sent Events progress notifications.", "body_md": "MCP servers are usually discussed as tools for AI assistants, but the same servers can feed your web app directly. A dashboard can call the same `search_orders` tool your agent uses, so both get one consistent data layer. This article shows how a frontend gets that data and what to watch for.\n\n**Option 1: an MCP client SDK in the browser.** The official TypeScript SDK (`@modelcontextprotocol/sdk`) can run in the browser and talk to a remote server over Streamable HTTP. It's quick for demos and internal tools, but the browser then needs a token for the MCP server and the server must allow CORS.\n\n**Option 2: a thin backend proxy (recommended).** Your backend runs the MCP client and exposes ordinary endpoints like `/api/tools/search_orders`. The frontend never sees MCP credentials, you check the user's session before every call, and you can allow only specific tools.\n\nMCP uses JSON-RPC 2.0. After an `initialize` handshake, the two calls a frontend cares about most look like this.\n\nCalling a tool:\n\n```\n{ \"jsonrpc\": \"2.0\", \"id\": 7, \"method\": \"tools/call\",\n  \"params\": { \"name\": \"search_orders\", \"arguments\": { \"customer\": \"Acme\" } } }\n```\n\nA successful result:\n\n```\n{ \"jsonrpc\": \"2.0\", \"id\": 7,\n  \"result\": { \"content\": [ { \"type\": \"text\", \"text\": \"[{\\\"id\\\":101,\\\"total\\\":250}]\" } ],\n              \"isError\": false } }\n```\n\nReading a resource uses `resources/read` with a URI, such as `{ \"uri\": \"orders://recent\" }`, and returns `contents` with text or binary data. Use `tools/list` and `resources/list` to discover what's available.\n\n``` python\nimport express from \"express\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\n\nconst client = new Client({ name: \"web-proxy\", version: \"1.0.0\" });\nawait client.connect(new StreamableHTTPClientTransport(new URL(process.env.MCP_URL!)));\n\nconst ALLOWED = new Set([\"search_orders\", \"get_order\"]);\nconst app = express();\napp.use(express.json());\n\napp.post(\"/api/tools/:name\", async (req, res) => {\n  if (!req.headers.authorization) return res.status(401).json({ error: \"Sign in first\" });\n  if (!ALLOWED.has(req.params.name)) return res.status(403).json({ error: \"Tool not allowed\" });\n  try {\n    const result = await client.callTool({ name: req.params.name, arguments: req.body });\n    if (result.isError) return res.status(422).json({ error: result.content });\n    res.json(result.content);\n  } catch (e) {\n    res.status(502).json({ error: \"MCP server unavailable\" });\n  }\n});\n\napp.listen(3000);\njs\nimport { useState } from \"react\";\n\nexport function OrderSearch() {\n  const [rows, setRows] = useState<any[]>([]);\n  const [error, setError] = useState(\"\");\n  const [loading, setLoading] = useState(false);\n\n  async function search(customer: string) {\n    setLoading(true); setError(\"\");\n    try {\n      const res = await fetch(\"/api/tools/search_orders\", {\n        method: \"POST\",\n        headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${localToken()}` },\n        body: JSON.stringify({ customer }),\n      });\n      const data = await res.json();\n      if (!res.ok) throw new Error(typeof data.error === \"string\" ? data.error : \"Tool failed\");\n      setRows(JSON.parse(data[0].text));\n    } catch (e: any) {\n      setError(e.message);\n    } finally {\n      setLoading(false);\n    }\n  }\n\n  return (\n    <div>\n      <button onClick={() => search(\"Acme\")} disabled={loading}>Search Acme</button>\n      {error && <p role=\"alert\">{error}</p>}\n      <ul>{rows.map(r => <li key={r.id}>#{r.id}: ${r.total}</li>)}</ul>\n    </div>\n  );\n}\n```\n\nThe same `fetch` call works unchanged in Vue or plain JavaScript.\n\nWith Streamable HTTP, the server can answer with Server-Sent Events, sending progress notifications before the final result. Pass those through from the proxy as SSE and read them with `fetch` plus a stream reader (or `EventSource` for GET):\n\n``` js\nconst res = await fetch(\"/api/tools/long_report/stream\", { method: \"POST\", body: \"{}\" });\nconst reader = res.body.getReader();\nconst decoder = new TextDecoder();\nfor (;;) {\n  const { value, done } = await reader.read();\n  if (done) break;\n  showProgress(decoder.decode(value));\n}\n```\n\nThere are three layers to handle:\n\n`error` object with a code, such as `-32602` for invalid params or `-32601` for an unknown method. Usually a bug, so log it.`isError: true`. These are expected failures like \"customer not found\". Show them to the user.\nAlso add timeouts with `AbortController`, and cancel requests when a component unmounts.\n\nAn MCP server can be a shared data layer for both your agents and your UI. Put a thin proxy in front of it, speak JSON-RPC behind the scenes, and handle the three kinds of errors, and your frontend gets the same data your AI tools do, safely.\n\nI write about Applied AI, MCP, Azure AI Search, and RAG. Follow me on [Dev.to](https://dev.to/who_ai) and [Hashnode](https://hashnode.com/@whoai) for more.", "url": "https://wpnews.pro/news/fetching-data-from-an-mcp-server-in-your-frontend", "canonical_source": "https://dev.to/who_ai/fetching-data-from-an-mcp-server-in-your-frontend-26ap", "published_at": "2026-11-06 12:46:26+00:00", "updated_at": "2026-11-06 12:53:34.780786+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "developer-tools", "ai-tools"], "entities": ["Model Context Protocol", "@modelcontextprotocol/sdk", "Express", "React", "Vue"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/fetching-data-from-an-mcp-server-in-your-frontend", "markdown": "https://wpnews.pro/news/fetching-data-from-an-mcp-server-in-your-frontend.md", "text": "https://wpnews.pro/news/fetching-data-from-an-mcp-server-in-your-frontend.txt", "jsonld": "https://wpnews.pro/news/fetching-data-from-an-mcp-server-in-your-frontend.jsonld"}}