@cyanmycelium/mcp-broker 0.4.0 → 1.2.0

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.
Files changed (151) hide show
  1. package/.mcp-broker.example/CONFIGURATION-EN.md +861 -0
  2. package/.mcp-broker.example/CONFIGURATION-FR.md +871 -0
  3. package/.mcp-broker.example/README.md +8 -3
  4. package/.mcp-broker.example/config.json +86 -0
  5. package/README.md +122 -3
  6. package/dist/bin.d.ts +0 -1
  7. package/dist/bin.js +180 -208
  8. package/dist/bin.js.map +1 -1
  9. package/dist/chunk-FTDKH2C4.js +3670 -0
  10. package/dist/chunk-FTDKH2C4.js.map +1 -0
  11. package/dist/{broker/grammars → grammars}/claude/en.json +1 -1
  12. package/dist/{broker/grammars → grammars}/claude/fr.json +1 -1
  13. package/dist/index.d.ts +1790 -18
  14. package/dist/index.js +2 -14
  15. package/dist/index.js.map +1 -1
  16. package/package.json +14 -8
  17. package/scripts/copy-assets.mjs +16 -10
  18. package/scripts/gen-cert.mjs +5 -5
  19. package/scripts/pack-mcpb.mjs +5 -5
  20. package/scripts/sign-bundle.mjs +6 -6
  21. package/src/auth/auth.config.ts +98 -0
  22. package/src/auth/auth.types.ts +120 -0
  23. package/src/auth/http.auth.ts +128 -0
  24. package/src/auth/index.ts +34 -0
  25. package/src/auth/jwt.validator.ts +63 -0
  26. package/src/auth/provider.auth.ts +114 -0
  27. package/src/auth/resource.metadata.ts +34 -0
  28. package/src/authorization/audit.ts +35 -0
  29. package/src/authorization/capability.classifier.ts +114 -0
  30. package/src/authorization/index.ts +37 -0
  31. package/src/authorization/policy.engine.ts +212 -0
  32. package/src/authorization/policy.types.ts +105 -0
  33. package/src/authorization/resource.path.ts +126 -0
  34. package/src/authorization/runtime.ts +56 -0
  35. package/src/authorization/slot.resource.ts +50 -0
  36. package/src/authorization/subject.mapper.ts +81 -0
  37. package/src/bin.ts +90 -7
  38. package/src/broker/adapters/broker.adapter.info.ts +3 -3
  39. package/src/broker/adapters/broker.adapter.providers.ts +3 -3
  40. package/src/broker/aggregate/aggregate.catalog.ts +49 -21
  41. package/src/broker/aggregate/aggregate.server.ts +149 -19
  42. package/src/broker/aggregate/provider.client.session.ts +22 -22
  43. package/src/broker/behaviors/broker.behavior.info.ts +4 -4
  44. package/src/broker/behaviors/broker.behavior.providers.ts +6 -6
  45. package/src/broker/broker.context.ts +10 -4
  46. package/src/broker/broker.grammars.ts +16 -13
  47. package/src/broker/broker.server.ts +12 -9
  48. package/src/broker/grammars/claude/en.json +1 -1
  49. package/src/broker/grammars/claude/fr.json +1 -1
  50. package/src/broker/index.ts +9 -9
  51. package/src/config.ts +81 -8
  52. package/src/index.ts +121 -20
  53. package/src/{mcpb.loader.ts → mcpb/mcpb.loader.ts} +24 -21
  54. package/src/{mcpb.unzip.ts → mcpb/mcpb.unzip.ts} +2 -2
  55. package/src/remote.transports.ts +11 -8
  56. package/src/remote.upstream.ts +11 -8
  57. package/src/stdio.upstream.ts +9 -6
  58. package/src/upstream.ts +6 -3
  59. package/src/ws/ws.interfaces.ts +357 -0
  60. package/src/{ws.tunnel.builder.ts → ws/ws.tunnel.builder.ts} +112 -9
  61. package/src/{ws.tunnel.ts → ws/ws.tunnel.ts} +646 -457
  62. package/web/README.md +94 -0
  63. package/web/assets/logo.png +0 -0
  64. package/web/broker-self-mcp.html +338 -0
  65. package/web/css/styles.css +580 -0
  66. package/web/demos/DemoPlaceholder.html +258 -0
  67. package/web/demos/broker-explorer/css/app.css +393 -0
  68. package/web/demos/broker-explorer/index.html +94 -0
  69. package/web/demos/broker-explorer/js/app.js +271 -0
  70. package/web/demos/broker-explorer/js/mcp-ws-client.js +132 -0
  71. package/web/demos/oauth-lab/README.md +94 -0
  72. package/web/demos/oauth-lab/config.json +117 -0
  73. package/web/demos/oauth-lab/css/app.css +1097 -0
  74. package/web/demos/oauth-lab/index.html +323 -0
  75. package/web/demos/oauth-lab/js/app.js +654 -0
  76. package/web/demos/oauth-lab/server/auth-server.mjs +426 -0
  77. package/web/demos/oauth-lab/server/factory-provider.mjs +269 -0
  78. package/web/demos/oauth-lab/server/smoke-test.mjs +308 -0
  79. package/web/demos/oauth-lab/server/start.mjs +106 -0
  80. package/web/demos/provider-tunnel/css/app.css +384 -0
  81. package/web/demos/provider-tunnel/index.html +99 -0
  82. package/web/demos/provider-tunnel/js/app.js +226 -0
  83. package/web/demos/provider-tunnel/js/toolbox-server.js +186 -0
  84. package/web/index.html +558 -0
  85. package/web/js/lib/broker-tunnel.js +173 -0
  86. package/dist/broker/adapters/broker.adapter.info.d.ts +0 -16
  87. package/dist/broker/adapters/broker.adapter.info.js +0 -43
  88. package/dist/broker/adapters/broker.adapter.info.js.map +0 -1
  89. package/dist/broker/adapters/broker.adapter.providers.d.ts +0 -18
  90. package/dist/broker/adapters/broker.adapter.providers.js +0 -61
  91. package/dist/broker/adapters/broker.adapter.providers.js.map +0 -1
  92. package/dist/broker/aggregate/aggregate.catalog.d.ts +0 -54
  93. package/dist/broker/aggregate/aggregate.catalog.js +0 -105
  94. package/dist/broker/aggregate/aggregate.catalog.js.map +0 -1
  95. package/dist/broker/aggregate/aggregate.server.d.ts +0 -47
  96. package/dist/broker/aggregate/aggregate.server.js +0 -151
  97. package/dist/broker/aggregate/aggregate.server.js.map +0 -1
  98. package/dist/broker/aggregate/provider.client.session.d.ts +0 -52
  99. package/dist/broker/aggregate/provider.client.session.js +0 -140
  100. package/dist/broker/aggregate/provider.client.session.js.map +0 -1
  101. package/dist/broker/behaviors/broker.behavior.info.d.ts +0 -15
  102. package/dist/broker/behaviors/broker.behavior.info.js +0 -41
  103. package/dist/broker/behaviors/broker.behavior.info.js.map +0 -1
  104. package/dist/broker/behaviors/broker.behavior.providers.d.ts +0 -19
  105. package/dist/broker/behaviors/broker.behavior.providers.js +0 -69
  106. package/dist/broker/behaviors/broker.behavior.providers.js.map +0 -1
  107. package/dist/broker/broker.context.d.ts +0 -59
  108. package/dist/broker/broker.context.js +0 -2
  109. package/dist/broker/broker.context.js.map +0 -1
  110. package/dist/broker/broker.grammars.d.ts +0 -130
  111. package/dist/broker/broker.grammars.js +0 -229
  112. package/dist/broker/broker.grammars.js.map +0 -1
  113. package/dist/broker/broker.server.d.ts +0 -66
  114. package/dist/broker/broker.server.js +0 -73
  115. package/dist/broker/broker.server.js.map +0 -1
  116. package/dist/broker/index.d.ts +0 -9
  117. package/dist/broker/index.js +0 -7
  118. package/dist/broker/index.js.map +0 -1
  119. package/dist/config.d.ts +0 -136
  120. package/dist/config.js +0 -61
  121. package/dist/config.js.map +0 -1
  122. package/dist/mcpb.loader.d.ts +0 -24
  123. package/dist/mcpb.loader.js +0 -161
  124. package/dist/mcpb.loader.js.map +0 -1
  125. package/dist/mcpb.unzip.d.ts +0 -6
  126. package/dist/mcpb.unzip.js +0 -95
  127. package/dist/mcpb.unzip.js.map +0 -1
  128. package/dist/remote.transports.d.ts +0 -16
  129. package/dist/remote.transports.js +0 -297
  130. package/dist/remote.transports.js.map +0 -1
  131. package/dist/remote.upstream.d.ts +0 -36
  132. package/dist/remote.upstream.js +0 -52
  133. package/dist/remote.upstream.js.map +0 -1
  134. package/dist/stdio.upstream.d.ts +0 -45
  135. package/dist/stdio.upstream.js +0 -85
  136. package/dist/stdio.upstream.js.map +0 -1
  137. package/dist/upstream.d.ts +0 -33
  138. package/dist/upstream.js +0 -2
  139. package/dist/upstream.js.map +0 -1
  140. package/dist/version.d.ts +0 -2
  141. package/dist/version.js +0 -9
  142. package/dist/version.js.map +0 -1
  143. package/dist/ws.tunnel.builder.d.ts +0 -139
  144. package/dist/ws.tunnel.builder.js +0 -205
  145. package/dist/ws.tunnel.builder.js.map +0 -1
  146. package/dist/ws.tunnel.d.ts +0 -373
  147. package/dist/ws.tunnel.js +0 -1090
  148. package/dist/ws.tunnel.js.map +0 -1
  149. /package/dist/{broker/grammars → grammars}/default/en.json +0 -0
  150. /package/dist/{broker/grammars → grammars}/default/fr.json +0 -0
  151. /package/dist/{broker/grammars → grammars}/default/zh.json +0 -0
@@ -0,0 +1,258 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Demo coming soon · @cyanmycelium/mcp-broker</title>
7
+ <meta name="description" content="This demo is not bundled yet." />
8
+ <link rel="icon" type="image/png" href="../assets/logo.png" />
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap">
12
+ <link rel="stylesheet" href="../css/styles.css" />
13
+ <style>
14
+ /* --- Placeholder layout ----------------------------------------------- */
15
+ main.center {
16
+ min-height: calc(100vh - 56px - 145px);
17
+ display: grid;
18
+ place-items: center;
19
+ padding: 64px 0;
20
+ }
21
+ .ph-shell {
22
+ max-width: 640px;
23
+ margin: 0 auto;
24
+ padding: 0 var(--gutter);
25
+ text-align: center;
26
+ }
27
+
28
+ /* Big terminal-style placeholder mark, repeats the dotted/striped pattern
29
+ we already use for placeholders elsewhere, but in the brand palette */
30
+ .ph-mark {
31
+ width: 140px;
32
+ height: 140px;
33
+ margin: 0 auto 28px;
34
+ border: 1px dashed var(--rule-strong);
35
+ border-radius: 12px;
36
+ display: grid;
37
+ place-items: center;
38
+ background:
39
+ repeating-linear-gradient(
40
+ 45deg,
41
+ transparent 0 6px,
42
+ rgba(59, 130, 246, 0.05) 6px 7px
43
+ ),
44
+ var(--bg-elev-1);
45
+ color: var(--blue);
46
+ position: relative;
47
+ }
48
+ .ph-mark::after {
49
+ content: "DEMO";
50
+ position: absolute;
51
+ bottom: -10px;
52
+ left: 50%;
53
+ transform: translateX(-50%);
54
+ background: var(--bg);
55
+ padding: 0 8px;
56
+ font-family: var(--font-mono);
57
+ font-size: 10px;
58
+ letter-spacing: 0.2em;
59
+ color: var(--fg-dim);
60
+ }
61
+ .ph-mark svg { opacity: 0.85; }
62
+
63
+ .ph-eyebrow {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ gap: 8px;
67
+ font-family: var(--font-mono);
68
+ font-size: 11px;
69
+ color: var(--fg-dim);
70
+ letter-spacing: 0.16em;
71
+ text-transform: uppercase;
72
+ margin-bottom: 14px;
73
+ }
74
+ .ph-eyebrow .dot {
75
+ width: 6px; height: 6px; border-radius: 50%;
76
+ background: var(--fg-dim);
77
+ }
78
+
79
+ .ph-title {
80
+ font-family: var(--font-mono);
81
+ font-size: clamp(26px, 4vw, 36px);
82
+ font-weight: 500;
83
+ letter-spacing: -0.02em;
84
+ line-height: 1.2;
85
+ margin: 0 0 16px;
86
+ color: var(--fg);
87
+ }
88
+ .ph-title .slot {
89
+ color: var(--blue);
90
+ }
91
+
92
+ .ph-sub {
93
+ font-size: 17px;
94
+ color: var(--fg-muted);
95
+ line-height: 1.55;
96
+ margin: 0 0 32px;
97
+ text-wrap: pretty;
98
+ }
99
+
100
+ /* Slot panel, pulls the slot name out of ?slot= or the URL path so the
101
+ page is generic-by-default but feels specific when linked to */
102
+ .ph-slot {
103
+ display: inline-flex;
104
+ align-items: center;
105
+ gap: 10px;
106
+ padding: 10px 14px;
107
+ margin-bottom: 32px;
108
+ background: var(--bg-elev-1);
109
+ border: 1px solid var(--rule);
110
+ border-radius: 6px;
111
+ font-family: var(--font-mono);
112
+ font-size: 12px;
113
+ color: var(--fg-muted);
114
+ }
115
+ .ph-slot .k { color: var(--fg-dim); }
116
+ .ph-slot .v { color: var(--blue); }
117
+
118
+ .ph-actions {
119
+ display: flex;
120
+ flex-wrap: wrap;
121
+ gap: 12px;
122
+ justify-content: center;
123
+ }
124
+
125
+ /* "What you can do instead": a compact 2-up below the fold */
126
+ .ph-meanwhile {
127
+ max-width: 640px;
128
+ margin: 56px auto 0;
129
+ padding: 22px 24px;
130
+ background: var(--bg-elev-1);
131
+ border: 1px solid var(--rule);
132
+ border-radius: 8px;
133
+ text-align: left;
134
+ }
135
+ .ph-meanwhile .h {
136
+ font-family: var(--font-mono);
137
+ font-size: 11px;
138
+ letter-spacing: 0.16em;
139
+ color: var(--blue);
140
+ text-transform: uppercase;
141
+ margin-bottom: 10px;
142
+ }
143
+ .ph-meanwhile p {
144
+ margin: 0 0 6px;
145
+ font-size: 14px;
146
+ color: var(--fg-muted);
147
+ line-height: 1.55;
148
+ }
149
+ .ph-meanwhile p:last-child { margin-bottom: 0; }
150
+ .ph-meanwhile code {
151
+ font-family: var(--font-mono);
152
+ font-size: 12px;
153
+ color: var(--blue);
154
+ background: var(--bg-inline);
155
+ border: 1px solid var(--rule);
156
+ padding: 1px 5px;
157
+ border-radius: 3px;
158
+ }
159
+ </style>
160
+ </head>
161
+ <body>
162
+
163
+ <!-- ============================== TOPBAR ============================== -->
164
+ <header class="topbar">
165
+ <div class="shell topbar-inner">
166
+ <a class="brand" href="/">
167
+ <img class="brand-mark" src="../assets/logo.png" alt="" aria-hidden="true" />
168
+ <span>@cyanmycelium/mcp-broker</span>
169
+ </a>
170
+ <nav class="topbar-links">
171
+ <a href="/">← Back to launcher</a>
172
+ <a href="https://github.com/pandaGaume/mcp-broker" target="_blank" rel="noopener">GitHub →</a>
173
+ </nav>
174
+ </div>
175
+ </header>
176
+
177
+ <main class="center">
178
+ <div class="ph-shell">
179
+
180
+ <div class="ph-mark" aria-hidden="true">
181
+ <svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round">
182
+ <rect x="3" y="4" width="18" height="14" rx="2"/>
183
+ <path d="M3 8h18"/>
184
+ <path d="M7 12h2M11 12h6"/>
185
+ <path d="M7 15h4"/>
186
+ </svg>
187
+ </div>
188
+
189
+ <div class="ph-eyebrow"><span class="dot"></span>placeholder · not bundled yet</div>
190
+
191
+ <h1 class="ph-title">
192
+ Demo <span class="slot" id="slot-name">, </span> isn't ready yet.
193
+ </h1>
194
+
195
+ <p class="ph-sub">
196
+ This page is a stand-in. The example it links to hasn't been packaged
197
+ into this build of the broker. The broker itself is running fine: it's
198
+ just this demo's static assets that are missing.
199
+ </p>
200
+
201
+ <div class="ph-slot">
202
+ <span class="k">requested slot</span>
203
+ <span class="v" id="slot-path">/examples/…</span>
204
+ </div>
205
+
206
+ <div class="ph-actions">
207
+ <a class="btn btn-primary" href="/">← Back to launcher</a>
208
+ <a class="btn btn-secondary" href="https://github.com/pandaGaume/mcp-broker" target="_blank" rel="noopener">
209
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8a8 8 0 0 0 5.47 7.59c.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
210
+ Contribute this demo
211
+ </a>
212
+ </div>
213
+
214
+ <div class="ph-meanwhile">
215
+ <div class="h">In the meantime</div>
216
+ <p>The broker is still serving the standard MCP endpoints. Point a client at <code>http://localhost:3000/_broker/mcp</code> to talk to the broker itself.</p>
217
+ <p>Or drop your own static demo into <code>.mcp-broker/www/</code> and refresh: the static mount picks it up immediately.</p>
218
+ </div>
219
+
220
+ </div>
221
+ </main>
222
+
223
+ <!-- ================================ FOOTER ============================= -->
224
+ <footer>
225
+ <div class="shell footer-inner">
226
+ <div class="footer-links">
227
+ <a href="https://github.com/pandaGaume/mcp-broker" target="_blank" rel="noopener">github.com/pandaGaume/mcp-broker</a>
228
+ <a href="https://npmjs.com/package/@cyanmycelium/mcp-broker" target="_blank" rel="noopener">npmjs.com/package/@cyanmycelium/mcp-broker</a>
229
+ <a href="https://github.com/pandaGaume/mcp-broker/tree/main/docs" target="_blank" rel="noopener">Docs</a>
230
+ </div>
231
+ <div>© CyanMycelium · Apache-2.0</div>
232
+ </div>
233
+ </footer>
234
+
235
+ <script>
236
+ /* Read ?slot=name (preferred) or fall back to the URL path so the page
237
+ can be linked to as either /placeholder.html?slot=babylon-scene
238
+ or /examples/babylon-scene/ (with this file served from there). */
239
+ (function () {
240
+ const url = new URL(window.location.href);
241
+ let slot = url.searchParams.get('slot');
242
+ if (!slot) {
243
+ const m = url.pathname.match(/\/examples\/([^/]+)/);
244
+ slot = m ? m[1] : null;
245
+ }
246
+ if (slot) {
247
+ document.getElementById('slot-name').textContent = slot;
248
+ document.getElementById('slot-path').textContent = '/examples/' + slot + '/';
249
+ document.title = 'Demo "' + slot + '" coming soon · @cyanmycelium/mcp-broker';
250
+ } else {
251
+ document.getElementById('slot-name').textContent = 'this';
252
+ document.getElementById('slot-path').textContent = '/examples/<name>/';
253
+ }
254
+ })();
255
+ </script>
256
+
257
+ </body>
258
+ </html>
@@ -0,0 +1,393 @@
1
+ /* =============================================================
2
+ Broker Explorer Demo, app-specific overrides
3
+ Base: ../../css/styles.css
4
+ ============================================================= */
5
+
6
+ :root {
7
+ --amber: #ffb86b;
8
+ --red: #f78c6c;
9
+ --bg-code: #0a0e13;
10
+ }
11
+
12
+ /* App shell: full-viewport, no scroll */
13
+ html, body {
14
+ height: 100dvh;
15
+ }
16
+ body {
17
+ font-size: 15px;
18
+ display: flex;
19
+ flex-direction: column;
20
+ overflow: hidden;
21
+ }
22
+ code {
23
+ font-family: var(--font-mono);
24
+ }
25
+
26
+ /* Topbar */
27
+ .topbar {
28
+ flex-shrink: 0;
29
+ height: 56px;
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 16px;
33
+ padding: 0 22px;
34
+ }
35
+ .brand {
36
+ color: var(--fg);
37
+ text-decoration: none;
38
+ }
39
+ .brand:hover {
40
+ text-decoration: none;
41
+ }
42
+ .brand-mark {
43
+ width: 26px;
44
+ height: 26px;
45
+ }
46
+ .topbar-tag {
47
+ font-family: var(--font-mono);
48
+ font-size: 11px;
49
+ letter-spacing: 0.14em;
50
+ text-transform: uppercase;
51
+ color: var(--blue);
52
+ padding: 3px 9px;
53
+ border: 1px solid var(--rule-strong);
54
+ border-radius: 4px;
55
+ }
56
+ .topbar-spacer {
57
+ flex: 1;
58
+ }
59
+ .topbar-link {
60
+ font-size: 13px;
61
+ color: var(--fg-muted);
62
+ text-decoration: none;
63
+ }
64
+ .topbar-link:hover {
65
+ color: var(--fg);
66
+ text-decoration: none;
67
+ }
68
+
69
+ /* Status badge */
70
+ .badge {
71
+ font-family: var(--font-mono);
72
+ font-size: 11px;
73
+ font-weight: 600;
74
+ letter-spacing: 0.08em;
75
+ text-transform: uppercase;
76
+ padding: 4px 11px;
77
+ border-radius: 99px;
78
+ border: 1px solid var(--rule-strong);
79
+ height: auto;
80
+ background: none;
81
+ color: var(--fg-muted);
82
+ }
83
+ .badge.disconnected {
84
+ color: var(--red);
85
+ background: rgba(247, 140, 108, 0.08);
86
+ border-color: rgba(247, 140, 108, 0.3);
87
+ }
88
+ .badge.connecting {
89
+ color: var(--amber);
90
+ background: rgba(255, 184, 107, 0.08);
91
+ border-color: rgba(255, 184, 107, 0.3);
92
+ }
93
+ .badge.connected {
94
+ color: var(--lime);
95
+ background: rgba(184, 224, 96, 0.08);
96
+ border-color: rgba(184, 224, 96, 0.32);
97
+ }
98
+
99
+ /* Buttons */
100
+ .btn {
101
+ height: 38px;
102
+ padding: 0 16px;
103
+ gap: 8px;
104
+ }
105
+ .btn:disabled {
106
+ opacity: 0.4;
107
+ cursor: not-allowed;
108
+ }
109
+ .btn-primary:not(:disabled):hover {
110
+ background: var(--blue-bright);
111
+ border-color: var(--blue-bright);
112
+ color: var(--blue-deep);
113
+ }
114
+ .btn-secondary:not(:disabled):hover {
115
+ border-color: var(--fg-muted);
116
+ background: var(--bg-elev-1);
117
+ }
118
+
119
+ /* Layout */
120
+ .layout {
121
+ flex: 1;
122
+ display: flex;
123
+ overflow: hidden;
124
+ }
125
+ .left-panel {
126
+ width: 360px;
127
+ flex-shrink: 0;
128
+ border-right: 1px solid var(--rule);
129
+ }
130
+ .right-column {
131
+ flex: 1;
132
+ display: flex;
133
+ flex-direction: column;
134
+ overflow: hidden;
135
+ }
136
+ .panel {
137
+ display: flex;
138
+ flex-direction: column;
139
+ overflow: hidden;
140
+ }
141
+ .panel-title {
142
+ flex-shrink: 0;
143
+ font-family: var(--font-mono);
144
+ font-size: 11px;
145
+ font-weight: 500;
146
+ letter-spacing: 0.14em;
147
+ text-transform: uppercase;
148
+ color: var(--blue);
149
+ padding: 11px 18px;
150
+ background: var(--bg-elev-1);
151
+ border-bottom: 1px solid var(--rule);
152
+ }
153
+ .panel-body {
154
+ flex: 1;
155
+ overflow-y: auto;
156
+ padding: 18px;
157
+ }
158
+ .catalog-panel {
159
+ flex: 1;
160
+ border-bottom: 1px solid var(--rule);
161
+ }
162
+ .console-panel {
163
+ height: 240px;
164
+ flex-shrink: 0;
165
+ }
166
+
167
+ /* Form controls */
168
+ .field {
169
+ display: flex;
170
+ flex-direction: column;
171
+ gap: 6px;
172
+ margin-bottom: 16px;
173
+ }
174
+ .field label {
175
+ font-size: 12px;
176
+ color: var(--fg-dim);
177
+ }
178
+ input[type="text"] {
179
+ width: 100%;
180
+ background: var(--bg-code);
181
+ border: 1px solid var(--rule-strong);
182
+ border-radius: 6px;
183
+ color: var(--fg);
184
+ font-family: var(--font-mono);
185
+ font-size: 13px;
186
+ padding: 8px 11px;
187
+ }
188
+ input[type="text"]:focus {
189
+ outline: none;
190
+ border-color: var(--blue-dim);
191
+ }
192
+ input[readonly] {
193
+ color: var(--blue-bright);
194
+ cursor: text;
195
+ }
196
+
197
+ /* Slot preset chips */
198
+ .slot-presets {
199
+ display: flex;
200
+ gap: 8px;
201
+ margin: -6px 0 16px;
202
+ }
203
+ .preset {
204
+ font-family: var(--font-mono);
205
+ font-size: 12px;
206
+ color: var(--fg-muted);
207
+ background: var(--bg-elev-1);
208
+ border: 1px solid var(--rule-strong);
209
+ border-radius: 4px;
210
+ padding: 4px 10px;
211
+ cursor: pointer;
212
+ transition: color 120ms, border-color 120ms;
213
+ }
214
+ .preset:hover {
215
+ color: var(--blue);
216
+ border-color: var(--blue-dim);
217
+ }
218
+
219
+ .btn-row {
220
+ display: flex;
221
+ align-items: center;
222
+ flex-wrap: wrap;
223
+ gap: 9px;
224
+ margin-top: 4px;
225
+ }
226
+
227
+ /* Endpoints */
228
+ .endpoints {
229
+ margin-top: 20px;
230
+ padding-top: 16px;
231
+ border-top: 1px solid var(--rule);
232
+ }
233
+ .endpoints .field {
234
+ margin-bottom: 12px;
235
+ }
236
+ .endpoints .field label {
237
+ font-family: var(--font-mono);
238
+ font-size: 10px;
239
+ letter-spacing: 0.1em;
240
+ text-transform: uppercase;
241
+ color: var(--blue);
242
+ }
243
+ .endpoints .field:last-child {
244
+ margin-bottom: 0;
245
+ }
246
+
247
+ .hint {
248
+ margin-top: 18px;
249
+ font-size: 13px;
250
+ line-height: 1.6;
251
+ color: var(--fg-dim);
252
+ }
253
+ .hint code {
254
+ font-size: 0.86em;
255
+ color: var(--blue);
256
+ background: var(--bg-inline);
257
+ border: 1px solid var(--rule);
258
+ padding: 1px 5px;
259
+ border-radius: 4px;
260
+ }
261
+ .hint strong {
262
+ color: var(--fg-muted);
263
+ font-weight: 600;
264
+ }
265
+
266
+ /* Catalog */
267
+ .empty-note {
268
+ color: var(--fg-dim);
269
+ font-size: 14px;
270
+ }
271
+ .cat-group {
272
+ margin-bottom: 20px;
273
+ }
274
+ .cat-group-title {
275
+ font-family: var(--font-mono);
276
+ font-size: 11px;
277
+ font-weight: 600;
278
+ letter-spacing: 0.12em;
279
+ text-transform: uppercase;
280
+ color: var(--fg-dim);
281
+ margin-bottom: 10px;
282
+ }
283
+ .cat-item {
284
+ background: var(--bg-elev-1);
285
+ border: 1px solid var(--rule);
286
+ border-radius: 8px;
287
+ padding: 12px 14px;
288
+ margin-bottom: 9px;
289
+ transition: border-color 140ms, background 140ms;
290
+ }
291
+ .cat-item:hover {
292
+ border-color: var(--blue-dim);
293
+ }
294
+ .cat-item-name {
295
+ font-family: var(--font-mono);
296
+ font-size: 13px;
297
+ font-weight: 600;
298
+ color: var(--blue);
299
+ word-break: break-all;
300
+ }
301
+ .cat-item-desc {
302
+ font-size: 13px;
303
+ color: var(--fg-muted);
304
+ margin-top: 3px;
305
+ line-height: 1.5;
306
+ }
307
+ .cat-item-params {
308
+ font-family: var(--font-mono);
309
+ font-size: 12px;
310
+ color: var(--fg-dim);
311
+ margin-top: 5px;
312
+ }
313
+ .cat-item-params .pname {
314
+ color: var(--amber);
315
+ }
316
+ .cat-item-params .preq {
317
+ color: var(--red);
318
+ }
319
+
320
+ /* Tool call row */
321
+ .cat-item-call {
322
+ display: flex;
323
+ gap: 8px;
324
+ margin-top: 10px;
325
+ }
326
+ .cat-item-call input {
327
+ flex: 1;
328
+ min-width: 0;
329
+ }
330
+ .call-btn {
331
+ flex-shrink: 0;
332
+ font-family: var(--font-mono);
333
+ font-size: 12px;
334
+ font-weight: 500;
335
+ color: var(--blue);
336
+ background: var(--bg-elev-2);
337
+ border: 1px solid var(--blue-dim);
338
+ border-radius: 6px;
339
+ padding: 0 14px;
340
+ cursor: pointer;
341
+ transition: background 120ms, color 120ms;
342
+ }
343
+ .call-btn:hover {
344
+ background: var(--blue);
345
+ color: #ffffff;
346
+ }
347
+ .call-btn:disabled {
348
+ opacity: 0.4;
349
+ cursor: not-allowed;
350
+ }
351
+ .cat-result {
352
+ margin-top: 9px;
353
+ background: var(--bg-code);
354
+ border: 1px solid var(--rule);
355
+ border-radius: 6px;
356
+ padding: 9px 11px;
357
+ font-family: var(--font-mono);
358
+ font-size: 12px;
359
+ line-height: 1.6;
360
+ color: var(--syn-fg);
361
+ white-space: pre-wrap;
362
+ overflow-x: auto;
363
+ }
364
+ .cat-result.error {
365
+ color: var(--red);
366
+ border-color: rgba(247, 140, 108, 0.3);
367
+ }
368
+
369
+ /* Console */
370
+ #console-output {
371
+ font-family: var(--font-mono);
372
+ font-size: 12.5px;
373
+ line-height: 1.65;
374
+ background: var(--bg-code);
375
+ }
376
+ .log-line {
377
+ display: flex;
378
+ gap: 10px;
379
+ }
380
+ .log-line .ts {
381
+ color: var(--fg-dim);
382
+ user-select: none;
383
+ flex-shrink: 0;
384
+ }
385
+ .log-line .msg {
386
+ min-width: 0;
387
+ overflow-wrap: anywhere;
388
+ }
389
+ .log-line.info .msg { color: var(--fg); }
390
+ .log-line.ok .msg { color: var(--lime); }
391
+ .log-line.warn .msg { color: var(--amber); }
392
+ .log-line.error .msg { color: var(--red); }
393
+ .log-line.dim .msg { color: var(--fg-dim); }