How the MCP Server Implements HTTP Transport with Origin-Guard Security in Code Review Graph

The MCP server in the Code Review Graph VS Code extension implements HTTP transport through a message-passing channel secured by nonce-based CSP headers and strict origin verification on every incoming message.

The Code Review Graph extension by tirth8205 embeds an MCP (Micro-Code-Protocol) server directly inside the VS Code extension host rather than exposing a network port. This architecture requires a secure HTTP-like transport mechanism to communicate with the webview UI. The implementation combines Content-Security-Policy nonces with origin-guard validation to prevent cross-origin attacks.

Webview Creation with Nonce-Based CSP Protection

The transport security begins when the extension opens a graph view. In graphWebview.ts (around line 300), the code generates a vscode.WebviewPanel and injects a strict Content-Security-Policy header using a cryptographically secure nonce:

const nonce = getNonce();
panel.webview.html = `
  <meta http-equiv="Content-Security-Policy"
        content="default-src 'none';
                 script-src 'nonce-${nonce}';
                 style-src 'unsafe-inline';
                 img-src ${webview.cspSource};">
`;

This CSP configuration achieves three critical security properties:

  • default-src 'none' — Blocks all resource loading by default
  • script-src 'nonce-${nonce}' — Permits execution only of scripts bearing the matching nonce
  • img-src ${webview.cspSource} — Restricts images to the webview's own CSP source

The getNonce() utility generates a unique value per webview instance, ensuring that injected scripts cannot be replayed across sessions.

HTTP Transport via VS Code Message Passing

Rather than raw HTTP, the MCP server uses VS Code's built-in message-passing API as its transport layer. The webview JavaScript obtains the API through acquireVsCodeApi():

<script nonce="${nonce}">
    const vscode = acquireVsCodeApi();
    // Requests travel through postMessage, not fetch/XHR
    vscode.postMessage({ command: 'buildGraph', args: [] });
</script>

The extension registers a listener on webview.onDidReceiveMessage to handle incoming requests. This creates an HTTP-like request/response pattern where messages carry commands, arguments, and metadata.

Origin-Guard Enforcement on Every Message

The critical security layer—the origin guard—validates each message before processing. In graphWebview.ts, the message handler performs this verification:

panel.webview.onDidReceiveMessage(message => {
    // Origin-guard check: reject messages from unexpected origins
    if (panel.webview.cspSource !== message.origin) {
        console.warn('Rejected message with invalid origin');
        return;
    }
    // Process validated commands...
});

This check couples the CSP source with runtime origin verification. Only scripts that were injected with the correct nonce—proving they originated from the extension's trusted HTML generation—can successfully post messages that pass validation.

Debounced Database Notifications

The MCP server also pushes updates from the backend to the UI. The GraphWatcher class in watcher.ts monitors the on-disk SQLite database (graph.db) and forwards change notifications through the same secured channel:

const debouncedOnChanged = debounce(onChanged, 500);
this.watcher.onDidChange(() => debouncedOnChanged());
  • Lines 37–49 implement a 500ms debounce to prevent UI flooding during rapid file changes
  • Notifications traverse the postMessage channel, inheriting the same origin-guard protection

The debounce utility itself:

function debounce<T extends (...args: any[]) => any>(fn: T, ms: number): T {
    let timer: ReturnType<typeof setTimeout> | undefined;
    const debounced = (...args: Parameters<T>) => {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => { timer = undefined; fn(...args); }, ms);
    };
    return debounced as unknown as T;
}

Complete Implementation Example

Here's the integrated pattern from graphWebview.ts:

import * as vscode from 'vscode';
import { getNonce } from './utils';

export function createGraphWebview(context: vscode.ExtensionContext) {
    const panel = vscode.window.createWebviewPanel(
        'codeReviewGraph',
        'Code Review Graph',
        vscode.ViewColumn.One,
        { enableScripts: true }
    );

    const nonce = getNonce();
    panel.webview.html = `
        <meta http-equiv="Content-Security-Policy"
              content="default-src 'none';
                       script-src 'nonce-${nonce}';
                       style-src 'unsafe-inline';
                       img-src ${panel.webview.cspSource};">
        <script nonce="${nonce}">
            const vscode = acquireVsCodeApi();
            vscode.postMessage({ command: 'buildGraph', args: [] });
        </script>
    `;

    panel.webview.onDidReceiveMessage(message => {
        if (panel.webview.cspSource !== message.origin) {
            console.warn('Rejected message with invalid origin');
            return;
        }
        // Command handling logic...
    });
}

Key Source Files

File Security Responsibility
graphWebview.ts CSP generation, nonce injection, origin-guard verification
watcher.ts Debounced file-system monitoring, secured change notifications
extension.ts Command registration, extension host entry points

Summary

  • Nonce-based CSP in graphWebview.ts prevents execution of unauthorized scripts
  • acquireVsCodeApi() provides a controlled message-passing transport layer
  • Runtime origin verification rejects messages whose origin mismatches cspSource
  • Debounced watchers in watcher.ts push updates without overwhelming the UI
  • No network port exposure eliminates entire classes of remote attack vectors

Frequently Asked Questions

What makes this transport "HTTP-like" rather than true HTTP?

The channel mimics HTTP semantics—requests carry commands and payloads, responses return data—but uses postMessage over VS Code's internal bridge instead of TCP sockets or HTTP requests. There's no localhost port, no CORS headers, and no browser networking stack involved.

Why does the origin guard check cspSource instead of a hardcoded value?

webview.cspSource is generated per-webview by VS Code itself, incorporating the extension's identifier and session context. Using this dynamic value prevents hardcoded bypasses and automatically invalidates if the webview context changes.

Can malicious extensions bypass the origin guard?

Extensions running in the same VS Code instance operate in separate extension hosts with isolated webviews. The nonce and CSP source are not exposed through any API, so another extension cannot forge valid messages without compromising VS Code's core webview isolation.

What happens if the SQLite database changes rapidly?

The debounce utility with a 500ms window in watcher.ts collapses multiple rapid changes into a single notification, preventing UI thrashing while maintaining responsiveness for legitimate updates.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →