@cyanheads/mcp-ts-core 0.6.0 → 0.6.1
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/README.md +1 -1
- package/changelog/0.6.x/0.6.1.md +22 -0
- package/dist/core/serverManifest.d.ts +22 -0
- package/dist/core/serverManifest.d.ts.map +1 -1
- package/dist/core/serverManifest.js +24 -0
- package/dist/core/serverManifest.js.map +1 -1
- package/dist/linter/rules/landing-rules.d.ts.map +1 -1
- package/dist/linter/rules/landing-rules.js +21 -1
- package/dist/linter/rules/landing-rules.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page.d.ts +6 -6
- package/dist/mcp-server/transports/http/landing-page.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page.js +900 -283
- package/dist/mcp-server/transports/http/landing-page.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
*
|
|
10
10
|
* ## Surfaces
|
|
11
11
|
*
|
|
12
|
-
* - Hero —
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* - Hero — eyebrow, display-size server name, version + pre-release chips,
|
|
13
|
+
* tagline, single-line status strip, terminal-chrome connection card,
|
|
14
|
+
* framework attribution pill
|
|
15
|
+
* - Tools section — responsive 2-column card grid; prefix-grouped; per-card
|
|
16
|
+
* annotation chips, invocation snippet, view-source link, schema preview
|
|
16
17
|
* - Resources section — URI template, mime type, description, view-source link
|
|
17
18
|
* - Prompts section — args list, view-source link
|
|
18
19
|
* - Extensions section — rendered when SEP-2133 extensions are present
|
|
19
|
-
* - Footer —
|
|
20
|
-
* attribution
|
|
20
|
+
* - Footer — single-row, dim, separator-dot delimited
|
|
21
21
|
*
|
|
22
22
|
* @module src/mcp-server/transports/http/landing-page
|
|
23
23
|
*/
|
|
@@ -34,59 +34,69 @@ function renderTokens(accent) {
|
|
|
34
34
|
:root {
|
|
35
35
|
--space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
|
|
36
36
|
--space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px;
|
|
37
|
-
--space-12: 48px; --space-16: 64px;
|
|
37
|
+
--space-12: 48px; --space-16: 64px; --space-20: 80px;
|
|
38
38
|
|
|
39
39
|
--text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem;
|
|
40
40
|
--text-lg: 1.125rem; --text-xl: 1.25rem; --text-2xl: 1.5rem;
|
|
41
|
-
--text-3xl: 1.875rem;
|
|
41
|
+
--text-3xl: 1.875rem;
|
|
42
|
+
--text-display: clamp(2rem, 4.5vw + 0.5rem, 3.5rem);
|
|
42
43
|
|
|
43
|
-
--radius-
|
|
44
|
+
--radius-xs: 4px; --radius-sm: 6px; --radius-md: 10px;
|
|
45
|
+
--radius-lg: 14px; --radius-pill: 999px;
|
|
44
46
|
|
|
45
|
-
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
46
|
-
--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
47
|
+
--font-sans: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
48
|
+
--font-mono: ui-monospace, "JetBrains Mono", SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
47
49
|
|
|
48
|
-
--duration-fast: 120ms; --duration-base: 200ms;
|
|
50
|
+
--duration-fast: 120ms; --duration-base: 200ms; --duration-slow: 320ms;
|
|
49
51
|
--ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
50
52
|
|
|
51
53
|
--accent: ${safeAccent};
|
|
52
|
-
--accent-
|
|
53
|
-
--accent-
|
|
54
|
-
--accent-soft: color-mix(in oklab, ${safeAccent}, transparent
|
|
54
|
+
--accent-hover: color-mix(in oklab, ${safeAccent}, black 10%);
|
|
55
|
+
--accent-edge: color-mix(in oklab, ${safeAccent}, transparent 65%);
|
|
56
|
+
--accent-soft: color-mix(in oklab, ${safeAccent}, transparent 82%);
|
|
57
|
+
--accent-softer: color-mix(in oklab, ${safeAccent}, transparent 92%);
|
|
55
58
|
|
|
56
|
-
--bg: #
|
|
57
|
-
--bg-subtle: #
|
|
59
|
+
--bg: #fcfcfd;
|
|
60
|
+
--bg-subtle: #f5f5f7;
|
|
58
61
|
--bg-elevated: #ffffff;
|
|
59
|
-
--
|
|
60
|
-
--fg
|
|
61
|
-
--fg-
|
|
62
|
-
--
|
|
63
|
-
--border
|
|
62
|
+
--bg-code: #f7f7f9;
|
|
63
|
+
--fg: #09090b;
|
|
64
|
+
--fg-muted: #52525b;
|
|
65
|
+
--fg-subtle: #a1a1aa;
|
|
66
|
+
--border: #e4e4e7;
|
|
67
|
+
--border-subtle: #ececf0;
|
|
68
|
+
--border-strong: #d4d4d8;
|
|
64
69
|
--shadow-sm: 0 1px 2px rgb(0 0 0 / 0.04);
|
|
65
|
-
--shadow-md: 0 4px
|
|
70
|
+
--shadow-md: 0 4px 20px -8px rgb(0 0 0 / 0.08), 0 1px 2px rgb(0 0 0 / 0.04);
|
|
71
|
+
--grid-dot: rgb(0 0 0 / 0.045);
|
|
72
|
+
--glow-strength: 0.08;
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
@media (prefers-color-scheme: dark) {
|
|
69
76
|
:root {
|
|
70
|
-
--bg: #
|
|
71
|
-
--bg-subtle: #
|
|
72
|
-
--bg-elevated: #
|
|
73
|
-
--
|
|
74
|
-
--fg
|
|
75
|
-
--fg-
|
|
76
|
-
--
|
|
77
|
-
--border
|
|
78
|
-
--
|
|
79
|
-
--
|
|
80
|
-
--
|
|
77
|
+
--bg: #08090d;
|
|
78
|
+
--bg-subtle: #0f1015;
|
|
79
|
+
--bg-elevated: #0d0e13;
|
|
80
|
+
--bg-code: #0a0b10;
|
|
81
|
+
--fg: #ededef;
|
|
82
|
+
--fg-muted: #a1a1a8;
|
|
83
|
+
--fg-subtle: #6b6b73;
|
|
84
|
+
--border: #1f2027;
|
|
85
|
+
--border-subtle: #16171c;
|
|
86
|
+
--border-strong: #2a2b33;
|
|
87
|
+
--shadow-sm: 0 1px 2px rgb(0 0 0 / 0.35);
|
|
88
|
+
--shadow-md: 0 4px 24px -8px rgb(0 0 0 / 0.55), 0 1px 2px rgb(0 0 0 / 0.3);
|
|
89
|
+
--grid-dot: rgb(255 255 255 / 0.028);
|
|
90
|
+
--glow-strength: 0.12;
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
*, *::before, *::after { box-sizing: border-box; }
|
|
85
|
-
|
|
86
95
|
html { color-scheme: light dark; }
|
|
87
96
|
|
|
88
97
|
body {
|
|
89
98
|
margin: 0;
|
|
99
|
+
min-height: 100vh;
|
|
90
100
|
font-family: var(--font-sans);
|
|
91
101
|
font-size: var(--text-base);
|
|
92
102
|
line-height: 1.5;
|
|
@@ -95,238 +105,728 @@ body {
|
|
|
95
105
|
-webkit-font-smoothing: antialiased;
|
|
96
106
|
-moz-osx-font-smoothing: grayscale;
|
|
97
107
|
text-rendering: optimizeLegibility;
|
|
108
|
+
position: relative;
|
|
109
|
+
overflow-x: hidden;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Top accent hairline — spans viewport, brand-variable gradient. */
|
|
113
|
+
body::before {
|
|
114
|
+
content: "";
|
|
115
|
+
position: fixed;
|
|
116
|
+
top: 0; left: 0; right: 0;
|
|
117
|
+
height: 1px;
|
|
118
|
+
background: linear-gradient(90deg,
|
|
119
|
+
transparent 0%,
|
|
120
|
+
color-mix(in oklab, var(--accent), transparent 60%) 15%,
|
|
121
|
+
var(--accent) 50%,
|
|
122
|
+
color-mix(in oklab, var(--accent), transparent 60%) 85%,
|
|
123
|
+
transparent 100%);
|
|
124
|
+
z-index: 100;
|
|
125
|
+
pointer-events: none;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Ambient background — radial accent glow top-left + fine dot grid. */
|
|
129
|
+
body::after {
|
|
130
|
+
content: "";
|
|
131
|
+
position: fixed;
|
|
132
|
+
inset: 0;
|
|
133
|
+
background-image:
|
|
134
|
+
radial-gradient(ellipse 70% 55% at 12% -5%, color-mix(in oklab, var(--accent), transparent calc((1 - var(--glow-strength)) * 100%)), transparent 60%),
|
|
135
|
+
radial-gradient(circle at center, var(--grid-dot) 1px, transparent 1.5px);
|
|
136
|
+
background-size: 100% 100%, 24px 24px;
|
|
137
|
+
pointer-events: none;
|
|
138
|
+
z-index: -1;
|
|
98
139
|
}
|
|
99
140
|
|
|
100
141
|
::selection { background: var(--accent-soft); color: var(--fg); }
|
|
101
142
|
|
|
102
|
-
main {
|
|
143
|
+
main {
|
|
144
|
+
max-width: 1120px;
|
|
145
|
+
margin: 0 auto;
|
|
146
|
+
padding: var(--space-10) var(--space-6) var(--space-20);
|
|
147
|
+
position: relative;
|
|
148
|
+
}
|
|
103
149
|
|
|
104
150
|
a {
|
|
105
151
|
color: var(--accent);
|
|
106
152
|
text-decoration: none;
|
|
107
153
|
transition: color var(--duration-fast) var(--ease-out);
|
|
108
154
|
}
|
|
109
|
-
a:hover { color: var(--accent-hover);
|
|
155
|
+
a:hover { color: var(--accent-hover); }
|
|
110
156
|
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
|
|
111
157
|
|
|
112
158
|
code, pre {
|
|
113
159
|
font-family: var(--font-mono);
|
|
160
|
+
font-size: 0.875em;
|
|
161
|
+
font-feature-settings: "liga" 0, "calt" 0;
|
|
162
|
+
}
|
|
163
|
+
code {
|
|
164
|
+
background: var(--bg-subtle);
|
|
165
|
+
padding: 0.1em 0.35em;
|
|
166
|
+
border-radius: var(--radius-xs);
|
|
167
|
+
border: 1px solid var(--border-subtle);
|
|
114
168
|
font-size: 0.85em;
|
|
115
169
|
}
|
|
116
|
-
code { background: var(--bg-subtle); padding: 0.15em 0.35em; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle); }
|
|
117
170
|
pre {
|
|
118
|
-
background: var(--bg-
|
|
171
|
+
background: var(--bg-code);
|
|
119
172
|
border: 1px solid var(--border-subtle);
|
|
120
|
-
border-radius: var(--radius-
|
|
173
|
+
border-radius: var(--radius-sm);
|
|
121
174
|
padding: var(--space-4);
|
|
122
175
|
margin: 0;
|
|
123
176
|
overflow-x: auto;
|
|
124
|
-
line-height: 1.
|
|
177
|
+
line-height: 1.55;
|
|
125
178
|
white-space: pre;
|
|
179
|
+
color: var(--fg);
|
|
126
180
|
}
|
|
127
|
-
pre code { background: transparent; padding: 0; border: 0; }
|
|
181
|
+
pre code { background: transparent; padding: 0; border: 0; font-size: inherit; }
|
|
128
182
|
|
|
129
|
-
|
|
130
|
-
.hero-top { display: flex; align-items: flex-start; gap: var(--space-4); margin-bottom: var(--space-4); }
|
|
131
|
-
.hero-logo { width: 56px; height: 56px; border-radius: var(--radius-md); object-fit: contain; background: var(--bg-subtle); border: 1px solid var(--border-subtle); flex-shrink: 0; }
|
|
132
|
-
.hero-identity { flex: 1; min-width: 0; }
|
|
133
|
-
.hero-heading { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-3); margin: 0; font-size: var(--text-3xl); font-weight: 700; letter-spacing: -0.02em; color: var(--fg); }
|
|
134
|
-
.hero-tagline { margin: var(--space-3) 0 0; color: var(--fg-muted); font-size: var(--text-lg); max-width: 60ch; }
|
|
183
|
+
/* -------------------- Hero -------------------- */
|
|
135
184
|
|
|
136
|
-
.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
185
|
+
.hero {
|
|
186
|
+
padding: var(--space-12) 0 var(--space-10);
|
|
187
|
+
display: flex;
|
|
188
|
+
flex-direction: column;
|
|
189
|
+
gap: var(--space-6);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.hero-eyebrow {
|
|
193
|
+
display: inline-flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
gap: var(--space-2);
|
|
196
|
+
font-family: var(--font-mono);
|
|
197
|
+
font-size: 0.6875rem;
|
|
141
198
|
font-weight: 500;
|
|
142
|
-
|
|
199
|
+
letter-spacing: 0.14em;
|
|
200
|
+
text-transform: uppercase;
|
|
201
|
+
color: var(--accent);
|
|
202
|
+
}
|
|
203
|
+
.hero-eyebrow::before {
|
|
204
|
+
content: "";
|
|
205
|
+
width: 5px; height: 5px;
|
|
206
|
+
background: var(--accent);
|
|
207
|
+
border-radius: 50%;
|
|
208
|
+
box-shadow: 0 0 10px var(--accent);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.hero-title-row {
|
|
212
|
+
display: flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
gap: var(--space-4);
|
|
215
|
+
flex-wrap: wrap;
|
|
216
|
+
}
|
|
217
|
+
.hero-logo {
|
|
218
|
+
width: 44px; height: 44px;
|
|
219
|
+
border-radius: var(--radius-md);
|
|
220
|
+
object-fit: contain;
|
|
143
221
|
border: 1px solid var(--border);
|
|
222
|
+
background: var(--bg-elevated);
|
|
223
|
+
flex-shrink: 0;
|
|
224
|
+
}
|
|
225
|
+
.hero-heading {
|
|
226
|
+
margin: 0;
|
|
227
|
+
font-size: var(--text-display);
|
|
228
|
+
font-weight: 700;
|
|
229
|
+
letter-spacing: -0.04em;
|
|
230
|
+
line-height: 1.02;
|
|
231
|
+
color: var(--fg);
|
|
232
|
+
word-break: break-word;
|
|
233
|
+
flex: 1 1 auto;
|
|
234
|
+
min-width: 0;
|
|
235
|
+
}
|
|
236
|
+
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
|
|
237
|
+
.hero-heading {
|
|
238
|
+
background: linear-gradient(180deg, var(--fg) 0%, color-mix(in oklab, var(--fg), transparent 25%) 100%);
|
|
239
|
+
-webkit-background-clip: text;
|
|
240
|
+
background-clip: text;
|
|
241
|
+
-webkit-text-fill-color: transparent;
|
|
242
|
+
color: transparent;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
.hero-tagline {
|
|
246
|
+
margin: 0;
|
|
144
247
|
color: var(--fg-muted);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
248
|
+
font-size: var(--text-lg);
|
|
249
|
+
line-height: 1.5;
|
|
250
|
+
max-width: 62ch;
|
|
148
251
|
}
|
|
149
|
-
.badge-version { color: var(--accent); border-color: var(--accent-soft); background: var(--accent-soft); font-weight: 600; }
|
|
150
|
-
.badge-version:hover { background: color-mix(in oklab, var(--accent), transparent 75%); text-decoration: none; }
|
|
151
|
-
.badge-pre { background: color-mix(in oklab, #f59e0b, transparent 85%); border-color: color-mix(in oklab, #f59e0b, transparent 60%); color: #b45309; }
|
|
152
|
-
@media (prefers-color-scheme: dark) { .badge-pre { color: #fbbf24; } }
|
|
153
252
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
.badge-shield {
|
|
253
|
+
/* Version + pre-release chips */
|
|
254
|
+
.badge-version {
|
|
157
255
|
display: inline-flex;
|
|
158
|
-
align-items:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
font-
|
|
256
|
+
align-items: center;
|
|
257
|
+
padding: 4px 10px;
|
|
258
|
+
border-radius: var(--radius-pill);
|
|
259
|
+
font-family: var(--font-mono);
|
|
260
|
+
font-size: 0.75rem;
|
|
162
261
|
font-weight: 600;
|
|
163
|
-
|
|
164
|
-
|
|
262
|
+
letter-spacing: -0.01em;
|
|
263
|
+
color: var(--accent);
|
|
264
|
+
background: var(--accent-softer);
|
|
265
|
+
border: 1px solid var(--accent-edge);
|
|
165
266
|
text-decoration: none;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
267
|
+
transition: background var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
|
|
268
|
+
}
|
|
269
|
+
.badge-version:hover {
|
|
270
|
+
background: var(--accent-soft);
|
|
271
|
+
color: var(--accent);
|
|
272
|
+
text-decoration: none;
|
|
273
|
+
transform: translateY(-1px);
|
|
274
|
+
}
|
|
275
|
+
.badge-pre {
|
|
276
|
+
display: inline-flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
padding: 4px 10px;
|
|
279
|
+
border-radius: var(--radius-pill);
|
|
280
|
+
font-family: var(--font-mono);
|
|
281
|
+
font-size: 0.75rem;
|
|
282
|
+
font-weight: 600;
|
|
283
|
+
letter-spacing: -0.01em;
|
|
284
|
+
color: #b45309;
|
|
285
|
+
background: color-mix(in oklab, #f59e0b, transparent 88%);
|
|
286
|
+
border: 1px solid color-mix(in oklab, #f59e0b, transparent 65%);
|
|
169
287
|
}
|
|
170
|
-
.badge-shield:hover { text-decoration: none; transform: translateY(-1px); box-shadow: 0 0 0 1px rgb(0 0 0 / 0.08), 0 2px 4px rgb(0 0 0 / 0.08); }
|
|
171
|
-
.badge-shield-label, .badge-shield-value { padding: 3px var(--space-2); white-space: nowrap; }
|
|
172
|
-
.badge-shield-label { background: #555; color: #fff; }
|
|
173
|
-
.badge-shield-value { background: #2259c9; color: #fff; }
|
|
174
288
|
@media (prefers-color-scheme: dark) {
|
|
175
|
-
.badge-
|
|
176
|
-
.badge-shield-value { background: #3b6fd4; }
|
|
289
|
+
.badge-pre { color: #fbbf24; }
|
|
177
290
|
}
|
|
178
291
|
|
|
179
|
-
.
|
|
180
|
-
|
|
292
|
+
/* Status strip — single line under the tagline. */
|
|
293
|
+
.status-strip {
|
|
294
|
+
display: flex;
|
|
295
|
+
flex-wrap: wrap;
|
|
296
|
+
align-items: center;
|
|
297
|
+
gap: var(--space-4);
|
|
298
|
+
padding: var(--space-3) 0;
|
|
299
|
+
border-top: 1px solid var(--border-subtle);
|
|
300
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
301
|
+
font-family: var(--font-mono);
|
|
302
|
+
font-size: var(--text-xs);
|
|
303
|
+
color: var(--fg-muted);
|
|
304
|
+
letter-spacing: 0.01em;
|
|
305
|
+
}
|
|
306
|
+
.status-item {
|
|
307
|
+
display: inline-flex;
|
|
308
|
+
align-items: center;
|
|
309
|
+
gap: 6px;
|
|
310
|
+
position: relative;
|
|
311
|
+
}
|
|
312
|
+
.status-item + .status-item::before {
|
|
313
|
+
content: "·";
|
|
314
|
+
color: var(--fg-subtle);
|
|
315
|
+
margin-right: var(--space-2);
|
|
316
|
+
opacity: 0.7;
|
|
317
|
+
}
|
|
318
|
+
.status-dot {
|
|
319
|
+
width: 6px; height: 6px;
|
|
320
|
+
border-radius: 50%;
|
|
321
|
+
display: inline-block;
|
|
322
|
+
flex-shrink: 0;
|
|
323
|
+
}
|
|
324
|
+
.status-dot-public {
|
|
325
|
+
background: #22c55e;
|
|
326
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, #22c55e, transparent 80%);
|
|
327
|
+
animation: status-pulse 2.4s ease-in-out infinite;
|
|
328
|
+
}
|
|
329
|
+
.status-dot-gated {
|
|
330
|
+
background: var(--accent);
|
|
331
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 75%);
|
|
332
|
+
}
|
|
333
|
+
@keyframes status-pulse {
|
|
334
|
+
0%, 100% { box-shadow: 0 0 0 3px color-mix(in oklab, #22c55e, transparent 80%); }
|
|
335
|
+
50% { box-shadow: 0 0 0 6px color-mix(in oklab, #22c55e, transparent 92%); }
|
|
336
|
+
}
|
|
337
|
+
.status-value {
|
|
338
|
+
color: var(--fg);
|
|
339
|
+
font-weight: 500;
|
|
340
|
+
}
|
|
341
|
+
.status-signin {
|
|
342
|
+
color: var(--fg-muted);
|
|
343
|
+
text-decoration: none;
|
|
344
|
+
border-bottom: 1px dotted var(--border-strong);
|
|
345
|
+
}
|
|
346
|
+
.status-signin:hover { color: var(--accent); border-color: var(--accent); }
|
|
347
|
+
|
|
348
|
+
/* -------------------- Connect card -------------------- */
|
|
349
|
+
|
|
350
|
+
.connect {
|
|
351
|
+
border: 1px solid var(--border);
|
|
352
|
+
border-radius: var(--radius-lg);
|
|
353
|
+
background: var(--bg-elevated);
|
|
354
|
+
overflow: hidden;
|
|
355
|
+
box-shadow: var(--shadow-md);
|
|
356
|
+
position: relative;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* Chrome header — three dots + endpoint path */
|
|
360
|
+
.connect-chrome {
|
|
361
|
+
display: flex;
|
|
362
|
+
align-items: center;
|
|
363
|
+
gap: var(--space-3);
|
|
181
364
|
padding: var(--space-3) var(--space-4);
|
|
182
|
-
border-
|
|
183
|
-
border: 1px solid var(--border-subtle);
|
|
365
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
184
366
|
background: var(--bg-subtle);
|
|
367
|
+
}
|
|
368
|
+
.connect-chrome-dots {
|
|
369
|
+
display: inline-flex;
|
|
370
|
+
gap: 6px;
|
|
371
|
+
flex-shrink: 0;
|
|
372
|
+
}
|
|
373
|
+
.connect-chrome-dot {
|
|
374
|
+
width: 10px; height: 10px;
|
|
375
|
+
border-radius: 50%;
|
|
376
|
+
background: color-mix(in oklab, var(--fg-subtle), transparent 60%);
|
|
377
|
+
display: inline-block;
|
|
378
|
+
}
|
|
379
|
+
.connect-chrome-endpoint {
|
|
380
|
+
margin-left: auto;
|
|
381
|
+
font-family: var(--font-mono);
|
|
382
|
+
font-size: 0.6875rem;
|
|
185
383
|
color: var(--fg-muted);
|
|
384
|
+
white-space: nowrap;
|
|
385
|
+
overflow: hidden;
|
|
386
|
+
text-overflow: ellipsis;
|
|
387
|
+
min-width: 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* Radio-hack tabs */
|
|
391
|
+
.connect-tab-input { position: absolute; opacity: 0; pointer-events: none; }
|
|
392
|
+
.connect-tabs {
|
|
393
|
+
display: flex;
|
|
394
|
+
gap: 0;
|
|
395
|
+
padding: 0 var(--space-4);
|
|
396
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
397
|
+
overflow-x: auto;
|
|
398
|
+
scrollbar-width: none;
|
|
399
|
+
}
|
|
400
|
+
.connect-tabs::-webkit-scrollbar { display: none; }
|
|
401
|
+
.connect-tab-label {
|
|
402
|
+
padding: var(--space-3) var(--space-4);
|
|
186
403
|
font-size: var(--text-sm);
|
|
187
|
-
|
|
404
|
+
font-weight: 500;
|
|
405
|
+
color: var(--fg-muted);
|
|
406
|
+
cursor: pointer;
|
|
407
|
+
border-bottom: 2px solid transparent;
|
|
408
|
+
margin-bottom: -1px;
|
|
409
|
+
white-space: nowrap;
|
|
410
|
+
transition: color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
|
|
411
|
+
}
|
|
412
|
+
.connect-tab-label:hover { color: var(--fg); }
|
|
413
|
+
.connect-tab-input:checked + .connect-tab-label {
|
|
414
|
+
color: var(--fg);
|
|
415
|
+
border-bottom-color: var(--accent);
|
|
416
|
+
}
|
|
417
|
+
.connect-tab-input:focus-visible + .connect-tab-label {
|
|
418
|
+
outline: 2px solid var(--accent);
|
|
419
|
+
outline-offset: -6px;
|
|
420
|
+
border-radius: var(--radius-sm);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.connect-panels { position: relative; padding: var(--space-5) var(--space-4); }
|
|
424
|
+
.connect-panel { display: none; }
|
|
425
|
+
.connect:has(#connect-tab-stdio:checked) .panel-stdio,
|
|
426
|
+
.connect:has(#connect-tab-http:checked) .panel-http,
|
|
427
|
+
.connect:has(#connect-tab-claude:checked) .panel-claude,
|
|
428
|
+
.connect:has(#connect-tab-curl:checked) .panel-curl { display: block; }
|
|
429
|
+
/* Fallback when :has() unsupported — show first visible panel */
|
|
430
|
+
@supports not selector(:has(*)) {
|
|
431
|
+
.connect-panel:first-of-type { display: block; }
|
|
188
432
|
}
|
|
189
|
-
.
|
|
190
|
-
|
|
191
|
-
|
|
433
|
+
.connect-panel pre {
|
|
434
|
+
padding: var(--space-4);
|
|
435
|
+
padding-right: var(--space-12);
|
|
436
|
+
background: var(--bg-code);
|
|
437
|
+
border: 1px solid var(--border-subtle);
|
|
438
|
+
border-radius: var(--radius-sm);
|
|
439
|
+
font-size: 0.8125rem;
|
|
440
|
+
line-height: 1.6;
|
|
441
|
+
}
|
|
442
|
+
.connect-copy {
|
|
443
|
+
position: absolute;
|
|
444
|
+
top: calc(var(--space-5) + 8px);
|
|
445
|
+
right: calc(var(--space-4) + 8px);
|
|
446
|
+
font-family: var(--font-mono);
|
|
447
|
+
font-size: 0.6875rem;
|
|
448
|
+
font-weight: 500;
|
|
449
|
+
padding: 4px 10px;
|
|
450
|
+
border-radius: var(--radius-sm);
|
|
451
|
+
border: 1px solid var(--border);
|
|
452
|
+
background: var(--bg);
|
|
453
|
+
color: var(--fg-muted);
|
|
454
|
+
cursor: pointer;
|
|
455
|
+
transition: all var(--duration-fast) var(--ease-out);
|
|
456
|
+
letter-spacing: 0.02em;
|
|
457
|
+
}
|
|
458
|
+
.connect-copy:hover {
|
|
459
|
+
color: var(--accent);
|
|
460
|
+
border-color: var(--accent-edge);
|
|
461
|
+
background: var(--accent-softer);
|
|
462
|
+
}
|
|
463
|
+
.connect-copy:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
464
|
+
.connect-copy[data-copied="true"] {
|
|
465
|
+
color: #16a34a;
|
|
466
|
+
border-color: color-mix(in oklab, #16a34a, transparent 60%);
|
|
467
|
+
background: color-mix(in oklab, #16a34a, transparent 92%);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/* Framework attribution pill */
|
|
471
|
+
.hero-badges {
|
|
472
|
+
display: inline-flex;
|
|
473
|
+
align-items: center;
|
|
474
|
+
gap: var(--space-2);
|
|
475
|
+
}
|
|
476
|
+
.badge-shield {
|
|
477
|
+
display: inline-flex;
|
|
478
|
+
align-items: center;
|
|
479
|
+
gap: 6px;
|
|
480
|
+
padding: 4px 10px;
|
|
481
|
+
border-radius: var(--radius-pill);
|
|
482
|
+
font-family: var(--font-mono);
|
|
483
|
+
font-size: 0.6875rem;
|
|
484
|
+
font-weight: 500;
|
|
485
|
+
letter-spacing: 0.01em;
|
|
486
|
+
color: var(--fg-muted);
|
|
487
|
+
background: var(--bg-subtle);
|
|
488
|
+
border: 1px solid var(--border-subtle);
|
|
489
|
+
text-decoration: none;
|
|
490
|
+
transition: all var(--duration-fast) var(--ease-out);
|
|
491
|
+
}
|
|
492
|
+
.badge-shield:hover {
|
|
493
|
+
color: var(--accent);
|
|
494
|
+
border-color: var(--accent-edge);
|
|
495
|
+
background: var(--accent-softer);
|
|
496
|
+
text-decoration: none;
|
|
497
|
+
transform: translateY(-1px);
|
|
498
|
+
}
|
|
499
|
+
.badge-shield-label { color: var(--fg-subtle); transition: color var(--duration-fast); }
|
|
500
|
+
.badge-shield-value { color: var(--fg-muted); transition: color var(--duration-fast); }
|
|
501
|
+
.badge-shield:hover .badge-shield-label,
|
|
502
|
+
.badge-shield:hover .badge-shield-value { color: var(--accent); }
|
|
503
|
+
|
|
504
|
+
/* -------------------- Sections -------------------- */
|
|
505
|
+
|
|
506
|
+
section { padding: var(--space-12) 0 0; }
|
|
192
507
|
|
|
193
|
-
section {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
508
|
+
.section-heading {
|
|
509
|
+
display: flex;
|
|
510
|
+
align-items: baseline;
|
|
511
|
+
gap: var(--space-3);
|
|
512
|
+
margin: 0 0 var(--space-6);
|
|
513
|
+
padding-bottom: var(--space-3);
|
|
514
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
515
|
+
}
|
|
516
|
+
.section-heading h2 {
|
|
517
|
+
margin: 0;
|
|
518
|
+
font-size: var(--text-2xl);
|
|
519
|
+
font-weight: 600;
|
|
520
|
+
letter-spacing: -0.025em;
|
|
521
|
+
color: var(--fg);
|
|
522
|
+
text-transform: lowercase;
|
|
523
|
+
}
|
|
524
|
+
.section-count {
|
|
525
|
+
font-family: var(--font-mono);
|
|
526
|
+
font-size: var(--text-2xl);
|
|
527
|
+
font-weight: 600;
|
|
528
|
+
color: var(--accent);
|
|
529
|
+
font-variant-numeric: tabular-nums;
|
|
530
|
+
letter-spacing: -0.02em;
|
|
531
|
+
line-height: 1;
|
|
532
|
+
}
|
|
197
533
|
|
|
198
|
-
.group-heading {
|
|
534
|
+
.group-heading {
|
|
535
|
+
margin: var(--space-6) 0 var(--space-3);
|
|
536
|
+
color: var(--fg-muted);
|
|
537
|
+
font-family: var(--font-mono);
|
|
538
|
+
font-size: 0.6875rem;
|
|
539
|
+
font-weight: 600;
|
|
540
|
+
text-transform: uppercase;
|
|
541
|
+
letter-spacing: 0.12em;
|
|
542
|
+
}
|
|
199
543
|
.group-heading:first-child { margin-top: 0; }
|
|
200
544
|
|
|
545
|
+
/* -------------------- Cards -------------------- */
|
|
546
|
+
|
|
547
|
+
.card-grid {
|
|
548
|
+
display: grid;
|
|
549
|
+
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
|
550
|
+
gap: var(--space-3);
|
|
551
|
+
}
|
|
201
552
|
.card {
|
|
202
553
|
border: 1px solid var(--border-subtle);
|
|
203
|
-
border-radius: var(--radius-
|
|
204
|
-
padding: var(--space-5);
|
|
205
|
-
margin-bottom: var(--space-3);
|
|
554
|
+
border-radius: var(--radius-md);
|
|
555
|
+
padding: var(--space-4) var(--space-5);
|
|
206
556
|
background: var(--bg-elevated);
|
|
207
|
-
|
|
557
|
+
display: flex;
|
|
558
|
+
flex-direction: column;
|
|
559
|
+
gap: var(--space-2);
|
|
560
|
+
transition: border-color var(--duration-fast) var(--ease-out),
|
|
561
|
+
transform var(--duration-fast) var(--ease-out),
|
|
562
|
+
box-shadow var(--duration-fast) var(--ease-out);
|
|
563
|
+
position: relative;
|
|
564
|
+
}
|
|
565
|
+
.card:hover {
|
|
566
|
+
border-color: var(--accent-edge);
|
|
567
|
+
transform: translateY(-1px);
|
|
568
|
+
box-shadow: var(--shadow-sm);
|
|
569
|
+
}
|
|
570
|
+
.card-head {
|
|
571
|
+
display: flex;
|
|
572
|
+
align-items: center;
|
|
573
|
+
gap: var(--space-2);
|
|
574
|
+
flex-wrap: wrap;
|
|
575
|
+
}
|
|
576
|
+
.card-title {
|
|
577
|
+
margin: 0;
|
|
578
|
+
font-size: 0.9375rem;
|
|
579
|
+
font-weight: 600;
|
|
580
|
+
font-family: var(--font-mono);
|
|
581
|
+
color: var(--fg);
|
|
582
|
+
letter-spacing: -0.015em;
|
|
208
583
|
}
|
|
209
|
-
.card:hover { border-color: var(--border); box-shadow: var(--shadow-sm); }
|
|
210
|
-
.card-head { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-bottom: var(--space-2); }
|
|
211
|
-
.card-title { margin: 0; font-size: var(--text-base); font-weight: 600; font-family: var(--font-mono); color: var(--fg); }
|
|
212
584
|
.card-title a { color: inherit; }
|
|
213
|
-
.card-
|
|
214
|
-
.card-
|
|
585
|
+
.card-title a:hover { color: var(--accent); text-decoration: none; }
|
|
586
|
+
.card-desc {
|
|
587
|
+
margin: 0;
|
|
588
|
+
color: var(--fg-muted);
|
|
589
|
+
font-size: var(--text-sm);
|
|
590
|
+
line-height: 1.5;
|
|
591
|
+
}
|
|
592
|
+
.card-meta {
|
|
593
|
+
display: flex;
|
|
594
|
+
flex-wrap: wrap;
|
|
595
|
+
gap: var(--space-1) var(--space-3);
|
|
596
|
+
font-size: var(--text-xs);
|
|
597
|
+
color: var(--fg-muted);
|
|
598
|
+
font-family: var(--font-mono);
|
|
599
|
+
align-items: center;
|
|
600
|
+
}
|
|
215
601
|
.card-meta-label { color: var(--fg-subtle); }
|
|
216
|
-
.card-meta code {
|
|
602
|
+
.card-meta code {
|
|
603
|
+
font-size: 1em;
|
|
604
|
+
color: var(--fg);
|
|
605
|
+
background: transparent;
|
|
606
|
+
border: 0;
|
|
607
|
+
padding: 0;
|
|
608
|
+
}
|
|
217
609
|
|
|
218
|
-
|
|
610
|
+
/* Annotation pills — dot-chip style */
|
|
611
|
+
.pill-row { display: inline-flex; flex-wrap: wrap; gap: 5px; align-items: center; }
|
|
219
612
|
.pill {
|
|
220
|
-
display: inline-flex;
|
|
221
|
-
|
|
613
|
+
display: inline-flex;
|
|
614
|
+
align-items: center;
|
|
615
|
+
gap: 5px;
|
|
616
|
+
padding: 2px 8px;
|
|
222
617
|
border-radius: var(--radius-pill);
|
|
223
|
-
font-
|
|
618
|
+
font-family: var(--font-mono);
|
|
619
|
+
font-size: 0.6875rem;
|
|
224
620
|
font-weight: 500;
|
|
225
|
-
line-height: 1.
|
|
226
|
-
border: 1px solid var(--border-subtle);
|
|
621
|
+
line-height: 1.4;
|
|
227
622
|
color: var(--fg-muted);
|
|
228
623
|
background: var(--bg-subtle);
|
|
624
|
+
border: 1px solid var(--border-subtle);
|
|
625
|
+
letter-spacing: 0.01em;
|
|
229
626
|
}
|
|
230
|
-
.pill
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
627
|
+
.pill::before {
|
|
628
|
+
content: "";
|
|
629
|
+
width: 4px; height: 4px;
|
|
630
|
+
border-radius: 50%;
|
|
631
|
+
background: currentColor;
|
|
632
|
+
flex-shrink: 0;
|
|
633
|
+
}
|
|
634
|
+
.pill-readonly { color: #16a34a; background: color-mix(in oklab, #16a34a, transparent 92%); border-color: color-mix(in oklab, #16a34a, transparent 72%); }
|
|
635
|
+
.pill-destructive { color: #dc2626; background: color-mix(in oklab, #dc2626, transparent 92%); border-color: color-mix(in oklab, #dc2626, transparent 72%); }
|
|
636
|
+
.pill-openworld { color: #2563eb; background: color-mix(in oklab, #2563eb, transparent 92%); border-color: color-mix(in oklab, #2563eb, transparent 72%); }
|
|
637
|
+
.pill-task { color: var(--accent); background: var(--accent-softer); border-color: var(--accent-edge); }
|
|
638
|
+
.pill-app { color: #9333ea; background: color-mix(in oklab, #9333ea, transparent 92%); border-color: color-mix(in oklab, #9333ea, transparent 72%); }
|
|
639
|
+
.pill-auth { color: var(--fg-subtle); font-size: 0.65rem; }
|
|
640
|
+
.pill-auth::before { display: none; }
|
|
236
641
|
|
|
237
|
-
|
|
238
|
-
.
|
|
239
|
-
.
|
|
240
|
-
|
|
241
|
-
|
|
642
|
+
@media (prefers-color-scheme: dark) {
|
|
643
|
+
.pill-readonly { color: #4ade80; }
|
|
644
|
+
.pill-destructive { color: #f87171; }
|
|
645
|
+
.pill-openworld { color: #60a5fa; }
|
|
646
|
+
.pill-app { color: #c084fc; }
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.source-link {
|
|
242
650
|
font-size: var(--text-xs);
|
|
243
|
-
|
|
651
|
+
color: var(--fg-subtle);
|
|
652
|
+
margin-left: auto;
|
|
653
|
+
font-family: var(--font-mono);
|
|
654
|
+
transition: color var(--duration-fast);
|
|
655
|
+
}
|
|
656
|
+
.source-link:hover { color: var(--accent); text-decoration: none; }
|
|
657
|
+
|
|
658
|
+
/* Inline snippet (tool invocation) */
|
|
659
|
+
.snippet {
|
|
660
|
+
position: relative;
|
|
661
|
+
margin-top: var(--space-2);
|
|
662
|
+
}
|
|
663
|
+
.snippet pre {
|
|
664
|
+
padding: var(--space-3);
|
|
665
|
+
padding-right: var(--space-12);
|
|
666
|
+
background: var(--bg-code);
|
|
667
|
+
border: 1px solid var(--border-subtle);
|
|
244
668
|
border-radius: var(--radius-sm);
|
|
669
|
+
font-size: 0.75rem;
|
|
670
|
+
line-height: 1.55;
|
|
671
|
+
}
|
|
672
|
+
.snippet-copy {
|
|
673
|
+
position: absolute;
|
|
674
|
+
top: 6px;
|
|
675
|
+
right: 6px;
|
|
676
|
+
font-family: var(--font-mono);
|
|
677
|
+
font-size: 0.65rem;
|
|
678
|
+
font-weight: 500;
|
|
679
|
+
padding: 3px 8px;
|
|
680
|
+
border-radius: var(--radius-xs);
|
|
245
681
|
border: 1px solid var(--border);
|
|
246
682
|
background: var(--bg);
|
|
247
683
|
color: var(--fg-muted);
|
|
248
684
|
cursor: pointer;
|
|
249
|
-
transition:
|
|
685
|
+
transition: all var(--duration-fast) var(--ease-out);
|
|
686
|
+
letter-spacing: 0.02em;
|
|
250
687
|
}
|
|
251
|
-
.snippet-copy:hover { color: var(--accent); border-color: var(--accent); }
|
|
688
|
+
.snippet-copy:hover { color: var(--accent); border-color: var(--accent-edge); }
|
|
252
689
|
.snippet-copy:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
253
|
-
.snippet-copy[data-copied="true"] {
|
|
690
|
+
.snippet-copy[data-copied="true"] {
|
|
691
|
+
color: #16a34a;
|
|
692
|
+
border-color: color-mix(in oklab, #16a34a, transparent 60%);
|
|
693
|
+
}
|
|
254
694
|
|
|
255
|
-
|
|
695
|
+
/* Collapsible details (schema preview) */
|
|
696
|
+
details {
|
|
697
|
+
margin-top: var(--space-1);
|
|
698
|
+
border: 0;
|
|
699
|
+
border-radius: 0;
|
|
700
|
+
}
|
|
256
701
|
details > summary {
|
|
257
702
|
cursor: pointer;
|
|
258
|
-
padding:
|
|
703
|
+
padding: 4px 0;
|
|
704
|
+
font-family: var(--font-mono);
|
|
259
705
|
font-size: var(--text-xs);
|
|
260
|
-
color: var(--fg-
|
|
706
|
+
color: var(--fg-subtle);
|
|
261
707
|
list-style: none;
|
|
262
708
|
user-select: none;
|
|
263
|
-
|
|
709
|
+
display: inline-flex;
|
|
710
|
+
align-items: center;
|
|
711
|
+
gap: var(--space-2);
|
|
712
|
+
transition: color var(--duration-fast);
|
|
264
713
|
}
|
|
265
714
|
details > summary::-webkit-details-marker { display: none; }
|
|
266
|
-
details > summary
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
715
|
+
details > summary::before {
|
|
716
|
+
content: "+";
|
|
717
|
+
display: inline-block;
|
|
718
|
+
width: 10px;
|
|
719
|
+
text-align: center;
|
|
720
|
+
color: var(--fg-subtle);
|
|
721
|
+
font-weight: 600;
|
|
722
|
+
transition: transform var(--duration-fast), color var(--duration-fast);
|
|
723
|
+
}
|
|
724
|
+
details[open] > summary::before { content: "−"; color: var(--accent); }
|
|
725
|
+
details > summary:hover { color: var(--accent); }
|
|
726
|
+
details > pre {
|
|
727
|
+
margin-top: var(--space-2);
|
|
728
|
+
font-size: 0.7rem;
|
|
729
|
+
line-height: 1.55;
|
|
730
|
+
}
|
|
271
731
|
|
|
272
|
-
|
|
273
|
-
.
|
|
274
|
-
|
|
275
|
-
padding:
|
|
276
|
-
|
|
732
|
+
/* Prompt args */
|
|
733
|
+
.args-list {
|
|
734
|
+
list-style: none;
|
|
735
|
+
padding: 0;
|
|
736
|
+
margin: var(--space-1) 0 0;
|
|
737
|
+
display: flex;
|
|
738
|
+
flex-direction: column;
|
|
739
|
+
gap: 4px;
|
|
740
|
+
font-size: var(--text-xs);
|
|
277
741
|
color: var(--fg-muted);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
742
|
+
font-family: var(--font-mono);
|
|
743
|
+
}
|
|
744
|
+
.args-list li { line-height: 1.6; }
|
|
745
|
+
.args-list code {
|
|
746
|
+
font-size: 1em;
|
|
747
|
+
background: transparent;
|
|
748
|
+
border: 0;
|
|
749
|
+
padding: 0;
|
|
750
|
+
color: var(--fg);
|
|
751
|
+
}
|
|
752
|
+
.args-required {
|
|
753
|
+
color: var(--accent);
|
|
754
|
+
font-size: 0.625rem;
|
|
755
|
+
font-weight: 600;
|
|
756
|
+
margin-left: 5px;
|
|
757
|
+
letter-spacing: 0.08em;
|
|
758
|
+
text-transform: uppercase;
|
|
282
759
|
}
|
|
283
|
-
.connect-tab-label:hover { color: var(--fg); }
|
|
284
|
-
.connect-tab-input:focus-visible + .connect-tab-label { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
|
|
285
|
-
.connect-panels { position: relative; margin-top: var(--space-3); }
|
|
286
|
-
.connect-panel { display: none; }
|
|
287
|
-
.connect-tab-input:checked + .connect-tab-label { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
|
|
288
|
-
${[0, 1, 2]
|
|
289
|
-
.map((i) => `.connect-tab-input:nth-of-type(${i + 1}):checked ~ .connect-panels .connect-panel:nth-child(${i + 1}) { display: block; }`)
|
|
290
|
-
.join('\n')}
|
|
291
760
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
761
|
+
/* Extensions */
|
|
762
|
+
.ext-card { background: var(--bg-subtle); border-color: var(--border-subtle); }
|
|
763
|
+
.ext-key { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--fg); }
|
|
764
|
+
.ext-preview {
|
|
765
|
+
margin: var(--space-2) 0 0;
|
|
766
|
+
font-size: 0.7rem;
|
|
767
|
+
line-height: 1.55;
|
|
298
768
|
}
|
|
299
|
-
.footer-links { display: flex; flex-wrap: wrap; gap: var(--space-4) var(--space-6); margin-bottom: var(--space-4); }
|
|
300
|
-
.footer-group { display: flex; flex-direction: column; gap: var(--space-2); min-width: 140px; }
|
|
301
|
-
.footer-group-label { color: var(--fg-subtle); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; }
|
|
302
|
-
.footer-attrib { font-size: var(--text-xs); color: var(--fg-subtle); }
|
|
303
|
-
.footer-attrib a { color: inherit; text-decoration: underline; text-decoration-color: var(--border); }
|
|
304
|
-
.footer-attrib a:hover { color: var(--accent); text-decoration-color: var(--accent); }
|
|
305
769
|
|
|
306
|
-
|
|
307
|
-
.
|
|
770
|
+
/* Empty / degraded state */
|
|
771
|
+
.empty-state {
|
|
772
|
+
padding: var(--space-10) var(--space-6);
|
|
773
|
+
text-align: center;
|
|
774
|
+
color: var(--fg-muted);
|
|
775
|
+
border: 1px dashed var(--border);
|
|
776
|
+
border-radius: var(--radius-md);
|
|
777
|
+
background: var(--bg-subtle);
|
|
778
|
+
font-size: var(--text-sm);
|
|
779
|
+
font-family: var(--font-mono);
|
|
780
|
+
}
|
|
308
781
|
|
|
309
|
-
|
|
310
|
-
.args-list code { font-size: 0.85em; }
|
|
311
|
-
.args-required { color: var(--accent); font-size: 0.7rem; font-weight: 600; margin-left: var(--space-1); }
|
|
782
|
+
/* -------------------- Footer -------------------- */
|
|
312
783
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
784
|
+
footer {
|
|
785
|
+
margin-top: var(--space-20);
|
|
786
|
+
padding: var(--space-6) 0;
|
|
787
|
+
border-top: 1px solid var(--border-subtle);
|
|
788
|
+
display: flex;
|
|
789
|
+
flex-wrap: wrap;
|
|
790
|
+
align-items: center;
|
|
791
|
+
gap: var(--space-2) var(--space-4);
|
|
792
|
+
font-family: var(--font-mono);
|
|
793
|
+
font-size: var(--text-xs);
|
|
794
|
+
color: var(--fg-subtle);
|
|
795
|
+
letter-spacing: 0.01em;
|
|
796
|
+
}
|
|
797
|
+
footer a {
|
|
798
|
+
color: var(--fg-muted);
|
|
799
|
+
text-decoration: none;
|
|
800
|
+
transition: color var(--duration-fast) var(--ease-out);
|
|
801
|
+
}
|
|
802
|
+
footer a:hover { color: var(--accent); text-decoration: none; }
|
|
803
|
+
.footer-sep { color: var(--fg-subtle); opacity: 0.5; user-select: none; }
|
|
804
|
+
.footer-spacer { flex: 1 0 var(--space-4); }
|
|
805
|
+
.footer-attrib { color: var(--fg-subtle); }
|
|
806
|
+
.footer-attrib a { color: var(--fg-muted); }
|
|
316
807
|
|
|
317
|
-
|
|
808
|
+
/* -------------------- Responsive -------------------- */
|
|
318
809
|
|
|
319
|
-
@media (max-width:
|
|
320
|
-
main { padding: var(--space-
|
|
321
|
-
.hero
|
|
322
|
-
.hero-
|
|
323
|
-
.hero-
|
|
810
|
+
@media (max-width: 760px) {
|
|
811
|
+
main { padding: var(--space-6) var(--space-4) var(--space-16); }
|
|
812
|
+
.hero { padding: var(--space-8) 0 var(--space-6); gap: var(--space-5); }
|
|
813
|
+
.hero-heading { font-size: clamp(1.875rem, 7vw + 0.5rem, 2.5rem); letter-spacing: -0.035em; }
|
|
814
|
+
.hero-title-row { gap: var(--space-3); }
|
|
815
|
+
.hero-logo { width: 40px; height: 40px; }
|
|
816
|
+
.hero-tagline { font-size: var(--text-base); }
|
|
817
|
+
.status-strip { gap: var(--space-2); font-size: 0.6875rem; }
|
|
818
|
+
.connect-chrome-endpoint { display: none; }
|
|
819
|
+
.card-grid { grid-template-columns: 1fr; }
|
|
820
|
+
section { padding: var(--space-8) 0 0; }
|
|
821
|
+
.section-heading h2 { font-size: var(--text-xl); }
|
|
822
|
+
.section-count { font-size: var(--text-xl); }
|
|
324
823
|
}
|
|
325
824
|
|
|
326
825
|
@media (prefers-reduced-motion: reduce) {
|
|
327
826
|
*, *::before, *::after {
|
|
328
827
|
transition-duration: 0.01ms !important;
|
|
329
828
|
animation-duration: 0.01ms !important;
|
|
829
|
+
animation-iteration-count: 1 !important;
|
|
330
830
|
}
|
|
331
831
|
}
|
|
332
832
|
`;
|
|
@@ -336,8 +836,6 @@ footer {
|
|
|
336
836
|
// Copy-to-clipboard — single inlined script, < 1KB
|
|
337
837
|
// ---------------------------------------------------------------------------
|
|
338
838
|
function renderCopyScript() {
|
|
339
|
-
// Works without JS too — the button is inert but the pre/code is still selectable.
|
|
340
|
-
// `data-copy-target` holds a CSS selector or inline text content.
|
|
341
839
|
const js = `
|
|
342
840
|
document.addEventListener('click', function(e) {
|
|
343
841
|
var btn = e.target.closest('[data-copy]');
|
|
@@ -366,17 +864,10 @@ document.addEventListener('click', function(e) {
|
|
|
366
864
|
// ---------------------------------------------------------------------------
|
|
367
865
|
// Primitives
|
|
368
866
|
// ---------------------------------------------------------------------------
|
|
369
|
-
function renderBadge(label, variant, href) {
|
|
370
|
-
const cls = variant === 'version' ? 'badge badge-version' : variant === 'pre' ? 'badge badge-pre' : 'badge';
|
|
371
|
-
if (href) {
|
|
372
|
-
return html `<a class="${cls}" href="${href}">${label}</a>`;
|
|
373
|
-
}
|
|
374
|
-
return html `<span class="${cls}">${label}</span>`;
|
|
375
|
-
}
|
|
376
867
|
/**
|
|
377
|
-
* shields.io-style bi-part badge:
|
|
378
|
-
*
|
|
379
|
-
*
|
|
868
|
+
* shields.io-style bi-part badge: "Built on" label + framework name. Links to
|
|
869
|
+
* the framework's npm page. Lives in the hero when `landing.attribution` is
|
|
870
|
+
* enabled.
|
|
380
871
|
*/
|
|
381
872
|
function renderFrameworkBadge(framework) {
|
|
382
873
|
const npmUrl = `https://www.npmjs.com/package/${encodeURIComponent(framework.name)}`;
|
|
@@ -385,45 +876,104 @@ function renderFrameworkBadge(framework) {
|
|
|
385
876
|
function renderPill(text, variant) {
|
|
386
877
|
return html `<span class="pill pill-${variant}">${text}</span>`;
|
|
387
878
|
}
|
|
388
|
-
function renderAuthBanner(auth) {
|
|
389
|
-
if (auth.mode === 'none') {
|
|
390
|
-
return html `<div class="auth-banner auth-banner-public" role="status"><span class="auth-banner-dot" aria-hidden="true"></span><span>Public access — no authentication required.</span></div>`;
|
|
391
|
-
}
|
|
392
|
-
if (auth.mode === 'jwt') {
|
|
393
|
-
return html `<div class="auth-banner auth-banner-gated" role="status"><span class="auth-banner-dot" aria-hidden="true"></span><span>Requires a bearer token.</span></div>`;
|
|
394
|
-
}
|
|
395
|
-
// oauth
|
|
396
|
-
const issuer = auth.oauthIssuer ? html ` <a href="${auth.oauthIssuer}">Sign in ↗</a>` : html ``;
|
|
397
|
-
return html `<div class="auth-banner auth-banner-gated" role="status"><span class="auth-banner-dot" aria-hidden="true"></span><span>Requires OAuth.${issuer}</span></div>`;
|
|
398
|
-
}
|
|
399
879
|
function renderSectionHeading(id, label, count) {
|
|
400
880
|
return html `
|
|
401
881
|
<div class="section-heading">
|
|
402
882
|
<h2 id="${id}">${label}</h2>
|
|
403
|
-
<span class="section-count" aria-label="${String(count)} ${label
|
|
883
|
+
<span class="section-count" aria-label="${String(count)} ${label}">${String(count)}</span>
|
|
404
884
|
</div>
|
|
405
885
|
`;
|
|
406
886
|
}
|
|
407
|
-
function renderSnippet(id, text
|
|
887
|
+
function renderSnippet(id, text) {
|
|
408
888
|
const targetId = `snippet-${id}`;
|
|
409
889
|
return html `
|
|
410
890
|
<div class="snippet">
|
|
411
|
-
<pre id="${targetId}"
|
|
891
|
+
<pre id="${targetId}"><code>${text}</code></pre>
|
|
412
892
|
<button type="button" class="snippet-copy" data-copy data-copy-target="#${targetId}" aria-label="Copy">Copy</button>
|
|
413
893
|
</div>
|
|
414
894
|
`;
|
|
415
895
|
}
|
|
416
896
|
// ---------------------------------------------------------------------------
|
|
897
|
+
// Status strip — replaces the old auth banner
|
|
898
|
+
// ---------------------------------------------------------------------------
|
|
899
|
+
/**
|
|
900
|
+
* Single-line status strip under the hero. Communicates auth mode, capability
|
|
901
|
+
* counts, and protocol version in one mono-spaced, dot-separated row.
|
|
902
|
+
*
|
|
903
|
+
* Accessibility:
|
|
904
|
+
* - `role="status"` so changes are announced live
|
|
905
|
+
* - `aria-label` carries the long-form auth phrase for screen readers
|
|
906
|
+
* ("Public access", "Requires OAuth", etc.) even when the visible label is
|
|
907
|
+
* compact ("public", "oauth")
|
|
908
|
+
*/
|
|
909
|
+
function renderStatusStrip(manifest, degraded) {
|
|
910
|
+
const { auth, definitionCounts, protocol } = manifest;
|
|
911
|
+
const authMeta = describeAuth(auth);
|
|
912
|
+
// Counts hidden in degraded mode to avoid leaking inventory shape.
|
|
913
|
+
const counts = degraded
|
|
914
|
+
? []
|
|
915
|
+
: [
|
|
916
|
+
{ n: definitionCounts.tools, label: 'tools' },
|
|
917
|
+
{ n: definitionCounts.resources, label: 'resources' },
|
|
918
|
+
{ n: definitionCounts.prompts, label: 'prompts' },
|
|
919
|
+
].filter((c) => c.n > 0);
|
|
920
|
+
const signin = auth.mode === 'oauth' && auth.oauthIssuer
|
|
921
|
+
? html ` <a class="status-signin" href="${auth.oauthIssuer}" rel="noopener">sign in ↗</a>`
|
|
922
|
+
: html ``;
|
|
923
|
+
return html `
|
|
924
|
+
<div class="status-strip" role="status" aria-label="${authMeta.ariaLabel}">
|
|
925
|
+
<span class="status-item">
|
|
926
|
+
<span class="status-dot ${authMeta.dotClass}" aria-hidden="true"></span>
|
|
927
|
+
<span class="status-value">${authMeta.label}</span>${signin}
|
|
928
|
+
</span>
|
|
929
|
+
${counts.map((c) => html `
|
|
930
|
+
<span class="status-item">
|
|
931
|
+
<span class="status-value">${String(c.n)}</span>
|
|
932
|
+
<span>${c.label}</span>
|
|
933
|
+
</span>
|
|
934
|
+
`)}
|
|
935
|
+
<span class="status-item">
|
|
936
|
+
<span>MCP</span>
|
|
937
|
+
<span class="status-value">${protocol.latestVersion}</span>
|
|
938
|
+
</span>
|
|
939
|
+
</div>
|
|
940
|
+
`;
|
|
941
|
+
}
|
|
942
|
+
/** Visible label, dot class, and long-form aria phrase for the auth strip item. */
|
|
943
|
+
function describeAuth(auth) {
|
|
944
|
+
if (auth.mode === 'none') {
|
|
945
|
+
return {
|
|
946
|
+
label: 'public',
|
|
947
|
+
dotClass: 'status-dot-public',
|
|
948
|
+
ariaLabel: 'Public access — no authentication required',
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
if (auth.mode === 'jwt') {
|
|
952
|
+
return {
|
|
953
|
+
label: 'bearer',
|
|
954
|
+
dotClass: 'status-dot-gated',
|
|
955
|
+
ariaLabel: 'Requires a bearer token',
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
return {
|
|
959
|
+
label: 'oauth',
|
|
960
|
+
dotClass: 'status-dot-gated',
|
|
961
|
+
ariaLabel: 'Requires OAuth',
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
// ---------------------------------------------------------------------------
|
|
417
965
|
// Hero
|
|
418
966
|
// ---------------------------------------------------------------------------
|
|
419
|
-
function renderHero(manifest, baseUrl) {
|
|
967
|
+
function renderHero(manifest, baseUrl, degraded) {
|
|
420
968
|
const { server, landing } = manifest;
|
|
421
969
|
const releaseUrl = landing.repoRoot
|
|
422
970
|
? `${landing.repoRoot.url}/releases/tag/v${server.version}`
|
|
423
971
|
: undefined;
|
|
424
|
-
const versionBadge =
|
|
972
|
+
const versionBadge = releaseUrl
|
|
973
|
+
? html `<a class="badge-version" href="${releaseUrl}" aria-label="v${server.version} release notes">v${server.version}</a>`
|
|
974
|
+
: html `<span class="badge-version">v${server.version}</span>`;
|
|
425
975
|
const preReleaseBadge = landing.preRelease.isPreRelease
|
|
426
|
-
?
|
|
976
|
+
? html `<span class="badge-pre">${landing.preRelease.label ?? 'pre-release'}</span>`
|
|
427
977
|
: html ``;
|
|
428
978
|
const tagline = landing.tagline ?? server.description ?? '';
|
|
429
979
|
const logo = landing.logo
|
|
@@ -432,73 +982,142 @@ function renderHero(manifest, baseUrl) {
|
|
|
432
982
|
const frameworkBadge = landing.attribution
|
|
433
983
|
? html `<div class="hero-badges">${renderFrameworkBadge(manifest.framework)}</div>`
|
|
434
984
|
: html ``;
|
|
985
|
+
const connect = degraded ? html `` : renderConnectSnippets(manifest, baseUrl);
|
|
435
986
|
return html `
|
|
436
987
|
<header class="hero">
|
|
437
|
-
<
|
|
988
|
+
<span class="hero-eyebrow" aria-hidden="true">MCP Server</span>
|
|
989
|
+
<div class="hero-title-row">
|
|
438
990
|
${logo}
|
|
439
|
-
<
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
${versionBadge}
|
|
443
|
-
${preReleaseBadge}
|
|
444
|
-
</h1>
|
|
445
|
-
${tagline ? html `<p class="hero-tagline">${tagline}</p>` : html ``}
|
|
446
|
-
${frameworkBadge}
|
|
447
|
-
</div>
|
|
991
|
+
<h1 class="hero-heading">${server.name}</h1>
|
|
992
|
+
${versionBadge}
|
|
993
|
+
${preReleaseBadge}
|
|
448
994
|
</div>
|
|
449
|
-
${
|
|
450
|
-
${
|
|
995
|
+
${tagline ? html `<p class="hero-tagline">${tagline}</p>` : html ``}
|
|
996
|
+
${renderStatusStrip(manifest, degraded)}
|
|
997
|
+
${connect}
|
|
998
|
+
${frameworkBadge}
|
|
451
999
|
</header>
|
|
452
1000
|
`;
|
|
453
1001
|
}
|
|
1002
|
+
/**
|
|
1003
|
+
* `@scope/pkg-name` → `pkg-name`. Fall through for bare names.
|
|
1004
|
+
* Used as the `mcpServers` key and the Claude CLI server alias.
|
|
1005
|
+
*/
|
|
1006
|
+
function deriveShortName(serverName) {
|
|
1007
|
+
const slash = serverName.lastIndexOf('/');
|
|
1008
|
+
return slash >= 0 ? serverName.slice(slash + 1) : serverName;
|
|
1009
|
+
}
|
|
1010
|
+
/** Convert ordered env entries to the `{ KEY: value }` shape MCP clients expect. */
|
|
1011
|
+
function envFromEntries(entries) {
|
|
1012
|
+
return Object.fromEntries(entries.map(({ key, value }) => [key, value]));
|
|
1013
|
+
}
|
|
1014
|
+
/** `claude mcp add --transport stdio <name> [--env K=V]* -- bunx <pkg>@latest` */
|
|
1015
|
+
function buildClaudeStdioCmd(shortName, npmPackage, envExample) {
|
|
1016
|
+
const envFlags = envExample.map(({ key, value }) => `--env ${key}=${value}`).join(' ');
|
|
1017
|
+
const envSegment = envFlags.length > 0 ? ` ${envFlags}` : '';
|
|
1018
|
+
return `claude mcp add --transport stdio ${shortName}${envSegment} -- bunx ${npmPackage}@latest`;
|
|
1019
|
+
}
|
|
1020
|
+
/** `claude mcp add --transport http <name> <url>` */
|
|
1021
|
+
function buildClaudeHttpCmd(shortName, endpoint) {
|
|
1022
|
+
return `claude mcp add --transport http ${shortName} ${endpoint}`;
|
|
1023
|
+
}
|
|
454
1024
|
function renderConnectSnippets(manifest, baseUrl) {
|
|
455
1025
|
const endpoint = `${baseUrl.replace(/\/$/, '')}${manifest.transport.endpointPath}`;
|
|
456
|
-
const
|
|
1026
|
+
const npmPackage = manifest.landing.npmPackage?.name;
|
|
1027
|
+
// `@cyanheads/mcp-ts-core` → `mcp-ts-core`. Short aliases match the convention
|
|
1028
|
+
// used in real Claude Desktop / Cursor configs and make the `claude mcp add`
|
|
1029
|
+
// command more ergonomic.
|
|
1030
|
+
const shortName = deriveShortName(manifest.server.name);
|
|
1031
|
+
const envExample = manifest.landing.envExample;
|
|
1032
|
+
const envObject = envExample.length > 0 ? envFromEntries(envExample) : undefined;
|
|
1033
|
+
// STDIO: prefer native `bunx <pkg>@latest` when the server is published;
|
|
1034
|
+
// fall back to `mcp-remote` as a stdio → HTTP bridge so the tab is always
|
|
1035
|
+
// useful even for unpublished servers.
|
|
1036
|
+
const stdioConfig = JSON.stringify({
|
|
457
1037
|
mcpServers: {
|
|
458
|
-
[
|
|
459
|
-
|
|
460
|
-
|
|
1038
|
+
[shortName]: {
|
|
1039
|
+
command: 'bunx',
|
|
1040
|
+
args: npmPackage ? [`${npmPackage}@latest`] : ['mcp-remote', endpoint],
|
|
1041
|
+
...(envObject && { env: envObject }),
|
|
461
1042
|
},
|
|
462
1043
|
},
|
|
463
1044
|
}, null, 2);
|
|
464
|
-
const
|
|
1045
|
+
const httpConfig = JSON.stringify({
|
|
465
1046
|
mcpServers: {
|
|
466
|
-
[
|
|
467
|
-
|
|
468
|
-
|
|
1047
|
+
[shortName]: {
|
|
1048
|
+
type: 'http',
|
|
1049
|
+
url: endpoint,
|
|
1050
|
+
...(envObject && { env: envObject }),
|
|
469
1051
|
},
|
|
470
1052
|
},
|
|
471
1053
|
}, null, 2);
|
|
1054
|
+
// `claude mcp add` — published package routes through stdio; otherwise
|
|
1055
|
+
// point it straight at the HTTP endpoint.
|
|
1056
|
+
const claudeCmd = npmPackage
|
|
1057
|
+
? buildClaudeStdioCmd(shortName, npmPackage, envExample)
|
|
1058
|
+
: buildClaudeHttpCmd(shortName, endpoint);
|
|
472
1059
|
const curl = [
|
|
473
1060
|
`curl -X POST ${endpoint} \\`,
|
|
474
1061
|
` -H "Content-Type: application/json" \\`,
|
|
475
1062
|
` -H "MCP-Protocol-Version: ${manifest.protocol.latestVersion}" \\`,
|
|
476
1063
|
` -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"${manifest.protocol.latestVersion}","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'`,
|
|
477
1064
|
].join('\n');
|
|
478
|
-
//
|
|
1065
|
+
// Chrome label — npm package when published, else the HTTP endpoint (trimmed).
|
|
1066
|
+
const chromeLabel = npmPackage ?? endpoint.replace(/^https?:\/\//, '');
|
|
1067
|
+
const panels = [
|
|
1068
|
+
{ id: 'stdio', label: 'STDIO', content: stdioConfig, copyAriaLabel: 'Copy stdio config' },
|
|
1069
|
+
{
|
|
1070
|
+
id: 'http',
|
|
1071
|
+
label: 'Streamable HTTP',
|
|
1072
|
+
content: httpConfig,
|
|
1073
|
+
copyAriaLabel: 'Copy HTTP config',
|
|
1074
|
+
},
|
|
1075
|
+
{
|
|
1076
|
+
id: 'claude',
|
|
1077
|
+
label: 'Claude',
|
|
1078
|
+
content: claudeCmd,
|
|
1079
|
+
copyAriaLabel: 'Copy claude mcp add command',
|
|
1080
|
+
},
|
|
1081
|
+
{ id: 'curl', label: 'curl', content: curl, copyAriaLabel: 'Copy curl command' },
|
|
1082
|
+
];
|
|
479
1083
|
return html `
|
|
480
|
-
<div class="connect
|
|
481
|
-
<
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
1084
|
+
<div class="connect" aria-label="Connection snippets">
|
|
1085
|
+
<div class="connect-chrome">
|
|
1086
|
+
<span class="connect-chrome-dots" aria-hidden="true">
|
|
1087
|
+
<span class="connect-chrome-dot"></span>
|
|
1088
|
+
<span class="connect-chrome-dot"></span>
|
|
1089
|
+
<span class="connect-chrome-dot"></span>
|
|
1090
|
+
</span>
|
|
1091
|
+
<span class="connect-chrome-endpoint" title="${endpoint}">${chromeLabel}</span>
|
|
1092
|
+
</div>
|
|
1093
|
+
${panels.map((p, i) => i === 0
|
|
1094
|
+
? html `<input type="radio" class="connect-tab-input" name="connect" id="connect-tab-${p.id}" checked />`
|
|
1095
|
+
: html `<input type="radio" class="connect-tab-input" name="connect" id="connect-tab-${p.id}" />`)}
|
|
1096
|
+
<div class="connect-tabs" role="tablist">
|
|
1097
|
+
${panels.map((p) => html `<label for="connect-tab-${p.id}" class="connect-tab-label" role="tab">${p.label}</label>`)}
|
|
1098
|
+
</div>
|
|
487
1099
|
<div class="connect-panels">
|
|
488
|
-
|
|
489
|
-
<div class="connect-panel" role="tabpanel">${renderSnippet('remote', mcpRemoteConfig)}</div>
|
|
490
|
-
<div class="connect-panel" role="tabpanel">${renderSnippet('curl', curl)}</div>
|
|
1100
|
+
${panels.map((p) => renderConnectPanel(p.id, p.content, p.copyAriaLabel))}
|
|
491
1101
|
</div>
|
|
492
1102
|
</div>
|
|
493
1103
|
`;
|
|
494
1104
|
}
|
|
1105
|
+
/** Single panel inside the connect card — pre/code + copy button. */
|
|
1106
|
+
function renderConnectPanel(id, content, copyAriaLabel) {
|
|
1107
|
+
const snippetId = `connect-snippet-${id}`;
|
|
1108
|
+
return html `
|
|
1109
|
+
<div class="connect-panel panel-${id}" role="tabpanel">
|
|
1110
|
+
<pre id="${snippetId}"><code>${content}</code></pre>
|
|
1111
|
+
<button type="button" class="connect-copy" data-copy data-copy-target="#${snippetId}" aria-label="${copyAriaLabel}">Copy</button>
|
|
1112
|
+
</div>
|
|
1113
|
+
`;
|
|
1114
|
+
}
|
|
495
1115
|
// ---------------------------------------------------------------------------
|
|
496
1116
|
// Tools section
|
|
497
1117
|
// ---------------------------------------------------------------------------
|
|
498
1118
|
function groupToolsByPrefix(tools) {
|
|
499
1119
|
if (tools.length < 3)
|
|
500
1120
|
return [{ label: null, tools }];
|
|
501
|
-
// Count first-segment prefixes. A prefix earns a group when >= 2 tools share it.
|
|
502
1121
|
const prefixCounts = new Map();
|
|
503
1122
|
for (const tool of tools) {
|
|
504
1123
|
const prefix = tool.name.split('_', 1)[0];
|
|
@@ -509,7 +1128,6 @@ function groupToolsByPrefix(tools) {
|
|
|
509
1128
|
const groupablePrefixes = new Set([...prefixCounts.entries()].filter(([, count]) => count >= 2).map(([p]) => p));
|
|
510
1129
|
if (groupablePrefixes.size === 0)
|
|
511
1130
|
return [{ label: null, tools }];
|
|
512
|
-
// Preserve encounter order; create one group per qualifying prefix plus "Other".
|
|
513
1131
|
const groups = new Map();
|
|
514
1132
|
const other = [];
|
|
515
1133
|
for (const tool of tools) {
|
|
@@ -574,9 +1192,14 @@ function renderToolCard(tool) {
|
|
|
574
1192
|
</details>
|
|
575
1193
|
`
|
|
576
1194
|
: html ``;
|
|
577
|
-
const invocation =
|
|
1195
|
+
const invocation = html `
|
|
1196
|
+
<details>
|
|
1197
|
+
<summary>Invocation</summary>
|
|
1198
|
+
${renderSnippet(`tool-${tool.name}`, buildInvocationSnippet(tool))}
|
|
1199
|
+
</details>
|
|
1200
|
+
`;
|
|
578
1201
|
const authBadges = tool.auth && tool.auth.length > 0
|
|
579
|
-
? html `<div class="card-meta"><span class="card-meta-label">scopes
|
|
1202
|
+
? 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>`
|
|
580
1203
|
: html ``;
|
|
581
1204
|
return html `
|
|
582
1205
|
<article class="card" id="${anchor}">
|
|
@@ -587,7 +1210,7 @@ function renderToolCard(tool) {
|
|
|
587
1210
|
</div>
|
|
588
1211
|
<p class="card-desc">${tool.description}</p>
|
|
589
1212
|
${authBadges}
|
|
590
|
-
${
|
|
1213
|
+
${invocation}
|
|
591
1214
|
${schemaPreview}
|
|
592
1215
|
</article>
|
|
593
1216
|
`;
|
|
@@ -598,7 +1221,7 @@ function renderToolsSection(tools) {
|
|
|
598
1221
|
const groups = groupToolsByPrefix(tools);
|
|
599
1222
|
const body = groups.map((group) => {
|
|
600
1223
|
const heading = group.label ? html `<h4 class="group-heading">${group.label}</h4>` : html ``;
|
|
601
|
-
return html `${heading}
|
|
1224
|
+
return html `${heading}<div class="card-grid">${group.tools.map(renderToolCard)}</div>`;
|
|
602
1225
|
});
|
|
603
1226
|
return html `
|
|
604
1227
|
<section aria-labelledby="section-tools">
|
|
@@ -629,8 +1252,8 @@ function renderResourceCard(resource) {
|
|
|
629
1252
|
</div>
|
|
630
1253
|
<p class="card-desc">${resource.description}</p>
|
|
631
1254
|
<div class="card-meta">
|
|
632
|
-
<span><span class="card-meta-label">uri
|
|
633
|
-
${resource.mimeType ? html `<span><span class="card-meta-label">mime
|
|
1255
|
+
<span><span class="card-meta-label">uri</span> <code>${resource.uriTemplate}</code></span>
|
|
1256
|
+
${resource.mimeType ? html `<span><span class="card-meta-label">mime</span> <code>${resource.mimeType}</code></span>` : html ``}
|
|
634
1257
|
</div>
|
|
635
1258
|
</article>
|
|
636
1259
|
`;
|
|
@@ -641,7 +1264,7 @@ function renderResourcesSection(resources) {
|
|
|
641
1264
|
return html `
|
|
642
1265
|
<section aria-labelledby="section-resources">
|
|
643
1266
|
${renderSectionHeading('section-resources', 'Resources', resources.length)}
|
|
644
|
-
|
|
1267
|
+
<div class="card-grid">${resources.map(renderResourceCard)}</div>
|
|
645
1268
|
</section>
|
|
646
1269
|
`;
|
|
647
1270
|
}
|
|
@@ -682,7 +1305,7 @@ function renderPromptsSection(prompts) {
|
|
|
682
1305
|
return html `
|
|
683
1306
|
<section aria-labelledby="section-prompts">
|
|
684
1307
|
${renderSectionHeading('section-prompts', 'Prompts', prompts.length)}
|
|
685
|
-
|
|
1308
|
+
<div class="card-grid">${prompts.map(renderPromptCard)}</div>
|
|
686
1309
|
</section>
|
|
687
1310
|
`;
|
|
688
1311
|
}
|
|
@@ -696,14 +1319,16 @@ function renderExtensionsSection(extensions) {
|
|
|
696
1319
|
return html `
|
|
697
1320
|
<section aria-labelledby="section-extensions">
|
|
698
1321
|
${renderSectionHeading('section-extensions', 'Extensions', entries.length)}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
<
|
|
702
|
-
<
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
1322
|
+
<div class="card-grid">
|
|
1323
|
+
${entries.map(([key, value]) => html `
|
|
1324
|
+
<article class="card ext-card">
|
|
1325
|
+
<div class="card-head">
|
|
1326
|
+
<h3 class="card-title ext-key">${key}</h3>
|
|
1327
|
+
</div>
|
|
1328
|
+
<pre class="ext-preview"><code>${JSON.stringify(value, null, 2)}</code></pre>
|
|
1329
|
+
</article>
|
|
1330
|
+
`)}
|
|
1331
|
+
</div>
|
|
707
1332
|
</section>
|
|
708
1333
|
`;
|
|
709
1334
|
}
|
|
@@ -712,48 +1337,39 @@ function renderExtensionsSection(extensions) {
|
|
|
712
1337
|
// ---------------------------------------------------------------------------
|
|
713
1338
|
function renderFooter(manifest) {
|
|
714
1339
|
const { landing, framework } = manifest;
|
|
715
|
-
const
|
|
1340
|
+
const links = [];
|
|
716
1341
|
// User-supplied links
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
label: 'Links',
|
|
720
|
-
links: landing.links.map((l) => ({ href: l.href, label: l.label })),
|
|
721
|
-
});
|
|
1342
|
+
for (const link of landing.links) {
|
|
1343
|
+
links.push({ href: link.href, label: link.label });
|
|
722
1344
|
}
|
|
723
1345
|
// Auto-derived GitHub cluster
|
|
724
1346
|
if (landing.repoRoot) {
|
|
725
1347
|
const repo = landing.repoRoot;
|
|
726
1348
|
const version = manifest.server.version;
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
],
|
|
1349
|
+
links.push({
|
|
1350
|
+
href: landing.changelogUrl ?? `${repo.url}/blob/main/CHANGELOG.md`,
|
|
1351
|
+
label: 'Changelog',
|
|
1352
|
+
});
|
|
1353
|
+
links.push({
|
|
1354
|
+
href: `${repo.url}/releases/tag/v${version}`,
|
|
1355
|
+
label: `v${version}`,
|
|
735
1356
|
});
|
|
1357
|
+
links.push({ href: `${repo.url}/issues`, label: 'Issues' });
|
|
1358
|
+
links.push({ href: repo.url, label: 'Source' });
|
|
736
1359
|
}
|
|
737
1360
|
// Package / registry
|
|
738
1361
|
if (landing.npmPackage) {
|
|
739
|
-
|
|
740
|
-
label: 'Registry',
|
|
741
|
-
links: [{ href: landing.npmPackage.url, label: `npm: ${landing.npmPackage.name}` }],
|
|
742
|
-
});
|
|
1362
|
+
links.push({ href: landing.npmPackage.url, label: 'npm' });
|
|
743
1363
|
}
|
|
744
|
-
const groupsHtml = groups.map((g) => html `
|
|
745
|
-
<div class="footer-group">
|
|
746
|
-
<span class="footer-group-label">${g.label}</span>
|
|
747
|
-
${g.links.map((l) => html `<a href="${l.href}" rel="noopener">${l.label}</a>`)}
|
|
748
|
-
</div>
|
|
749
|
-
`);
|
|
750
1364
|
const frameworkNpm = `https://www.npmjs.com/package/${encodeURIComponent(framework.name)}`;
|
|
1365
|
+
const linkEls = links.map((l, i) => html `${i > 0 ? html `<span class="footer-sep">·</span>` : html ``}<a href="${l.href}" rel="noopener">${l.label}</a>`);
|
|
751
1366
|
const attribution = landing.attribution
|
|
752
|
-
? html `<
|
|
1367
|
+
? html `<span class="footer-attrib">built on <a href="${framework.homepage}">${framework.name}</a> v${framework.version} · <a href="${frameworkNpm}" rel="noopener">npm</a></span>`
|
|
753
1368
|
: html ``;
|
|
754
1369
|
return html `
|
|
755
1370
|
<footer>
|
|
756
|
-
${
|
|
1371
|
+
${linkEls}
|
|
1372
|
+
<span class="footer-spacer"></span>
|
|
757
1373
|
${attribution}
|
|
758
1374
|
</footer>
|
|
759
1375
|
`;
|
|
@@ -771,6 +1387,7 @@ function renderHead(manifest, pageUrl) {
|
|
|
771
1387
|
const favicon = landing.logo && isImageDataUri(landing.logo)
|
|
772
1388
|
? html `<link rel="icon" href="${landing.logo}" />`
|
|
773
1389
|
: html ``;
|
|
1390
|
+
const themeColor = html `<meta name="theme-color" content="${landing.theme.accent}" />`;
|
|
774
1391
|
const jsonLd = JSON.stringify({
|
|
775
1392
|
'@context': 'https://schema.org',
|
|
776
1393
|
'@type': 'SoftwareApplication',
|
|
@@ -785,6 +1402,7 @@ function renderHead(manifest, pageUrl) {
|
|
|
785
1402
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
786
1403
|
<title>${title}</title>
|
|
787
1404
|
<meta name="description" content="${description}" />
|
|
1405
|
+
${themeColor}
|
|
788
1406
|
<meta property="og:title" content="${title}" />
|
|
789
1407
|
<meta property="og:description" content="${description}" />
|
|
790
1408
|
<meta property="og:type" content="website" />
|
|
@@ -828,10 +1446,9 @@ function escapeLdJson(json) {
|
|
|
828
1446
|
*/
|
|
829
1447
|
export function renderLandingPage(manifest, baseUrl, degraded = false) {
|
|
830
1448
|
const pageUrl = `${baseUrl.replace(/\/$/, '')}/`;
|
|
831
|
-
const landing = manifest.landing;
|
|
832
1449
|
const body = degraded
|
|
833
1450
|
? html `
|
|
834
|
-
${renderHero(manifest, baseUrl)}
|
|
1451
|
+
${renderHero(manifest, baseUrl, true)}
|
|
835
1452
|
<section>
|
|
836
1453
|
<p class="empty-state">
|
|
837
1454
|
Full server inventory is available to authenticated callers.
|
|
@@ -840,7 +1457,7 @@ export function renderLandingPage(manifest, baseUrl, degraded = false) {
|
|
|
840
1457
|
${renderFooter(manifest)}
|
|
841
1458
|
`
|
|
842
1459
|
: html `
|
|
843
|
-
${renderHero(manifest, baseUrl)}
|
|
1460
|
+
${renderHero(manifest, baseUrl, false)}
|
|
844
1461
|
${renderToolsSection(manifest.definitions.tools)}
|
|
845
1462
|
${renderResourcesSection(manifest.definitions.resources)}
|
|
846
1463
|
${renderPromptsSection(manifest.definitions.prompts)}
|
|
@@ -851,7 +1468,7 @@ export function renderLandingPage(manifest, baseUrl, degraded = false) {
|
|
|
851
1468
|
<html lang="en">
|
|
852
1469
|
<head>
|
|
853
1470
|
${renderHead(manifest, pageUrl)}
|
|
854
|
-
${renderTokens(landing.theme.accent)}
|
|
1471
|
+
${renderTokens(manifest.landing.theme.accent)}
|
|
855
1472
|
</head>
|
|
856
1473
|
<body>
|
|
857
1474
|
<main>${body}</main>
|