@pymodel/niblet 0.2.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.
Files changed (3) hide show
  1. package/README.md +59 -41
  2. package/package.json +1 -1
  3. package/src/server.mjs +37 -2
package/README.md CHANGED
@@ -5,8 +5,6 @@
5
5
  </picture>
6
6
  </p>
7
7
 
8
- <h1 align="center">Niblet MCP</h1>
9
-
10
8
  <p align="center">Real screen references and a design skill, for coding agents that build UI.</p>
11
9
 
12
10
  <p align="center">
@@ -27,34 +25,44 @@
27
25
  By <a href="https://github.com/elkaix">elkaix</a> for <a href="https://github.com/PyModel">PyModel</a>
28
26
  </p>
29
27
 
30
- An MCP server that gives a coding agent two things while it builds UI: real screen references from the [Niblet](https://niblet.com) catalogue, and a design skill that keeps the agent working from your product instead of a generic template.
28
+ A coding agent building UI gets two things here: a design skill that keeps it working from your product rather than a generic template, and real screen references from the [Niblet](https://niblet.com) catalogue when a specific visual question is still open.
31
29
 
32
- The skill works on its own. The MCP server is optional, and needs a token.
30
+ The skill works alone. The server is optional and needs a token.
33
31
 
34
- ## What you get
32
+ ## Install the skill
35
33
 
36
- Two tools, matching the hosted service exactly:
34
+ ```sh
35
+ npx skills add PyModel/niblet-skill-mcp --skill niblet -y
36
+ ```
37
37
 
38
- | Tool | Use it for |
39
- | --- | --- |
40
- | `find_ui_references` | One concrete unresolved question about a layout, state, or interaction. Returns one to three real screens as inline images. |
41
- | `find_ui_materials` | A named font, icon, or animated icon role your design system does not already cover. Returns the recorded license with each result. |
38
+ Or copy it yourself, keeping `references/`, `agents/`, `LICENSE`, and `NOTICE` alongside it:
42
39
 
43
- | `niblet_help` | “What can Niblet do?”, or choosing between commands. Lists the four surface modes and every command with its purpose; pass `command` for one entry. |
44
- | `niblet_status` | Diagnosing the connection. Reports the configured origins, whether a usable token is present (never the token), which bundled documents are readable, and whether the API actually answers. |
40
+ ```sh
41
+ cp -r skill/niblet ~/.claude/skills/niblet
42
+ ```
45
43
 
46
- The bundled documents are served as resources and need no token: `niblet://skill` for the workflow, plus `niblet://skill/commands`, `niblet://skill/connection`, `niblet://skill/evidence`, and `niblet://skill/native`. Their cross-links are rewritten to these URIs on the way out, so an agent reading them over MCP can follow every reference.
44
+ Then ask for it by name:
47
45
 
48
- ## Connect
46
+ > Use Niblet to design the checkout empty and error states.
47
+
48
+ The [workflow](skill/niblet/SKILL.md) settles the screen's job, primary action, hierarchy, existing tokens, real states, and acceptance criteria before writing anything. It finishes by rendering the surface and exercising it, so a green build on its own does not count as a pass.
49
49
 
50
- You do not have to run this server. If your host speaks HTTP MCP, point it at the hosted endpoint and skip to [the skill](#the-skill):
50
+ ## Connect the server
51
+
52
+ Pick one. Hosted, if your host speaks HTTP MCP:
51
53
 
52
54
  ```sh
53
55
  claude mcp add --transport http niblet https://api.niblet.com/mcp \
54
56
  --header "Authorization: Bearer $NIBLET_TOKEN"
55
57
  ```
56
58
 
57
- To run it locally over stdio instead, add this to your host's MCP config. Node.js 24.15 or later is required; npx fetches the package on first launch.
59
+ Local over stdio, via the Claude Code CLI:
60
+
61
+ ```sh
62
+ claude mcp add niblet --env NIBLET_TOKEN=$NIBLET_TOKEN -- npx -y @pymodel/niblet
63
+ ```
64
+
65
+ Or the equivalent in any host's MCP config file:
58
66
 
59
67
  ```json
60
68
  {
@@ -68,40 +76,49 @@ To run it locally over stdio instead, add this to your host's MCP config. Node.j
68
76
  }
69
77
  ```
70
78
 
71
- Or with the Claude Code CLI:
79
+ Node.js 24.15+; npx fetches the package on first launch. Get a token from [niblet.com/docs](https://niblet.com/docs) and keep it in your host's environment, never in a committed file or a chat message.
72
80
 
73
- ```sh
74
- claude mcp add niblet --env NIBLET_TOKEN=$NIBLET_TOKEN -- npx -y @pymodel/niblet
75
- ```
81
+ Saving the config does not register the server, so confirm it worked. `niblet_status` reports the configured origins, whether a usable token is present, and whether the API answers:
76
82
 
77
- Saving the config does not register the server — check the tool list your host actually reports.
78
-
79
- Get a token from [niblet.com/docs](https://niblet.com/docs). Keep it in `.env` or your host's environment; never in a committed file or a chat message.
83
+ ```
84
+ Token: present (44 characters, not shown).
85
+ Documents: 5/5 readable (niblet://skill, …).
86
+ API check: OK
87
+ ```
80
88
 
81
- `NIBLET_API_ORIGIN` and `NIBLET_MEDIA_ORIGIN` retarget the server at a local deployment. Leave them unset for production.
89
+ ## Tools
82
90
 
83
- ## The skill
91
+ | Tool | Use it for | Token |
92
+ | --- | --- | --- |
93
+ | `find_ui_references` | One concrete unresolved question about a layout, state, or interaction. Returns one to three real screens as inline images. | yes |
94
+ | `find_ui_materials` | A font, icon, or animated icon role your design system does not already cover. Returns the recorded license with each result. | yes |
95
+ | `get_design_reference` | The colours, typography, and components recorded for a web screen you already picked. Pass the `screenId` from a reference, or a pack slug. | yes |
96
+ | `niblet_help` | "What can Niblet do?", or choosing between commands. Lists the four surface modes and every command; pass `command` for one entry. | no |
97
+ | `niblet_status` | Diagnosing the connection before concluding the catalogue is empty. Never prints the token. | no |
84
98
 
85
- <img src="https://raw.githubusercontent.com/PyModel/niblet-skill-mcp/main/assets/niblet-mascot-working.svg" alt="" width="72" height="72" align="right">
99
+ The three catalogue tools match the hosted service exactly. `niblet_help` and `niblet_status` are local-only.
86
100
 
87
- Install it straight from the repository:
101
+ Only web screens carry a design reference, and a web result says so in its own text, so an agent that finds a screen worth borrowing from can read the system behind it in one follow-up call.
88
102
 
89
- ```sh
90
- npx skills add PyModel/niblet-skill-mcp --skill niblet -y
91
- ```
103
+ ## Resources
92
104
 
93
- Or copy [skill/niblet](skill/niblet) into your host's skill directory yourself, keeping `references/`, `agents/`, `LICENSE`, and `NOTICE` alongside it:
105
+ The bundled documents, served without a token. Cross-links between them are rewritten to these URIs, so an agent reading one can follow every reference.
94
106
 
95
- ```sh
96
- cp -r skill/niblet ~/.claude/skills/niblet
97
- ```
98
-
99
- Then ask for it by name:
107
+ | URI | Contents |
108
+ | --- | --- |
109
+ | `niblet://skill` | The design workflow: modes, contract, state coverage, finish gate |
110
+ | `niblet://skill/commands` | Every command, its scope, and what completion means |
111
+ | `niblet://skill/connection` | Installing, invoking, and diagnosing the tools |
112
+ | `niblet://skill/evidence` | When to pull an external reference, and how to use one |
113
+ | `niblet://skill/native` | Platform constraints and the native finish gate |
100
114
 
101
- > Use Niblet to design the checkout empty and error states.
102
- > Run a niblet-skill review of the settings screen.
115
+ ## Configuration
103
116
 
104
- The [workflow](skill/niblet/SKILL.md) makes the agent establish the screen's job, primary action, hierarchy, existing tokens, real states, and acceptance criteria before it writes anything. It pulls references only when a specific question is still open, and it finishes by rendering the surface and exercising it. A green build is not a pass.
117
+ | Variable | Purpose |
118
+ | --- | --- |
119
+ | `NIBLET_TOKEN` | Required by the two catalogue tools. |
120
+ | `NIBLET_API_ORIGIN` | Retarget at a local deployment. Unset for production. |
121
+ | `NIBLET_MEDIA_ORIGIN` | Same, for images. Unset for production. |
105
122
 
106
123
  ## Contributing
107
124
 
@@ -110,11 +127,12 @@ git clone https://github.com/PyModel/niblet-skill-mcp
110
127
  cd niblet-skill-mcp
111
128
  npm ci --ignore-scripts
112
129
  cp .env.example .env # then put your token in NIBLET_TOKEN
130
+ npm test
113
131
  ```
114
132
 
115
- `npm test` covers the tool contract and its failure boundaries. For anything touching startup or configuration, also connect a real MCP client and confirm the tool list and every `niblet://skill` resource a resource that registers but never appears in `resources/list` is the failure the unit tests cannot catch. For documentation, check that relative links resolve and that `npm pack --dry-run` still ships what you expect.
133
+ `npm test` covers the tool contract and its failure boundaries. For anything touching startup or configuration, also connect a real MCP client and confirm the reported tool list and every `niblet://skill` resource. A resource that registers but never appears in `resources/list` is the failure unit tests cannot catch. For documentation, check that `npm pack --dry-run` still ships what you expect.
116
134
 
117
- [AGENTS.md](AGENTS.md) has the details if you are pointing a coding agent at this repository.
135
+ [AGENTS.md](AGENTS.md) has the working agreement for pointing a coding agent at this repository.
118
136
 
119
137
  ## License
120
138
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pymodel/niblet",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Niblet MCP server and design skill: real UI screen references for coding agents",
5
5
  "keywords": [
6
6
  "mcp",
package/src/server.mjs CHANGED
@@ -157,7 +157,7 @@ export function createServer({
157
157
 
158
158
  const server = new McpServer(
159
159
  { name: 'niblet', version: pkg.version, websiteUrl: 'https://niblet.com' },
160
- { instructions: `Read niblet://skill for the Niblet design workflow; its reference documents are served alongside it (niblet://skill/commands, /connection, /evidence, /native). Call niblet_help to list the surface modes and every design command, or when asked what Niblet can do; call niblet_status to diagnose the connection before concluding the catalogue is empty. ${UNTRUSTED_DATA} The two catalogue tools require NIBLET_TOKEN; the bundled skill, niblet_help, and niblet_status do not. This server only reads ${API_ORIGIN}/v1 and does not provide a remote UI review service.` },
160
+ { instructions: `Read niblet://skill for the Niblet design workflow; its reference documents are served alongside it (niblet://skill/commands, /connection, /evidence, /native). Call niblet_help to list the surface modes and every design command, or when asked what Niblet can do; call niblet_status to diagnose the connection before concluding the catalogue is empty. ${UNTRUSTED_DATA} After picking a web reference, call get_design_reference with its screenId for the recorded colors, typography, and components. The catalogue tools require NIBLET_TOKEN; the bundled skill, niblet_help, and niblet_status do not. This server only reads ${API_ORIGIN}/v1 and does not provide a remote UI review service.` },
161
161
  );
162
162
 
163
163
  function credentialError() {
@@ -296,7 +296,11 @@ export function createServer({
296
296
  // The API treats `limit` as advisory, so bound the fan-out here: one image fetch per ref.
297
297
  const refs = all.slice(0, input.limit);
298
298
 
299
- const content = [{ type: 'text', text: [REFERENCE_PREAMBLE, '', ...refs.map(refText)].join('\n') }];
299
+ // Only web screens belong to a design pack, so only they get the follow-up pointer.
300
+ const pointer = refs.some((ref) => ref.platform === 'web')
301
+ ? ['', 'A full style reference is recorded for the web screens above. Call get_design_reference with the screenId to read its colors, typography, and components.']
302
+ : [];
303
+ const content = [{ type: 'text', text: [REFERENCE_PREAMBLE, '', ...refs.map(refText), ...pointer].join('\n') }];
300
304
  for (const [index, ref] of refs.entries()) {
301
305
  const image = await fetchImage(ref.thumbUrl, extra.signal);
302
306
  // Keep one block per reference so position still identifies which screen an image belongs to.
@@ -349,6 +353,37 @@ export function createServer({
349
353
 
350
354
  const localAnnotations = { ...annotations, openWorldHint: false };
351
355
 
356
+ server.registerTool('get_design_reference', {
357
+ title: 'Get design reference',
358
+ description: 'Read the recorded style reference for a web screen returned by find_ui_references, or for a design pack by slug: colors with their roles, typography, and component inventory, as markdown. Only web screens have one.',
359
+ inputSchema: z.object({
360
+ screenId: screenId.optional().describe('A screen ID from find_ui_references.'),
361
+ packSlug: z.string().min(1).max(160).optional().describe('A design pack slug, when the pack is already known.'),
362
+ clientSkillVersion: clientSkillVersion.optional(),
363
+ }).strict().refine((value) => value.screenId !== undefined || value.packSlug !== undefined, 'Pass screenId or packSlug.'),
364
+ annotations,
365
+ }, async (input, extra) => {
366
+ const credential = credentialError();
367
+ if (credential) return errorResult(credential);
368
+
369
+ const result = await requestJson(['design-reference'], { screenId: input.screenId, slug: input.packSlug }, extra.signal);
370
+ if (!result.ok) {
371
+ if (result.status === 404) {
372
+ return textResult(
373
+ input.screenId
374
+ ? 'No style reference is recorded for that screen. Only web screens have one; continue with the local design system.'
375
+ : 'No design pack with that slug. Continue with the local design system.',
376
+ );
377
+ }
378
+ return errorResult(result.message);
379
+ }
380
+ const markdown = result.data?.markdown;
381
+ if (typeof markdown !== 'string' || markdown.trim() === '') return errorResult('Niblet API returned an invalid response.');
382
+ const slug = field(result.data.slug ?? '', 160);
383
+ const source = slug ? `\n\nSource: https://niblet.com/packs/${slug}` : '';
384
+ return { content: [{ type: 'text', text: `${REFERENCE_PREAMBLE}${source}` }, { type: 'text', text: field(markdown, 40_000) }] };
385
+ });
386
+
352
387
  server.registerTool('niblet_help', {
353
388
  title: 'Niblet help',
354
389
  description: 'List everything Niblet offers: the surface modes, every design command with its purpose, and the reference documents available as resources. Use when asked what Niblet can do, which command fits, or to present the choice menu before making changes.',