@cyanheads/mcp-ts-core 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agent Protocol
2
2
 
3
- **Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.8.1
3
+ **Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.8.2
4
4
  **npm:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) · **Docker:** [ghcr.io/cyanheads/mcp-ts-core](https://ghcr.io/cyanheads/mcp-ts-core)
5
5
 
6
6
  > **Developer note:** Never assume. Read related files and docs before making changes. Read full file content for context. Never edit a file before reading it.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  <div align="center">
7
7
 
8
- [![Version](https://img.shields.io/badge/Version-0.8.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE)
8
+ [![Version](https://img.shields.io/badge/Version-0.8.2-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE)
9
9
 
10
10
  [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.2-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
@@ -0,0 +1,18 @@
1
+ ---
2
+ summary: "Landing page tools section grouped by mutability with chip + search filter; status strip gains repo link; add-app-tool host-theming guidance; maintenance v2.0 hard-rule on framework adoption"
3
+ breaking: false
4
+ ---
5
+
6
+ # 0.8.2 — 2026-04-28
7
+
8
+ Landing-page UX pass plus skill tightening. No runtime API changes.
9
+
10
+ ## Changed
11
+
12
+ - **Tools section** (`src/mcp-server/transports/http/landing-page/sections/tools.ts`) — replaces prefix-based grouping with mutability buckets (`read` / `write` / `destructive`) using `tool.annotations.{readOnlyHint, destructiveHint}`. Annotation-less tools bucket as `write` (matches the existing `pill-destructive === true` rule). Cards now carry `data-tool-card`, `data-mutability`, `data-name`, `data-search` for the filter script. Auth display reduced to the trailing access-level chip (`tool:foo:read` → `read`); full scope list still in the `title` tooltip.
13
+ - **Tool filter bar** — new chip row (`all` + populated buckets) with `aria-pressed` toggling, plus a `<input type="search">` indexed against `data-search` (lowercased `name + description`). Empty state renders `No tools match the current filter.` Filter logic lives in the inlined script — no framework, no transitions, ships under 2 KB.
14
+ - **Status strip** (`sections/status-strip.ts`) — adds a dim `github` repo link when `manifest.landing.repoRoot` is set. Same visual weight as the count + protocol items; the prominent "Source" footer entry stays for scrollers.
15
+ - **Inlined client script** (`assets/copy-script.ts`) — wraps copy-to-clipboard + tool filtering in a single IIFE. Filter handler bails early when `[data-tools-section]` or `[data-tool-card]` is absent, so the empty server still ships clean.
16
+ - **Styles** (`assets/styles.ts`) — adds CSS for the filter bar, mutability-tinted chips, group-count pills, and the `tools-empty` placeholder. Mobile breakpoint preserved.
17
+ - **`add-app-tool` skill** — sharper "ship self-contained HTML" guidance (CSP / supply-chain / offline footguns enumerated). New "Adopt the host's visual identity" section documenting `applyDocumentTheme` / `applyHostStyleVariables` / `applyHostFonts` and the pre-connect `prefers-color-scheme` baseline pattern that prevents first-paint flashes.
18
+ - **`maintenance` skill v1.9 → v2.0** — framework-adoption default hardened from "default adopt" to "auto-adopt every applicable site, in this pass." Adds an explicit valid/invalid deferrals table and forbids scope/effort/marginal-benefit deferrals in the "Open decisions" section. Cost/benefit reasoning now applies to third-party adoptions only.
@@ -1,4 +1,4 @@
1
- {"level":50,"time":1777415595855,"env":"testing","version":"0.0.0-test","pid":99019,"requestId":"63RGD-YP65A","timestamp":"2026-04-28T22:33:15.853Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"5a13b0091a5f90f81e77c9eb13f93c113b034065cf9fae82c794997d56c9cf18","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"5a13b0091a5f90f81e77c9eb13f93c113b034065cf9fae82c794997d56c9cf18","toolName":"scoped_echo","requestId":"63RGD-YP65A","timestamp":"2026-04-28T22:33:15.853Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:72:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:107:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
- {"level":50,"time":1777415596525,"env":"testing","version":"0.8.1","pid":99023,"requestId":"SJ7A2-YIFMY","timestamp":"2026-04-28T22:33:16.524Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"SJ7A2-YIFMY","timestamp":"2026-04-28T22:33:16.524Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
- {"level":50,"time":1777415596539,"env":"testing","version":"0.8.1","pid":99023,"requestId":"OO6W9-ZL8EV","timestamp":"2026-04-28T22:33:16.539Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"OO6W9-ZL8EV","timestamp":"2026-04-28T22:33:16.539Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
- {"level":50,"time":1777415596544,"env":"testing","version":"0.8.1","pid":99023,"requestId":"3NQXB-GTIJC","timestamp":"2026-04-28T22:33:16.544Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"3NQXB-GTIJC","timestamp":"2026-04-28T22:33:16.544Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
1
+ {"level":50,"time":1777420386266,"env":"testing","version":"0.0.0-test","pid":93036,"requestId":"9SR3Y-96Q5Q","timestamp":"2026-04-28T23:53:06.265Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"b6d7f21343ef3d0098ee5154572d21e4f3453c49f591d9a06424ae7d25e95865","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"b6d7f21343ef3d0098ee5154572d21e4f3453c49f591d9a06424ae7d25e95865","toolName":"scoped_echo","requestId":"9SR3Y-96Q5Q","timestamp":"2026-04-28T23:53:06.265Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:72:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:107:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
+ {"level":50,"time":1777420387550,"env":"testing","version":"0.8.2","pid":93075,"requestId":"EOXXJ-UGLBI","timestamp":"2026-04-28T23:53:07.549Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"EOXXJ-UGLBI","timestamp":"2026-04-28T23:53:07.549Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
+ {"level":50,"time":1777420387567,"env":"testing","version":"0.8.2","pid":93075,"requestId":"H0RA2-3FZ2J","timestamp":"2026-04-28T23:53:07.567Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"H0RA2-3FZ2J","timestamp":"2026-04-28T23:53:07.567Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
+ {"level":50,"time":1777420387570,"env":"testing","version":"0.8.2","pid":93075,"requestId":"GUJJZ-AY2OQ","timestamp":"2026-04-28T23:53:07.570Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"GUJJZ-AY2OQ","timestamp":"2026-04-28T23:53:07.570Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
@@ -1,4 +1,4 @@
1
- {"level":50,"time":1777415595855,"env":"testing","version":"0.0.0-test","pid":99019,"requestId":"63RGD-YP65A","timestamp":"2026-04-28T22:33:15.853Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"5a13b0091a5f90f81e77c9eb13f93c113b034065cf9fae82c794997d56c9cf18","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"5a13b0091a5f90f81e77c9eb13f93c113b034065cf9fae82c794997d56c9cf18","toolName":"scoped_echo","requestId":"63RGD-YP65A","timestamp":"2026-04-28T22:33:15.853Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:72:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:107:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
- {"level":50,"time":1777415596525,"env":"testing","version":"0.8.1","pid":99023,"requestId":"SJ7A2-YIFMY","timestamp":"2026-04-28T22:33:16.524Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"SJ7A2-YIFMY","timestamp":"2026-04-28T22:33:16.524Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
- {"level":50,"time":1777415596539,"env":"testing","version":"0.8.1","pid":99023,"requestId":"OO6W9-ZL8EV","timestamp":"2026-04-28T22:33:16.539Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"OO6W9-ZL8EV","timestamp":"2026-04-28T22:33:16.539Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
- {"level":50,"time":1777415596544,"env":"testing","version":"0.8.1","pid":99023,"requestId":"3NQXB-GTIJC","timestamp":"2026-04-28T22:33:16.544Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"3NQXB-GTIJC","timestamp":"2026-04-28T22:33:16.544Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
1
+ {"level":50,"time":1777420386266,"env":"testing","version":"0.0.0-test","pid":93036,"requestId":"9SR3Y-96Q5Q","timestamp":"2026-04-28T23:53:06.265Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"b6d7f21343ef3d0098ee5154572d21e4f3453c49f591d9a06424ae7d25e95865","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"b6d7f21343ef3d0098ee5154572d21e4f3453c49f591d9a06424ae7d25e95865","toolName":"scoped_echo","requestId":"9SR3Y-96Q5Q","timestamp":"2026-04-28T23:53:06.265Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:72:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:107:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
+ {"level":50,"time":1777420387550,"env":"testing","version":"0.8.2","pid":93075,"requestId":"EOXXJ-UGLBI","timestamp":"2026-04-28T23:53:07.549Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"EOXXJ-UGLBI","timestamp":"2026-04-28T23:53:07.549Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
+ {"level":50,"time":1777420387567,"env":"testing","version":"0.8.2","pid":93075,"requestId":"H0RA2-3FZ2J","timestamp":"2026-04-28T23:53:07.567Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"H0RA2-3FZ2J","timestamp":"2026-04-28T23:53:07.567Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
+ {"level":50,"time":1777420387570,"env":"testing","version":"0.8.2","pid":93075,"requestId":"GUJJZ-AY2OQ","timestamp":"2026-04-28T23:53:07.570Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"GUJJZ-AY2OQ","timestamp":"2026-04-28T23:53:07.570Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
@@ -1,8 +1,17 @@
1
1
  /**
2
- * @fileoverview Inlined copy-to-clipboard script. Single delegated click
3
- * handler triggered by any element carrying `[data-copy]`; the copy target is
4
- * either a CSS selector (`[data-copy-target]`) or the literal `data-copy`
5
- * value. Renders under 1 KB so it ships inline with the page.
2
+ * @fileoverview Inlined client-side scripts for the landing page. Two
3
+ * delegated handlers, both keyed by data attributes:
4
+ *
5
+ * - Copy-to-clipboard: any element carrying `[data-copy]` triggers a copy
6
+ * of the target's `textContent` (CSS selector via `[data-copy-target]`)
7
+ * or the literal `data-copy` value.
8
+ * - Tool filtering: chip clicks toggle a mutability filter, the search
9
+ * input filters by `data-name` + indexed `data-search` substring, and
10
+ * cards/groups update via `hidden`. No framework, no transitions,
11
+ * fully accessible (chips use `aria-pressed`).
12
+ *
13
+ * The whole bundle stays well under 2 KB so it ships inline alongside the
14
+ * page HTML.
6
15
  *
7
16
  * @module src/mcp-server/transports/http/landing-page/assets/copy-script
8
17
  */
@@ -1 +1 @@
1
- {"version":3,"file":"copy-script.d.ts","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/assets/copy-script.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,QAAQ,EAAa,MAAM,4BAA4B,CAAC;AAEtE,wBAAgB,gBAAgB,IAAI,QAAQ,CAyB3C"}
1
+ {"version":3,"file":"copy-script.d.ts","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/assets/copy-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,KAAK,QAAQ,EAAa,MAAM,4BAA4B,CAAC;AAEtE,wBAAgB,gBAAgB,IAAI,QAAQ,CA0F3C"}
@@ -1,36 +1,110 @@
1
1
  /**
2
- * @fileoverview Inlined copy-to-clipboard script. Single delegated click
3
- * handler triggered by any element carrying `[data-copy]`; the copy target is
4
- * either a CSS selector (`[data-copy-target]`) or the literal `data-copy`
5
- * value. Renders under 1 KB so it ships inline with the page.
2
+ * @fileoverview Inlined client-side scripts for the landing page. Two
3
+ * delegated handlers, both keyed by data attributes:
4
+ *
5
+ * - Copy-to-clipboard: any element carrying `[data-copy]` triggers a copy
6
+ * of the target's `textContent` (CSS selector via `[data-copy-target]`)
7
+ * or the literal `data-copy` value.
8
+ * - Tool filtering: chip clicks toggle a mutability filter, the search
9
+ * input filters by `data-name` + indexed `data-search` substring, and
10
+ * cards/groups update via `hidden`. No framework, no transitions,
11
+ * fully accessible (chips use `aria-pressed`).
12
+ *
13
+ * The whole bundle stays well under 2 KB so it ships inline alongside the
14
+ * page HTML.
6
15
  *
7
16
  * @module src/mcp-server/transports/http/landing-page/assets/copy-script
8
17
  */
9
18
  import { unsafeRaw } from '../../../../../utils/formatting/html.js';
10
19
  export function renderCopyScript() {
11
20
  const js = `
12
- document.addEventListener('click', function(e) {
13
- var btn = e.target.closest('[data-copy]');
14
- if (!btn) return;
15
- var selector = btn.getAttribute('data-copy-target');
16
- var text = '';
17
- if (selector) {
18
- var node = document.querySelector(selector);
19
- if (node) text = node.textContent || '';
20
- } else {
21
- text = btn.getAttribute('data-copy') || '';
22
- }
23
- if (!text || !navigator.clipboard) return;
24
- navigator.clipboard.writeText(text).then(function() {
25
- var prev = btn.textContent;
26
- btn.setAttribute('data-copied', 'true');
27
- btn.textContent = 'Copied';
28
- setTimeout(function() {
29
- btn.removeAttribute('data-copied');
30
- btn.textContent = prev;
31
- }, 1500);
21
+ (function() {
22
+ // ---------- Copy to clipboard ----------
23
+ document.addEventListener('click', function(e) {
24
+ var btn = e.target.closest('[data-copy]');
25
+ if (!btn) return;
26
+ var selector = btn.getAttribute('data-copy-target');
27
+ var text = '';
28
+ if (selector) {
29
+ var node = document.querySelector(selector);
30
+ if (node) text = node.textContent || '';
31
+ } else {
32
+ text = btn.getAttribute('data-copy') || '';
33
+ }
34
+ if (!text || !navigator.clipboard) return;
35
+ navigator.clipboard.writeText(text).then(function() {
36
+ var prev = btn.textContent;
37
+ btn.setAttribute('data-copied', 'true');
38
+ btn.textContent = 'Copied';
39
+ setTimeout(function() {
40
+ btn.removeAttribute('data-copied');
41
+ btn.textContent = prev;
42
+ }, 1500);
43
+ });
32
44
  });
33
- });`;
45
+
46
+ // ---------- Tool filter + search ----------
47
+ var section = document.querySelector('[data-tools-section]');
48
+ if (!section) return;
49
+ var cards = Array.prototype.slice.call(section.querySelectorAll('[data-tool-card]'));
50
+ if (cards.length === 0) return;
51
+ var chips = Array.prototype.slice.call(section.querySelectorAll('[data-filter-mutability]'));
52
+ var searchInput = section.querySelector('[data-tool-search]');
53
+ var groups = Array.prototype.slice.call(section.querySelectorAll('[data-group]'));
54
+ var grids = Array.prototype.slice.call(section.querySelectorAll('[data-grid]'));
55
+ var emptyState = section.querySelector('.tools-empty');
56
+
57
+ var activeMutability = 'all';
58
+ var query = '';
59
+
60
+ function apply() {
61
+ var visibleCount = 0;
62
+ var perGroup = Object.create(null);
63
+ for (var i = 0; i < cards.length; i++) {
64
+ var card = cards[i];
65
+ var m = card.getAttribute('data-mutability') || '';
66
+ var search = card.getAttribute('data-search') || '';
67
+ var matchesMutability = activeMutability === 'all' || m === activeMutability;
68
+ var matchesSearch = query === '' || search.indexOf(query) !== -1;
69
+ var visible = matchesMutability && matchesSearch;
70
+ card.hidden = !visible;
71
+ if (visible) {
72
+ visibleCount++;
73
+ perGroup[m] = (perGroup[m] || 0) + 1;
74
+ }
75
+ }
76
+ // Hide group headings + grids whose buckets are now empty.
77
+ for (var g = 0; g < groups.length; g++) {
78
+ var key = groups[g].getAttribute('data-group');
79
+ groups[g].hidden = !perGroup[key];
80
+ }
81
+ for (var k = 0; k < grids.length; k++) {
82
+ var gk = grids[k].getAttribute('data-grid');
83
+ grids[k].hidden = !perGroup[gk];
84
+ }
85
+ if (emptyState) emptyState.hidden = visibleCount > 0;
86
+ }
87
+
88
+ for (var c = 0; c < chips.length; c++) {
89
+ chips[c].addEventListener('click', function(ev) {
90
+ var target = ev.currentTarget;
91
+ var value = target.getAttribute('data-filter-mutability') || 'all';
92
+ activeMutability = value;
93
+ for (var j = 0; j < chips.length; j++) {
94
+ var pressed = chips[j] === target;
95
+ chips[j].setAttribute('aria-pressed', pressed ? 'true' : 'false');
96
+ }
97
+ apply();
98
+ });
99
+ }
100
+
101
+ if (searchInput) {
102
+ searchInput.addEventListener('input', function(ev) {
103
+ query = (ev.currentTarget.value || '').trim().toLowerCase();
104
+ apply();
105
+ });
106
+ }
107
+ })();`;
34
108
  return unsafeRaw(`<script>${js}</script>`);
35
109
  }
36
110
  //# sourceMappingURL=copy-script.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"copy-script.js","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/assets/copy-script.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAiB,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAEtE,MAAM,UAAU,gBAAgB;IAC9B,MAAM,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;IAsBT,CAAC;IACH,OAAO,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAC7C,CAAC"}
1
+ {"version":3,"file":"copy-script.js","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/assets/copy-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAiB,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAEtE,MAAM,UAAU,gBAAgB;IAC9B,MAAM,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAuFP,CAAC;IACL,OAAO,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAC7C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/assets/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,KAAK,QAAQ,EAAa,MAAM,4BAA4B,CAAC;AAKtE;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAi4BrD"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/assets/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,KAAK,QAAQ,EAAa,MAAM,4BAA4B,CAAC;AAKtE;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAurCrD"}
@@ -196,6 +196,23 @@ pre code { background: transparent; padding: 0; border: 0; font-size: inherit; }
196
196
  gap: var(--space-6);
197
197
  }
198
198
 
199
+ /* Page-load choreography — staggered rise on the hero stack. The
200
+ "backwards" animation-fill-mode applies the from-state during the
201
+ delay so the page never flashes the final layout before animating.
202
+ Reduced-motion override at the bottom of this stylesheet collapses
203
+ the animation. */
204
+ @keyframes hero-rise {
205
+ from { opacity: 0; transform: translateY(8px); }
206
+ to { opacity: 1; transform: none; }
207
+ }
208
+ .hero > * { animation: hero-rise 600ms var(--ease-out) backwards; }
209
+ .hero > .hero-eyebrow { animation-delay: 60ms; }
210
+ .hero > .hero-title-row { animation-delay: 140ms; }
211
+ .hero > .hero-tagline { animation-delay: 220ms; }
212
+ .hero > .status-strip { animation-delay: 300ms; }
213
+ .hero > .connect { animation-delay: 380ms; }
214
+ .hero > .hero-badges { animation-delay: 460ms; }
215
+
199
216
  .hero-eyebrow {
200
217
  display: inline-flex;
201
218
  align-items: center;
@@ -270,6 +287,7 @@ pre code { background: transparent; padding: 0; border: 0; font-size: inherit; }
270
287
  font-size: 0.75rem;
271
288
  font-weight: 600;
272
289
  letter-spacing: -0.01em;
290
+ font-variant-numeric: tabular-nums;
273
291
  color: var(--accent);
274
292
  background: var(--accent-softer);
275
293
  border: 1px solid var(--accent-edge);
@@ -344,6 +362,7 @@ pre code { background: transparent; padding: 0; border: 0; font-size: inherit; }
344
362
  .status-value {
345
363
  color: var(--fg);
346
364
  font-weight: 500;
365
+ font-variant-numeric: tabular-nums;
347
366
  }
348
367
  .status-value-accent {
349
368
  color: var(--accent);
@@ -444,7 +463,14 @@ pre code { background: transparent; padding: 0; border: 0; font-size: inherit; }
444
463
  border-radius: 50%;
445
464
  background: color-mix(in oklab, var(--fg-subtle), transparent 60%);
446
465
  display: inline-block;
447
- }
466
+ transition: background var(--duration-base) var(--ease-out);
467
+ }
468
+ /* Hovering the dot cluster colors them like real OS window controls.
469
+ Pure delight — fits the terminal-chrome metaphor without adding
470
+ anything to the meaningful surface. */
471
+ .connect-chrome-dots:hover .connect-chrome-dot:nth-child(1) { background: #ff5f57; }
472
+ .connect-chrome-dots:hover .connect-chrome-dot:nth-child(2) { background: #febc2e; }
473
+ .connect-chrome-dots:hover .connect-chrome-dot:nth-child(3) { background: #28c840; }
448
474
  .connect-chrome-endpoint {
449
475
  margin-left: auto;
450
476
  font-family: var(--font-mono);
@@ -622,10 +648,164 @@ section { padding: var(--space-12) 0 0; }
622
648
  font-family: var(--font-mono);
623
649
  font-size: 0.6875rem;
624
650
  font-weight: 600;
625
- text-transform: uppercase;
626
- letter-spacing: 0.12em;
651
+ text-transform: lowercase;
652
+ letter-spacing: 0.06em;
653
+ display: flex;
654
+ align-items: baseline;
655
+ gap: var(--space-2);
656
+ position: relative;
657
+ padding-left: var(--space-3);
658
+ }
659
+ .group-heading::before {
660
+ content: "";
661
+ position: absolute;
662
+ left: 0;
663
+ top: 50%;
664
+ transform: translateY(-50%);
665
+ width: 2px;
666
+ height: 0.85em;
667
+ background: var(--accent);
668
+ border-radius: 1px;
627
669
  }
628
670
  .group-heading:first-child { margin-top: 0; }
671
+ .group-heading[data-group="read"]::before { background: #16a34a; }
672
+ .group-heading[data-group="destructive"]::before { background: #dc2626; }
673
+ .group-count {
674
+ color: var(--fg-subtle);
675
+ font-weight: 500;
676
+ font-variant-numeric: tabular-nums;
677
+ }
678
+
679
+ /* -------------------- Tool filter bar -------------------- */
680
+
681
+ .tool-filter-bar {
682
+ display: flex;
683
+ flex-wrap: wrap;
684
+ align-items: center;
685
+ gap: var(--space-3) var(--space-4);
686
+ margin-bottom: var(--space-5);
687
+ }
688
+ .tool-chips {
689
+ display: inline-flex;
690
+ flex-wrap: wrap;
691
+ gap: 4px;
692
+ }
693
+ .tool-chip {
694
+ appearance: none;
695
+ border: 1px solid var(--border);
696
+ background: var(--bg-elevated);
697
+ color: var(--fg-muted);
698
+ font-family: var(--font-mono);
699
+ font-size: 0.75rem;
700
+ font-weight: 500;
701
+ padding: 4px 10px;
702
+ border-radius: var(--radius-pill);
703
+ cursor: pointer;
704
+ letter-spacing: 0.01em;
705
+ transition:
706
+ color var(--duration-fast) var(--ease-out),
707
+ border-color var(--duration-fast) var(--ease-out),
708
+ background var(--duration-fast) var(--ease-out);
709
+ }
710
+ .tool-chip:hover { color: var(--fg); border-color: var(--border-strong); }
711
+ .tool-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
712
+ .tool-chip[aria-pressed="true"] {
713
+ color: var(--accent);
714
+ background: var(--accent-softer);
715
+ border-color: var(--accent-edge);
716
+ }
717
+ .tool-chip--read[aria-pressed="true"] {
718
+ color: #16a34a;
719
+ background: color-mix(in oklab, #16a34a, transparent 92%);
720
+ border-color: color-mix(in oklab, #16a34a, transparent 72%);
721
+ }
722
+ .tool-chip--destructive[aria-pressed="true"] {
723
+ color: #dc2626;
724
+ background: color-mix(in oklab, #dc2626, transparent 92%);
725
+ border-color: color-mix(in oklab, #dc2626, transparent 72%);
726
+ }
727
+ @media (prefers-color-scheme: dark) {
728
+ .tool-chip--read[aria-pressed="true"] { color: #4ade80; }
729
+ .tool-chip--destructive[aria-pressed="true"] { color: #f87171; }
730
+ }
731
+
732
+ .tool-search {
733
+ flex: 1 1 200px;
734
+ min-width: 0;
735
+ max-width: 360px;
736
+ margin-left: auto;
737
+ display: flex;
738
+ align-items: center;
739
+ position: relative;
740
+ }
741
+ .tool-search input {
742
+ width: 100%;
743
+ font-family: var(--font-mono);
744
+ font-size: var(--text-sm);
745
+ padding: 6px 12px 6px 30px;
746
+ border: 1px solid var(--border);
747
+ border-radius: var(--radius-sm);
748
+ background: var(--bg-elevated);
749
+ color: var(--fg);
750
+ transition:
751
+ border-color var(--duration-fast) var(--ease-out),
752
+ box-shadow var(--duration-fast) var(--ease-out);
753
+ }
754
+ .tool-search input::placeholder { color: var(--fg-subtle); }
755
+ .tool-search input:focus {
756
+ outline: none;
757
+ border-color: var(--accent);
758
+ box-shadow: 0 0 0 3px var(--accent-softer);
759
+ }
760
+ .tool-search::before {
761
+ /* Search glyph — no icon font, just a styled box. */
762
+ content: "";
763
+ position: absolute;
764
+ left: 10px;
765
+ top: 50%;
766
+ width: 12px;
767
+ height: 12px;
768
+ border: 1.5px solid var(--fg-subtle);
769
+ border-radius: 50%;
770
+ transform: translateY(-50%);
771
+ pointer-events: none;
772
+ }
773
+ .tool-search::after {
774
+ content: "";
775
+ position: absolute;
776
+ left: 19px;
777
+ top: calc(50% + 4px);
778
+ width: 5px;
779
+ height: 1.5px;
780
+ background: var(--fg-subtle);
781
+ transform: rotate(45deg);
782
+ pointer-events: none;
783
+ border-radius: 1px;
784
+ }
785
+
786
+ .visually-hidden {
787
+ position: absolute;
788
+ width: 1px;
789
+ height: 1px;
790
+ padding: 0;
791
+ margin: -1px;
792
+ overflow: hidden;
793
+ clip: rect(0, 0, 0, 0);
794
+ white-space: nowrap;
795
+ border: 0;
796
+ }
797
+
798
+ .tools-empty {
799
+ margin: var(--space-6) 0;
800
+ padding: var(--space-8) var(--space-6);
801
+ text-align: center;
802
+ color: var(--fg-muted);
803
+ border: 1px dashed var(--border);
804
+ border-radius: var(--radius-md);
805
+ background: var(--bg-subtle);
806
+ font-size: var(--text-sm);
807
+ font-family: var(--font-mono);
808
+ }
629
809
 
630
810
  /* -------------------- Cards -------------------- */
631
811
 
@@ -633,7 +813,7 @@ section { padding: var(--space-12) 0 0; }
633
813
  display: grid;
634
814
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
635
815
  gap: var(--space-3);
636
- align-items: start;
816
+ align-items: stretch;
637
817
  }
638
818
  .card {
639
819
  border: 1px solid var(--border-subtle);
@@ -647,6 +827,30 @@ section { padding: var(--space-12) 0 0; }
647
827
  transform var(--duration-fast) var(--ease-out),
648
828
  box-shadow var(--duration-fast) var(--ease-out);
649
829
  position: relative;
830
+ height: 100%;
831
+ }
832
+
833
+ /* Mutability spine — 3px tonal strip down the left edge of each tool
834
+ card. Bird's-eye scannability: a row of cards reads as a row of
835
+ colored bars before the eye lands on any title. */
836
+ .tool-card { padding-left: calc(var(--space-5) + 3px); }
837
+ .tool-card::before {
838
+ content: "";
839
+ position: absolute;
840
+ left: 0;
841
+ top: var(--space-2);
842
+ bottom: var(--space-2);
843
+ width: 3px;
844
+ border-radius: 0 2px 2px 0;
845
+ background: var(--card-spine, var(--border-strong));
846
+ transition: background var(--duration-fast) var(--ease-out);
847
+ }
848
+ .tool-card[data-mutability="read"] { --card-spine: #16a34a; }
849
+ .tool-card[data-mutability="write"] { --card-spine: var(--border-strong); }
850
+ .tool-card[data-mutability="destructive"] { --card-spine: #dc2626; }
851
+ @media (prefers-color-scheme: dark) {
852
+ .tool-card[data-mutability="read"] { --card-spine: #4ade80; }
853
+ .tool-card[data-mutability="destructive"] { --card-spine: #f87171; }
650
854
  }
651
855
  .card:hover {
652
856
  border-color: var(--accent-edge);
@@ -674,6 +878,15 @@ section { padding: var(--space-12) 0 0; }
674
878
  color: var(--fg-muted);
675
879
  font-size: var(--text-sm);
676
880
  line-height: 1.5;
881
+ /* 3-line preview clamp keeps cards uniform; full text stays in DOM
882
+ for screen readers and view-source. Non-WebKit engines fall back
883
+ to the natural height — overflow:hidden truncates to 3 lines via
884
+ max-height as a graceful degradation. */
885
+ display: -webkit-box;
886
+ -webkit-line-clamp: 3;
887
+ -webkit-box-orient: vertical;
888
+ overflow: hidden;
889
+ max-height: calc(1.5em * 3);
677
890
  }
678
891
  .card-meta {
679
892
  display: flex;
@@ -684,7 +897,12 @@ section { padding: var(--space-12) 0 0; }
684
897
  font-family: var(--font-mono);
685
898
  align-items: center;
686
899
  }
687
- .card-meta-label { color: var(--fg-subtle); }
900
+ .card-meta-label {
901
+ color: var(--fg-subtle);
902
+ text-transform: lowercase;
903
+ letter-spacing: 0.04em;
904
+ font-size: 0.65rem;
905
+ }
688
906
  .card-meta code {
689
907
  font-size: 1em;
690
908
  color: var(--fg);
@@ -693,6 +911,96 @@ section { padding: var(--space-12) 0 0; }
693
911
  padding: 0;
694
912
  }
695
913
 
914
+ /* Card footer — pinned to the bottom so cards align across a row even
915
+ when descriptions vary in length. Carries the scope chip on the left
916
+ and the invocation/schema action triggers on the right. */
917
+ .card-foot {
918
+ margin-top: auto;
919
+ padding-top: var(--space-3);
920
+ border-top: 1px solid var(--border-subtle);
921
+ display: flex;
922
+ flex-wrap: wrap;
923
+ align-items: center;
924
+ justify-content: space-between;
925
+ gap: var(--space-2) var(--space-3);
926
+ }
927
+ .card-scope {
928
+ display: inline-flex;
929
+ align-items: center;
930
+ flex-wrap: wrap;
931
+ gap: 4px;
932
+ font-family: var(--font-mono);
933
+ font-size: var(--text-xs);
934
+ color: var(--fg-muted);
935
+ }
936
+ .scope-chip {
937
+ display: inline-flex;
938
+ align-items: center;
939
+ font-family: var(--font-mono);
940
+ font-size: 0.65rem;
941
+ font-weight: 500;
942
+ padding: 1px 6px;
943
+ border-radius: var(--radius-xs);
944
+ background: var(--bg-subtle);
945
+ color: var(--fg);
946
+ border: 1px solid var(--border-subtle);
947
+ letter-spacing: 0.02em;
948
+ }
949
+ .card-actions {
950
+ display: inline-flex;
951
+ align-items: center;
952
+ gap: var(--space-3);
953
+ margin-left: auto;
954
+ }
955
+ .card-detail { margin: 0; }
956
+ .card-detail > summary {
957
+ display: inline-flex;
958
+ align-items: center;
959
+ gap: 3px;
960
+ padding: 0;
961
+ font-family: var(--font-mono);
962
+ font-size: var(--text-xs);
963
+ color: var(--fg-muted);
964
+ cursor: pointer;
965
+ transition: color var(--duration-fast);
966
+ }
967
+ .card-detail > summary::before {
968
+ content: "▸";
969
+ display: inline-block;
970
+ font-size: 0.6rem;
971
+ transition: transform var(--duration-fast);
972
+ color: var(--fg-subtle);
973
+ }
974
+ .card-detail[open] > summary::before {
975
+ transform: rotate(90deg);
976
+ color: var(--accent);
977
+ }
978
+ .card-detail > summary:hover { color: var(--accent); }
979
+ .card-detail[open] > summary { color: var(--accent); }
980
+ /* When a detail opens, push its content full-width below the footer. The
981
+ negative side margins recover the card's horizontal padding so the
982
+ panel spans edge-to-edge. */
983
+ .card-detail[open] {
984
+ width: 100%;
985
+ margin: var(--space-3) calc(var(--space-5) * -1) calc(var(--space-4) * -1);
986
+ padding: 0 var(--space-5) var(--space-4);
987
+ background: var(--bg-subtle);
988
+ border-top: 1px solid var(--border-subtle);
989
+ }
990
+ .card-detail[open] > summary { padding-top: var(--space-3); }
991
+ /* When any detail is open the actions row needs to wrap so the open
992
+ panel can claim full width. The :has() selector handles this in
993
+ modern engines; older browsers tolerate the actions row stacking
994
+ inline-flex without the layout flip. */
995
+ .card-foot:has(.card-detail[open]) {
996
+ flex-direction: column;
997
+ align-items: stretch;
998
+ }
999
+ .card-foot:has(.card-detail[open]) .card-actions {
1000
+ width: 100%;
1001
+ margin-left: 0;
1002
+ }
1003
+
696
1004
  /* Annotation pills — dot-chip style */
697
1005
  .pill-row { display: inline-flex; flex-wrap: wrap; gap: 5px; align-items: center; }
698
1006
  .pill {
@@ -717,8 +1025,10 @@ section { padding: var(--space-12) 0 0; }
717
1025
  background: currentColor;
718
1026
  flex-shrink: 0;
719
1027
  }
720
- .pill-readonly { color: #16a34a; background: color-mix(in oklab, #16a34a, transparent 92%); border-color: color-mix(in oklab, #16a34a, transparent 72%); }
721
- .pill-destructive { color: #dc2626; background: color-mix(in oklab, #dc2626, transparent 92%); border-color: color-mix(in oklab, #dc2626, transparent 72%); }
1028
+ /* Mutability badges primary safety signal, sized to pass a squint test. */
1029
+ .pill-read { color: #16a34a; background: color-mix(in oklab, #16a34a, transparent 88%); border-color: color-mix(in oklab, #16a34a, transparent 65%); }
1030
+ .pill-write { color: var(--fg-muted); background: var(--bg-subtle); border-color: var(--border); }
1031
+ .pill-destructive { color: #dc2626; background: color-mix(in oklab, #dc2626, transparent 88%); border-color: color-mix(in oklab, #dc2626, transparent 65%); font-weight: 600; }
722
1032
  .pill-openworld { color: #2563eb; background: color-mix(in oklab, #2563eb, transparent 92%); border-color: color-mix(in oklab, #2563eb, transparent 72%); }
723
1033
  .pill-task { color: var(--accent); background: var(--accent-softer); border-color: var(--accent-edge); }
724
1034
  .pill-app { color: #9333ea; background: color-mix(in oklab, #9333ea, transparent 92%); border-color: color-mix(in oklab, #9333ea, transparent 72%); }
@@ -726,7 +1036,7 @@ section { padding: var(--space-12) 0 0; }
726
1036
  .pill-auth::before { display: none; }
727
1037
 
728
1038
  @media (prefers-color-scheme: dark) {
729
- .pill-readonly { color: #4ade80; }
1039
+ .pill-read { color: #4ade80; }
730
1040
  .pill-destructive { color: #f87171; }
731
1041
  .pill-openworld { color: #60a5fa; }
732
1042
  .pill-app { color: #c084fc; }
@@ -1 +1 @@
1
- {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/assets/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAiB,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAEtE,2EAA2E;AAC3E,MAAM,cAAc,GAAG,SAAS,CAAC;AAEjC;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC;IACpE,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;cAoBA,UAAU;wCACgB,UAAU;uCACX,UAAU;uCACV,UAAU;yCACR,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAq2BlD,CAAC;IACA,OAAO,SAAS,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC;AAC5C,CAAC"}
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/assets/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAiB,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAEtE,2EAA2E;AAC3E,MAAM,cAAc,GAAG,SAAS,CAAC;AAEjC;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC;IACpE,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;cAoBA,UAAU;wCACgB,UAAU;uCACX,UAAU;uCACV,UAAU;yCACR,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2pClD,CAAC;IACA,OAAO,SAAS,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC;AAC5C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"status-strip.d.ts","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/sections/status-strip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC7E,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAEjE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,GAAG,QAAQ,CAuCvF"}
1
+ {"version":3,"file":"status-strip.d.ts","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/sections/status-strip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC7E,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAEjE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,GAAG,QAAQ,CA2DvF"}
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { html } from '../../../../../utils/formatting/html.js';
16
16
  export function renderStatusStrip(manifest, degraded) {
17
- const { auth, definitionCounts, protocol } = manifest;
17
+ const { auth, definitionCounts, protocol, landing } = manifest;
18
18
  const authMeta = describeAuth(auth);
19
19
  // Counts hidden in degraded mode to avoid leaking inventory shape.
20
20
  const counts = degraded
@@ -27,6 +27,24 @@ export function renderStatusStrip(manifest, degraded) {
27
27
  const signin = auth.mode === 'oauth' && auth.oauthIssuer
28
28
  ? html ` <a class="status-signin" href="${auth.oauthIssuer}" rel="noopener">sign in ↗</a>`
29
29
  : html ``;
30
+ // Repo link — same dim treatment as the count + protocol items so the
31
+ // strip stays information-dense without a flashy CTA. The "Source"
32
+ // footer entry stays for users who scroll to the bottom.
33
+ const repo = landing.repoRoot;
34
+ const repoLink = repo
35
+ ? html `
36
+ <a
37
+ class="status-item status-link"
38
+ href="${repo.url}"
39
+ rel="noopener"
40
+ title="View ${repo.owner}/${repo.repo} on GitHub"
41
+ >
42
+ <span>github</span>
43
+ <span class="status-value">${repo.owner}/${repo.repo}</span>
44
+ <span aria-hidden="true">↗</span>
45
+ </a>
46
+ `
47
+ : html ``;
30
48
  return html `
31
49
  <div class="status-strip" aria-label="${authMeta.ariaLabel}">
32
50
  <span class="status-item" title="${authMeta.ariaLabel}">
@@ -43,6 +61,7 @@ export function renderStatusStrip(manifest, degraded) {
43
61
  <span>protocol</span>
44
62
  <span class="status-value status-value-accent">${protocol.latestVersion}</span>
45
63
  </span>
64
+ ${repoLink}
46
65
  </div>
47
66
  `;
48
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"status-strip.js","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/sections/status-strip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,IAAI,EAAiB,MAAM,4BAA4B,CAAC;AAEjE,MAAM,UAAU,iBAAiB,CAAC,QAAwB,EAAE,QAAiB;IAC3E,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC;IAEtD,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAEpC,mEAAmE;IACnE,MAAM,MAAM,GAAG,QAAQ;QACrB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;YACE,EAAE,CAAC,EAAE,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE;YAC7C,EAAE,CAAC,EAAE,gBAAgB,CAAC,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE;YACrD,EAAE,CAAC,EAAE,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE;SAClD,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAE7B,MAAM,MAAM,GACV,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,WAAW;QACvC,CAAC,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,WAAW,gCAAgC;QACzF,CAAC,CAAC,IAAI,CAAA,EAAE,CAAC;IAEb,OAAO,IAAI,CAAA;4CAC+B,QAAQ,CAAC,SAAS;yCACrB,QAAQ,CAAC,SAAS;kCACzB,QAAQ,CAAC,QAAQ;qCACd,QAAQ,CAAC,KAAK,UAAU,MAAM;;QAE3D,MAAM,CAAC,GAAG,CACV,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAA;8DAC2C,CAAC,CAAC,KAAK;yCAC5B,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;oBAChC,CAAC,CAAC,KAAK;;SAElB,CACF;8DACuD,QAAQ,CAAC,aAAa;;yDAE3B,QAAQ,CAAC,aAAa;;;GAG5E,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,SAAS,YAAY,CAAC,IAAkB;IAKtC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO;YACL,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,mBAAmB;YAC7B,SAAS,EAAE,4CAA4C;SACxD,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACxB,OAAO;YACL,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,kBAAkB;YAC5B,SAAS,EAAE,yBAAyB;SACrC,CAAC;IACJ,CAAC;IACD,OAAO;QACL,KAAK,EAAE,OAAO;QACd,QAAQ,EAAE,kBAAkB;QAC5B,SAAS,EAAE,gBAAgB;KAC5B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"status-strip.js","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/sections/status-strip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,IAAI,EAAiB,MAAM,4BAA4B,CAAC;AAEjE,MAAM,UAAU,iBAAiB,CAAC,QAAwB,EAAE,QAAiB;IAC3E,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC;IAE/D,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAEpC,mEAAmE;IACnE,MAAM,MAAM,GAAG,QAAQ;QACrB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;YACE,EAAE,CAAC,EAAE,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE;YAC7C,EAAE,CAAC,EAAE,gBAAgB,CAAC,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE;YACrD,EAAE,CAAC,EAAE,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE;SAClD,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAE7B,MAAM,MAAM,GACV,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,WAAW;QACvC,CAAC,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,WAAW,gCAAgC;QACzF,CAAC,CAAC,IAAI,CAAA,EAAE,CAAC;IAEb,sEAAsE;IACtE,mEAAmE;IACnE,yDAAyD;IACzD,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;IAC9B,MAAM,QAAQ,GAAG,IAAI;QACnB,CAAC,CAAC,IAAI,CAAA;;;kBAGQ,IAAI,CAAC,GAAG;;wBAEF,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI;;;uCAGR,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI;;;OAGvD;QACH,CAAC,CAAC,IAAI,CAAA,EAAE,CAAC;IAEX,OAAO,IAAI,CAAA;4CAC+B,QAAQ,CAAC,SAAS;yCACrB,QAAQ,CAAC,SAAS;kCACzB,QAAQ,CAAC,QAAQ;qCACd,QAAQ,CAAC,KAAK,UAAU,MAAM;;QAE3D,MAAM,CAAC,GAAG,CACV,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAA;8DAC2C,CAAC,CAAC,KAAK;yCAC5B,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;oBAChC,CAAC,CAAC,KAAK;;SAElB,CACF;8DACuD,QAAQ,CAAC,aAAa;;yDAE3B,QAAQ,CAAC,aAAa;;QAEvE,QAAQ;;GAEb,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,SAAS,YAAY,CAAC,IAAkB;IAKtC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO;YACL,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,mBAAmB;YAC7B,SAAS,EAAE,4CAA4C;SACxD,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACxB,OAAO;YACL,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,kBAAkB;YAC5B,SAAS,EAAE,yBAAyB;SACrC,CAAC;IACJ,CAAC;IACD,OAAO;QACL,KAAK,EAAE,OAAO;QACd,QAAQ,EAAE,kBAAkB;QAC5B,SAAS,EAAE,gBAAgB;KAC5B,CAAC;AACJ,CAAC"}
@@ -1,9 +1,10 @@
1
1
  /**
2
- * @fileoverview Tools section — responsive 2-column card grid, optionally
3
- * prefix-grouped when ≥2 tools share a common `snake_case` prefix. Each card
4
- * carries annotation pills (read-only / destructive / open-world / task /
5
- * app), auth scope chips, a JSON-RPC invocation snippet, and a collapsible
6
- * input-schema preview.
2
+ * @fileoverview Tools section — responsive card grid grouped by safety
3
+ * mutability (read / write / destructive). Each card carries annotation
4
+ * pills, a scope chip, a JSON-RPC invocation snippet, and a collapsible
5
+ * input-schema preview. A filter bar above the grid wires chip + search
6
+ * filtering through `data-mutability` / `data-name` attributes consumed
7
+ * by the inline filter script.
7
8
  *
8
9
  * @module src/mcp-server/transports/http/landing-page/sections/tools
9
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/sections/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAIjE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,QAAQ,CAkBlE"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/sections/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAYjE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,QAAQ,CA0BlE"}
@@ -1,40 +1,73 @@
1
1
  /**
2
- * @fileoverview Tools section — responsive 2-column card grid, optionally
3
- * prefix-grouped when ≥2 tools share a common `snake_case` prefix. Each card
4
- * carries annotation pills (read-only / destructive / open-world / task /
5
- * app), auth scope chips, a JSON-RPC invocation snippet, and a collapsible
6
- * input-schema preview.
2
+ * @fileoverview Tools section — responsive card grid grouped by safety
3
+ * mutability (read / write / destructive). Each card carries annotation
4
+ * pills, a scope chip, a JSON-RPC invocation snippet, and a collapsible
5
+ * input-schema preview. A filter bar above the grid wires chip + search
6
+ * filtering through `data-mutability` / `data-name` attributes consumed
7
+ * by the inline filter script.
7
8
  *
8
9
  * @module src/mcp-server/transports/http/landing-page/sections/tools
9
10
  */
10
11
  import { html } from '../../../../../utils/formatting/html.js';
11
12
  import { renderPill, renderSectionHeading, renderSnippet } from '../primitives.js';
13
+ /**
14
+ * Mutability bucket order — safe defaults first, deliberate engagement last.
15
+ * Filter chips render in this order too.
16
+ */
17
+ const MUTABILITY_ORDER = ['read', 'write', 'destructive'];
12
18
  export function renderToolsSection(tools) {
13
19
  if (tools.length === 0)
14
20
  return html ``;
15
- const groups = groupToolsByPrefix(tools);
16
- // A single group whether labeled or not — would render as redundant with
17
- // the section header. Skip the sub-heading; render a flat grid.
18
- const showHeadings = groups.length > 1;
19
- const body = groups.map((group) => {
20
- const heading = showHeadings && group.label ? html `<h4 class="group-heading">${group.label}</h4>` : html ``;
21
- return html `${heading}<div class="card-grid">${group.tools.map(renderToolCard)}</div>`;
21
+ const buckets = bucketByMutability(tools);
22
+ const populatedBuckets = MUTABILITY_ORDER.filter((m) => buckets[m].length > 0);
23
+ // A single bucket is redundant with the section header skip per-group
24
+ // labels in that case but keep `data-mutability` on cards so the filter
25
+ // chips still work.
26
+ const showHeadings = populatedBuckets.length > 1;
27
+ const groups = populatedBuckets.map((mutability) => {
28
+ const bucketTools = buckets[mutability];
29
+ const heading = showHeadings
30
+ ? html `<h4 class="group-heading" data-group="${mutability}">${mutability} <span class="group-count">${String(bucketTools.length)}</span></h4>`
31
+ : html ``;
32
+ return html `${heading}<div class="card-grid" data-grid="${mutability}">${bucketTools.map((t) => renderToolCard(t, mutability))}</div>`;
22
33
  });
23
34
  return html `
24
- <section aria-labelledby="section-tools">
35
+ <section aria-labelledby="section-tools" data-tools-section>
25
36
  ${renderSectionHeading('section-tools', 'Tools', tools.length)}
26
- ${body}
37
+ ${renderToolFilterBar(populatedBuckets)}
38
+ <div class="tools-body">${groups}</div>
39
+ <p class="tools-empty" hidden>No tools match the current filter.</p>
27
40
  </section>
28
41
  `;
29
42
  }
30
- function renderToolCard(tool) {
43
+ function renderToolFilterBar(populatedBuckets) {
44
+ const chips = [
45
+ html `<button type="button" class="tool-chip" data-filter-mutability="all" aria-pressed="true">all</button>`,
46
+ ];
47
+ for (const m of populatedBuckets) {
48
+ chips.push(html `<button type="button" class="tool-chip tool-chip--${m}" data-filter-mutability="${m}" aria-pressed="false">${m}</button>`);
49
+ }
50
+ return html `
51
+ <div class="tool-filter-bar" role="search" aria-label="Filter tools">
52
+ <div class="tool-chips" role="group" aria-label="Filter by mutability">${chips}</div>
53
+ <label class="tool-search">
54
+ <span class="visually-hidden">Search tools</span>
55
+ <input
56
+ type="search"
57
+ data-tool-search
58
+ placeholder="Search tools…"
59
+ autocomplete="off"
60
+ spellcheck="false"
61
+ />
62
+ </label>
63
+ </div>
64
+ `;
65
+ }
66
+ function renderToolCard(tool, mutability) {
31
67
  const anchor = `tool-${tool.name}`;
32
68
  const annotations = tool.annotations;
33
- const pills = [];
34
- if (annotations?.readOnlyHint)
35
- pills.push(renderPill('read-only', 'readonly'));
36
- if (annotations?.destructiveHint === true)
37
- pills.push(renderPill('destructive', 'destructive'));
69
+ // Mutability badge first — the safety signal readers track at a glance.
70
+ const pills = [renderPill(mutability, mutability)];
38
71
  if (annotations?.openWorldHint)
39
72
  pills.push(renderPill('open-world', 'openworld'));
40
73
  if (tool.isTask)
@@ -42,75 +75,87 @@ function renderToolCard(tool) {
42
75
  if (tool.isApp)
43
76
  pills.push(renderPill('app', 'app'));
44
77
  const source = tool.sourceUrl
45
- ? html `<a class="source-link" href="${tool.sourceUrl}" rel="noopener">view source ↗</a>`
78
+ ? html `<a class="source-link" href="${tool.sourceUrl}" rel="noopener" aria-label="View source for ${tool.name}">view source ↗</a>`
79
+ : html ``;
80
+ const scopeChips = tool.auth && tool.auth.length > 0
81
+ ? html `<span class="card-scope" title="${tool.auth.join(', ')}"><span class="card-meta-label">scope</span>${tool.auth.map((scope) => html ` <code class="scope-chip">${scopeAccessLevel(scope)}</code>`)}</span>`
46
82
  : html ``;
47
83
  const schemaPreview = tool.inputSchema
48
84
  ? html `
49
- <details>
50
- <summary>Input schema</summary>
85
+ <details class="card-detail">
86
+ <summary>schema</summary>
51
87
  <pre><code>${JSON.stringify(tool.inputSchema, null, 2)}</code></pre>
52
88
  </details>
53
89
  `
54
90
  : html ``;
55
91
  const invocation = html `
56
- <details>
57
- <summary>Invocation</summary>
92
+ <details class="card-detail">
93
+ <summary>invocation</summary>
58
94
  ${renderSnippet(`tool-${tool.name}`, buildInvocationSnippet(tool))}
59
95
  </details>
60
96
  `;
61
- const authBadges = tool.auth && tool.auth.length > 0
62
- ? html `<div class="card-meta"><span class="card-meta-label">scopes</span>${tool.auth.map((scope) => html ` <span class="pill pill-auth">${scope}</span>`)}</div>`
63
- : html ``;
97
+ // Search target: name + description as a single lowercase string. Hidden
98
+ // attribute (not visible) so the filter script can match without parsing
99
+ // DOM text repeatedly. Description gets normalized whitespace so multi-line
100
+ // entries don't waste haystack length.
101
+ const searchTarget = `${tool.name} ${tool.description}`.replace(/\s+/g, ' ').toLowerCase();
64
102
  return html `
65
- <article class="card" id="${anchor}">
66
- <div class="card-head">
103
+ <article
104
+ class="card tool-card"
105
+ id="${anchor}"
106
+ data-tool-card
107
+ data-mutability="${mutability}"
108
+ data-name="${tool.name}"
109
+ data-search="${searchTarget}"
110
+ >
111
+ <header class="card-head">
67
112
  <h3 class="card-title"><a href="#${anchor}">${tool.name}</a></h3>
68
113
  <div class="pill-row" role="list">${pills}</div>
69
114
  ${source}
70
- </div>
115
+ </header>
71
116
  <p class="card-desc">${tool.description}</p>
72
- ${authBadges}
73
- ${invocation}
74
- ${schemaPreview}
117
+ <footer class="card-foot">
118
+ ${scopeChips}
119
+ <div class="card-actions">
120
+ ${invocation}
121
+ ${schemaPreview}
122
+ </div>
123
+ </footer>
75
124
  </article>
76
125
  `;
77
126
  }
78
- function groupToolsByPrefix(tools) {
79
- if (tools.length < 3)
80
- return [{ label: null, tools }];
81
- const prefixCounts = new Map();
82
- for (const tool of tools) {
83
- const prefix = tool.name.split('_', 1)[0];
84
- if (!prefix)
85
- continue;
86
- prefixCounts.set(prefix, (prefixCounts.get(prefix) ?? 0) + 1);
87
- }
88
- const groupablePrefixes = new Set([...prefixCounts.entries()].filter(([, count]) => count >= 2).map(([p]) => p));
89
- if (groupablePrefixes.size === 0)
90
- return [{ label: null, tools }];
91
- const groups = new Map();
92
- const other = [];
93
- for (const tool of tools) {
94
- const prefix = tool.name.split('_', 1)[0];
95
- if (prefix && groupablePrefixes.has(prefix)) {
96
- const list = groups.get(prefix) ?? [];
97
- list.push(tool);
98
- groups.set(prefix, list);
99
- }
100
- else {
101
- other.push(tool);
102
- }
103
- }
104
- const out = [];
105
- for (const [prefix, list] of groups) {
106
- out.push({ label: titleCase(prefix), tools: list });
107
- }
108
- if (other.length > 0)
109
- out.push({ label: 'Other', tools: other });
110
- return out;
127
+ /**
128
+ * Map a tool to a mutability bucket using its annotations. The MCP spec
129
+ * defaults `destructiveHint` to `true`, but treating annotation-less tools
130
+ * as "destructive" surprises readers — bucket as `write` unless the
131
+ * destructive hint is explicitly set. Mirrors how the annotation pills
132
+ * render today (`pill-destructive` requires `=== true`).
133
+ */
134
+ function classifyMutability(tool) {
135
+ const a = tool.annotations;
136
+ if (a?.readOnlyHint === true)
137
+ return 'read';
138
+ if (a?.destructiveHint === true)
139
+ return 'destructive';
140
+ return 'write';
141
+ }
142
+ function bucketByMutability(tools) {
143
+ const buckets = { read: [], write: [], destructive: [] };
144
+ for (const tool of tools)
145
+ buckets[classifyMutability(tool)].push(tool);
146
+ return buckets;
111
147
  }
112
- function titleCase(s) {
113
- return s.charAt(0).toUpperCase() + s.slice(1);
148
+ /**
149
+ * Reduce a colon-delimited scope (`tool:foo:read`) to its trailing access
150
+ * level (`read`). Scopes that don't match the convention render verbatim —
151
+ * the linter doesn't enforce shape, so falling back is friendlier than
152
+ * eating the value.
153
+ */
154
+ function scopeAccessLevel(scope) {
155
+ const idx = scope.lastIndexOf(':');
156
+ if (idx < 0 || idx === scope.length - 1)
157
+ return scope;
158
+ return scope.slice(idx + 1);
114
159
  }
115
160
  function buildInvocationSnippet(tool) {
116
161
  const args = {};
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/sections/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/sections/tools.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/mcp-ts-core",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "mcpName": "io.github.cyanheads/mcp-ts-core",
5
5
  "description": "Agent-native TypeScript framework for building MCP servers. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Bun/Node/Cloudflare Workers.",
6
6
  "main": "dist/core/index.js",
@@ -108,8 +108,10 @@ const APP_HTML = `<!DOCTYPE html>
108
108
  <!-- your UI markup -->
109
109
 
110
110
  <script type="module">
111
- // Prefer a bundled or inlined SDK for the final shipped HTML. Leaving a live
112
- // CDN import in the served ui:// resource is not the recommended default.
111
+ // PROTOTYPING ONLY replace before shipping. Bundle via Vite +
112
+ // vite-plugin-singlefile or inline the SDK. Live CDN imports require
113
+ // CSP whitelisting, add supply-chain risk, and break offline use.
114
+ // See UI Notes below.
113
115
  import {
114
116
  App,
115
117
  applyDocumentTheme,
@@ -186,13 +188,27 @@ export const {{RESOURCE_EXPORT}} = appResource('ui://{{tool-name}}/app.html', {
186
188
  });
187
189
  ```
188
190
 
189
- ## UI Design Notes
191
+ ## UI Notes
190
192
 
191
- - **Bundling:** Prefer Vite + `vite-plugin-singlefile` for any UI that uses `@modelcontextprotocol/ext-apps`. The served `ui://` HTML should ideally be self-contained. The inline template literal pattern is fine for zero-dependency UIs or when you inline the SDK yourself.
192
- - **Client-side SDK:** Author against `@modelcontextprotocol/ext-apps`, but ship a bundled or inlined artifact when possible. Avoid relying on a live CDN import as the default final pattern for portable host compatibility.
193
- - **CSP:** MCP Apps iframes run under deny-by-default CSP. With `appResource()`, put `_meta.ui.csp.resourceDomains` on the definition and the builder will mirror it into returned `resources/read` content items. With plain `resource()`, you still need to attach `_meta.ui` yourself in `format()`.
194
- - **App resource `format()`:** `appResource()` already preserves raw HTML for the default app MIME type and mirrors definition `_meta.ui` into content items. Add a custom `format()` only when you need extra per-read metadata or non-default content shaping.
195
- - **format() for app tools:** The first `text` content block is typically JSON that the UI parses via `ontoolresult`. Additional blocks provide a human-readable fallback that non-app hosts and LLMs consume. Do not rely on the JSON block alone for model-visible detail; the fallback blocks still need to render the fields the LLM must reason about.
193
+ - **Ship self-contained HTML.** Author with Vite + `vite-plugin-singlefile` or inline the SDK. Live CDN imports in a `ui://` resource are a CSP footgun (every domain has to be whitelisted on `_meta.ui.csp.resourceDomains`), a supply-chain footgun (third-party JS executes inside the host's iframe), and a runtime footgun (every render needs network). The `unpkg` line in the template is for prototyping only.
194
+ - **CSP.** MCP Apps iframes run under deny-by-default CSP. With `appResource()`, put `_meta.ui.csp.resourceDomains` on the definition; the builder mirrors it into returned `resources/read` content items. With plain `resource()`, attach `_meta.ui` yourself in `format()`.
195
+ - **Adopt the host's visual identity, don't impose your own.** App UIs render inside the host's iframe alongside its native UI. Three host hooks layer on top of your CSS:
196
+ - `applyDocumentTheme(hostContext.theme)` sets `color-scheme` and a `data-theme` attribute on `<html>`
197
+ - `applyHostStyleVariables(hostContext.styles.variables)` installs host CSS custom properties on `:root` (host decides the names, e.g. `--mcp-color-bg-primary`)
198
+ - `applyHostFonts(hostContext.styles.css.fonts)` — installs `@font-face` rules for the host's font stack
199
+
200
+ Author CSS to *consume* these via `var(--mcp-color-bg-primary, /* fallback */ #fff)`. Don't hardcode brand colors that fight the host.
201
+ - **Pre-connect baseline.** `app.connect()` is async — host context arrives a frame or two after first paint. Without a baseline, the UI flashes unstyled or wrong-themed on light hosts. Ship a `prefers-color-scheme`-aware default so the first frame is sensible:
202
+
203
+ ```css
204
+ :root { color-scheme: light dark; --bg: #fff; --fg: #111; }
205
+ @media (prefers-color-scheme: dark) { :root { --bg: #0c0d12; --fg: #ededef; } }
206
+ body { background: var(--bg); color: var(--fg); }
207
+ ```
208
+
209
+ Host vars override these once `onhostcontextchanged` fires.
210
+ - **`format()` for app tools.** The first `text` content block is typically JSON that the UI parses via `ontoolresult`. Additional blocks are the human-readable fallback that non-app hosts and LLMs consume — they must render every field the LLM needs to reason about. JSON-only payloads leave model-visible context blind.
211
+ - **App resource `format()`.** `appResource()` already preserves raw HTML for the default app MIME type and mirrors definition `_meta.ui` into content items. Add a custom `format()` only when you need extra per-read metadata or non-default content shaping.
196
212
 
197
213
  ## Registration
198
214
 
@@ -4,7 +4,7 @@ description: >
4
4
  Investigate, adopt, and verify dependency updates — with special handling for `@cyanheads/mcp-ts-core`. Captures what changed, understands why, cross-references against the codebase, adopts framework improvements, syncs project skills, and runs final checks. Supports two entry modes: run the full flow end-to-end, or review updates you already applied.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.9"
7
+ version: "2.0"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -148,16 +148,28 @@ If the consumer customized a framework script, the overwrite discards those chan
148
148
 
149
149
  Apply the findings from Steps 3 and 4. Framework changes and third-party library changes have different adoption defaults — the asymmetry is deliberate.
150
150
 
151
- **Framework changes (`@cyanheads/mcp-ts-core`) — default adopt.**
151
+ **Framework changes (`@cyanheads/mcp-ts-core`) — auto-adopt every applicable site, in this pass.**
152
152
 
153
- The consumer opted into the framework; its templates, skills, scripts, linter rules, and conventions are authoritative. Adopt directly unless the change genuinely conflicts with a documented local override.
153
+ The consumer opted into the framework; its templates, skills, scripts, linter rules, conventions, and new APIs that supersede local code are authoritative. Adopt them now not as a follow-up.
154
154
 
155
155
  - **Breaking changes** — fix call sites. Not optional.
156
156
  - **Deprecations** — migrate now, while context is fresh.
157
157
  - **New linter rules** — if the rule now flags existing code, fix the code; don't silence the rule.
158
- - **New utilities that supersede local code** — swap them in. The point of the framework is to centralize.
158
+ - **New utilities that supersede local code** — swap them in. The point of the framework is to centralize. This applies even when the local helper has richer messages or branch handling — port the domain detail onto the framework path; don't leave the local helper as-is. (E.g., `httpErrorFromResponse` replacing a project-local `throwForStatus`: keep the per-route message map, but route it through the framework utility.)
159
159
  - **New conventions** (template changes, new config keys, renamed env vars) — adopt and update `.env.example`, server config schema, `server.json`, and README if user-facing.
160
- - **New framework features that don't match existing use cases** — skip; those are for future features, not retroactive refactors.
160
+ - **New patterns that match existing surfaces** — refactor *every* matching site in this pass. Examples: typed error contracts (`errors[]` + `ctx.fail`) on tools that already throw domain-specific failures; factory adoption (`notFound()`, `validationError()`, …) replacing ad-hoc `new McpError(...)`; new logging/observability hooks supplanting bespoke logging. If the framework added a pattern that fits N tools/services, do all N — partial adoption fragments the surface and rots faster.
161
+ - **New framework features that don't match existing use cases** — skip. These are for future features, not retroactive refactors. "Don't match" means *the surface doesn't exist in this server* (e.g., a new Speech API in a non-speech server) — not "I'd have to touch a few files."
162
+
163
+ **Hard rule — invalid framework deferrals.**
164
+
165
+ | ❌ Not a valid reason to defer | ✅ Valid reason to defer |
166
+ |:-------------------------------|:-------------------------|
167
+ | "Larger change than fits this pass" | Code-commented or `CLAUDE.md`-documented local override that intentionally diverges from the framework convention |
168
+ | "Marginal benefit / leaving as-is" | Breaking change with multiple migration paths that need user input |
169
+ | "Per-tool refactor — worth doing as a focused follow-up" | Feature genuinely doesn't apply (the surface doesn't exist in this server) |
170
+ | "Existing helper has rich domain messages we'd lose" | — (port the messages onto the framework path) |
171
+
172
+ If you find yourself writing the left-column phrasing in Step 8's "Open decisions", stop and adopt it instead. Cost/benefit reasoning belongs to third-party changes only.
161
173
 
162
174
  **Third-party library changes — default cost/benefit.**
163
175
 
@@ -189,7 +201,7 @@ Present a concise numbered summary to the user:
189
201
  3. **Features adopted** — new framework APIs now in use
190
202
  4. **Skills synced** — added/updated with versions (Phase A) and agent directories refreshed (Phase B)
191
203
  5. **New/changed skills available** — skills that appeared in Phase A for the first time or had materially-changed step sequences. Frame as "consider running when the time is right" rather than immediate actions; the user decides when to invoke them.
192
- 6. **Open decisions** — genuinely ambiguous items: breaking changes with multiple migration paths, framework changes that conflict with a documented local override, third-party adoptions where the cost/benefit is close
204
+ 6. **Open decisions** — genuinely ambiguous items only. Valid: breaking changes with multiple migration paths needing user input, framework changes that conflict with a code-commented or `CLAUDE.md`-documented local override, third-party adoptions where cost/benefit is close. **Not valid:** framework adoptions deferred for scope, effort, or marginal-benefit reasoning — those were already adopted in Step 6 and belong under "Features adopted." If this section is empty, that's the expected outcome of a clean framework upgrade.
193
205
  7. **Status** — rebuild / devcheck / test results
194
206
 
195
207
  ## Checklist
@@ -197,7 +209,7 @@ Present a concise numbered summary to the user:
197
209
  - [ ] Update applied (`bun update --latest`) — Mode A, or already done by user — Mode B
198
210
  - [ ] `changelog` skill invoked for each updated package
199
211
  - [ ] Framework CHANGELOG reviewed if `@cyanheads/mcp-ts-core` was updated
200
- - [ ] Adoption opportunities identified and applied
212
+ - [ ] Every applicable framework adoption opportunity applied in this pass — no scope/effort/marginal-benefit deferrals; third-party adoptions evaluated on cost/benefit
201
213
  - [ ] Project `skills/` synced from package (Phase A), with a change report
202
214
  - [ ] Agent skill directories (`.claude/skills/`, `.agents/skills/`, etc.) refreshed from project `skills/` (Phase B)
203
215
  - [ ] Framework `scripts/` resynced from package via content-hash compare (Phase C), with a change report; `scripts/` diff reviewed before committing