How to Build an Interactive, Camera-Powered Interactive Face-Scanning MCP App: A Technical Deep…
If you’ve been tracking the evolution of AI integrations, you’ve likely encountered the Model Context Protocol (MCP). MCP provides a…
How to Build an Interactive, Camera-Powered Interactive Face-Scanning MCP App: A Technical Deep Dive

If you’ve been tracking the evolution of AI integrations, you’ve likely encountered the Model Context Protocol (MCP). MCP provides a standardized way for AI models to interact with local tools, data sources, and even rich interactive frontends. But what does a production-grade, highly interactive MCP application actually look like under the hood?
In this teardown, we are going to look at the architecture of a Face Scanning App — a custom MCP application that accesses a user’s webcam, captures a selfie, and runs a Python-based machine learning model (OpenCV and Dlib).
Rather than a simple text-in, text-out tool, this codebase demonstrates how to serve a full web UI through MCP, handle heavy image payloads, proxy legacy assets to bypass CORS, and seamlessly bridge frontend logic with an AI host.
Here is how the system is put together.
The Architecture Overview
The system is split into three main operational layers:
- The MCP Node Server (
server.ts): An Express-based server that utilizes@modelcontextprotocol/sdkto register UI resources and handle tool execution. - The Client-Side Bridge (
src/mcp-app.ts): A TypeScript frontend layer injected into the UI that intercepts camera actions and routes data back to the MCP server. - The Python Engine (
extract_and_match.py): A standalone Python script containing the heavy OpenCV/ML logic that the Node server executes on demand.
Let’s break down how these pieces communicate.
1. Setting Up the Stateless MCP Server
The core of the application lives in server.ts. Because MCP heavily favors stateless HTTP transport for reliability, the application encapsulates the server instantiation inside a createMcpServer() function. This creates a clean, independent instance per session or request.
function createMcpServer() {
const server = new McpServer({
name: "Face Scanning MCP Server",
version: "1.0.0",
});
// Tool definitions go here...
return server;
}
This server is then exposed over a standard Express POST endpoint (/mcp) using StreamableHTTPServerTransport. This handles the JSON-RPC communication required by the protocol.
Handling Heavy Workloads via Python Interop
MCP servers are often written in Node.js or TypeScript, but heavy image processing usually requires Python. The codebase solves this by defining a process-shade-image tool.
When the UI captures a base64 selfie, it passes the data to this tool. The Node server decodes the base64 image, writes it to a temporary .png file, and spawns a Python process using a specific virtual environment interpreter.
const command = `"${venvPython}" "${pythonScript}" "${tempPath}"`;
const { stdout } = await execPromise(command);
// Clean up temp file, parse stdout JSON, and return payload
This architecture keeps the Node server lightweight while allowing the Python environment to load large assets like shape_predictor_81_face_landmarks.dat without blocking the main event loop.
2. Serving the UI and Bypassing CORS
One of the biggest hurdles when injecting an interactive HTML UI into an MCP host (which usually runs the UI inside a sandboxed iframe) is dealing with strict MIME-type checking and Cross-Origin Resource Sharing (CORS) blocks.
The developers used a clever server-side proxying and HTML injection strategy to get around this. Using the registerAppResource method from @modelcontextprotocol/ext-apps/server, the Node server intercepts the raw HTML template and performs surgical string replacements before sending it to the client.
First, it forces all relative assets to resolve to the local server, completely eliminating cross-origin issues:
html = html.replace("<head>", `<head>\n <base href="http://localhost:3001/">`);
Then, it injects custom CSS and appends a lightweight MCP bridge script right before the closing </body> tag:
html = html.replace("</body>", ` <script type="module" src="http://localhost:3001/static-mcp/mcp-app.js"></script>\n</body>`);
To ensure assets like WebAssembly (opencv.wasm) load with the correct headers, the Express server acts as a same-origin proxy, explicitly setting Access-Control-Allow-Origin: * and preserving the original Content-Type.
3. The Client-Side Bridge: Intercepting Legacy Logic
The frontend code (src/mcp-app.ts) is where the real integration magic happens. The application supports two modes: running inside an MCP Host (like a desktop app) or running in a standalone browser playground.
It determines the mode on load:
const isStandalone = window.self === window.parent;
let app = null;
if (!isStandalone) {
app = new App({ name: "Face Scanning", version: "1.0.0" });
app.connect();
}
Instead of rewriting the entire legacy camera application, the bridge uses jQuery Monkeypatching. It intercepts the legacy app’s $.ajax calls at two crucial stages:
- Capture Stage (
/getcordinates): When the legacy UI tries to upload the camera frame, the bridge intercepts the request, saves the base64 string locally, and returns a mocked success response instantly. This allows the UI to smoothly transition to the next screen without hitting a real backend. - Analysis Stage (
/web-scan): When the user triggers the analysis, the bridge intercepts the call again. It takes the cached base64 image and routes it through the MCP protocol usingapp.callServerTool().
const response = await app.callServerTool({
name: "process-shade-image",
arguments: { imageBase64: cachedBase64 }
});
Once the Node server responds with the skin profile and product suggestions, the bridge translates the payload back into the exact JSON schema the legacy UI expects and resolves the intercepted jQuery promise. The original UI code runs its animation and displays the results organically — completely unaware that its backend was swapped out for a local MCP engine.
Key Takeaways
Building complex, UI-heavy MCP applications requires a different mindset than building standard REST APIs. Based on this codebase, a few patterns emerge:
- Decouple heavy compute: Keep your MCP server in
Node.jsfor easy SDK integration, but spawn separate processes (like Python) for memory-intensive machine learning tasks. - Proxy strategically: If you are embedding an existing web app into an MCP host, use
<base>tag injections and same-origin proxying to prevent CORS and MIME-type headaches. - Monkeypatch legacy code: Instead of rewriting a complex, fragile legacy frontend (like a WebRTC camera handler), intercept its API calls at the network layer and route them through the MCP SDK.
By structuring the app this way, the team successfully bridged a standard computer-vision Python pipeline with a highly constrained MCP frontend environment, delivering a seamless user experience.
mcp, model-context-protocol, software-engineering, system-architecture, nodejs, typescript, python, frontend-development, api-design
메타데이터
- post_id
- 16f308fd99f7
- slug
- how-to-build-an-interactive-camera-powered-interactive-face-scanning-mcp-app-a-technical-deep-16f308fd99f7
- url
- https://medium.com/@kkumarsantosh/how-to-build-an-interactive-camera-powered-interactive-face-scanning-mcp-app-a-technical-deep-16f308fd99f7
- canonical_url
- https://medium.com/@kkumarsantosh/how-to-build-an-interactive-camera-powered-interactive-face-scanning-mcp-app-a-technical-deep-16f308fd99f7
- author_url
- https://medium.com/@kkumarsantosh
- status
- ok
- fetched_at
- 2026-06-16 19:09:56