@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 +1 -1
- package/README.md +1 -1
- package/changelog/0.8.x/0.8.2.md +18 -0
- package/dist/logs/combined.log +4 -4
- package/dist/logs/error.log +4 -4
- package/dist/mcp-server/transports/http/landing-page/assets/copy-script.d.ts +13 -4
- package/dist/mcp-server/transports/http/landing-page/assets/copy-script.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/assets/copy-script.js +99 -25
- package/dist/mcp-server/transports/http/landing-page/assets/copy-script.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/assets/styles.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/assets/styles.js +318 -8
- package/dist/mcp-server/transports/http/landing-page/assets/styles.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/status-strip.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/status-strip.js +20 -1
- package/dist/mcp-server/transports/http/landing-page/sections/status-strip.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/tools.d.ts +6 -5
- package/dist/mcp-server/transports/http/landing-page/sections/tools.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/tools.js +114 -69
- package/dist/mcp-server/transports/http/landing-page/sections/tools.js.map +1 -1
- package/package.json +1 -1
- package/skills/add-app-tool/SKILL.md +24 -8
- package/skills/maintenance/SKILL.md +19 -7
package/CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent Protocol
|
|
2
2
|
|
|
3
|
-
**Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.8.
|
|
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
|
-
[](./CHANGELOG.md) [](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [](https://modelcontextprotocol.io/) [](./LICENSE)
|
|
9
9
|
|
|
10
10
|
[](https://www.typescriptlang.org/) [](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.
|
package/dist/logs/combined.log
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{"level":50,"time":
|
|
2
|
-
{"level":50,"time":
|
|
3
|
-
{"level":50,"time":
|
|
4
|
-
{"level":50,"time":
|
|
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."}
|
package/dist/logs/error.log
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{"level":50,"time":
|
|
2
|
-
{"level":50,"time":
|
|
3
|
-
{"level":50,"time":
|
|
4
|
-
{"level":50,"time":
|
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
var
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
btn.
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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,
|
|
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:
|
|
626
|
-
letter-spacing: 0.
|
|
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:
|
|
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 {
|
|
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
|
-
|
|
721
|
-
.pill-
|
|
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-
|
|
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
|
|
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,
|
|
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;
|
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
${
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
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>
|
|
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>
|
|
92
|
+
<details class="card-detail">
|
|
93
|
+
<summary>invocation</summary>
|
|
58
94
|
${renderSnippet(`tool-${tool.name}`, buildInvocationSnippet(tool))}
|
|
59
95
|
</details>
|
|
60
96
|
`;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
66
|
-
|
|
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
|
-
</
|
|
115
|
+
</header>
|
|
71
116
|
<p class="card-desc">${tool.description}</p>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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":"AAAA
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../../../../src/mcp-server/transports/http/landing-page/sections/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,IAAI,EAAiB,MAAM,4BAA4B,CAAC;AAEjE,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAInF;;;GAGG;AACH,MAAM,gBAAgB,GAA0B,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;AAEjF,MAAM,UAAU,kBAAkB,CAAC,KAAqB;IACtD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA,EAAE,CAAC;IAEtC,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/E,wEAAwE;IACxE,wEAAwE;IACxE,oBAAoB;IACpB,MAAM,YAAY,GAAG,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC;IAEjD,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE;QACjD,MAAM,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,YAAY;YAC1B,CAAC,CAAC,IAAI,CAAA,yCAAyC,UAAU,KAAK,UAAU,8BAA8B,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,cAAc;YAC9I,CAAC,CAAC,IAAI,CAAA,EAAE,CAAC;QACX,OAAO,IAAI,CAAA,GAAG,OAAO,qCAAqC,UAAU,KAAK,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC;IACzI,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,CAAA;;QAEL,oBAAoB,CAAC,eAAe,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC;QAC5D,mBAAmB,CAAC,gBAAgB,CAAC;gCACb,MAAM;;;GAGnC,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB,CAAC,gBAAuC;IAClE,MAAM,KAAK,GAAe;QACxB,IAAI,CAAA,uGAAuG;KAC5G,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,gBAAgB,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CACR,IAAI,CAAA,qDAAqD,CAAC,6BAA6B,CAAC,0BAA0B,CAAC,WAAW,CAC/H,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAA;;+EAEkE,KAAK;;;;;;;;;;;;GAYjF,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,IAAkB,EAAE,UAAsB;IAChE,MAAM,MAAM,GAAG,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;IACnC,MAAM,WAAW,GAAG,IAAI,CAAC,WAAsD,CAAC;IAEhF,wEAAwE;IACxE,MAAM,KAAK,GAAe,CAAC,UAAU,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;IAC/D,IAAI,WAAW,EAAE,aAAa;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC;IAClF,IAAI,IAAI,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,IAAI,IAAI,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAErD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS;QAC3B,CAAC,CAAC,IAAI,CAAA,gCAAgC,IAAI,CAAC,SAAS,gDAAgD,IAAI,CAAC,IAAI,qBAAqB;QAClI,CAAC,CAAC,IAAI,CAAA,EAAE,CAAC;IAEX,MAAM,UAAU,GACd,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAC/B,CAAC,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,+CAA+C,IAAI,CAAC,IAAI,CAAC,GAAG,CACrH,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAA,6BAA6B,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAC7E,SAAS;QACZ,CAAC,CAAC,IAAI,CAAA,EAAE,CAAC;IAEb,MAAM,aAAa,GAAG,IAAI,CAAC,WAAW;QACpC,CAAC,CAAC,IAAI,CAAA;;;uBAGa,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;;OAEzD;QACH,CAAC,CAAC,IAAI,CAAA,EAAE,CAAC;IAEX,MAAM,UAAU,GAAG,IAAI,CAAA;;;QAGjB,aAAa,CAAC,QAAQ,IAAI,CAAC,IAAI,EAAE,EAAE,sBAAsB,CAAC,IAAI,CAAC,CAAC;;GAErE,CAAC;IAEF,yEAAyE;IACzE,yEAAyE;IACzE,4EAA4E;IAC5E,uCAAuC;IACvC,MAAM,YAAY,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAE3F,OAAO,IAAI,CAAA;;;YAGD,MAAM;;yBAEO,UAAU;mBAChB,IAAI,CAAC,IAAI;qBACP,YAAY;;;2CAGU,MAAM,KAAK,IAAI,CAAC,IAAI;4CACnB,KAAK;UACvC,MAAM;;6BAEa,IAAI,CAAC,WAAW;;UAEnC,UAAU;;YAER,UAAU;YACV,aAAa;;;;GAItB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAC,IAAkB;IAC5C,MAAM,CAAC,GAAG,IAAI,CAAC,WAAgF,CAAC;IAChG,IAAI,CAAC,EAAE,YAAY,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAC5C,IAAI,CAAC,EAAE,eAAe,KAAK,IAAI;QAAE,OAAO,aAAa,CAAC;IACtD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAqB;IAC/C,MAAM,OAAO,GAAuC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC7F,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,OAAO,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,KAAa;IACrC,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACtD,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,sBAAsB,CAAC,IAAkB;IAChD,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,KAAK,GAAG,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CACnB;QACE,OAAO,EAAE,KAAK;QACd,EAAE,EAAE,CAAC;QACL,MAAM,EAAE,YAAY;QACpB,MAAM,EAAE;YACN,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI;SAChB;KACF,EACD,IAAI,EACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/mcp-ts-core",
|
|
3
|
-
"version": "0.8.
|
|
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
|
-
//
|
|
112
|
-
//
|
|
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
|
|
191
|
+
## UI Notes
|
|
190
192
|
|
|
191
|
-
- **
|
|
192
|
-
- **
|
|
193
|
-
- **
|
|
194
|
-
-
|
|
195
|
-
-
|
|
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: "
|
|
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`) —
|
|
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
|
|
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
|
|
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
|
|
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
|
-
- [ ]
|
|
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
|