Skip to content

feat: Phase 6.1 - MCP Apps 基盤実装#101

Merged
proofofprotocol merged 6 commits intomainfrom
feature/phase6-apps-foundation
Feb 7, 2026
Merged

feat: Phase 6.1 - MCP Apps 基盤実装#101
proofofprotocol merged 6 commits intomainfrom
feature/phase6-apps-foundation

Conversation

@Shin-R2un
Copy link
Copy Markdown
Collaborator

PR1 - Apps基盤

概要

MCP Apps Extension (SEP-1865) の基盤実装。UIが表示できる「最小成功ルート」を実装しました。

変更ファイル

  • src/html/trace-viewer.html (新規) - UI HTML (JS/CSS同梱)
  • src/proxy/types.ts - Apps拡張の型定義追加
  • src/proxy/mcp-server.ts - ハンドラー追加 (resources/list, resources/read, ui/initialize)

実装内容

1. resources/list

  • ui://proofscan/trace-viewer を追加
  • mimeType: "text/html;profile=mcp-app" を指定

2. resources/read

  • trace-viewer 単一HTMLを返す
  • JS/CSS同梱、外部CDN禁止

3. tools/list

  • proofscan_getEvents を追加
  • outputSchema 宣言済み
  • _meta.ui.resourceUri 追加済み
  • _meta.outputSchemaVersion = "1" 追加済み
  • handler はスタブ実装(空配列返す)

4. ui/initialize

  • sessionToken 発行
  • protocolVersion: "2025-11-21"

5. UI側

  • ui/initialize 送信で token 取得
  • token検証ロジック実装済み(token不一致メッセージは無視)
  • "Connected" 表示まで実装

ACチェック結果

  • resources/listtext/html;profile=mcp-app mimeType を返す
  • resources/read が単一HTMLを返す(JS/CSS同梱、外部CDNなし)
  • tools/list が outputSchema と _meta.ui.resourceUri を返す
  • UIが token を受け取り、token無し/不一致メッセージをrejectする

手動確認ログ

  1. npm run build 成功
  2. npm test 全パス (1778 tests)
  3. HTML に外部JS/CSSなし (inlineのみ)
  4. CSP: default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'

テスト結果

Test Files  72 passed (72)
     Tests  1778 passed (1778)

スコープ

  • PR1のスコープ外の変更は一切行っていません
  • 依存: なし

関連ドキュメント

- resources/list: ui://proofscan/trace-viewer (text/html;profile=mcp-app)
- resources/read: trace-viewer 単一HTML (JS/CSS同梱)
- tools/list: proofscan_getEvents (outputSchema + _meta.ui.resourceUri)
- ui/initialize: sessionToken 発行
- UI: postMessage token検証

See: SEP-1865, /home/shin/vault/03_Projects/proofscan/3032 - proofscan Phase 6 - MCP Apps 設計書.md
@claude
Copy link
Copy Markdown

claude bot commented Feb 7, 2026

Code Review - PR #101: Phase 6.1 MCP Apps 基盤実装

Summary

This PR implements the foundation for MCP Apps Extension (SEP-1865) with a minimal viable path to display a UI. The implementation adds three new MCP protocol handlers (resources/list, resources/read, ui/initialize) and introduces a new tool (proofscan_getEvents) with UI integration metadata.

✅ Positive Aspects

1. Architecture & Design

  • Clean separation of concerns: HTML UI, type definitions, and server handlers are properly separated
  • Minimal viable approach: Correctly implements only what's needed for "Connected" status (stub tool implementation is appropriate for Phase 6.1)
  • Follows existing patterns: Uses established error handling (MCP_ERROR codes), logging patterns, and protocol structure

2. Security Implementation

  • Strong CSP policy: default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline' properly restricts execution context
  • Token-based authentication: Session token generation and validation prevents unauthorized message injection
  • Self-contained HTML: No external CDN dependencies eliminates supply chain risks
  • Token validation in UI: Properly rejects messages with mismatched or missing tokens (lines 169-174 in trace-viewer.html)

3. Protocol Compliance

  • Proper JSON-RPC 2.0 structure: Validates jsonrpc: "2.0" field (line 165)
  • Correct MCP capability declaration: Adds resources: {} to capabilities in initialize response (line 569)
  • Standard error codes: Uses MCP_ERROR constants for protocol-compliant error responses

4. Code Quality

  • Type safety: New TypeScript interfaces properly define protocol types (ResourcesListResult, ResourcesReadParams, etc.)
  • Good logging: Appropriate info/warn/error logging for debugging
  • Proper cleanup: Uses readFileSync synchronously in appropriate context (initialization)

🔴 Issues Found

1. CRITICAL: Missing postMessage Origin Validation (Security)

Location: src/html/trace-viewer.html:156

window.parent.postMessage(request, '*');

Issue: Using wildcard '*' allows any parent window to receive messages containing the session token.

Risk: If the HTML is ever served in an unexpected context (browser extension, third-party iframe, etc.), sensitive data could leak to malicious origins.

Recommendation:

// Store the expected origin after first verified message
let trustedOrigin = null;

function sendRequest(method, params) {
  const targetOrigin = trustedOrigin || '*'; // Use * only for initialization
  window.parent.postMessage(request, targetOrigin);
}

function handleMessage(event) {
  // Lock to first received origin
  if (!trustedOrigin && event.origin) {
    trustedOrigin = event.origin;
  }
  // ... rest of validation
}

2. CRITICAL: Race Condition in RPC ID Handling

Location: src/html/trace-viewer.html:181

if (msg.id === 1 && msg.method === undefined) {
  // Response to ui/initialize

Issue: Hardcoded check for msg.id === 1 will break if:

  • Multiple initialization attempts occur
  • Messages arrive out of order
  • Other RPC calls are made before initialization completes

Recommendation:

// Track pending requests
const pendingRequests = new Map();

function sendRequest(method, params) {
  const id = currentRpcId++;
  pendingRequests.set(id, { method });
  // ... send message
  return id;
}

function handleMessage(event) {
  if ('id' in msg && !msg.method) {
    const pending = pendingRequests.get(msg.id);
    if (pending && pending.method === 'ui/initialize') {
      // Handle ui/initialize response
      pendingRequests.delete(msg.id);
    }
  }
}

3. HIGH: Missing Input Validation in resources/read

Location: src/proxy/mcp-server.ts:528-531

if (!params || typeof params.uri !== 'string') {
  this.sendError(id, MCP_ERROR.INVALID_PARAMS, 'Missing required parameter: uri');
  return;
}

Issue: No validation for:

  • URI length (DoS via extremely long URIs)
  • URI format/characters (path traversal attempts like ui://../../../etc/passwd)
  • Null bytes or control characters

Recommendation:

const MAX_URI_LENGTH = 2048;

if (!params || typeof params.uri !== 'string') {
  this.sendError(id, MCP_ERROR.INVALID_PARAMS, 'Missing required parameter: uri');
  return;
}

const { uri } = params;

// Validate length
if (uri.length > MAX_URI_LENGTH) {
  this.sendError(id, MCP_ERROR.INVALID_PARAMS, 'URI too long');
  return;
}

// Validate format (whitelist approach)
if (!uri.startsWith('ui://proofscan/')) {
  this.sendError(id, MCP_ERROR.INVALID_PARAMS, `Invalid URI scheme or host: ${uri}`);
  return;
}

4. MEDIUM: Incomplete Error Handling in getTraceViewerHtml()

Location: src/proxy/mcp-server.ts:62-65

function getTraceViewerHtml(): string {
  const __filename = fileURLToPath(import.meta.url);
  const __dirname = dirname(__filename);
  const htmlPath = join(__dirname, '../html/trace-viewer.html');
  return readFileSync(htmlPath, 'utf-8');
}

Issue: No error handling if file doesn't exist or can't be read. This will crash the entire server.

Recommendation:

function getTraceViewerHtml(): string {
  try {
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = dirname(__filename);
    const htmlPath = join(__dirname, '../html/trace-viewer.html');
    return readFileSync(htmlPath, 'utf-8');
  } catch (error) {
    logger.error('Failed to load trace-viewer.html:', error);
    throw new Error('UI resource unavailable');
  }
}

And in the handler:

try {
  const html = getTraceViewerHtml();
  // ... send result
} catch (error) {
  this.sendError(id, MCP_ERROR.INTERNAL_ERROR, 'Failed to load UI resource');
  return;
}

5. MEDIUM: Missing Test Coverage

Issue: No automated tests for the new functionality:

  • No tests for handleResourcesList()
  • No tests for handleResourcesRead()
  • No tests for handleUiInitialize()
  • No tests for proofscan_getEvents stub
  • No tests for token validation in UI

Recommendation: Add test file src/proxy/mcp-server-resources.test.ts:

describe('MCP Resources', () => {
  describe('resources/list', () => {
    it('should return trace-viewer resource', async () => {
      const result = await server.handleResourcesList(null);
      expect(result.resources).toHaveLength(1);
      expect(result.resources[0].uri).toBe('ui://proofscan/trace-viewer');
      expect(result.resources[0].mimeType).toBe('text/html;profile=mcp-app');
    });
  });

  describe('resources/read', () => {
    it('should return HTML for trace-viewer', async () => {
      const result = await server.handleResourcesRead(null, { 
        uri: 'ui://proofscan/trace-viewer' 
      });
      expect(result.contents[0].text).toContain('<!DOCTYPE html>');
      expect(result.contents[0].mimeType).toBe('text/html;profile=mcp-app');
    });

    it('should reject unknown URIs', async () => {
      // Test error handling
    });

    it('should reject invalid URI formats', async () => {
      // Test path traversal attempts
    });
  });

  describe('ui/initialize', () => {
    it('should generate unique session tokens', async () => {
      const result1 = await server.handleUiInitialize(null, {});
      const result2 = await server.handleUiInitialize(null, {});
      expect(result1.sessionToken).not.toBe(result2.sessionToken);
    });
  });
});

6. LOW: Inconsistent Protocol Version Constants

Location: src/proxy/mcp-server.ts:40-41

const PROTOCOL_VERSION = '2024-11-05';
const UI_PROTOCOL_VERSION = '2025-11-21';

Issue: Two different version constants may cause confusion. Consider if these need to be synchronized or documented.

Recommendation: Add comment explaining the difference:

/** MCP Core protocol version */
const PROTOCOL_VERSION = '2024-11-05';

/** MCP Apps Extension protocol version (SEP-1865) */
const UI_PROTOCOL_VERSION = '2025-11-21';

7. LOW: Potential Memory Leak in UI

Location: src/html/trace-viewer.html:216

window.addEventListener('message', handleMessage);

Issue: Event listener is never removed. While not critical for a long-lived UI, it's best practice to clean up.

Recommendation:

// Add cleanup if UI supports reload/unmount
function cleanup() {
  window.removeEventListener('message', handleMessage);
}

// Expose for testing or if reload is implemented
window.__cleanup = cleanup;

🟡 Observations & Suggestions

1. Code Organization

The proofscan_getEvents tool definition inside handleToolsList() (lines 591-634) makes this function very long. Consider extracting to a constant:

const PROOFSCAN_GET_EVENTS_TOOL = {
  name: 'proofscan_getEvents',
  description: 'Get protocol events (paginated). Returns text summary + structured data.',
  // ... rest of definition
};

// In handleToolsList:
const toolsList = [
  ...tools.map((t) => ({ ... })),
  PROOFSCAN_GET_EVENTS_TOOL,
];

2. Type Safety Enhancement

The ToolsCallResult interface now has optional fields that weren't required before:

export interface ToolsCallResult {
  content?: unknown[];
  isError?: boolean;
  structuredContent?: unknown;  // ← New
  _meta?: { ... };               // ← New
}

Consider if existing tools expect content to be required. May want to create a separate interface for extended tool results.

3. Documentation

Missing JSDoc comments for new public functions:

  • getTraceViewerHtml()
  • New handler methods

4. Protocol Validation

The UI validates msg.jsonrpc === '2.0' but doesn't validate the structure of msg.result or msg.error. Consider adding schema validation.


📊 Acceptance Criteria Review

Based on the PR description:

Criterion Status Notes
resources/list returns correct mimeType Line 568: text/html;profile=mcp-app
resources/read returns single HTML Lines 547-559, self-contained HTML
tools/list includes outputSchema + _meta Lines 609-632
UI receives token and validates Lines 169-174, 184
No external JS/CSS All inline in HTML
No CDN dependencies Verified

All acceptance criteria met


🎯 Recommendations Priority

Must Fix (before merge):

  1. ❗ Add origin validation to postMessage calls
  2. ❗ Fix hardcoded RPC ID check (race condition)
  3. ❗ Add URI validation (length, format, path traversal)
  4. ❗ Add error handling to getTraceViewerHtml()

Should Fix (this PR or next):

  1. Add test coverage for new handlers
  2. Extract tool definition to constant for readability

Nice to Have (can defer):

  1. Add JSDoc comments
  2. Add cleanup handler for event listeners
  3. Document protocol version differences

📝 Manual Testing Recommendations

Since tests weren't run in CI, please verify:

  1. Start proxy server with trace-viewer resource
  2. Connect MCP client that supports resources
  3. Call resources/list - verify trace-viewer appears
  4. Call resources/read - verify HTML is returned
  5. Render UI - verify "Connected" status appears
  6. Call proofscan_getEvents - verify stub returns empty array
  7. Send message without token - verify it's rejected
  8. Send message with wrong token - verify it's rejected
  9. Test with very long URI - verify graceful error
  10. Test with invalid URI (ui://../../etc/passwd) - verify rejection

🎉 Conclusion

This is a well-structured foundation for MCP Apps support with good architectural decisions. The minimal viable approach is appropriate for Phase 6.1, and the code follows project conventions.

However, there are critical security issues that must be addressed before merging:

  • Origin validation for postMessage
  • URI input validation
  • Better error handling

Recommendation: Request changes to address the security issues, then approve. The stub implementation approach is correct for this phase.

Great work on the CSP policy and token validation! 🔒


Reviewed by: Claude Code (Sonnet 4.5)
Review Date: 2026-02-07

- postMessage origin validation: lock to first origin received
- RPC ID race condition: use pending requests Map instead of hardcoded id
- URI validation: add length, format, and path traversal checks
- getTraceViewerHtml error handling: wrap in try/catch with proper logging
@claude
Copy link
Copy Markdown

claude bot commented Feb 7, 2026

Code Review: Phase 6.1 - MCP Apps 基盤実装

Summary

This PR implements the foundational infrastructure for MCP Apps Extension (SEP-1865), adding UI resource capabilities to the ProofScan MCP proxy server. The implementation follows a minimal success path approach, enabling basic UI display functionality.

✅ Strengths

1. Clean Architecture

  • Well-separated concerns: new HTML resource, type definitions, and handler implementations
  • Follows existing codebase patterns (EventEmitter-based server, type-safe interfaces)
  • Minimal scope - only implements what's necessary for Phase 6.1

2. Security Considerations

  • CSP Implementation: Strict Content Security Policy in HTML (default-src 'none')
  • Origin Validation: UI implements origin locking pattern (lines 167-174 in trace-viewer.html)
  • Token Validation: Session token mechanism to authenticate messages (lines 183-189)
  • URI Validation: Multiple checks in handleResourcesRead (lines 747-760 in mcp-server.ts)
    • Length validation (MAX_URI_LENGTH = 2048)
    • Scheme/host validation
    • Path traversal prevention (.. check)

3. Type Safety

  • Proper TypeScript interfaces for all new MCP methods
  • Type definitions follow existing patterns in types.ts
  • Correct use of optional parameters with runtime validation

4. Error Handling

  • Appropriate JSON-RPC error codes (MCP_ERROR.*)
  • Graceful fallback for missing HTML file
  • Input validation before processing

🔍 Issues & Concerns

HIGH PRIORITY: Security Vulnerability

Location: src/proxy/mcp-server.ts:695

Issue: Using Node.js crypto.randomUUID() without importing it

const sessionToken = crypto.randomUUID();

Problem: The crypto module is not imported at the top of the file. This will cause a runtime error. In Node.js, crypto is a global in some contexts, but best practice is to explicitly import it.

Fix: Add import at the top of the file:

import { randomUUID } from 'crypto';
// Then use:
const sessionToken = randomUUID();

MEDIUM PRIORITY: Code Quality

1. Duplicate File Path Resolution Logic

Location: src/proxy/mcp-server.ts:66-76

The getTraceViewerHtml() helper uses fileURLToPath and dirname which are already imported. However, this pattern might be fragile during bundling or deployment. Consider:

  • Adding error handling for production builds where the HTML might be in a different location
  • Adding a test to ensure the HTML file can be loaded
  • Consider embedding the HTML as a string constant for reliability (trade-off: harder to edit)

2. Magic Numbers

Location: src/proxy/mcp-server.ts:62

const MAX_URI_LENGTH = 2048;

Good to have this constant! However, consider documenting why 2048 was chosen (is it based on browser limits, security concerns, or arbitrary?).

3. Inconsistent Error Messages

Location: src/proxy/mcp-server.ts:773-774

throw new Error('UI resource unavailable');

vs.

this.sendError(id, MCP_ERROR.INTERNAL_ERROR, 'Failed to load UI resource');

The caught error message differs from the thrown message. Consider using the original error message or being consistent.

LOW PRIORITY: Improvements

1. Missing Test Coverage

The PR description mentions all tests pass (1778 tests), but there don't appear to be new tests for:

  • resources/list handler
  • resources/read handler
  • ui/initialize handler
  • getTraceViewerHtml() function
  • New type interfaces

Recommendation: Add integration tests for the new handlers, especially around:

  • URI validation edge cases
  • Session token generation
  • HTML file loading failures
  • Error response formats

2. HTML JavaScript Could Be More Robust

Location: src/html/trace-viewer.html:165-174

The origin locking mechanism locks to the first origin that sends a message. This could be problematic if a malicious iframe sends a message before the legitimate host.

Consider:

  • Requiring origin to be set via initialization parameter
  • Adding a timeout for initial origin lock
  • Logging a warning if origin changes

3. Stub Implementation Clarity

Location: src/proxy/mcp-server.ts:683-703

The proofscan_getEvents stub returns an empty array. While clearly documented as stub implementation, consider:

  • Adding a TODO comment in code
  • Including version/phase info in the stub response
  • Logging when stub is called

4. Type Definition Completeness

Location: src/proxy/types.ts:134-146

The ToolsCallResult interface now has optional fields:

structuredContent?: unknown;
_meta?: {
  ui?: { resourceUri?: string; };
  outputSchemaVersion?: string;
  [key: string]: unknown;
};

The _meta field allows arbitrary keys via index signature. While flexible, this reduces type safety. Consider defining known fields more strictly.

🔒 Security Assessment

✅ Good Practices

  1. Content Security Policy blocks external resources
  2. URI validation prevents path traversal
  3. Token-based message authentication
  4. Origin validation in UI

⚠️ Concerns

  1. Missing crypto import (will crash at runtime)
  2. Origin locking could be bypassed by race condition
  3. No rate limiting on UI initialization (could spam tokens)
  4. No token expiration mechanism
  5. unsafe-inline in CSP is necessary but reduces security guarantees

🎯 Performance Considerations

  • HTML file is read synchronously on every resources/read request

    • Impact: Minor - file is small (~249 lines)
    • Recommendation: Consider caching in production or reading once at startup
  • Session token uses randomUUID() which is cryptographically secure but has overhead

    • Impact: Negligible for initialization path

📋 Recommendations

Must Fix Before Merge

  1. Fix crypto import - This will cause runtime errors

Should Fix Before Merge

  1. Add basic tests for new handlers
  2. Cache HTML content or add error handling for missing file in production

Consider for Future PRs

  1. Implement token expiration/refresh mechanism
  2. Add rate limiting for ui/initialize
  3. More robust origin validation
  4. Add metrics/logging for UI resource access
  5. Consider CSP nonce-based approach instead of unsafe-inline

✅ Acceptance Criteria Validation

Based on PR description:

  • resources/list returns text/html;profile=mcp-app mimeType
  • resources/read returns single HTML (no external CDN)
  • tools/list includes outputSchema and _meta.ui.resourceUri
  • ✅ UI receives token and validates it
  • ✅ Build succeeds
  • ✅ Tests pass (though no new tests added)

📝 Conclusion

This is a solid foundation for MCP Apps support with good security awareness and clean architecture. The implementation is minimal and focused, which is appropriate for a Phase 6.1 baseline.

Primary blocker: Fix the crypto import issue before merging.

Overall assessment: Approve with required changes ✨


Great work on maintaining code quality and security considerations! The architecture is clean and the incremental approach is wise. Looking forward to seeing the full implementation in future phases.

@claude
Copy link
Copy Markdown

claude bot commented Feb 7, 2026

PR Review: Phase 6.1 - MCP Apps 基盤実装

Summary

This PR implements the foundation for MCP Apps Extension (SEP-1865), adding UI capabilities to the MCP proxy server. The implementation follows a minimal viable path to display UI and includes three new handlers: resources/list, resources/read, and ui/initialize.

Code Quality ⭐⭐⭐⭐ (4/5)

Strengths

  • Clean, focused implementation that stays within the defined scope
  • Good use of TypeScript types with proper interface definitions
  • Consistent error handling patterns matching existing codebase style
  • Proper validation of inputs (URI length, origin checks, token validation)
  • Security-conscious with CSP headers and input sanitization

Areas for Improvement

1. CRITICAL: HTML File Not Included in Build Output

Location: tsconfig.json:18, package.json:14-18

The TypeScript compiler only processes .ts files. The new src/html/trace-viewer.html file will not be copied to the dist/ directory during build, causing runtime failures.

Impact: The getTraceViewerHtml() function in src/proxy/mcp-server.ts:66-76 will fail in production with "UI resource unavailable" error.

Recommended Fix:

// package.json - add copy script
"scripts": {
  "build": "tsc && npm run copy-assets",
  "copy-assets": "mkdir -p dist/html && cp src/html/*.html dist/html/",
}

2. Missing Test Coverage

Location: No test files for new functionality

The PR mentions "1778 tests passed" but no new tests were added. The new features lack:

  • Unit tests for handleResourcesList(), handleResourcesRead(), handleUiInitialize()
  • Integration tests for the HTML UI postMessage communication
  • Tests for security validations (URI checks, token validation, origin locking)
  • Tests for the proofscan_getEvents stub tool

3. Security: Weak Token Generation

Location: src/proxy/mcp-server.ts:803

const sessionToken = crypto.randomUUID();

While UUIDs are random, they are not cryptographically strong for session tokens. Consider:

import { randomBytes } from "crypto";
const sessionToken = randomBytes(32).toString("base64url");

Severity: Medium - UUIDs are probably sufficient, but stronger tokens are better practice.

4. Type Safety: Unsafe Type Assertions

Location: src/proxy/mcp-server.ts:679

The args parameter could be undefined or have unexpected structure. Add validation before using it.

5. HTML: Inline Scripts Violate CSP Best Practices

Location: src/html/trace-viewer.html:6-7

Using unsafe-inline for scripts defeats much of CSP protection. While this may be a deliberate choice for the "single HTML file" requirement, consider documenting this security trade-off in comments.

Performance Considerations ✅

Good

  • Synchronous file read is acceptable since it is only called once per resource request
  • No obvious performance bottlenecks
  • Proper use of Map for pending requests

Suggestions

  • Memoization: Cache the HTML content after first read to avoid repeated file I/O

Security Concerns 🔒

Implemented Protections ✅

  • URI length limit (2048 chars) - prevents buffer overflow
  • Path traversal protection (uri.includes(".."))
  • URI scheme validation (ui://proofscan/)
  • Token-based message authentication
  • Origin locking on first message
  • Content Security Policy headers

Additional Recommendations

1. Origin Validation Too Permissive

Location: src/html/trace-viewer.html:166-168

The code locks to the first origin that sends a message, which could be an attacker if they win the race. Consider using an origin whitelist instead.

2. Missing Rate Limiting

The ui/initialize endpoint has no rate limiting. An attacker could spam token generation requests.

3. Token Not Stored or Validated on Server

The server generates a token but never stores or validates it on subsequent requests. The client validates it, but the server should also validate that tokens match expected sessions.

Test Coverage Assessment ⚠️

  • Existing tests: Still passing ✅
  • New functionality tests: Missing
  • Security validation tests: Missing
  • Integration tests: Missing

Test coverage appears to be 0% for new code.

Recommendations Summary

Must Fix Before Merge 🔴

  1. Add build step to copy HTML files to dist/
  2. Add test coverage for new handlers
  3. Validate token storage and validation on server side

Should Fix 🟡

  1. Use stronger session token generation
  2. Add rate limiting to ui/initialize
  3. Add input validation before type assertions
  4. Improve origin validation with whitelist

Nice to Have 🟢

  1. Cache HTML content in memory
  2. Document CSP trade-offs in comments
  3. Add JSDoc comments for new public methods

Overall Assessment

Status: ⚠️ Needs Work Before Merge

The implementation is clean and well-structured, demonstrating good understanding of the MCP protocol and security principles. However, the missing build configuration for HTML assets is a blocking issue that will cause runtime failures in production. Additionally, the lack of test coverage for critical new functionality is concerning.

Once the build issue and test coverage are addressed, this will be a solid foundation for the MCP Apps feature.

Estimated effort to address: 2-3 hours

  • Build script: 15 minutes
  • Test suite: 2 hours
  • Token validation: 30 minutes

@claude
Copy link
Copy Markdown

claude bot commented Feb 7, 2026

PR Review: Phase 6.1 - MCP Apps 基盤実装

Summary

This PR implements the foundational infrastructure for MCP Apps Extension (SEP-1865), adding UI capabilities through resources/list, resources/read, and ui/initialize endpoints. The implementation looks solid overall with good security considerations. Here's my detailed feedback:


✅ Strengths

1. Security Improvements

The security fixes from the second commit are excellent:

  • Origin validation with lock-to-first-origin pattern (src/html/trace-viewer.html:166-174)
  • RPC ID race condition fix using pendingRequests Map instead of hardcoded IDs (src/html/trace-viewer.html:135, 160)
  • URI validation with length, format, and path traversal checks (src/proxy/mcp-server.ts:766-779)
  • Error handling with try/catch in getTraceViewerHtml() (src/proxy/mcp-server.ts:66-76)

2. Clean Architecture

  • New types properly defined in types.ts
  • Constants well-organized (TRACE_VIEWER_URI, MAX_URI_LENGTH, etc.)
  • Clear separation of concerns with dedicated handler methods

3. CSP Implementation

The Content Security Policy is appropriately restrictive (src/html/trace-viewer.html:6-7):
default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'


🔍 Issues & Recommendations

Critical: Test Coverage

Issue: No test files were added for the new functionality. The PR description mentions "1778 tests pass", but there are no specific tests for handleResourcesList(), handleResourcesRead(), handleUiInitialize(), URI validation logic, or HTML file loading.

Recommendation: Add src/proxy/mcp-server-apps.test.ts with tests covering resources/list, resources/read, and ui/initialize endpoints. Test URI validation (length limits, path traversal, invalid schemes) and error handling.

High: Session Token Storage and Validation

Issue: The session token is generated but never stored or validated server-side (src/proxy/mcp-server.ts:814). Tokens are "fire and forget" with no server-side tracking.

Recommendation:

  • Add a Map to track active sessions with tokens and timestamps
  • Implement token validation in future requests
  • Add token expiration logic (e.g., 1-hour TTL)
  • Clean up expired tokens periodically

Note: If tokens aren't meant to be validated server-side in Phase 6.1, add a comment explaining this is intentional.

Medium: Build Script Platform Compatibility

Issue: The build script uses Unix-specific cp command (package.json:20):
"build": "tsc && cp -r src/html dist/"

This will fail on Windows systems.

Recommendation: Use cross-platform solution with Node.js fs.cpSync() or create a separate build script.

Medium: Missing Import Statement

Issue: crypto is used but not explicitly imported (src/proxy/mcp-server.ts:814).

Recommendation: Add explicit import:
import { randomUUID } from 'crypto';

Low: HTML File Loading on Every Request

Issue: getTraceViewerHtml() reads the file from disk on every resources/read request.

Performance Impact: Minimal for now since this is infrequent.

Recommendation (future): Cache HTML content at startup to avoid repeated disk I/O.

Low: Magic String in Tools List

Issue: proofscan_getEvents is hardcoded in the tools list (src/proxy/mcp-server.ts:607-641).

Recommendation: Extract to a constant for maintainability.


🔐 Security Assessment

✅ Well Handled

  • Origin validation with lock-to-first pattern
  • URI validation (length, scheme, path traversal)
  • CSP header properly restrictive
  • No external dependencies in HTML
  • Error messages don't leak sensitive information

⚠️ Concerns

  1. Session token lifetime: No expiration → potential memory leak
  2. Session token validation: Not implemented server-side (may be intentional)
  3. Rate limiting: No protection against rapid ui/initialize requests (DoS concern)

Recommendation: Add rate limiting or document that this is handled at infrastructure level.


📋 Acceptance Criteria

  • ✅ resources/list returns text/html;profile=mcp-app mimeType
  • ✅ resources/read returns single HTML (JS/CSS inline, no external CDN)
  • ✅ tools/list returns outputSchema and _meta.ui.resourceUri
  • ✅ UI receives token and rejects mismatched/missing token messages
  • ✅ Build succeeds
  • ⚠️ Tests pass (but no new tests added for new functionality)

🎬 Verdict

Overall Assessment: ✅ Approve with recommendations

This is a solid foundation for Phase 6.1. The security improvements show good attention to detail. The main gap is test coverage.

Priority Actions:

  1. High: Add test coverage for new handlers
  2. Medium: Fix cross-platform build script
  3. Medium: Add explicit crypto import
  4. Discussion: Clarify session token lifecycle design

Merge Recommendation: Ready to merge after test coverage is added.

Great work on the security improvements! The origin locking and URI validation are particularly well done.

- Add mcp-server-resources.test.ts with 12 tests:
  - resources/list returns trace-viewer with correct mimeType
  - resources/read returns HTML and validates URIs (length, scheme, path traversal)
  - ui/initialize generates unique session tokens
  - Session tokens are stored and validated

- Add session token management to McpProxyServer:
  - Add sessionTokens Set to store active tokens
  - Store tokens on ui/initialize
  - Add isValidSessionToken() for future use

Addresses PR #101 review comments
@claude
Copy link
Copy Markdown

claude bot commented Feb 7, 2026

Code Review - PR #101: MCP Apps Foundation

Overview

This PR implements the foundation for MCP Apps Extension (SEP-1865), providing a minimal viable UI capability. The implementation is well-structured and follows the project's patterns.

✅ Strengths

1. Security-First Design

  • URI validation with comprehensive checks (src/proxy/mcp-server.ts:847-860):
    • Max length validation (MAX_URI_LENGTH = 2048)
    • Path traversal protection (.. rejection)
    • Scheme and host validation (ui://proofscan/ only)
  • CSP policy in HTML (src/html/trace-viewer.html:25-26):
    • default-src 'none' - deny by default
    • Inline scripts/styles only (no external CDNs)
  • Session token validation (src/html/trace-viewer.html:203-208):
    • Origin locking on first message
    • Token-based message authentication
    • crypto.randomUUID() for token generation

2. Test Coverage

  • 330 lines of comprehensive tests (src/proxy/tests/mcp-server-resources.test.ts)
  • Tests cover:
    • Happy paths (resources/list, resources/read, ui/initialize)
    • Security edge cases (path traversal, URI length, invalid schemes)
    • Token uniqueness and validation
    • Multiple concurrent sessions

3. Type Safety

  • Proper TypeScript interfaces for all new endpoints (src/proxy/types.ts:925-984)
  • Complete type definitions for MCP Apps protocol

4. Clean Architecture

  • Separation of concerns (UI, server, tests)
  • Self-contained HTML with inline CSS/JS
  • Minimal scope - only foundation functionality

🔍 Issues & Recommendations

Critical Issues

None - the security validations are solid.

High Priority

1. Build Script Reliability (package.json:10)

"build": "tsc && cp -r src/html dist/"

Issue: cp -r silently succeeds even if source doesn't exist, making build failures hard to debug.

Recommendation: Add validation or use a more robust build tool.

2. HTML Loading Error Handling (src/proxy/mcp-server.ts:656-665)

function getTraceViewerHtml(): string {
  try {
    return readFileSync(htmlPath, 'utf-8');
  } catch (error) {
    throw new Error('UI resource unavailable');
  }
}

Issue: File read happens synchronously on every resources/read request. If the file is missing, every request will fail with a generic error.

Recommendation:

  • Cache the HTML content at server startup
  • Fail fast during initialization if file is missing
  • Add a startup validation test

Medium Priority

3. Session Token Management (src/proxy/mcp-server.ts:104)

private sessionTokens: Set<string> = new Set();

Issue: Tokens are never cleaned up, leading to unbounded memory growth.

Recommendation: Add token expiration with TTL-based cleanup.

4. Origin Lock Race Condition (src/html/trace-viewer.html:186-193)

if (!trustedOrigin && event.origin) {
  trustedOrigin = event.origin;
}

Issue: In theory, a malicious frame could send a message before the legitimate parent, though this is low risk in practice.

Recommendation: Accept origin as initialization parameter or use postMessage's source property to validate parent window.

5. Resource URI Constant Duplication
The URI 'ui://proofscan/trace-viewer' appears in multiple places without a shared constant location.

Recommendation: Export from a shared constants file to ensure consistency.

Low Priority

6. Console Logging in Production (src/html/trace-viewer.html)
Multiple console.log() statements will appear in production UI. Consider adding a debug flag or removing them.

7. Protocol Version Hardcoding (src/proxy/mcp-server.ts:42)

const UI_PROTOCOL_VERSION = '2025-11-21';

Recommendation: Document why this differs from PROTOCOL_VERSION and consider consolidating version management.

8. Unused isValidSessionToken Method (src/proxy/mcp-server.ts:917-919)
This method is only called in tests. If it's for future use, add a JSDoc comment explaining the roadmap.

📊 Test Results Confirmation

PR description claims:

  • npm run build success
  • npm test all pass (1778 tests)
  • ✅ No external JS/CSS dependencies
  • ✅ CSP properly configured

All acceptance criteria met for Phase 6.1.

🎯 Performance Considerations

  • Synchronous file reads: Consider caching HTML at startup
  • No rate limiting: Multiple ui/initialize calls could be abused (though low severity)
  • Set operations: All O(1), efficient for token validation

🔒 Security Assessment

Overall: Strong

The implementation demonstrates good security practices:

  • Input validation (URI length, path traversal, scheme)
  • CSP headers prevent XSS
  • Token-based authentication
  • Origin validation

No critical vulnerabilities found.

📝 Code Quality

  • TypeScript: Proper typing throughout
  • Error handling: Consistent error codes and messages
  • Logging: Appropriate log levels
  • Comments: Good JSDoc documentation
  • Testing: Comprehensive test coverage

🚀 Suggestions for Follow-up PRs

  1. Implement token cleanup/expiration strategy
  2. Add integration test for full HTML loading flow
  3. Consider caching HTML content at startup
  4. Add rate limiting for ui/initialize
  5. Implement actual event fetching in proofscan_getEvents

✅ Approval Recommendation

APPROVED with suggestions for follow-up improvements.

This is a solid foundation PR that implements the minimal success path correctly. The security validations are thorough, tests are comprehensive, and the code follows project conventions. The suggested improvements are enhancements rather than blockers.


Reviewed by: Claude Code
Date: 2026-02-07
Test Status: All 1778 tests passing

@claude
Copy link
Copy Markdown

claude bot commented Feb 7, 2026

PR Review: Phase 6.1 - MCP Apps 基盤実装

Summary

This PR implements the foundation for MCP Apps Extension (SEP-1865), adding support for serving HTML-based UIs through the MCP protocol. The implementation follows a minimal viable path to display UI content.

✅ Strengths

1. Strong Security Posture

  • CSP Implementation: Content Security Policy restricts external resources (default-src 'none')
  • Path Traversal Protection: URI validation blocks .. sequences (src/proxy/mcp-server.ts:780)
  • URI Length Limits: Prevents buffer overflow with MAX_URI_LENGTH = 2048 (line 64)
  • URI Scheme Validation: Only allows ui://proofscan/ scheme (line 775)
  • Token-based Authentication: Session tokens using UUID for UI validation (lines 105-106, 817-821)
  • Origin Locking: Client-side origin validation on first message (trace-viewer.html:167-174)

2. Comprehensive Test Coverage

  • 330 lines of new tests in mcp-server-resources.test.ts
  • Tests cover security edge cases (path traversal, URI length, invalid schemes)
  • Token generation and validation tested thoroughly
  • All tests passing (1778 total)

3. Clean Architecture

  • Well-separated concerns: types, handlers, and UI code
  • Consistent with existing codebase patterns
  • Proper TypeScript typing throughout

4. Good Documentation

  • Clear PR description with implementation checklist
  • Inline code comments where needed
  • Proper JSDoc annotations

🔍 Issues Found

Critical Issues

None found.

High Priority

1. Missing Error Handling in Copy Script (scripts/copy-html.cjs:11)

fs.cpSync(src, dest, { recursive: true });

Issue: No error handling if source directory doesn't exist or copy fails.
Impact: Build will silently fail or crash without clear error message.
Recommendation: Add try-catch with clear error message:

try {
  fs.cpSync(src, dest, { recursive: true });
  console.log('Copied src/html -> dist/html');
} catch (err) {
  console.error('Failed to copy HTML assets:', err.message);
  process.exit(1);
}

2. HTML Not Self-Contained (trace-viewer.html:1-249)
Issue: While the HTML contains inline JS/CSS (good!), it doesn't handle the case where window.parent.postMessage is unavailable or fails.
Impact: UI will hang on "Connecting..." if postMessage doesn't work.
Recommendation: Add timeout handler:

// Add after line 238
setTimeout(() => {
  if (!sessionToken) {
    setStatus('error', 'Connection timeout');
    messageDiv.textContent = 'Failed to connect to MCP host. Please reload.';
  }
}, 10000); // 10 second timeout

Medium Priority

3. Session Token Never Expires (src/proxy/mcp-server.ts:106, 821)
Issue: Tokens are added to Set but never removed. Long-running proxy will accumulate tokens indefinitely.
Impact: Minor memory leak over time.
Recommendation: Either:

  • Implement token expiration with Map<token, timestamp>
  • Clear tokens on client disconnect
  • Add comment explaining tokens are intentionally persistent for this phase

4. Unused Private Method (src/proxy/mcp-server.ts:840-842)

private isValidSessionToken(token: string): boolean {
  return this.sessionTokens.has(token);
}

Issue: Method is defined but never called (only in tests).
Impact: Dead code in production.
Recommendation: Either use it for future ui/call validation or remove it and add TODO comment for Phase 6.2.

5. Tool outputSchema Mismatch (src/proxy/mcp-server.ts:622-641, 696-705)
Issue: The proofscan_getEvents tool declares outputSchema with duration_ms (line 634), but the stub implementation returns data without this field.
Impact: Schema validation may fail in strict clients.
Recommendation: Either:

  • Return data matching the schema (even with null values)
  • Update schema to mark optional fields
  • Add comment that schema/impl will align in next phase

Low Priority

6. Magic Number in HTML (trace-viewer.html:158)

const targetOrigin = trustedOrigin || '*';

Issue: Using wildcard origin before locking is safe but could be documented.
Recommendation: Add comment explaining this is safe because we lock on first message.

7. Console.log in Production Code (trace-viewer.html:157, 169, 186, 191, 202, 217, 222)
Issue: Multiple console.log statements will clutter browser console.
Impact: Minor UX issue.
Recommendation: Wrap in debug flag or remove for production.

💡 Suggestions

Code Quality

  1. Type Safety: Consider adding runtime validation for UiInitializeParams (currently accepts any object)

  2. Constants: Extract magic strings to constants:

    • '2025-11-21' appears in multiple places (mcp-server.ts:53, trace-viewer.html:236)
    • Consider shared constants file
  3. Test Organization: Excellent test coverage! Consider grouping related tests with nested describes for better organization.

Performance

  1. HTML Loading: getTraceViewerHtml() reads file on every request. Consider caching:
private cachedTraceViewerHtml: string | null = null;

function getTraceViewerHtml(): string {
  if (!this.cachedTraceViewerHtml) {
    this.cachedTraceViewerHtml = readFileSync(htmlPath, 'utf-8');
  }
  return this.cachedTraceViewerHtml;
}
  1. Build Script: fs.cpSync is available in Node 16.7+. Verify package.json engines field matches.

🔒 Security Assessment

Overall security posture is excellent for a Phase 1 implementation:

  • ✅ No XSS vectors (CSP blocks all external resources)
  • ✅ No path traversal vulnerabilities (proper validation)
  • ✅ No injection attacks (token-based auth)
  • ✅ No DoS vectors (buffer limits, URI limits)
  • ⚠️ Session tokens never expire (minor, acceptable for Phase 1)

🧪 Test Coverage Assessment

Test coverage is comprehensive:

  • ✅ All happy paths tested
  • ✅ Security edge cases covered
  • ✅ Token generation and validation
  • ✅ Error conditions handled
  • 📝 Consider adding integration test for full UI flow (optional)

📊 Performance Considerations

  • HTML file size is 249 lines (~7KB) - acceptable
  • No performance bottlenecks identified
  • Consider caching HTML content (see suggestion above)

✅ Acceptance Criteria Check

Per the PR description, all ACs are met:

  • resources/list returns text/html;profile=mcp-app
  • resources/read returns single HTML (no external CDN)
  • tools/list includes outputSchema and _meta.ui.resourceUri
  • ✅ UI receives token and rejects mismatched messages
  • ✅ Build succeeds, tests pass

🎯 Recommendation

Approve with minor fixes recommended.

This is a solid foundation for MCP Apps. The implementation is clean, secure, and well-tested. The issues identified are mostly minor and can be addressed in follow-up PRs or before merge.

Before Merge (Optional but Recommended):

  1. Add error handling to copy-html.cjs
  2. Add connection timeout to trace-viewer.html
  3. Add TODO comment for token expiration
  4. Remove unused isValidSessionToken or add TODO for Phase 6.2

Can Be Deferred:

  • Console.log cleanup
  • HTML caching optimization
  • outputSchema alignment (if stub is temporary)

🙌 Nice Work!

The code quality, test coverage, and security considerations demonstrate careful engineering. The "minimum viable path" approach is exactly right for a foundational PR.


Review completed by Claude Code

@proofofprotocol proofofprotocol merged commit 990c6e5 into main Feb 7, 2026
2 checks passed
@proofofprotocol proofofprotocol deleted the feature/phase6-apps-foundation branch February 7, 2026 08:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants