{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "mcp-server-registry-manager",
  "title": "Mcp Server Registry Manager",
  "type": "registry:page",
  "files": [
    {
      "path": "packages/registry-angular/blocks/mcp-server-registry-manager/mcp-server-registry-manager.component.ts",
      "content": "// Anthropic Model Context Protocol (MCP) server registry manager with connection health\n// monitoring, stdio/SSE transport metrics, live tool schema explorer, parameterized\n// resource URI templates, and JSON-RPC 2.0 payload inspection drawer. Port of the React\n// block 1:1 (single-file Angular port of the Vue multi-file block).\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  Activity,\n  Boxes,\n  Check,\n  CheckCircle2,\n  Copy,\n  Link2,\n  LucideAngularModule,\n  Play,\n  Plus,\n  RefreshCw,\n  Search,\n  Server,\n  ShieldCheck,\n  Trash2,\n  Wrench,\n  X,\n  Zap,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport {\n  UiDialogComponent,\n  UiDialogContentComponent,\n  UiDialogDescriptionComponent,\n  UiDialogFooterComponent,\n  UiDialogHeaderComponent,\n  UiDialogTitleComponent,\n} from '@/ui/dialog/dialog.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport {\n  UiSheetCloseComponent,\n  UiSheetComponent,\n  UiSheetContentComponent,\n  UiSheetDescriptionComponent,\n  UiSheetFooterComponent,\n  UiSheetHeaderComponent,\n  UiSheetTitleComponent,\n} from '@/ui/sheet/sheet.component'\nimport {\n  UiTableBodyComponent,\n  UiTableCellComponent,\n  UiTableComponent,\n  UiTableHeadComponent,\n  UiTableHeaderComponent,\n  UiTableRowComponent,\n} from '@/ui/table/table.component'\nimport { UiTabsComponent, UiTabsListComponent, UiTabsTriggerComponent } from '@/ui/tabs/tabs.component'\nimport { UiTextareaComponent } from '@/ui/textarea/textarea.component'\n\nexport type TransportType = 'stdio' | 'sse'\nexport type ServerStatus = 'healthy' | 'reconnecting' | 'degraded'\n\nexport interface ToolParam {\n  name: string\n  type: 'string' | 'number' | 'boolean' | 'object' | 'array'\n  required: boolean\n  description: string\n  default?: string\n  enum?: string[]\n}\n\nexport interface ToolSchema {\n  name: string\n  displayName: string\n  description: string\n  category: 'documentation' | 'database' | 'version-control' | 'deployment' | 'web-search' | 'observability'\n  parameters: ToolParam[]\n  sampleRpcRequest: {\n    jsonrpc: '2.0'\n    id: string\n    method: 'tools/call'\n    params: { name: string; arguments: Record<string, unknown> }\n  }\n  sampleRpcResponse: {\n    jsonrpc: '2.0'\n    id: string\n    result: { content: Array<{ type: 'text' | 'resource' | 'image'; text?: string }>; isError: boolean }\n  }\n}\n\nexport interface ResourceTemplate {\n  uriTemplate: string\n  name: string\n  description: string\n  mimeType: string\n}\n\nexport interface McpServer {\n  id: string\n  name: string\n  scope: string\n  version: string\n  transport: TransportType\n  commandOrUrl: string\n  status: ServerStatus\n  uptime: string\n  latencyMs: number\n  callsToday: number\n  description: string\n  envVars: string[]\n  resourceTemplates: ResourceTemplate[]\n  tools: ToolSchema[]\n}\n\nfunction rpcResult(text: string) {\n  return { content: [{ type: 'text' as const, text }], isError: false }\n}\n\nexport const DEFAULT_MCP_SERVERS: McpServer[] = [\n  {\n    id: 'server-ctx-1',\n    name: 'mcp/context7-docs',\n    scope: '@context7/mcp-server',\n    version: 'v1.4.2',\n    transport: 'stdio',\n    commandOrUrl: 'npx -y @context7/mcp-server',\n    status: 'healthy',\n    uptime: '99.98%',\n    latencyMs: 18,\n    callsToday: 4120,\n    description: 'Context-aware documentation retriever and code snippet indexer for public libraries and frameworks.',\n    envVars: ['CONTEXT7_API_KEY=c7_live_***', 'CACHE_TTL=3600'],\n    resourceTemplates: [\n      {\n        uriTemplate: 'docs://{library}/{version}',\n        name: 'Library Documentation AST',\n        description: 'Direct access to indexed AST documentation, type exports, and code snippets',\n        mimeType: 'application/json',\n      },\n      {\n        uriTemplate: 'cheatsheets://{framework}/api-reference',\n        name: 'Framework Cheatsheet',\n        description: 'High-density API method cheat sheets and migration summaries',\n        mimeType: 'text/markdown',\n      },\n    ],\n    tools: [\n      {\n        name: 'query_docs',\n        displayName: 'Query Library Docs',\n        description:\n          'Retrieves up-to-date documentation and code examples from Context7 for any programming library or framework.',\n        category: 'documentation',\n        parameters: [\n          {\n            name: 'libraryId',\n            type: 'string',\n            required: true,\n            description: 'Exact Context7-compatible library ID (e.g. /tailwindlabs/tailwindcss or /unovue/reka-ui).',\n          },\n          {\n            name: 'query',\n            type: 'string',\n            required: true,\n            description: 'What to look up in the library documentation, scoped to a single concept.',\n          },\n          {\n            name: 'limit',\n            type: 'number',\n            required: false,\n            default: '5',\n            description: 'Maximum number of relevant code snippets to return (1-20).',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-ctx-001',\n          method: 'tools/call',\n          params: {\n            name: 'query_docs',\n            arguments: { libraryId: '/tailwindlabs/tailwindcss', query: 'CSS variables and OKLCH color palettes v4' },\n          },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-ctx-001',\n          result: rpcResult(\n            'Tailwind CSS v4 introduces native CSS-first theme configuration using `@theme` and OKLCH color functions without javascript config files.',\n          ),\n        },\n      },\n      {\n        name: 'resolve_library_id',\n        displayName: 'Resolve Library ID',\n        description:\n          'Resolves a package or product name to a Context7-compatible library ID and returns matching ranked libraries.',\n        category: 'documentation',\n        parameters: [\n          {\n            name: 'libraryName',\n            type: 'string',\n            required: true,\n            description: 'Library name to search for (e.g. Next.js, Reka UI, Lucide).',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-ctx-002',\n          method: 'tools/call',\n          params: { name: 'resolve_library_id', arguments: { libraryName: 'Tailwind CSS' } },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-ctx-002',\n          result: rpcResult('Resolved library ID: /tailwindlabs/tailwindcss (trust score 9.8).'),\n        },\n      },\n    ],\n  },\n  {\n    id: 'server-pg-2',\n    name: 'mcp/postgres-inspector',\n    scope: '@modelcontextprotocol/server-postgres',\n    version: 'v0.9.1',\n    transport: 'stdio',\n    commandOrUrl: 'npx -y @modelcontextprotocol/server-postgres postgresql://app_user:***@db.internal:5432/prod_main',\n    status: 'healthy',\n    uptime: '99.95%',\n    latencyMs: 24,\n    callsToday: 3450,\n    description:\n      'Direct PostgreSQL read-only query execution, EXPLAIN execution plan inspector, and live schema reflection.',\n    envVars: ['PGSSLMODE=require', 'MAX_CONNECTIONS=10'],\n    resourceTemplates: [\n      {\n        uriTemplate: 'postgres://{schema}/{table}',\n        name: 'Table Schema Definition',\n        description: 'PostgreSQL table column constraints, foreign keys, row count estimations, and B-Tree indexes',\n        mimeType: 'application/json',\n      },\n      {\n        uriTemplate: 'postgres://metrics/slow-queries',\n        name: 'PgStatStatements Log Buffer',\n        description: 'Aggregated query execution statistics and cache hit ratios',\n        mimeType: 'application/json',\n      },\n    ],\n    tools: [\n      {\n        name: 'execute_sql',\n        displayName: 'Execute Read-Only SQL',\n        description:\n          'Executes read-only SQL queries against Postgres connection with safe execution timeout safeguards.',\n        category: 'database',\n        parameters: [\n          {\n            name: 'query',\n            type: 'string',\n            required: true,\n            description: 'SQL statement (SELECT only) to execute on the cluster.',\n          },\n          {\n            name: 'readOnly',\n            type: 'boolean',\n            required: false,\n            default: 'true',\n            description: 'Enforces transaction read-only flag before executing SQL.',\n          },\n          {\n            name: 'timeoutMs',\n            type: 'number',\n            required: false,\n            default: '5000',\n            description: 'Statement cancellation timeout in milliseconds.',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-pg-001',\n          method: 'tools/call',\n          params: {\n            name: 'execute_sql',\n            arguments: {\n              query: 'SELECT id, email, role, created_at FROM public.users WHERE is_active = true LIMIT 5;',\n              readOnly: true,\n              timeoutMs: 5000,\n            },\n          },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-pg-001',\n          result: rpcResult(\n            '{\"rows\":[{\"id\":\"usr_8492\",\"email\":\"alex@example.com\",\"role\":\"admin\",\"created_at\":\"2026-02-14T08:30:00Z\"}],\"rowCount\":1,\"durationMs\":14.2}',\n          ),\n        },\n      },\n      {\n        name: 'explain_query',\n        displayName: 'Explain Query Plan',\n        description: 'Runs EXPLAIN ANALYZE on query plans to detect slow sequential scans and missing indexes.',\n        category: 'database',\n        parameters: [\n          {\n            name: 'query',\n            type: 'string',\n            required: true,\n            description: 'SQL query to analyze execution graph and node timings.',\n          },\n          {\n            name: 'analyze',\n            type: 'boolean',\n            required: false,\n            default: 'true',\n            description: 'Execute statement to measure actual wall-clock row counts and buffer usage.',\n          },\n          {\n            name: 'format',\n            type: 'string',\n            required: false,\n            default: 'json',\n            enum: ['json', 'text', 'yaml'],\n            description: 'Postgres execution plan output serialization format.',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-pg-002',\n          method: 'tools/call',\n          params: {\n            name: 'explain_query',\n            arguments: {\n              query: 'SELECT * FROM orders WHERE user_id = $1 ORDER BY created_at DESC;',\n              analyze: true,\n              format: 'json',\n            },\n          },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-pg-002',\n          result: rpcResult(\n            '{\"Plan\":{\"Node Type\":\"Index Scan\",\"Index Name\":\"idx_orders_user_id\",\"Startup Cost\":0.42,\"Total Cost\":8.44,\"Plan Rows\":1,\"Actual Rows\":1,\"Actual Total Time\":0.114}}',\n          ),\n        },\n      },\n      {\n        name: 'inspect_schema',\n        displayName: 'Inspect Schema Meta',\n        description: 'Returns table column definitions, constraints, foreign keys, and indexes for a database schema.',\n        category: 'database',\n        parameters: [\n          {\n            name: 'schema',\n            type: 'string',\n            required: false,\n            default: 'public',\n            description: 'Target namespace schema name (e.g. public, auth, billing).',\n          },\n          {\n            name: 'includeForeignKeys',\n            type: 'boolean',\n            required: false,\n            default: 'true',\n            description: 'Include relation cardinality graphs and ON DELETE cascade actions.',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-pg-003',\n          method: 'tools/call',\n          params: { name: 'inspect_schema', arguments: { schema: 'public', includeForeignKeys: true } },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-pg-003',\n          result: rpcResult(\n            '{\"schema\":\"public\",\"tablesCount\":18,\"tables\":[\"users\",\"teams\",\"orders\",\"invoices\",\"audit_logs\"]}',\n          ),\n        },\n      },\n    ],\n  },\n  {\n    id: 'server-gh-3',\n    name: 'mcp/github-tools',\n    scope: '@modelcontextprotocol/server-github',\n    version: 'v2.1.0',\n    transport: 'stdio',\n    commandOrUrl: 'npx -y @modelcontextprotocol/server-github',\n    status: 'healthy',\n    uptime: '99.91%',\n    latencyMs: 92,\n    callsToday: 2890,\n    description:\n      'GitHub REST & GraphQL API bindings for pull request management, diff inspection, and GitHub Actions CI triggers.',\n    envVars: ['GITHUB_PERSONAL_ACCESS_TOKEN=ghp_live_***'],\n    resourceTemplates: [\n      {\n        uriTemplate: 'github://repos/{owner}/{repo}/pulls/{number}',\n        name: 'Pull Request Snapshot',\n        description: 'Structured pull request metadata, commits, CI checks, and review discussions',\n        mimeType: 'application/json',\n      },\n      {\n        uriTemplate: 'github://repos/{owner}/{repo}/actions/{runId}',\n        name: 'CI Workflow Run Logs',\n        description: 'GitHub Actions build step outputs and failure diagnostics',\n        mimeType: 'text/plain',\n      },\n    ],\n    tools: [\n      {\n        name: 'create_pr',\n        displayName: 'Create Pull Request',\n        description: 'Creates a GitHub pull request with title, description, branch target, and reviewer tags.',\n        category: 'version-control',\n        parameters: [\n          { name: 'owner', type: 'string', required: true, description: 'GitHub account or organization name.' },\n          { name: 'repo', type: 'string', required: true, description: 'Repository name.' },\n          { name: 'title', type: 'string', required: true, description: 'Pull request header title.' },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-gh-001',\n          method: 'tools/call',\n          params: { name: 'create_pr', arguments: { owner: 'uipkge', repo: 'uipkge-ui', title: 'feat: add block' } },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-gh-001',\n          result: rpcResult('Created PR #482: feat: add block (draft=false, checks pending).'),\n        },\n      },\n      {\n        name: 'review_diff',\n        displayName: 'Review Diff',\n        description: 'Fetches and analyzes pull request unified diffs against target base branch.',\n        category: 'version-control',\n        parameters: [\n          { name: 'owner', type: 'string', required: true, description: 'Repository owner.' },\n          { name: 'repo', type: 'string', required: true, description: 'Repository name.' },\n          { name: 'pullNumber', type: 'number', required: true, description: 'GitHub pull request number.' },\n          {\n            name: 'contextLines',\n            type: 'number',\n            required: false,\n            default: '3',\n            description: 'Number of surrounding context lines to include.',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-gh-002',\n          method: 'tools/call',\n          params: {\n            name: 'review_diff',\n            arguments: { owner: 'uday-a', repo: 'uipkge', pullNumber: 142, contextLines: 3 },\n          },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-gh-002',\n          result: rpcResult(\n            'diff --git a/components/blocks/McpServerRegistryManager.tsx b/components/blocks/McpServerRegistryManager.tsx\\n+export function McpServerRegistryManager() { return <div>...</div> }',\n          ),\n        },\n      },\n      {\n        name: 'merge_pr',\n        displayName: 'Merge Pull Request',\n        description: 'Performs squash-merge or merge commit with branch auto-deletion.',\n        category: 'version-control',\n        parameters: [\n          { name: 'owner', type: 'string', required: true, description: 'Repository owner.' },\n          { name: 'repo', type: 'string', required: true, description: 'Repository name.' },\n          { name: 'pullNumber', type: 'number', required: true, description: 'Pull request number.' },\n          {\n            name: 'mergeMethod',\n            type: 'string',\n            required: false,\n            default: 'squash',\n            enum: ['squash', 'merge', 'rebase'],\n            description: 'Git merge strategy to apply.',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-gh-003',\n          method: 'tools/call',\n          params: {\n            name: 'merge_pr',\n            arguments: { owner: 'uday-a', repo: 'uipkge', pullNumber: 142, mergeMethod: 'squash' },\n          },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-gh-003',\n          result: rpcResult(\n            '{\"sha\":\"8a7c2b3d...\",\"merged\":true,\"message\":\"Pull Request successfully merged and closed\"}',\n          ),\n        },\n      },\n      {\n        name: 'get_workflow_runs',\n        displayName: 'Get Workflow Runs',\n        description: 'Lists GitHub Actions CI workflow run statuses and failure logs.',\n        category: 'version-control',\n        parameters: [\n          { name: 'owner', type: 'string', required: true, description: 'Repository owner.' },\n          { name: 'repo', type: 'string', required: true, description: 'Repository name.' },\n          {\n            name: 'status',\n            type: 'string',\n            required: false,\n            default: 'completed',\n            enum: ['completed', 'in_progress', 'queued', 'failure'],\n            description: 'Filter CI runs by lifecycle status.',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-gh-004',\n          method: 'tools/call',\n          params: {\n            name: 'get_workflow_runs',\n            arguments: { owner: 'uday-a', repo: 'uipkge', status: 'completed' },\n          },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-gh-004',\n          result: rpcResult(\n            '{\"total_count\":24,\"workflow_runs\":[{\"id\":9812401,\"name\":\"CI Registry Build\",\"conclusion\":\"success\"}]}',\n          ),\n        },\n      },\n    ],\n  },\n  {\n    id: 'server-cf-4',\n    name: 'mcp/cloudflare-deployer',\n    scope: '@cloudflare/mcp-pages-worker',\n    version: 'v3.0.4',\n    transport: 'sse',\n    commandOrUrl: 'https://mcp-gateway.cloudflare.com/sse/v1/uipkge-prod',\n    status: 'healthy',\n    uptime: '100.0%',\n    latencyMs: 48,\n    callsToday: 1180,\n    description:\n      'Edge runtime deployment automation, real-time Cloudflare Worker telemetry log tailing, and CDN cache invalidation.',\n    envVars: ['CLOUDFLARE_API_TOKEN=cft_live_***', 'CLOUDFLARE_ACCOUNT_ID=8bb9e07a...'],\n    resourceTemplates: [\n      {\n        uriTemplate: 'cloudflare://zones/{zoneId}/pages',\n        name: 'Pages Deployment Manifest',\n        description: 'Active production and preview deployment bundles with rollback pointers',\n        mimeType: 'application/json',\n      },\n      {\n        uriTemplate: 'cloudflare://workers/{scriptName}/tail',\n        name: 'Live Worker Telemetry Stream',\n        description: 'Real-time structured event logs, CPU execution times, and exceptions',\n        mimeType: 'application/x-ndjson',\n      },\n    ],\n    tools: [\n      {\n        name: 'deploy_pages',\n        displayName: 'Deploy Cloudflare Pages',\n        description: 'Triggers atomic Cloudflare Pages deployment bundle upload with verification.',\n        category: 'deployment',\n        parameters: [\n          {\n            name: 'projectName',\n            type: 'string',\n            required: true,\n            description: 'Cloudflare Pages project name (uipkge).',\n          },\n          { name: 'branch', type: 'string', required: true, description: 'Git branch trigger (e.g. main).' },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-cf-001',\n          method: 'tools/call',\n          params: { name: 'deploy_pages', arguments: { projectName: 'uipkge', branch: 'main' } },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-cf-001',\n          result: rpcResult('Deployment uipkge-prod-9f31 live at https://uipkge.dev (26s, verified).'),\n        },\n      },\n      {\n        name: 'purge_cache',\n        displayName: 'Purge CDN Cache',\n        description: 'Purges Cloudflare CDN cache zones by URL pattern or Cache-Tag headers.',\n        category: 'deployment',\n        parameters: [\n          { name: 'zoneId', type: 'string', required: true, description: 'Cloudflare Zone identifier.' },\n          {\n            name: 'purgeEverything',\n            type: 'boolean',\n            required: false,\n            default: 'false',\n            description: 'Purge entire CDN edge cache.',\n          },\n          { name: 'tags', type: 'array', required: false, description: 'Array of Cache-Tag headers to invalidate.' },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-cf-002',\n          method: 'tools/call',\n          params: { name: 'purge_cache', arguments: { zoneId: '8bb9e07a', purgeEverything: true } },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-cf-002',\n          result: rpcResult('{\"success\":true,\"purged\":[\"https://uipkge.dev/*\"],\"durationMs\":812}'),\n        },\n      },\n      {\n        name: 'tail_worker_logs',\n        displayName: 'Tail Worker Logs',\n        description: 'Subscribes to real-time Cloudflare Worker telemetry logs and exception traces.',\n        category: 'deployment',\n        parameters: [\n          { name: 'scriptName', type: 'string', required: true, description: 'Worker script identifier.' },\n          {\n            name: 'samplingRate',\n            type: 'number',\n            required: false,\n            default: '1.0',\n            description: 'Trace event sampling ratio (0.1 to 1.0).',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-cf-003',\n          method: 'tools/call',\n          params: { name: 'tail_worker_logs', arguments: { scriptName: 'uipkge-edge', samplingRate: 1.0 } },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-cf-003',\n          result: rpcResult('Streaming 142 events/s from uipkge-edge (p99 CPU 3.1ms, 0 exceptions).'),\n        },\n      },\n    ],\n  },\n  {\n    id: 'server-bv-5',\n    name: 'mcp/brave-search',\n    scope: '@modelcontextprotocol/server-brave-search',\n    version: 'v1.1.2',\n    transport: 'sse',\n    commandOrUrl: 'https://mcp-stream.brave.internal/sse?channel=research',\n    status: 'reconnecting',\n    uptime: '98.40%',\n    latencyMs: 165,\n    callsToday: 840,\n    description: 'Privacy-preserving web search, index lookup, and clean markdown extraction from live URLs.',\n    envVars: ['BRAVE_API_KEY=bsa_***'],\n    resourceTemplates: [\n      {\n        uriTemplate: 'search://web/{query}',\n        name: 'Real-Time Web Search Cache',\n        description: 'Cached organic search result nodes with domain trust rankings',\n        mimeType: 'application/json',\n      },\n      {\n        uriTemplate: 'webpage://cached/{urlHash}',\n        name: 'Normalized Page Mirror',\n        description: 'Clean reader-mode markdown representation of fetched webpage',\n        mimeType: 'text/markdown',\n      },\n    ],\n    tools: [\n      {\n        name: 'brave_search',\n        displayName: 'Brave Web Search',\n        description: 'Queries Brave Search API for web results, snippets, publishing dates, and ranking scores.',\n        category: 'web-search',\n        parameters: [\n          { name: 'query', type: 'string', required: true, description: 'Search term or technical keyword.' },\n          {\n            name: 'count',\n            type: 'number',\n            required: false,\n            default: '10',\n            description: 'Number of organic results (1-20).',\n          },\n          {\n            name: 'freshness',\n            type: 'string',\n            required: false,\n            enum: ['pd', 'pw', 'pm', 'py'],\n            description: 'Filter by time horizon: past day, week, month, or year.',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-bv-001',\n          method: 'tools/call',\n          params: {\n            name: 'brave_search',\n            arguments: { query: 'Reka UI Vue 3.5 release notes', count: 5, freshness: 'pm' },\n          },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-bv-001',\n          result: rpcResult(\n            '{\"results\":[{\"title\":\"Reka UI v1.4 Documentation\",\"url\":\"https://reka-ui.com\",\"snippet\":\"Radix-vue is now Reka UI with Vue 3.5 TypeScript improvements.\"}]}',\n          ),\n        },\n      },\n      {\n        name: 'fetch_web_content',\n        displayName: 'Fetch Web Content',\n        description: 'Fetches full clean markdown representations from verified web URLs.',\n        category: 'web-search',\n        parameters: [\n          { name: 'url', type: 'string', required: true, description: 'Fully qualified HTTP/HTTPS URL to crawl.' },\n          {\n            name: 'format',\n            type: 'string',\n            required: false,\n            default: 'markdown',\n            enum: ['markdown', 'text', 'html'],\n            description: 'Target format of extracted webpage body content.',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-bv-002',\n          method: 'tools/call',\n          params: { name: 'fetch_web_content', arguments: { url: 'https://reka-ui.com/docs', format: 'markdown' } },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-bv-002',\n          result: rpcResult('# Reka UI — 4,210 words extracted (markdown, 0.9s).'),\n        },\n      },\n    ],\n  },\n  {\n    id: 'server-sn-6',\n    name: 'mcp/sentry-telemetry',\n    scope: '@sentry/mcp-telemetry',\n    version: 'v1.8.0',\n    transport: 'stdio',\n    commandOrUrl: 'npx -y @sentry/mcp-telemetry --org=uipkge',\n    status: 'healthy',\n    uptime: '99.99%',\n    latencyMs: 35,\n    callsToday: 820,\n    description:\n      'Production crash monitoring, unhandled exceptions triage, and stacktrace source-mapping for AI agents.',\n    envVars: ['SENTRY_AUTH_TOKEN=sntrys_live_***', 'SENTRY_ORG=uipkge'],\n    resourceTemplates: [\n      {\n        uriTemplate: 'sentry://projects/{project}/issues/{issueId}',\n        name: 'Sentry Issue Context',\n        description: 'Resolved source maps, breadcrumbs trail, device context, and user impact rate',\n        mimeType: 'application/json',\n      },\n      {\n        uriTemplate: 'sentry://projects/{project}/stats/24h',\n        name: 'Hourly Error Volume',\n        description: 'Aggregated error frequency histograms across release versions',\n        mimeType: 'application/json',\n      },\n    ],\n    tools: [\n      {\n        name: 'query_sentry_issues',\n        displayName: 'Query Sentry Issues',\n        description: 'Lists unhandled exceptions and regression issues by release tag and frequency.',\n        category: 'observability',\n        parameters: [\n          { name: 'project', type: 'string', required: true, description: 'Sentry project slug.' },\n          {\n            name: 'query',\n            type: 'string',\n            required: false,\n            description: 'Sentry search query (e.g. is:unresolved).',\n          },\n          {\n            name: 'statsPeriod',\n            type: 'string',\n            required: false,\n            default: '24h',\n            enum: ['24h', '14d', '30d'],\n            description: 'Time window for issue occurrence statistics.',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-sn-001',\n          method: 'tools/call',\n          params: {\n            name: 'query_sentry_issues',\n            arguments: { project: 'uipkge-astro-site', query: 'is:unresolved level:error', statsPeriod: '24h' },\n          },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-sn-001',\n          result: rpcResult(\n            `{\"issues\":[{\"id\":\"iss_991\",\"title\":\"TypeError: Cannot read properties of undefined (reading 'dataset')\",\"count\":3,\"lastSeen\":\"2026-08-21T09:12:00Z\"}]}`,\n          ),\n        },\n      },\n      {\n        name: 'get_issue_stacktrace',\n        displayName: 'Get De-minified Stacktrace',\n        description: 'Retrieves de-minified source code stacktrace and breadcrumb chain for an error ID.',\n        category: 'observability',\n        parameters: [\n          { name: 'issueId', type: 'string', required: true, description: 'Unique Sentry issue identifier.' },\n          {\n            name: 'expandContext',\n            type: 'boolean',\n            required: false,\n            default: 'true',\n            description: 'Include 5 lines of source code surrounding the crashing line.',\n          },\n        ],\n        sampleRpcRequest: {\n          jsonrpc: '2.0',\n          id: 'req-sn-002',\n          method: 'tools/call',\n          params: { name: 'get_issue_stacktrace', arguments: { issueId: 'iss_991', expandContext: true } },\n        },\n        sampleRpcResponse: {\n          jsonrpc: '2.0',\n          id: 'req-sn-002',\n          result: rpcResult(\n            '{\"filename\":\"src/components/story/Story.tsx\",\"lineno\":42,\"colno\":14,\"function\":\"handleCopy\",\"contextLine\":\"navigator.clipboard.writeText(props.code)\"}',\n          ),\n        },\n      },\n    ],\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-mcp-server-registry-manager, [ui-mcp-server-registry-manager]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiDialogComponent,\n    UiDialogContentComponent,\n    UiDialogDescriptionComponent,\n    UiDialogFooterComponent,\n    UiDialogHeaderComponent,\n    UiDialogTitleComponent,\n    UiInputComponent,\n    UiSheetCloseComponent,\n    UiSheetComponent,\n    UiSheetContentComponent,\n    UiSheetDescriptionComponent,\n    UiSheetFooterComponent,\n    UiSheetHeaderComponent,\n    UiSheetTitleComponent,\n    UiTableBodyComponent,\n    UiTableCellComponent,\n    UiTableComponent,\n    UiTableHeadComponent,\n    UiTableHeaderComponent,\n    UiTableRowComponent,\n    UiTabsComponent,\n    UiTabsListComponent,\n    UiTabsTriggerComponent,\n    UiTextareaComponent,\n  ],\n  template: `\n    <div data-slot=\"mcp-server-registry-manager\" [class]=\"rootClass\">\n      <!-- Top System Header -->\n      <header class=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n        <div class=\"space-y-1.5\">\n          <div class=\"flex flex-wrap items-center gap-2.5\">\n            <div\n              class=\"bg-primary/10 text-primary border-primary/20 flex size-9 items-center justify-center rounded-lg border\"\n            >\n              <lucide-icon [img]=\"Server\" class=\"size-4.5\" />\n            </div>\n            <h1 class=\"text-xl font-bold tracking-tight sm:text-2xl\">Model Context Protocol (MCP) Server Manager</h1>\n          </div>\n          <div class=\"text-muted-foreground flex flex-wrap items-center gap-2 text-xs\">\n            <span ui-badge variant=\"secondary\" class=\"font-mono text-xs font-normal\">\n              MCP Spec 2024-11-05 · JSON-RPC 2.0\n            </span>\n            <span>•</span>\n            <span class=\"text-success inline-flex items-center gap-1.5 font-medium\">\n              <span class=\"bg-success flex size-2 animate-pulse rounded-full\"></span>\n              {{ totalConnectedServers() }} Connected · {{ totalRegisteredTools() }} Tools Active\n            </span>\n            <span class=\"hidden sm:inline\">•</span>\n            <span class=\"hidden sm:inline\">Anthropic Runtime & Agent Registry</span>\n          </div>\n        </div>\n\n        <!-- Action CTA Buttons -->\n        <div class=\"flex flex-wrap items-center gap-2.5\">\n          <button\n            ui-button\n            variant=\"outline\"\n            size=\"sm\"\n            class=\"h-9 gap-1.5 text-xs font-medium\"\n            [disabled]=\"isHandshaking()\"\n            (click)=\"runHandshakeTest()\"\n          >\n            <lucide-icon [img]=\"RefreshCw\" [class]=\"handshakeIconClass\" />\n            <span>{{ isHandshaking() ? 'Handshaking...' : 'Test Tool Handshake' }}</span>\n          </button>\n          <button\n            ui-button\n            size=\"sm\"\n            class=\"h-9 gap-1.5 text-xs font-medium shadow-xs\"\n            (click)=\"isInstallDialogOpen.set(true)\"\n          >\n            <lucide-icon [img]=\"Plus\" class=\"size-3.5\" />\n            <span>Install New MCP Server</span>\n          </button>\n        </div>\n      </header>\n\n      <!-- Handshake Diagnostic Alert Banner -->\n      @if (handshakeAlertVisible()) {\n        <div\n          class=\"border-success/30 bg-success/10 text-foreground dark:text-foreground flex items-start justify-between rounded-lg border p-3.5 text-xs transition-colors\"\n        >\n          <div class=\"flex items-start gap-2.5\">\n            <lucide-icon [img]=\"CheckCircle2\" class=\"text-success mt-0.5 size-4 shrink-0\" />\n            <div class=\"space-y-0.5\">\n              <p class=\"text-success font-semibold\">Handshake Verified · All 6 MCP Servers Responded (Avg 42ms)</p>\n              <p class=\"text-success\">\n                34 tool schemas, 18 resource templates, and prompt interfaces validated against MCP Specification\n                2024-11-05.\n              </p>\n            </div>\n          </div>\n          <button\n            type=\"button\"\n            aria-label=\"Dismiss notification\"\n            class=\"text-success hover:text-foreground rounded-md p-1 transition-colors\"\n            (click)=\"handshakeAlertVisible.set(false)\"\n          >\n            <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n          </button>\n        </div>\n      }\n\n      <!-- 4 MCP System KPI Cards -->\n      <div class=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n        <div ui-card class=\"border-border bg-card shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">\n              Connected Servers\n            </h3>\n            <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n              <lucide-icon [img]=\"Server\" class=\"size-3.5\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-1\">\n            <div class=\"text-2xl font-bold tracking-tight\">{{ servers().length }} Active Servers</div>\n            <div class=\"text-muted-foreground flex items-center gap-1.5 text-xs\">\n              <span class=\"bg-success inline-block size-1.5 rounded-full\"></span>\n              <span>{{ totalConnectedServers() }} healthy</span>\n              <span>•</span>\n              <span class=\"text-warning\">{{ servers().length - totalConnectedServers() }} reconnecting</span>\n            </div>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border bg-card shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">\n              Registered Tools\n            </h3>\n            <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n              <lucide-icon [img]=\"Wrench\" class=\"size-3.5\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-1\">\n            <div class=\"text-2xl font-bold tracking-tight\">34 Tool Functions</div>\n            <div class=\"text-success flex items-center gap-1.5 text-xs\">\n              <lucide-icon [img]=\"ShieldCheck\" class=\"size-3.5\" />\n              <span>100% Schema Validated</span>\n            </div>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border bg-card shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">\n              Resource Templates\n            </h3>\n            <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n              <lucide-icon [img]=\"Link2\" class=\"size-3.5\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-1\">\n            <div class=\"text-2xl font-bold tracking-tight\">18 Parameterized URIs</div>\n            <div class=\"text-muted-foreground flex items-center gap-1.5 text-xs\">\n              <lucide-icon [img]=\"Boxes\" class=\"size-3.5\" />\n              <span>Dynamic URI Subscriptions</span>\n            </div>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border bg-card shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">\n              Invocation Volume\n            </h3>\n            <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n              <lucide-icon [img]=\"Zap\" class=\"size-3.5\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-1\">\n            <div class=\"text-2xl font-bold tracking-tight\">12,480 calls today</div>\n            <div class=\"text-muted-foreground flex items-center gap-1.5 text-xs\">\n              <lucide-icon [img]=\"Activity\" class=\"text-success size-3.5\" />\n              <span><span class=\"text-foreground font-medium\">99.8%</span> success · 142ms avg</span>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Main Content Navigation & Filter Bar -->\n      <div class=\"space-y-4\">\n        <div class=\"flex flex-col gap-3 md:flex-row md:items-center md:justify-between\">\n          <ui-tabs [value]=\"activeViewTab()\" (valueChange)=\"setActiveViewTab($event)\" class=\"w-full md:w-auto\">\n            <ui-tabs-list class=\"grid h-9 w-full max-w-[380px] grid-cols-3 md:w-full\">\n              <button ui-tabs-trigger value=\"servers\" class=\"text-xs\">Servers & Tools ({{ servers().length }})</button>\n              <button ui-tabs-trigger value=\"handshake-logs\" class=\"text-xs\">Handshake Logs</button>\n              <button ui-tabs-trigger value=\"config-json\" class=\"text-xs\">Raw JSON Config</button>\n            </ui-tabs-list>\n          </ui-tabs>\n\n          <div class=\"flex flex-wrap items-center gap-2\">\n            <div class=\"relative w-full sm:w-64\">\n              <lucide-icon\n                [img]=\"Search\"\n                class=\"text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2\"\n              />\n              <ui-input\n                [value]=\"searchQuery()\"\n                (valueChange)=\"searchQuery.set($event)\"\n                placeholder=\"Search servers, tools, URIs...\"\n                class=\"bg-background h-8.5 pl-8 text-xs\"\n              />\n            </div>\n\n            <div class=\"border-border bg-muted/40 flex items-center rounded-lg border p-0.5 text-xs\">\n              <button [class]=\"transportBtnClass('all')\" (click)=\"transportFilter.set('all')\">All</button>\n              <button [class]=\"transportBtnClass('stdio')\" (click)=\"transportFilter.set('stdio')\">stdio</button>\n              <button [class]=\"transportBtnClass('sse')\" (click)=\"transportFilter.set('sse')\">SSE Stream</button>\n            </div>\n          </div>\n        </div>\n\n        <!-- Tab View 1: Servers & Tools List -->\n        @if (activeViewTab() === 'servers') {\n          <div class=\"space-y-4\">\n            @if (filteredServers().length === 0) {\n              <div\n                class=\"border-border bg-card/40 flex flex-col items-center justify-center rounded-xl border border-dashed p-10 text-center\"\n              >\n                <lucide-icon [img]=\"Server\" class=\"text-muted-foreground/60 mb-3 size-10\" />\n                <h3 class=\"text-sm font-semibold\">No MCP Servers Found</h3>\n                <p class=\"text-muted-foreground mt-1 max-w-sm text-xs\">\n                  No servers match your current search query or filter criteria. Clear filters or add a new MCP server\n                  connection.\n                </p>\n                <button ui-button size=\"sm\" variant=\"outline\" class=\"mt-4 h-8 text-xs\" (click)=\"resetFilters()\">\n                  Reset Filters\n                </button>\n              </div>\n            }\n\n            @for (server of filteredServers(); track server.id) {\n              <div ui-card class=\"border-border bg-card shadow-xs\">\n                <div ui-card-header class=\"pb-2\">\n                  <div class=\"flex flex-wrap items-start justify-between gap-2\">\n                    <div class=\"space-y-1\">\n                      <div class=\"flex flex-wrap items-center gap-2\">\n                        <h3 ui-card-title class=\"font-mono text-sm font-bold\">{{ server.name }}</h3>\n                        <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">{{ server.version }}</span>\n                        <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">{{ server.transport }}</span>\n                        <span\n                          ui-badge\n                          [variant]=\"server.status === 'healthy' ? 'secondary' : 'destructive'\"\n                          class=\"text-xs\"\n                        >\n                          {{ server.status }}\n                        </span>\n                      </div>\n                      <p class=\"text-muted-foreground text-xs\">{{ server.scope }} · {{ server.description }}</p>\n                      <p class=\"text-muted-foreground font-mono text-xs\">\n                        uptime {{ server.uptime }} · {{ server.latencyMs }}ms · {{ server.callsToday }} calls today\n                      </p>\n                    </div>\n                    <div class=\"flex items-center gap-1.5\">\n                      <button\n                        ui-button\n                        variant=\"outline\"\n                        size=\"sm\"\n                        class=\"h-8 gap-1.5 text-xs\"\n                        (click)=\"copyToClipboard(server.commandOrUrl, 'cmd-' + server.id)\"\n                      >\n                        @if (copiedKey() === 'cmd-' + server.id) {\n                          <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                        } @else {\n                          <lucide-icon [img]=\"Copy\" class=\"size-3.5\" />\n                        }\n                        <span class=\"max-w-[220px] truncate font-mono\">{{ server.commandOrUrl }}</span>\n                      </button>\n                      <button\n                        ui-button\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        class=\"h-8 text-xs\"\n                        aria-label=\"Restart server\"\n                        (click)=\"restartServer(server)\"\n                      >\n                        <lucide-icon [img]=\"RefreshCw\" class=\"size-3.5\" />\n                      </button>\n                      <button\n                        ui-button\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        class=\"text-destructive hover:text-destructive h-8 text-xs\"\n                        aria-label=\"Remove server\"\n                        (click)=\"removeServer(server.id)\"\n                      >\n                        <lucide-icon [img]=\"Trash2\" class=\"size-3.5\" />\n                      </button>\n                    </div>\n                  </div>\n                </div>\n                <div ui-card-content class=\"space-y-3\">\n                  <div class=\"flex flex-wrap items-center gap-1.5\">\n                    @for (tool of server.tools; track tool.name) {\n                      <button\n                        ui-button\n                        variant=\"outline\"\n                        size=\"sm\"\n                        class=\"h-7 gap-1.5 font-mono text-xs\"\n                        (click)=\"openToolInspector(server, tool)\"\n                      >\n                        <lucide-icon [img]=\"Wrench\" class=\"size-3\" />\n                        {{ tool.name }}\n                      </button>\n                    }\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"h-7 text-xs\"\n                      (click)=\"openServerInspector(server)\"\n                    >\n                      Inspect server →\n                    </button>\n                  </div>\n                  <div class=\"flex flex-wrap items-center gap-1.5\">\n                    @for (res of server.resourceTemplates; track res.uriTemplate) {\n                      <span ui-badge variant=\"secondary\" class=\"font-mono text-xs font-normal\">\n                        {{ res.uriTemplate }}\n                      </span>\n                    }\n                  </div>\n                </div>\n              </div>\n            }\n          </div>\n        } @else if (activeViewTab() === 'handshake-logs') {\n          <div ui-card class=\"border-border bg-card\">\n            <div ui-card-header class=\"pb-3\">\n              <div class=\"flex items-center justify-between\">\n                <div>\n                  <h3 ui-card-title class=\"text-sm font-bold\">JSON-RPC Handshake & Telemetry Logs</h3>\n                  <p class=\"text-muted-foreground text-xs\">Latest capability negotiation per registered server.</p>\n                </div>\n                <button\n                  ui-button\n                  size=\"sm\"\n                  variant=\"outline\"\n                  class=\"h-8 gap-1.5 text-xs\"\n                  [disabled]=\"isHandshaking()\"\n                  (click)=\"runHandshakeTest()\"\n                >\n                  <lucide-icon [img]=\"RefreshCw\" [class]=\"handshakeIconClass\" />\n                  {{ isHandshaking() ? 'Probing...' : 'Re-probe' }}\n                </button>\n              </div>\n            </div>\n            <div ui-card-content>\n              <table ui-table>\n                <thead ui-table-header>\n                  <tr ui-table-row>\n                    <th ui-table-head class=\"text-xs\">Server</th>\n                    <th ui-table-head class=\"text-xs\">Transport</th>\n                    <th ui-table-head class=\"text-xs\">Status</th>\n                    <th ui-table-head class=\"text-xs\">Latency</th>\n                    <th ui-table-head class=\"text-xs\">Uptime</th>\n                  </tr>\n                </thead>\n                <tbody ui-table-body>\n                  @for (server of servers(); track server.id) {\n                    <tr ui-table-row>\n                      <td ui-table-cell class=\"font-mono text-xs font-medium\">{{ server.name }}</td>\n                      <td ui-table-cell class=\"font-mono text-xs\">{{ server.transport }}</td>\n                      <td ui-table-cell>\n                        <span\n                          ui-badge\n                          [variant]=\"server.status === 'healthy' ? 'secondary' : 'destructive'\"\n                          class=\"text-xs\"\n                        >\n                          {{ server.status }}\n                        </span>\n                      </td>\n                      <td ui-table-cell class=\"font-mono text-xs\">{{ server.latencyMs }}ms</td>\n                      <td ui-table-cell class=\"font-mono text-xs\">{{ server.uptime }}</td>\n                    </tr>\n                  }\n                </tbody>\n              </table>\n            </div>\n          </div>\n        } @else {\n          <div ui-card class=\"border-border bg-card\">\n            <div ui-card-header class=\"pb-3\">\n              <div class=\"flex items-center justify-between\">\n                <div>\n                  <h3 ui-card-title class=\"text-sm font-bold\">Claude Desktop & Cursor MCP Config</h3>\n                  <p class=\"text-muted-foreground text-xs\">\n                    Exportable JSON format compatible with <code class=\"font-mono\">claude_desktop_config.json</code>.\n                  </p>\n                </div>\n                <button\n                  ui-button\n                  size=\"sm\"\n                  variant=\"outline\"\n                  class=\"h-8 gap-1.5 text-xs\"\n                  (click)=\"copyToClipboard(activeMcpConfigJson(), 'mcp-config-json')\"\n                >\n                  @if (copiedKey() === 'mcp-config-json') {\n                    <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                  } @else {\n                    <lucide-icon [img]=\"Copy\" class=\"size-3.5\" />\n                  }\n                  {{ copiedKey() === 'mcp-config-json' ? 'JSON Copied!' : 'Copy Config JSON' }}\n                </button>\n              </div>\n            </div>\n            <div ui-card-content>\n              <pre\n                class=\"bg-muted/60 border-border text-foreground max-h-[420px] overflow-x-auto rounded-lg border p-4 font-mono text-xs leading-relaxed\"\n                >{{ activeMcpConfigJson() }}</pre>\n            </div>\n          </div>\n        }\n      </div>\n\n      <!-- Tool Schema Inspection Drawer (Sheet) -->\n      <div ui-sheet [open]=\"isDrawerOpen()\" (openChange)=\"isDrawerOpen.set($event)\">\n        <div ui-sheet-content class=\"w-full space-y-6 overflow-y-auto p-6 sm:max-w-xl md:max-w-2xl\">\n          @if (selectedTool(); as tool) {\n            <div ui-sheet-header class=\"space-y-2 p-0 text-left\">\n              <div class=\"flex items-center gap-2\">\n                <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">{{ selectedServer().transport }}</span>\n                <span ui-badge variant=\"secondary\" class=\"font-mono text-xs\">{{ tool.category }}</span>\n              </div>\n              <h2 ui-sheet-title class=\"font-mono text-lg font-bold tracking-tight\">{{ tool.name }}</h2>\n              <p ui-sheet-description class=\"text-xs\">{{ selectedServer().name }} · {{ tool.displayName }}</p>\n              <p class=\"text-muted-foreground text-xs\">{{ tool.description }}</p>\n            </div>\n\n            <ui-tabs [value]=\"inspectorTab()\" (valueChange)=\"setInspectorTab($event)\" class=\"w-full\">\n              <ui-tabs-list class=\"grid h-9 w-full grid-cols-3\">\n                <button ui-tabs-trigger value=\"request\" class=\"text-xs\">Sample Request</button>\n                <button ui-tabs-trigger value=\"response\" class=\"text-xs\">Sample Response</button>\n                <button ui-tabs-trigger value=\"schema\" class=\"text-xs\">Parameters</button>\n              </ui-tabs-list>\n            </ui-tabs>\n\n            @if (inspectorTab() === 'request') {\n              <pre\n                class=\"bg-muted/60 border-border max-h-[280px] overflow-auto rounded-lg border p-4 font-mono text-xs leading-relaxed\"\n                >{{ toolRequestJson() }}</pre>\n            } @else if (inspectorTab() === 'response') {\n              <pre\n                class=\"bg-muted/60 border-border max-h-[280px] overflow-auto rounded-lg border p-4 font-mono text-xs leading-relaxed\"\n                >{{ toolResponseJson() }}</pre>\n            } @else {\n              <div class=\"space-y-2\">\n                @for (param of tool.parameters; track param.name) {\n                  <div class=\"border-border flex items-start justify-between gap-2 rounded-lg border p-3 text-xs\">\n                    <div class=\"space-y-0.5\">\n                      <div class=\"flex items-center gap-2\">\n                        <code class=\"font-mono font-semibold\">{{ param.name }}</code>\n                        <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">{{ param.type }}</span>\n                        @if (param.required) {\n                          <span ui-badge variant=\"destructive\" class=\"text-xs\">required</span>\n                        }\n                      </div>\n                      <p class=\"text-muted-foreground text-xs\">{{ param.description }}</p>\n                    </div>\n                  </div>\n                }\n              </div>\n            }\n\n            <div class=\"space-y-2\">\n              <div class=\"flex items-center justify-between\">\n                <span class=\"text-xs font-semibold tracking-wider uppercase\">Dry Run</span>\n                <button\n                  ui-button\n                  size=\"sm\"\n                  variant=\"outline\"\n                  class=\"h-8 gap-1.5 text-xs\"\n                  [disabled]=\"isDryRunning()\"\n                  (click)=\"runDryRunTest()\"\n                >\n                  <lucide-icon [img]=\"Play\" class=\"size-3.5\" />\n                  {{ isDryRunning() ? 'Running...' : 'Run Dry-Run Test' }}\n                </button>\n              </div>\n              @if (dryRunOutput()) {\n                <pre\n                  class=\"bg-muted/60 border-border max-h-[240px] overflow-auto rounded-lg border p-4 font-mono text-xs leading-relaxed\"\n                  >{{ dryRunOutput() }}</pre>\n              }\n            </div>\n\n            <div ui-sheet-footer class=\"flex flex-row items-center justify-end gap-2 border-t pt-4\">\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"h-8 gap-1.5 text-xs\"\n                (click)=\"copyToClipboard(toolRequestJson(), 'tool-request')\"\n              >\n                @if (copiedKey() === 'tool-request') {\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                } @else {\n                  <lucide-icon [img]=\"Copy\" class=\"size-3.5\" />\n                }\n                Copy Request JSON\n              </button>\n              <div ui-sheet-close>\n                <button ui-button size=\"sm\" class=\"h-8 text-xs\">Close Inspector</button>\n              </div>\n            </div>\n          }\n        </div>\n      </div>\n\n      <!-- Install New MCP Server Dialog -->\n      <ui-dialog [open]=\"isInstallDialogOpen()\" (openChange)=\"isInstallDialogOpen.set($event)\">\n        <ui-dialog-content class=\"sm:max-w-lg\">\n          <div ui-dialog-header class=\"space-y-1.5\">\n            <h2 ui-dialog-title class=\"flex items-center gap-2 text-base font-bold\">\n              <lucide-icon [img]=\"Server\" class=\"size-4\" />\n              Install New MCP Server\n            </h2>\n            <p ui-dialog-description class=\"text-muted-foreground text-xs\">\n              Register a stdio command or remote SSE gateway with the Anthropic runtime.\n            </p>\n          </div>\n          <div class=\"space-y-4 py-2\">\n            <div class=\"space-y-1.5\">\n              <label class=\"text-xs font-medium\">Server Name</label>\n              <ui-input\n                [value]=\"newServerName()\"\n                (valueChange)=\"newServerName.set($event)\"\n                placeholder=\"e.g. mcp/my-tools\"\n                class=\"font-mono text-xs\"\n              />\n            </div>\n            <div class=\"space-y-1.5\">\n              <label class=\"text-xs font-medium\">Scope</label>\n              <ui-input\n                [value]=\"newServerScope()\"\n                (valueChange)=\"newServerScope.set($event)\"\n                placeholder=\"@custom/my-tools\"\n                class=\"font-mono text-xs\"\n              />\n            </div>\n            <div class=\"flex items-center gap-2\">\n              <button type=\"button\" [class]=\"installTransportClass('stdio')\" (click)=\"newTransport.set('stdio')\">\n                stdio\n              </button>\n              <button type=\"button\" [class]=\"installTransportClass('sse')\" (click)=\"newTransport.set('sse')\">\n                SSE Stream\n              </button>\n            </div>\n            <div class=\"space-y-1.5\">\n              <label class=\"text-xs font-medium\">Command or SSE URL</label>\n              <ui-input\n                [value]=\"newCommandOrUrl()\"\n                (valueChange)=\"newCommandOrUrl.set($event)\"\n                placeholder=\"npx -y @custom/mcp-server\"\n                class=\"font-mono text-xs\"\n              />\n            </div>\n            <div class=\"space-y-1.5\">\n              <label class=\"text-xs font-medium\">Description</label>\n              <ui-textarea\n                [value]=\"newDescription()\"\n                (valueChange)=\"newDescription.set($event)\"\n                [rows]=\"2\"\n                placeholder=\"What does this server expose?\"\n                class=\"text-xs\"\n              />\n            </div>\n          </div>\n          <div ui-dialog-footer class=\"gap-2 sm:gap-0\">\n            <button ui-button variant=\"ghost\" size=\"sm\" (click)=\"isInstallDialogOpen.set(false)\">Cancel</button>\n            <button\n              ui-button\n              size=\"sm\"\n              [disabled]=\"!newServerName().trim() || !newCommandOrUrl().trim()\"\n              (click)=\"handleAddServer()\"\n            >\n              <lucide-icon [img]=\"Plus\" class=\"size-3.5\" />\n              Install Server\n            </button>\n          </div>\n        </ui-dialog-content>\n      </ui-dialog>\n    </div>\n  `,\n})\nexport class UiMcpServerRegistryManagerComponent {\n  protected readonly Activity = Activity\n  protected readonly Boxes = Boxes\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly Copy = Copy\n  protected readonly Link2 = Link2\n  protected readonly Play = Play\n  protected readonly Plus = Plus\n  protected readonly RefreshCw = RefreshCw\n  protected readonly Search = Search\n  protected readonly Server = Server\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly Trash2 = Trash2\n  protected readonly Wrench = Wrench\n  protected readonly X = X\n  protected readonly Zap = Zap\n\n  @Input() set initialSearch(v: string | undefined) {\n    this.searchQuery.set(v ?? '')\n  }\n  @Input() set initialTransportFilter(v: 'all' | 'stdio' | 'sse') {\n    this.transportFilter.set(v ?? 'all')\n  }\n  @Input() set initialStatusFilter(v: 'all' | 'healthy' | 'reconnecting') {\n    this.statusFilter.set(v ?? 'all')\n  }\n  @Input() set initialSelectedToolName(v: string | undefined) {\n    if (!v) return\n    for (const s of this.servers()) {\n      const t = s.tools.find((tool) => tool.name === v)\n      if (t) {\n        this.selectedServer.set(s)\n        this.selectedTool.set(t)\n        break\n      }\n    }\n  }\n  @Input() set initialDrawerOpen(v: boolean) {\n    this.isDrawerOpen.set(!!v)\n  }\n  @Input() set initialInstallDialogOpen(v: boolean) {\n    this.isInstallDialogOpen.set(!!v)\n  }\n  @Input() set initialHandshakeAlert(v: boolean) {\n    this.handshakeAlertVisible.set(!!v)\n  }\n  @Input() set initialServers(v: McpServer[] | undefined) {\n    if (v) {\n      this.servers.set([...v])\n      this.selectedServer.set(v[0] ?? DEFAULT_MCP_SERVERS[0]!)\n      const firstTool = v[0]?.tools[0] ?? DEFAULT_MCP_SERVERS[0]!.tools[0]!\n      this.selectedTool.set(firstTool)\n    }\n  }\n  @Input('class') className?: string\n\n  readonly servers = signal<McpServer[]>(DEFAULT_MCP_SERVERS.map((s) => ({ ...s })))\n  readonly searchQuery = signal('')\n  readonly transportFilter = signal<'all' | 'stdio' | 'sse'>('all')\n  readonly statusFilter = signal<'all' | 'healthy' | 'reconnecting'>('all')\n  readonly isDrawerOpen = signal(false)\n  readonly isInstallDialogOpen = signal(false)\n  readonly handshakeAlertVisible = signal(false)\n  readonly isHandshaking = signal(false)\n  readonly copiedKey = signal<string | null>(null)\n  readonly inspectorTab = signal<'request' | 'response' | 'schema'>('request')\n  readonly activeViewTab = signal<'servers' | 'handshake-logs' | 'config-json'>('servers')\n\n  readonly selectedServer = signal<McpServer>(DEFAULT_MCP_SERVERS[0]!)\n  readonly selectedTool = signal<ToolSchema>(DEFAULT_MCP_SERVERS[0]!.tools[0]!)\n\n  readonly isDryRunning = signal(false)\n  readonly dryRunOutput = signal<string | null>(null)\n\n  readonly newServerName = signal('')\n  readonly newServerScope = signal('')\n  readonly newTransport = signal<TransportType>('stdio')\n  readonly newCommandOrUrl = signal('')\n  readonly newDescription = signal('')\n\n  readonly filteredServers = computed(() => {\n    const q = this.searchQuery().trim().toLowerCase()\n    return this.servers().filter((s) => {\n      const matchTransport = this.transportFilter() === 'all' || s.transport === this.transportFilter()\n      const matchStatus = this.statusFilter() === 'all' || s.status === this.statusFilter()\n      if (!matchTransport || !matchStatus) return false\n      if (!q) return true\n      const inName = s.name.toLowerCase().includes(q)\n      const inScope = s.scope.toLowerCase().includes(q)\n      const inDesc = s.description.toLowerCase().includes(q)\n      const inCmd = s.commandOrUrl.toLowerCase().includes(q)\n      const inTools = s.tools.some((t) => t.name.toLowerCase().includes(q) || t.displayName.toLowerCase().includes(q))\n      const inUris = s.resourceTemplates.some((r) => r.uriTemplate.toLowerCase().includes(q))\n      return inName || inScope || inDesc || inCmd || inTools || inUris\n    })\n  })\n\n  readonly totalConnectedServers = computed(() => this.servers().filter((s) => s.status === 'healthy').length)\n  readonly totalRegisteredTools = computed(() => this.servers().reduce((acc, s) => acc + s.tools.length, 0))\n  readonly totalResourceTemplates = computed(() =>\n    this.servers().reduce((acc, s) => acc + s.resourceTemplates.length, 0),\n  )\n  readonly totalInvocationsToday = computed(() => this.servers().reduce((acc, s) => acc + s.callsToday, 0))\n\n  readonly toolRequestJson = computed(() => JSON.stringify(this.selectedTool().sampleRpcRequest, null, 2))\n  readonly toolResponseJson = computed(() => JSON.stringify(this.selectedTool().sampleRpcResponse, null, 2))\n\n  readonly activeMcpConfigJson = computed(() => {\n    const config: { mcpServers: Record<string, unknown> } = { mcpServers: {} }\n    for (const s of this.servers()) {\n      const key = s.name.replace(/^mcp\\//, '')\n      if (s.transport === 'stdio') {\n        const parts = s.commandOrUrl.split(' ')\n        config.mcpServers[key] = {\n          command: parts[0],\n          args: parts.slice(1),\n          env: s.envVars.reduce((acc: Record<string, string>, ev) => {\n            const [k, v] = ev.split('=')\n            if (k && v) acc[k] = v\n            return acc\n          }, {}),\n        }\n      } else {\n        config.mcpServers[key] = { transport: 'sse', url: s.commandOrUrl }\n      }\n    }\n    return JSON.stringify(config, null, 2)\n  })\n\n  get rootClass(): string {\n    return cn(\n      'bg-background text-foreground border-border w-full space-y-6 overflow-hidden rounded-xl border p-4 shadow-xs sm:p-6 md:p-8',\n      this.className,\n    )\n  }\n\n  get handshakeIconClass(): string {\n    return cn('size-3.5', this.isHandshaking() && 'text-primary animate-spin')\n  }\n\n  transportBtnClass(t: 'all' | 'stdio' | 'sse'): string {\n    return cn(\n      'rounded-md px-2.5 py-1 font-medium transition-colors',\n      this.transportFilter() === t\n        ? 'bg-background text-foreground shadow-xs'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  installTransportClass(t: TransportType): string {\n    return cn(\n      'rounded-md border px-2.5 py-1 text-xs font-medium transition-colors',\n      this.newTransport() === t\n        ? 'border-primary bg-primary/10 text-primary'\n        : 'border-border text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  setActiveViewTab(v: string): void {\n    this.activeViewTab.set(v as 'servers' | 'handshake-logs' | 'config-json')\n  }\n\n  setInspectorTab(v: string): void {\n    this.inspectorTab.set(v as 'request' | 'response' | 'schema')\n  }\n\n  resetFilters(): void {\n    this.searchQuery.set('')\n    this.transportFilter.set('all')\n    this.statusFilter.set('all')\n  }\n\n  openToolInspector(server: McpServer, tool: ToolSchema): void {\n    this.selectedServer.set(server)\n    this.selectedTool.set(tool)\n    this.dryRunOutput.set(null)\n    this.isDrawerOpen.set(true)\n  }\n\n  openServerInspector(server: McpServer): void {\n    if (server.tools.length > 0) this.openToolInspector(server, server.tools[0]!)\n  }\n\n  copyToClipboard(text: string, key: string): void {\n    if (typeof navigator !== 'undefined' && navigator.clipboard) {\n      navigator.clipboard.writeText(text)\n      this.copiedKey.set(key)\n      setTimeout(() => {\n        if (this.copiedKey() === key) this.copiedKey.set(null)\n      }, 2000)\n    }\n  }\n\n  runHandshakeTest(): void {\n    this.isHandshaking.set(true)\n    this.handshakeAlertVisible.set(false)\n    setTimeout(() => {\n      this.isHandshaking.set(false)\n      this.handshakeAlertVisible.set(true)\n    }, 650)\n  }\n\n  handleAddServer(): void {\n    if (!this.newServerName().trim() || !this.newCommandOrUrl().trim()) return\n    const base = this.newServerName()\n      .trim()\n      .replace(/^mcp\\//, '')\n    const newServer: McpServer = {\n      id: `server-${Date.now()}`,\n      name: this.newServerName().trim(),\n      scope: this.newServerScope().trim() || `@custom/${base}`,\n      version: 'v1.0.0',\n      transport: this.newTransport(),\n      commandOrUrl: this.newCommandOrUrl().trim(),\n      status: 'healthy',\n      uptime: '100.0%',\n      latencyMs: this.newTransport() === 'stdio' ? 22 : 64,\n      callsToday: 0,\n      description: this.newDescription().trim() || 'Custom registered Model Context Protocol runtime server.',\n      envVars: ['MCP_ENABLED=true'],\n      resourceTemplates: [\n        {\n          uriTemplate: `custom://${base}/{resourceId}`,\n          name: 'Custom Parameterized Resource',\n          description: 'Dynamic schema endpoint for custom tool payloads',\n          mimeType: 'application/json',\n        },\n      ],\n      tools: [\n        {\n          name: `${base.replace(/[^a-zA-Z0-9_]/g, '_')}_action`,\n          displayName: 'Execute Custom Action',\n          description: 'Default action handler registered for this server.',\n          category: 'deployment',\n          parameters: [{ name: 'payload', type: 'string', required: true, description: 'Action parameter payload.' }],\n          sampleRpcRequest: {\n            jsonrpc: '2.0',\n            id: 'req-custom-001',\n            method: 'tools/call',\n            params: {\n              name: `${base.replace(/[^a-zA-Z0-9_]/g, '_')}_action`,\n              arguments: { payload: 'execute' },\n            },\n          },\n          sampleRpcResponse: {\n            jsonrpc: '2.0',\n            id: 'req-custom-001',\n            result: rpcResult('{\"status\":\"success\",\"executedAt\":\"2026-08-21T12:00:00Z\"}'),\n          },\n        },\n      ],\n    }\n    this.servers.update((prev) => [newServer, ...prev])\n    this.newServerName.set('')\n    this.newServerScope.set('')\n    this.newCommandOrUrl.set('')\n    this.newDescription.set('')\n    this.isInstallDialogOpen.set(false)\n  }\n\n  removeServer(serverId: string): void {\n    this.servers.update((prev) => prev.filter((s) => s.id !== serverId))\n  }\n\n  restartServer(server: McpServer): void {\n    this.servers.update((prev) =>\n      prev.map((s) =>\n        s.id === server.id\n          ? { ...s, status: 'healthy' as ServerStatus, latencyMs: Math.floor(Math.random() * 20) + 15 }\n          : s,\n      ),\n    )\n  }\n\n  runDryRunTest(): void {\n    const tool = this.selectedTool()\n    if (!tool) return\n    this.isDryRunning.set(true)\n    setTimeout(() => {\n      this.isDryRunning.set(false)\n      this.dryRunOutput.set(JSON.stringify(tool.sampleRpcResponse.result, null, 2))\n    }, 450)\n  }\n}\n",
      "type": "registry:page",
      "target": "~/src/app/components/blocks/mcp-server-registry-manager/mcp-server-registry-manager.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/mcp-server-registry-manager/index.ts",
      "content": "export {\n  UiMcpServerRegistryManagerComponent,\n  DEFAULT_MCP_SERVERS,\n  type McpServer,\n  type ResourceTemplate,\n  type ServerStatus,\n  type ToolParam,\n  type ToolSchema,\n  type TransportType,\n} from './mcp-server-registry-manager.component'\n",
      "type": "registry:page",
      "target": "~/src/app/components/blocks/mcp-server-registry-manager/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/dialog.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/sheet.json",
    "https://uipkge.dev/r/angular/table.json",
    "https://uipkge.dev/r/angular/tabs.json",
    "https://uipkge.dev/r/angular/textarea.json"
  ],
  "description": "Anthropic Model Context Protocol (MCP) server registry manager with connection health monitoring, stdio/SSE stream transport metrics, live tool schema explorer, parameterized resource URI templates, and JSON-RPC 2.0 payload inspection drawer.",
  "categories": [
    "ai",
    "app",
    "devops",
    "dashboard"
  ]
}