@santi020k/lumen-mcp 0.1.0 → 0.3.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.
package/README.md CHANGED
@@ -15,7 +15,11 @@ not need a checkout of the Lumen repository.
15
15
  | `lumen_list_components` | List components with descriptions, categories, framework availability, collections, and recipe membership. Filter by `framework`, `recipe`, or natural-language `query`. |
16
16
  | `lumen_get_component` | Get a component for `astro`, `react`, or `elements` at `summary`, `usage`, or `source` detail. Usage includes imports, styles, props or attributes, examples, accessibility, guidance, and events. |
17
17
  | `lumen_get_recipe` | Get a recipe or component set with its purpose, components, files, categories, and framework-specific install command. |
18
- | `lumen_search` | Rank natural-language matches across component descriptions, curated collections, props, attributes, recipes, tokens, and agent rules. |
18
+ | `lumen_search` | Rank tolerant natural-language matches across component descriptions, curated collections, framework contracts, props, attributes, recipes, tokens, and agent rules. Optionally filter component contracts by framework. |
19
+ | `lumen_get_meta` | Return deterministic snapshot provenance, package versions, schema version, component count, and catalog hash. |
20
+ | `lumen_get_catalog_manifest` | Return stable per-component and per-recipe fingerprints that clients can retain between upgrades. |
21
+ | `lumen_diff_catalog` | Compare a retained manifest with the current snapshot and report added, changed, removed, and unchanged entries. |
22
+ | `lumen_diagnose` | Verify snapshot integrity and report framework coverage when testing a connection. |
19
23
  | `lumen_get_tokens` | Return semantic token names, base colors, glass tokens, and the theme attribute. |
20
24
  | `lumen_get_rules` | Return the Lumen agent rules from `llms.txt`. |
21
25
 
@@ -25,6 +29,9 @@ Every tool returns both readable text and validated `structuredContent`.
25
29
 
26
30
  | Resource | Contents |
27
31
  | --- | --- |
32
+ | `lumen://meta` | Snapshot provenance, package versions, and deterministic catalog hash. |
33
+ | `lumen://catalog-manifest` | Stable component and recipe fingerprints for change detection. |
34
+ | `lumen://diagnostics` | Snapshot integrity results and framework coverage. |
28
35
  | `lumen://rules` | Agent rules as Markdown. |
29
36
  | `lumen://tokens` | Structured design tokens. |
30
37
  | `lumen://components` | Compact component catalog. |
@@ -39,6 +46,8 @@ The stdio command for any MCP client is:
39
46
  npx -y @santi020k/lumen-mcp
40
47
  ```
41
48
 
49
+ Node.js 20.20 or newer is supported.
50
+
42
51
  ### Codex
43
52
 
44
53
  Register it from the Codex CLI:
@@ -108,19 +117,38 @@ Use the equivalent of this generic configuration:
108
117
  Client configuration envelopes differ, but the command and arguments remain
109
118
  the same.
110
119
 
120
+ ### Streamable HTTP
121
+
122
+ For local network clients or a self-hosted deployment, start the included
123
+ Streamable HTTP transport:
124
+
125
+ ```bash
126
+ npx -y --package @santi020k/lumen-mcp lumen-mcp-http
127
+ ```
128
+
129
+ It binds to `127.0.0.1:3000` by default and exposes MCP at
130
+ `http://127.0.0.1:3000/mcp` plus a health check at `/health`. Configure it with
131
+ `LUMEN_MCP_HOST`, `LUMEN_MCP_PORT`, and a comma-separated
132
+ `LUMEN_MCP_ALLOWED_HOSTS`. Binding to a public interface requires the
133
+ deployment's authentication, TLS, rate limiting, and host allowlist; the
134
+ package does not provide those infrastructure controls.
135
+
111
136
  ## Recommended agent workflow
112
137
 
113
- 1. Read `lumen://rules`.
114
- 2. Call `lumen_search` with the requested use case and target framework.
115
- 3. Call `lumen_get_component` with `detail: "usage"`.
116
- 4. Inspect a related recipe with `lumen_get_recipe` when the UI needs multiple primitives.
117
- 5. Request `detail: "source"` only when the usage contract is insufficient.
118
- 6. Use `lumen_get_tokens` before adding custom styling.
138
+ 1. Read `lumen://meta` and call `lumen_diagnose` to identify and verify the bundled snapshot.
139
+ 2. Read `lumen://rules`.
140
+ 3. Call `lumen_search` with the requested use case and target framework.
141
+ 4. Call `lumen_get_component` with `detail: "usage"`.
142
+ 5. Follow the returned framework behavior section: mount Astro `UIPrimitives` once, use the named React hook/controller, or register custom elements once.
143
+ 6. Inspect a related recipe with `lumen_get_recipe` when the UI needs multiple primitives.
144
+ 7. Request `detail: "source"` only when the usage contract is insufficient.
145
+ 8. Use `lumen_get_tokens` before adding custom styling.
146
+ 9. Cache `lumen://catalog-manifest`; after an upgrade, pass it to `lumen_diff_catalog` to refresh only changed contracts.
119
147
 
120
148
  Example calls:
121
149
 
122
150
  ```json
123
- { "name": "lumen_search", "arguments": { "query": "accessible date input", "limit": 5 } }
151
+ { "name": "lumen_search", "arguments": { "query": "accessible date input", "framework": "react", "limit": 5 } }
124
152
  ```
125
153
 
126
154
  ```json
@@ -163,6 +191,9 @@ pnpm --filter @santi020k/lumen-mcp generate
163
191
  pnpm --filter @santi020k/lumen-mcp build
164
192
  pnpm --filter @santi020k/lumen-mcp test
165
193
  pnpm --filter @santi020k/lumen-mcp test:coverage
194
+ pnpm --filter @santi020k/lumen-mcp check:snapshot
195
+ pnpm --filter @santi020k/lumen-mcp eval
196
+ pnpm --filter @santi020k/lumen-mcp smoke:package
166
197
  ```
167
198
 
168
199
  ## Programmatic use
@@ -172,11 +203,13 @@ The reusable handlers return readable text plus typed data:
172
203
  ```ts
173
204
  import {
174
205
  getComponent,
206
+ getMeta,
175
207
  getRecipe,
176
208
  search
177
209
  } from '@santi020k/lumen-mcp/tools'
178
210
 
179
211
  const matches = search({ limit: 5, query: 'date input' }).data.results
212
+ const snapshot = getMeta().data.meta
180
213
 
181
214
  const component = getComponent({
182
215
  detail: 'usage',
@@ -209,7 +242,18 @@ const server = createLumenServer()
209
242
  - Design tokens, recipes, `docs/ai-usage.md`, and `llms.txt`.
210
243
 
211
244
  The output is deterministic and carries the package version used by the MCP
212
- initialization response.
245
+ initialization response. It also includes every framework package version, a
246
+ schema version, and a SHA-256 catalog hash. CI regenerates the snapshot and
247
+ fails when the committed file is stale. The release group versions the MCP
248
+ alongside Lumen framework packages, and the package smoke test installs the
249
+ packed artifact into a temporary consumer project before making a real stdio
250
+ handshake.
251
+
252
+ The evaluation gate also runs a curated natural-language search benchmark,
253
+ calls every available component/framework contract through MCP, parses all
254
+ Astro and custom-element examples, and type-checks every React example against
255
+ the built React package. A generated example that is syntactically valid but
256
+ uses an unsupported React prop therefore fails before release.
213
257
 
214
258
  ## License
215
259
 
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import { startLumenMcpHttp } from '../dist/index.js'
3
+
4
+ const port = Number.parseInt(process.env.LUMEN_MCP_PORT ?? '3000', 10)
5
+ const host = process.env.LUMEN_MCP_HOST ?? '127.0.0.1'
6
+
7
+ const allowedHosts = process.env.LUMEN_MCP_ALLOWED_HOSTS
8
+ ?.split(',')
9
+ .map((value) => value.trim())
10
+ .filter(Boolean)
11
+
12
+ try {
13
+ const { url } = await startLumenMcpHttp({ allowedHosts, host, port })
14
+
15
+ process.stderr.write(`lumen-mcp listening at ${url.href}\n`)
16
+ } catch (error) {
17
+ process.stderr.write('lumen-mcp HTTP transport failed to start: ' + String(error) + '\n')
18
+
19
+ process.exitCode = 1
20
+ }
@@ -1,6 +1,4 @@
1
-
2
1
  import { spawnSync } from 'node:child_process'
3
- import { access, readdir as readDirectory, stat as getStat } from 'node:fs/promises'
4
2
  import { dirname, join, resolve } from 'node:path'
5
3
  import { fileURLToPath } from 'node:url'
6
4
 
@@ -9,7 +7,8 @@ const repoRoot = resolve(packageRoot, '../..')
9
7
  const distEntry = join(packageRoot, 'dist/index.js')
10
8
 
11
9
  const filesIn = async (directory) => {
12
- const entries = await readDirectory(directory, { withFileTypes: true })
10
+ const fs = await import('node:fs/promises')
11
+ const entries = await fs.readdir(directory, { withFileTypes: true })
13
12
 
14
13
  const nested = await Promise.all(entries.map((entry) => {
15
14
  const path = join(directory, entry.name)
@@ -21,15 +20,17 @@ const filesIn = async (directory) => {
21
20
  }
22
21
 
23
22
  const needsBuild = async () => {
23
+ const fs = await import('node:fs/promises')
24
+
24
25
  try {
25
- await access(distEntry)
26
+ await fs.access(distEntry)
26
27
  } catch {
27
28
  return true
28
29
  }
29
30
 
30
- const outputTime = (await getStat(distEntry)).mtimeMs
31
+ const outputTime = (await fs.stat(distEntry)).mtimeMs
31
32
  const sourceFiles = await filesIn(join(packageRoot, 'src'))
32
- const sourceTimes = await Promise.all(sourceFiles.map(async (path) => (await getStat(path)).mtimeMs))
33
+ const sourceTimes = await Promise.all(sourceFiles.map(async (path) => (await fs.stat(path)).mtimeMs))
33
34
 
34
35
  return sourceTimes.some((modifiedTime) => modifiedTime > outputTime)
35
36
  }