@pascalcouturier/skill-builder-mcp 3.0.0 → 3.1.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/CHANGELOG.md CHANGED
@@ -9,6 +9,36 @@ The install line names the major version (`@3`), so restarting your AI tool or i
9
9
  fetches the newest release of that major (see **Updating** in the
10
10
  [README](./README.md#updating)).
11
11
 
12
+ ## 3.1.1
13
+
14
+ Security update. Same protocol, same tools, same install line (`@3`): restarting your AI tool
15
+ picks it up.
16
+
17
+ - Updates the bundled MCP SDK from 1.31.0 to 1.32.1, which fixes two published advisories
18
+ (tasks not tied to the session that created them, and cross-origin redirects that re-sent
19
+ headers and bodies). The connector only talks to Skill Builder over a local connection, so the
20
+ exposure was low, but it ships that code.
21
+
22
+ ## 3.1.0
23
+
24
+ Same protocol and tools; the install line stays `@3`, so restarting your AI tool picks it up.
25
+ The tool definitions it offers before Skill Builder connects now match the app's:
26
+
27
+ - `validate` takes `documentId` (check one skill or agent) and `since` (the revision of your
28
+ last result: only what changed comes back), for loops that check after every round.
29
+ - `add_document` adds a whole workflow when `templateId` is a workflow id from
30
+ `list_templates`.
31
+ - `get_standards` has the topic `improve`, the `/improve-until` routine, for AI tools that do
32
+ not show slash commands.
33
+ - `apply_fix` accepts `set-loop-limit`, the fix for a loop with no limit.
34
+ - **A refresh keeps the pairing.** After you pair, the connector hands Skill Builder a
35
+ one-use resume secret (kept in memory only). If you refresh the page within two minutes, Skill
36
+ Builder proves it holds the secret and the AI tool stays connected, with no new code. Closing
37
+ the tab, a new window or tab, restoring tabs after a browser restart, or restarting the AI
38
+ tool still asks for a new code. Older tabs and connectors are unaffected: the messages are
39
+ only used between 3.1.0 and an app that knows them. Disconnecting or denying in Skill Builder,
40
+ or turning Connect AI off, closes with code 4000 and the connector forgets the secret at once.
41
+
12
42
  ## 3.0.0
13
43
 
14
44
  Fewer, grouped tools, so AI tools read about a third less with every request and choose tools
package/README.md CHANGED
@@ -155,7 +155,10 @@ The code is made by the connector and shown only in your AI tool; it never trave
155
155
  connection. Both sides prove they know it, so no other program on your computer can pose as
156
156
  Skill Builder or as the connector. Three wrong codes close the connection; ten in one run
157
157
  turn pairing off until you restart the AI tool. You pair again after restarting the AI tool
158
- or reloading Skill Builder; the code is new every time and you never need to keep it.
158
+ or in a new Skill Builder window; the code is new every time and you never need to keep it.
159
+ Refreshing the page keeps the pairing (within two minutes, and until the AI tool restarts): the
160
+ connector gives Skill Builder a one-use secret that it keeps as a key no script can read, and
161
+ Skill Builder proves it holds it, so there is no new code.
159
162
 
160
163
  ## What you can ask
161
164
 
@@ -210,49 +213,49 @@ The AI can do what you can do in the app, except anything to do with the cloud (
210
213
  sync, the AI check, the community library). Tools marked **asks** wait for your confirmation
211
214
  in Skill Builder.
212
215
 
213
- | Tool | What it does | Asks |
214
- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
215
- | `get_status` | What is open: project, skill or agent, file, unsaved changes | |
216
- | `list_sessions` | Search, filter, sort and page projects | |
217
- | `get_session` | A project's skills and agents, files and links; with `canvas`, the canvas graph | |
218
- | `get_document` | A skill or agent with every block (uids, types, values); with `fileId`, the markdown a file produces or a script's text | |
219
- | `list_block_types` | Block types and their fields | |
220
- | `list_templates` | Templates for new skills, agents and rules, with best practice for writing them | |
221
- | `list_library` | Built-in skills and your library items | |
222
- | `get_library_item` | One library item with its files, scan result and versions | |
223
- | `validate` | The checks, with one-click fixes, each with why it matters and its source; with `files`, checks skill or agent files from your repository (nothing is stored) | |
224
- | `analyze_project` | The project report's analysis: scorecard, findings with evidence, context cost per AI tool, competition, portability, tests; returns a baseline id | |
225
- | `compare_analysis` | Before and after a baseline, computed by Skill Builder (characters, findings, tests) | |
226
- | `get_standards` | Best practice per topic and per AI tool, with sources and the date each was checked | |
227
- | `use_skill` | Use a skill, agent, rule or instructions you shared: main file, then files on demand | first use |
228
- | `preview_export` | The files the zip would contain (ready for a project, or plain folders) | |
229
- | `versions` | Saved versions (`list`), or line changes between versions or against the working copy (`diff`) | |
230
- | `create_session` | New project | |
231
- | `open_session` | Show a project, skill or agent, or file in the app | |
232
- | `update_session` | Rename, change author or description, star | |
233
- | `duplicate` | Copy a project (`session`) or a skill or agent (`document`) | |
234
- | `delete` | Delete a project and its versions, a skill or agent, a file, a custom block type or a library item | asks |
235
- | `add_document` | Add a skill, agent or tool file, optionally from a template, or a copy of a library item (`fromLibrary`) | |
236
- | `update_document` | Set name, description, licence, allowed tools, model… | |
237
- | `write_project_instructions` | Create or update AGENTS.md and CLAUDE.md from your repository (`/init-agents-md`) | |
238
- | `repository_import` | Import your repository's AI files into a new project: `start`, `add_files` (up to 50 per call), `finish` (`/import-project`) | |
239
- | `add_block` | Add a block with values at a position | |
240
- | `update_block` | Change block values, collapse or expand | |
241
- | `block_action` | `move` (up, down or to another file), `duplicate`, `remove`, `convert` to Custom markdown, `break_down` into built-in blocks, `extract` to a new reference file | |
242
- | `add_files` | Add several supporting files by path (scanned like uploads) | |
243
- | `file_action` | `add` a reference, script or asset, `write` a script or data file (scanned, never run), `move` it to another folder (`scripts/test`), `rename` a reference | |
244
- | `link` | `connect` an agent to a skill, or point a reference or Resource link block; `disconnect` removes the link | |
245
- | `update_evaluation` | Save tests for a skill or agent, or the results of running them (`/evaluate-skill`) | |
246
- | `apply_fix` | Apply a fix from `validate` | |
247
- | `save_version` | Save a version, only when you ask (bump suggested from the changes) | asks |
248
- | `restore_version` | Replace the working copy with a version | asks |
249
- | `download` | Download the zip (refused if the scan finds a secret) or the project report as HTML or Markdown (`project`), or a backup (`backup`) | asks |
250
- | `tidy_canvas` | Tidy the canvas layout | |
251
- | `undo_ai_changes` | Same as **Undo AI changes** | if you edited |
252
- | `save_block_type` | Create or edit a custom block type | |
253
- | `library_item` | Your library: `add` a skill, agent or rule from markdown, `update` it (a new version), `restore_version`, `copy_builtin` (an editable copy of a built-in skill), `set_hidden` | if warnings |
254
- | `import_from_github` | List and import skills and agents from a public GitHub repo | if warnings |
255
- | `set_preferences` | Default author, Blocks or Canvas view, block matching on import | |
216
+ | Tool | What it does | Asks |
217
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
218
+ | `get_status` | What is open: project, skill or agent, file, unsaved changes | |
219
+ | `list_sessions` | Search, filter, sort and page projects | |
220
+ | `get_session` | A project's skills and agents, files and links; with `canvas`, the canvas graph | |
221
+ | `get_document` | A skill or agent with every block (uids, types, values); with `fileId`, the markdown a file produces or a script's text | |
222
+ | `list_block_types` | Block types and their fields | |
223
+ | `list_templates` | Templates for new skills, agents and rules, with best practice for writing them | |
224
+ | `list_library` | Built-in skills, agents and rules, and your library items | |
225
+ | `get_library_item` | One library item with its files, scan result and versions | |
226
+ | `validate` | The checks, with one-click fixes, each with why it matters and its source; with `files`, checks skill or agent files from your repository (nothing is stored); `documentId` checks one item and `since` returns only what changed since your last result | |
227
+ | `analyze_project` | The project report's analysis: scorecard, findings with evidence, context cost per AI tool, competition, portability, tests; returns a baseline id | |
228
+ | `compare_analysis` | Before and after a baseline, computed by Skill Builder (characters, findings, tests) | |
229
+ | `get_standards` | Best practice per topic and per AI tool, with sources and the date each was checked; topic `improve` is the `/improve-until` routine | |
230
+ | `use_skill` | Use a skill, agent, rule or instructions you shared: main file, then files on demand | first use |
231
+ | `preview_export` | The files the zip would contain (ready for a project, or plain folders) | |
232
+ | `versions` | Saved versions (`list`), or line changes between versions or against the working copy (`diff`) | |
233
+ | `create_session` | New project | |
234
+ | `open_session` | Show a project, skill or agent, or file in the app | |
235
+ | `update_session` | Rename, change author or description, star | |
236
+ | `duplicate` | Copy a project (`session`) or a skill or agent (`document`) | |
237
+ | `delete` | Delete a project and its versions, a skill or agent, a file, a custom block type or a library item | asks |
238
+ | `add_document` | Add a skill, agent or tool file, optionally from a template or a whole workflow (a workflow id from `list_templates`), or a copy of a library item (`fromLibrary`) | |
239
+ | `update_document` | Set name, description, licence, allowed tools, model… | |
240
+ | `write_project_instructions` | Create or update AGENTS.md and CLAUDE.md from your repository (`/init-agents-md`) | |
241
+ | `repository_import` | Import your repository's AI files into a new project: `start`, `add_files` (up to 50 per call), `finish` (`/import-project`) | |
242
+ | `add_block` | Add a block with values at a position | |
243
+ | `update_block` | Change block values, collapse or expand | |
244
+ | `block_action` | `move` (up, down or to another file), `duplicate`, `remove`, `convert` to Custom markdown, `break_down` into built-in blocks, `extract` to a new reference file | |
245
+ | `add_files` | Add several supporting files by path (scanned like uploads) | |
246
+ | `file_action` | `add` a reference, script or asset, `write` a script or data file (scanned, never run), `move` it to another folder (`scripts/test`), `rename` a reference | |
247
+ | `link` | `connect` an agent to a skill, or point a reference or Resource link block; `disconnect` removes the link | |
248
+ | `update_evaluation` | Save tests for a skill or agent, or the results of running them (`/evaluate-skill`) | |
249
+ | `apply_fix` | Apply a fix from `validate`, such as adding or limiting a Completion goal | |
250
+ | `save_version` | Save a version, only when you ask (bump suggested from the changes) | asks |
251
+ | `restore_version` | Replace the working copy with a version | asks |
252
+ | `download` | Download the zip (refused if the scan finds a secret) or the project report as HTML or Markdown (`project`), or a backup (`backup`) | asks |
253
+ | `tidy_canvas` | Tidy the canvas layout | |
254
+ | `undo_ai_changes` | Same as **Undo AI changes** | if you edited |
255
+ | `save_block_type` | Create or edit a custom block type | |
256
+ | `library_item` | Your library: `add` a skill, agent or rule from markdown, `update` it (a new version), `restore_version`, `copy_builtin` (an editable copy of a built-in skill), `set_hidden` | if warnings |
257
+ | `import_from_github` | List and import skills and agents from a public GitHub repo | if warnings |
258
+ | `set_preferences` | Default author, Blocks or Canvas view, block matching on import | |
256
259
 
257
260
  Edits carry the project's revision. If you changed the project since the AI last read it,
258
261
  the edit is refused and the AI reads it again, so it never overwrites your work by accident.
@@ -434,7 +437,8 @@ Nothing else is installed, and the connector leaves no files behind.
434
437
  - **ChatGPT** (web and desktop) only supports remote MCP servers, so it can not use this
435
438
  connector.
436
439
  - **Safari** is not supported.
437
- - **Pair on each start:** pairing is not remembered between restarts or reloads.
440
+ - **Pair on each start:** pairing is not remembered after the AI tool restarts, or in a new
441
+ window or tab. A refresh keeps it.
438
442
  - **Cloud features** (sign-in, sync, the AI check, the community library) are not available
439
443
  to AI tools.
440
444
 
package/SECURITY.md CHANGED
@@ -23,7 +23,11 @@ it does and does not do, how that is enforced, and how to check it yourself.
23
23
  - **No code from outside.** It never evaluates code it receives, and it does not generate
24
24
  code at runtime (`eval` and `new Function` are never called).
25
25
  - **No telemetry, no analytics, no accounts, no cloud.**
26
- - **No secrets stored.** Keys are made fresh for each connection and kept in memory only.
26
+ - **No secrets stored.** Keys are made fresh for each connection and kept in memory only. The
27
+ one exception is the resume secret that lets a refreshed Skill Builder tab reconnect without a
28
+ new code: it is random, used once, kept in the connector's memory only (never written
29
+ anywhere), accepted for two minutes after the tab left, and gone when the connector exits or when you
30
+ disconnect or turn Connect AI off in Skill Builder.
27
31
 
28
32
  These are checked by an automated test (`src/sandbox.test.ts`) that runs the exact file
29
33
  published to npm under Node's permission model (`--permission`, with read access to the
@@ -54,7 +58,8 @@ name, program start, file write or runtime code generation.
54
58
  `use_skill`): only items marked **Available to AI tools** in the project open in the tab, only
55
59
  after the person allows the first use in each connection, scanned each time (blocked content
56
60
  is never sent), script code only when allowed per item, and framed as guidance that never
57
- overrides the person.
61
+ overrides the person. An approval holds only for the text that was shown: a changed item asks
62
+ again.
58
63
 
59
64
  ## Secrets
60
65
 
@@ -65,8 +70,9 @@ name, program start, file write or runtime code generation.
65
70
  records every file and variable it touches; another checks the file loads nothing beyond
66
71
  networking, crypto and streams.
67
72
  - **Skill Builder refuses secrets in what the AI writes:** credentials by format (AWS, GitHub,
68
- Anthropic, OpenAI, Stripe, Google, npm, Slack tokens, JWTs, private keys), `.env`-style lines
69
- with a random-looking value, passwords in connection strings, and files named like secrets
73
+ Anthropic, OpenAI, Stripe, Google, GitLab, npm, Slack tokens, JWTs, private keys), `Bearer`
74
+ tokens, `.env`-style lines with a random-looking value (also when the value is on the next
75
+ line), passwords in connection strings, and files named like secrets
70
76
  (`.env`, `*.pem`, `id_rsa`, `credentials.json`). Placeholders such as `YOUR_API_KEY` are fine.
71
77
  The AI is told never to look for, repeat or store a secret.
72
78
  - **Your AI tool is what reads your project**, and only its own settings can limit that. For
@@ -122,9 +128,9 @@ commands, edit files). Treat any tool's output as untrusted, including this one'
122
128
 
123
129
  ```bash
124
130
  # What it contains, and its checksum
125
- npm pack @pascalcouturier/skill-builder-mcp@3.0.0
126
- tar -tzf pascalcouturier-skill-builder-mcp-2.5.0.tgz
127
- npm view @pascalcouturier/skill-builder-mcp@3.0.0 dist.integrity
131
+ npm pack @pascalcouturier/skill-builder-mcp@3.1.1
132
+ tar -tzf pascalcouturier-skill-builder-mcp-3.1.1.tgz
133
+ npm view @pascalcouturier/skill-builder-mcp@3.1.1 dist.integrity
128
134
 
129
135
  # That the registry signed it (in a project that installs it)
130
136
  npm audit signatures
@@ -146,7 +152,7 @@ the approved GitHub Actions workflow (no npm token exists).
146
152
 
147
153
  This means a release reaches you without you reviewing it first. If you want to review each one
148
154
  (for example in a company), put an exact version after the `@` in your AI tool's settings, such as
149
- `@3.0.0`, and update it yourself; Skill Builder still shows when your connector is out of date.
155
+ `@3.1.1`, and update it yourself; Skill Builder still shows when your connector is out of date.
150
156
  Use the commands above to look at any release before you run it.
151
157
 
152
158
  ## Reporting a problem
package/dist/cli.js CHANGED
@@ -7845,7 +7845,7 @@ var StdioServerTransport = class {
7845
7845
  };
7846
7846
  var package_default = {
7847
7847
  name: "@pascalcouturier/skill-builder-mcp",
7848
- version: "3.0.0",
7848
+ version: "3.1.1",
7849
7849
  description: "Connect AI tools (GitHub Copilot, Claude Code, Codex, Cursor, Claude Desktop) to Skill Builder open in your browser, over MCP.",
7850
7850
  license: "MIT",
7851
7851
  author: "Pascal Couturier (https://pascalcouturier.com)",
@@ -11532,6 +11532,13 @@ function isAllowedHost(host, port) {
11532
11532
  * the connector forwards calls, the tab answers with results.
11533
11533
  * 4. The tab also sends its prompt list (slash commands for the skills and
11534
11534
  * agents the person shared), and the connector asks for a prompt with `prompt`.
11535
+ * 5. Resuming (connector 3.1.0 and later): a page refresh loses the connection, but not the
11536
+ * pairing. The connector hands the tab a one-use resume secret after pairing (`paired.grant`,
11537
+ * asked for with `pair.resume`). The tab keeps it as a key it can use but never read, and
11538
+ * after a refresh proves it holds it (`resume`) instead of asking for a code. Each resume
11539
+ * replaces the secret, and the connector only accepts it for a short time after the tab left.
11540
+ * Nothing here changes what an older tab or connector can read: the tab only uses these
11541
+ * messages with a connector that says, by its version, that it understands them.
11535
11542
  */
11536
11543
  config({ jitless: true });
11537
11544
  /**
@@ -11540,6 +11547,11 @@ config({ jitless: true });
11540
11547
  * install line (`outdatedConnectorText` in install.ts), rather than keeping two tool lists.
11541
11548
  */
11542
11549
  var SUPPORTED_PROTOCOLS = [3];
11550
+ /**
11551
+ * The close code a tab sends when the person disconnected or denied on purpose (not a refresh or a
11552
+ * closed window). The connector forgets the way back at once instead of waiting out the window.
11553
+ */
11554
+ var CLOSE_LEFT_ON_PURPOSE = 4e3;
11543
11555
  var BRIDGE_LIMITS = {
11544
11556
  /** Largest frame either side accepts. */
11545
11557
  maxFrameBytes: 1048576,
@@ -11553,6 +11565,8 @@ var BRIDGE_LIMITS = {
11553
11565
  pairingTimeoutMs: 3e5,
11554
11566
  /** A socket that has not said hello by then is closed. */
11555
11567
  handshakeTimeoutMs: 1e4,
11568
+ /** How long after a tab leaves it may resume without a code: long enough for a refresh. */
11569
+ resumeWindowMs: 12e4,
11556
11570
  /** Wrong pairing codes allowed per connection. */
11557
11571
  pairingAttempts: 3,
11558
11572
  /** Wrong pairing codes allowed per connector run; then it stops pairing until restarted. */
@@ -11666,12 +11680,36 @@ var promptResult = strictObject({
11666
11680
  text: string().max(9e5)
11667
11681
  })).min(1).max(10)
11668
11682
  });
11683
+ /** What lets a tab resume after a refresh: an id, and the secret it is the id of (base64, 32 bytes). */
11684
+ var resumeGrant = strictObject({
11685
+ id: string().min(16).max(64),
11686
+ secret: string().min(40).max(64)
11687
+ });
11669
11688
  /** Messages after pairing, by type. Bodies are validated on receipt. */
11670
11689
  var signedBodies = {
11671
- /** Tab: proof that the user typed the pairing code the connector showed in the AI tool. */
11672
- pair: strictObject({ proof: string().min(40).max(64) }),
11673
- /** Connector: the proof was right; here is the connector's own proof. */
11674
- paired: strictObject({ proof: string().min(40).max(64) }),
11690
+ /**
11691
+ * Tab: proof that the user typed the pairing code the connector showed in the AI tool.
11692
+ * `resume` asks for a resume grant; only sent to a connector that understands it.
11693
+ */
11694
+ pair: strictObject({
11695
+ proof: string().min(40).max(64),
11696
+ resume: boolean().optional()
11697
+ }),
11698
+ /**
11699
+ * Connector: the proof was right; here is the connector's own proof. `grant` answers `resume`
11700
+ * (after a code or after a resume): the secret to use next time.
11701
+ */
11702
+ paired: strictObject({
11703
+ proof: string().min(40).max(64),
11704
+ grant: resumeGrant.optional()
11705
+ }),
11706
+ /** Tab: proof that it holds the resume secret of `id`, bound to this connection. */
11707
+ resume: strictObject({
11708
+ id: string().min(16).max(64),
11709
+ proof: string().min(40).max(64)
11710
+ }),
11711
+ /** Connector: can not resume (unknown, used or expired). A code still works on this connection. */
11712
+ resumeFailed: strictObject({}),
11675
11713
  /** Connector: the code was wrong. The connection closes when no attempts are left. */
11676
11714
  pairFailed: strictObject({ attemptsLeft: number().int().min(0).max(10) }),
11677
11715
  /** Tab: its tool list. Sent after approval and again whenever it changes. */
@@ -11784,6 +11822,16 @@ var SOURCES = {
11784
11822
  url: "https://github.com/microsoft/vscode/blob/c480e9e49e2a116c458518ecbdec56d50e4af3d2/extensions/copilot/src/platform/configuration/common/configurationService.ts",
11785
11823
  checked: "2026-10-12"
11786
11824
  },
11825
+ "anthropic-building-agents": {
11826
+ title: "Anthropic: building effective agents",
11827
+ url: "https://www.anthropic.com/engineering/building-effective-agents",
11828
+ checked: "2026-10-13"
11829
+ },
11830
+ "claude-code-hooks": {
11831
+ title: "Claude Code: hooks",
11832
+ url: "https://code.claude.com/docs/en/hooks",
11833
+ checked: "2026-10-13"
11834
+ },
11787
11835
  "skill-creator": {
11788
11836
  title: "Anthropic skill-creator: testing skills",
11789
11837
  url: "https://github.com/anthropics/skills/tree/main/skills/skill-creator",
@@ -11796,7 +11844,7 @@ var cite = (...ids) => ids.map((id) => ({
11796
11844
  title: SOURCES[id].title,
11797
11845
  url: SOURCES[id].url
11798
11846
  }));
11799
- cite("agent-skills-spec", "anthropic-best-practices", "claude-code-skills", "vscode-skills"), cite("claude-code-subagents", "vscode-agents"), cite("agents-md", "claude-code-memory", "vscode-instructions"), cite("vscode-instructions", "claude-code-memory", "cursor-rules"), cite("skill-creator", "anthropic-best-practices");
11847
+ cite("agent-skills-spec", "anthropic-best-practices", "anthropic-building-agents", "claude-code-skills", "vscode-skills"), cite("claude-code-subagents", "vscode-agents", "anthropic-building-agents"), cite("agents-md", "claude-code-memory", "vscode-instructions"), cite("vscode-instructions", "claude-code-memory", "cursor-rules"), cite("skill-creator", "anthropic-best-practices");
11800
11848
  [
11801
11849
  "Context cost, from what is paid most often to least:",
11802
11850
  "1. Every request: skill and agent names and descriptions, AGENTS.md / CLAUDE.md, rules without paths. Keep descriptions to the main use case and trigger words; move procedures out of AGENTS.md into skills; give rules paths.",
@@ -11814,6 +11862,11 @@ cite("agent-skills-spec", "anthropic-best-practices", "claude-code-skills", "vsc
11814
11862
  "5. Report what compare_analysis returned, numbers as they are: characters saved per AI tool, findings cleared and added, test results before and after. Token counts are estimates. Never compute or estimate a change yourself.",
11815
11863
  "6. Do not save a version unless the person asks."
11816
11864
  ].join("\n");
11865
+ /** Rounds `/improve-until` takes by default, and at most. */
11866
+ var IMPROVE_ROUNDS = {
11867
+ default: 3,
11868
+ max: 10
11869
+ };
11817
11870
  [
11818
11871
  "To make one skill work in Claude Code, GitHub Copilot and Codex:",
11819
11872
  "- Use the fields every tool reads: name (lowercase, hyphens, the folder name) and description (under 1,024 characters, main use case first).",
@@ -11855,6 +11908,7 @@ function reviewPrompt(name) {
11855
11908
  " - The description, in the third person, says what it does and then when to use it (\"Use when the user asks to…\"), main use case first.",
11856
11909
  " - Skills: a short overview, when to use it (and when not), numbered steps, rules, the output, how to check the result, and one example. Keep SKILL.md under 500 lines; move detail into references/ files linked straight from SKILL.md, each with when to read it. Mention every script and data file and whether to run or read it.",
11857
11910
  " - Agents: a clear role, responsibilities, boundaries (always, ask first, never), only the tools the job needs, what to hand back (an Output block) and a definition of done. Link only skills it needs every time; refer to the others with a condition.",
11911
+ " - Work that repeats: when a step runs a check (tests, lint, a build) or says to repeat, add a Completion goal (the goal, the check each round, at most a few rounds, then ask). validate reports these as loop-opportunity or loop-implied with a one-click fix.",
11858
11912
  " - Plain, specific language; no filler, no dated text, forward slashes in paths; nothing the agent can not act on.",
11859
11913
  " - Each finding from validate says why it matters and where the rule comes from: use that to decide, and explain it in your summary.",
11860
11914
  "4. AI review: validate also returns aiReview, a rubric for what the coded checks can not judge (whether the description would be picked for the right requests and not near-misses, clear steps, the body matching the description, contradictions, missing inputs or outputs, overlap with other skills, safety). Apply each criterion to the files, fix what it finds the same way, and list the rest for the person with the evidence.",
@@ -11864,6 +11918,7 @@ function reviewPrompt(name) {
11864
11918
  }]
11865
11919
  };
11866
11920
  }
11921
+ `${IMPROVE_ROUNDS.max}${IMPROVE_ROUNDS.default}`;
11867
11922
  //#endregion
11868
11923
  //#region ../../src/core/bridge/tools.ts
11869
11924
  /**
@@ -11979,6 +12034,17 @@ var issueAction = discriminatedUnion("kind", [
11979
12034
  strictObject({
11980
12035
  kind: literal("preload-linked"),
11981
12036
  documentId: id
12037
+ }),
12038
+ strictObject({
12039
+ kind: literal("set-loop-limit"),
12040
+ documentId: id,
12041
+ blockUid: id,
12042
+ value: string().regex(/^\d{1,2}$/)
12043
+ }),
12044
+ strictObject({
12045
+ kind: literal("add-loop-goal"),
12046
+ documentId: id,
12047
+ check: string().max(300)
11982
12048
  })
11983
12049
  ]);
11984
12050
  var endpoint = strictObject({
@@ -12383,7 +12449,7 @@ var bridgeTools = {
12383
12449
  name: "list_library",
12384
12450
  title: "List library",
12385
12451
  kind: "read",
12386
- description: "Skills, agents and rules (Copilot path instructions, Cursor rules) in the library: built-in skills and your own items (imported, uploaded or copied). Items that failed the security scan are never listed.",
12452
+ description: "Skills, agents and rules (Copilot path instructions, Cursor rules) in the library: built-in skills, agents and rules, and your own items (imported, uploaded or copied). Built-in rules are added as path rules (mode claude-rule), which Claude Code, Copilot and Codex all follow. Items that failed the security scan are never listed.",
12387
12453
  input: strictObject({
12388
12454
  kind: libraryKind.optional(),
12389
12455
  source: _enum([
@@ -12409,14 +12475,16 @@ var bridgeTools = {
12409
12475
  name: "validate",
12410
12476
  title: "Check project",
12411
12477
  kind: "read",
12412
- description: "Runs the checks on a project (Agent Skills specification, official authoring guidance, links, long sections, syntax); issues with an `action` can be fixed with apply_fix. With files: checks skill and agent files you send instead, such as a skill folder in the user's repository, without storing anything (adds the security scan); send each file's repository path (e.g. .claude/skills/pdf/SKILL.md) and text, and never secret files (.env, keys). Run it before and after you edit a skill and fix every error. aiReview (on by default) is a rubric for what only a model can judge (clear steps, the description matching the body, contradictions, overlap): apply it yourself and report its findings with the rule ones; turn it off when re-checking.",
12478
+ description: "Runs the checks on a project (Agent Skills specification, official authoring guidance, links, long sections, syntax); issues with an `action` can be fixed with apply_fix. With files: checks skill and agent files you send instead, such as a skill folder in the user's repository, without storing anything (adds the security scan); send each file's repository path (e.g. .claude/skills/pdf/SKILL.md) and text, and never secret files (.env, keys). Run it before and after you edit a skill and fix every error. aiReview (on by default) is a rubric for what only a model can judge (clear steps, the description matching the body, contradictions, overlap): apply it yourself and report its findings with the rule ones. Re-checking in a loop: pass documentId and since (the revision of your last result) to get only what changed.",
12413
12479
  input: strictObject({
12414
12480
  sessionId: sessionRef,
12415
12481
  files: array(strictObject({
12416
12482
  path: string().min(1).max(300).describe("Path in the repository."),
12417
12483
  content: string().max(INPUT_LIMITS.code)
12418
12484
  })).min(1).max(INPUT_LIMITS.files).optional().describe("The files of one or more skills or agents, under 1 MB in total."),
12419
- aiReview: boolean().default(true)
12485
+ aiReview: boolean().default(true),
12486
+ documentId: id.optional().describe("Only findings about this skill or agent."),
12487
+ since: string().max(100).optional().describe("The revision of your last validate result: return only what changed since.")
12420
12488
  })
12421
12489
  }),
12422
12490
  analyze_project: tool({
@@ -12465,7 +12533,7 @@ var bridgeTools = {
12465
12533
  name: "get_standards",
12466
12534
  title: "Get best practice",
12467
12535
  kind: "read",
12468
- description: "Best practice for writing skills, agents, AGENTS.md and rules, checked against each AI tool's documentation, with the source and date of every fact. topic: skill, agent, instructions, rule, evaluation (testing), optimize (improving a project with analyze_project and compare_analysis), portability (one file for Claude Code, Copilot and Codex) or context (what costs context). tool: the facts for one AI tool. Call it before you write or rewrite a skill, agent or instructions file.",
12536
+ description: "Best practice for writing skills, agents, AGENTS.md and rules, checked against each AI tool's documentation, with the source and date of every fact. topic: skill, agent, instructions, rule, evaluation (testing), optimize (improving a project with analyze_project and compare_analysis), improve (the /improve-until routine: rounds with a check after each), portability (one file for Claude Code, Copilot and Codex) or context (what costs context). tool: the facts for one AI tool. Call it before you write or rewrite a skill, agent or instructions file.",
12469
12537
  input: strictObject({
12470
12538
  topic: _enum([
12471
12539
  "skill",
@@ -12474,6 +12542,7 @@ var bridgeTools = {
12474
12542
  "rule",
12475
12543
  "evaluation",
12476
12544
  "optimize",
12545
+ "improve",
12477
12546
  "portability",
12478
12547
  "context"
12479
12548
  ]),
@@ -12578,11 +12647,11 @@ var bridgeTools = {
12578
12647
  name: "add_document",
12579
12648
  title: "Add skill, agent or tool file",
12580
12649
  kind: "write",
12581
- description: "Adds a skill, agent or tool file to a project, optionally from a template (call list_templates first for the template and best practice). Tool files: 'copilot-instructions' (one per project), 'copilot-rule' (.github/instructions; set applyTo with update_document), 'cursor-rule' (.cursor/rules) and 'claude-rule' (.claude/rules; set paths with update_document). The description says, in the third person, what it does and when to use it. For AGENTS.md use write_project_instructions. fromLibrary adds a library item (list_library) as an editable copy instead, optionally linked to an agent.",
12650
+ description: "Adds a skill, agent or tool file to a project, optionally from a template (call list_templates first for the template and best practice). Tool files: 'copilot-instructions' (one per project), 'copilot-rule' (.github/instructions; set applyTo with update_document), 'cursor-rule' (.cursor/rules) and 'claude-rule' (.claude/rules; set paths with update_document). The description says, in the third person, what it does and when to use it. For AGENTS.md use write_project_instructions. fromLibrary adds a library item (list_library) as an editable copy instead, optionally linked to an agent. A workflow id from list_templates as templateId adds all its items, already linked.",
12582
12651
  input: strictObject({
12583
12652
  baseRevision,
12584
12653
  sessionId: sessionRef,
12585
- mode: addableMode.optional().describe("Required unless fromLibrary is given."),
12654
+ mode: addableMode.optional().describe("Required unless fromLibrary or a workflow is given."),
12586
12655
  templateId: id.optional(),
12587
12656
  name: name.optional(),
12588
12657
  description: string().max(1024).optional(),
@@ -12594,7 +12663,7 @@ var bridgeTools = {
12594
12663
  name: "update_document",
12595
12664
  title: "Edit skill or agent details",
12596
12665
  kind: "write",
12597
- description: "Sets frontmatter fields; lists are arrays of strings. Skills: name, description, license, compatibility, allowed-tools, metadata ('key: value' lines), argument-hint, disable-model-invocation, user-invocable. Agents: name, description, tools, model. Copilot path instructions: name, applyTo, description, excludeAgent. Cursor rules: name, description, globs, alwaysApply. Claude rules: name, paths. Fields kept from an imported file are get_document's otherFields: setting one replaces it, removeOtherFields drops them. Descriptions say, in the third person, what it does and when to use it.",
12666
+ description: "Sets frontmatter fields; lists are arrays of strings. Skills: name, description, license, compatibility, allowed-tools, metadata ('key: value' lines), argument-hint, disable-model-invocation, user-invocable. Agents: name, description, tools, model. Copilot path instructions: name, applyTo, description, excludeAgent. Cursor rules: name, description, globs, alwaysApply. Path rules (claude-rule): name, paths. Fields kept from an imported file are get_document's otherFields: setting one replaces it, removeOtherFields drops them. Descriptions say, in the third person, what it does and when to use it.",
12598
12667
  input: strictObject({
12599
12668
  baseRevision,
12600
12669
  documentId: id,
@@ -12940,6 +13009,13 @@ async function createKeyPair() {
12940
13009
  };
12941
13010
  }
12942
13011
  var INFO = encoder.encode("skill-builder-bridge/1");
13012
+ /** The handshake as text, length-prefixed so parts can not run into each other. */
13013
+ var transcriptText = (transcript) => [
13014
+ transcript.tabKey,
13015
+ transcript.connectorKey,
13016
+ transcript.tabNonce,
13017
+ transcript.connectorNonce
13018
+ ].map((part) => part.length + ":" + part).join("|");
12943
13019
  /** Derives the MAC key from our private key and the other side's public key. */
12944
13020
  async function derivePairing(own, peerPublicKey, transcript) {
12945
13021
  const peer = await subtle().importKey("raw", fromBase64(peerPublicKey), {
@@ -12950,12 +13026,7 @@ async function derivePairing(own, peerPublicKey, transcript) {
12950
13026
  name: "ECDH",
12951
13027
  public: peer
12952
13028
  }, own.privateKey, 256);
12953
- const salt = await subtle().digest("SHA-256", encoder.encode([
12954
- transcript.tabKey,
12955
- transcript.connectorKey,
12956
- transcript.tabNonce,
12957
- transcript.connectorNonce
12958
- ].map((part) => part.length + ":" + part).join("|")));
13029
+ const salt = await subtle().digest("SHA-256", encoder.encode(transcriptText(transcript)));
12959
13030
  const hkdf = await subtle().importKey("raw", shared, "HKDF", false, ["deriveBits"]);
12960
13031
  const bits = new Uint8Array(await subtle().deriveBits({
12961
13032
  name: "HKDF",
@@ -13011,6 +13082,39 @@ function pairingProof(key, role, code) {
13011
13082
  function checkPairingProof(key, role, code, proof) {
13012
13083
  return verify(key, proofPayload(role, code), proof);
13013
13084
  }
13085
+ /**
13086
+ * A new resume grant: a random id and a 256-bit secret. Made by the connector only, kept in its
13087
+ * memory only, and used once.
13088
+ */
13089
+ function createResumeGrant() {
13090
+ return {
13091
+ id: randomNonce(16),
13092
+ secret: toBase64(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)))
13093
+ };
13094
+ }
13095
+ /**
13096
+ * The secret as a key. The tab imports it as not extractable, so it can be kept in the browser
13097
+ * and used to sign, but no script can read it back out.
13098
+ */
13099
+ function importResumeKey(secret, extractable = false) {
13100
+ return subtle().importKey("raw", fromBase64(secret), {
13101
+ name: "HMAC",
13102
+ hash: "SHA-256"
13103
+ }, extractable, ["sign", "verify"]);
13104
+ }
13105
+ var resumePayload = (role, id, transcript) => `skill-builder-resume/1|${role}|${id}|${transcriptText(transcript)}`;
13106
+ /**
13107
+ * Proof that `role` holds the secret of `id`. Bound to this connection's handshake, so a proof
13108
+ * captured on one connection is worth nothing on the next, and different for each role, so one
13109
+ * side's proof can not stand for the other's.
13110
+ */
13111
+ function resumeProof(key, role, id, transcript) {
13112
+ return sign(key, resumePayload(role, id, transcript));
13113
+ }
13114
+ /** Checks a resume proof in constant time. */
13115
+ function checkResumeProof(key, role, id, transcript, proof) {
13116
+ return verify(key, resumePayload(role, id, transcript), proof);
13117
+ }
13014
13118
  //#endregion
13015
13119
  //#region ../../src/core/bridge/channel.ts
13016
13120
  /**
@@ -13089,6 +13193,9 @@ var TabLink = class {
13089
13193
  protocol = 0;
13090
13194
  channel = null;
13091
13195
  key = null;
13196
+ transcript = null;
13197
+ /** A tab gets one try at resuming per connection; after that it types a code. */
13198
+ resumeTried = false;
13092
13199
  attemptsLeft = BRIDGE_LIMITS.pairingAttempts;
13093
13200
  handshakeTimer;
13094
13201
  pending = /* @__PURE__ */ new Map();
@@ -13106,7 +13213,7 @@ var TabLink = class {
13106
13213
  const text = isBinary ? "" : data.toString();
13107
13214
  this.queue = this.queue.then(() => this.receive(text)).catch(() => this.close(1008));
13108
13215
  });
13109
- socket.on("close", () => this.closed());
13216
+ socket.on("close", (code) => this.closed(code === CLOSE_LEFT_ON_PURPOSE));
13110
13217
  socket.on("error", () => this.close(1011));
13111
13218
  this.handshakeTimer = setTimeout(() => {
13112
13219
  if (this.state === "handshake") this.close(1008);
@@ -13127,12 +13234,14 @@ var TabLink = class {
13127
13234
  if (protocol === null) return this.reject("protocol", hello.protocols.every((version) => version < 3) ? "Skill Builder is older than this connector. Reload the app to update it." : "This connector is older than Skill Builder. Update the version in your AI tool settings (Skill Builder → Settings → Connect AI shows the current one).");
13128
13235
  const own = await createKeyPair();
13129
13236
  const nonce = randomNonce();
13130
- const pairing = await derivePairing(own, hello.key, {
13237
+ const transcript = {
13131
13238
  tabKey: hello.key,
13132
13239
  connectorKey: own.publicKey,
13133
13240
  tabNonce: hello.nonce,
13134
13241
  connectorNonce: nonce
13135
- });
13242
+ };
13243
+ const pairing = await derivePairing(own, hello.key, transcript);
13244
+ this.transcript = transcript;
13136
13245
  this.socket.send(JSON.stringify({
13137
13246
  type: "welcome",
13138
13247
  protocol,
@@ -13155,7 +13264,8 @@ var TabLink = class {
13155
13264
  }
13156
13265
  async handle(message) {
13157
13266
  switch (message.type) {
13158
- case "pair": return this.pair(message.body.proof);
13267
+ case "pair": return this.pair(message.body.proof, message.body.resume === true);
13268
+ case "resume": return this.resume(message.body.id, message.body.proof);
13159
13269
  case "tools":
13160
13270
  if (this.state !== "paired") throw new ChannelError("Tools before pairing.");
13161
13271
  this.tools = message.body.tools;
@@ -13192,13 +13302,17 @@ var TabLink = class {
13192
13302
  * with our own proof, so the tab knows it reached the real connector. Wrong:
13193
13303
  * a few more tries, then the connection closes.
13194
13304
  */
13195
- async pair(proof) {
13305
+ async pair(proof, wantsResume) {
13196
13306
  if (this.state !== "pairing" || !this.key || !this.code) throw new ChannelError("Unexpected pairing message.");
13197
13307
  if (await checkPairingProof(this.key, "tab", this.code, proof)) {
13198
13308
  clearTimeout(this.pairingTimer);
13199
13309
  this.state = "paired";
13200
- await this.send("paired", { proof: await pairingProof(this.key, "connector", this.code) });
13201
- this.events.onPaired?.();
13310
+ const grant = wantsResume ? this.newGrant() : void 0;
13311
+ await this.send("paired", {
13312
+ proof: await pairingProof(this.key, "connector", this.code),
13313
+ ...grant ? { grant } : {}
13314
+ });
13315
+ this.events.onPaired?.(false);
13202
13316
  return;
13203
13317
  }
13204
13318
  this.attemptsLeft--;
@@ -13206,6 +13320,37 @@ var TabLink = class {
13206
13320
  await this.send("pairFailed", { attemptsLeft: Math.max(0, this.attemptsLeft) });
13207
13321
  if (this.attemptsLeft <= 0) this.close(1008);
13208
13322
  }
13323
+ /**
13324
+ * The tab came back after a refresh and says it holds the resume secret it was given. Right:
13325
+ * the secret is used up, the connector proves it holds it too (so the tab knows it reached the
13326
+ * real connector), and the tab gets a new secret. Anything else: the tab is told, and the code
13327
+ * on this connection still works. A wrong try does not count against the pairing codes, and
13328
+ * does not use the secret up, but each connection gets one try.
13329
+ */
13330
+ async resume(id, proof) {
13331
+ if (this.state !== "pairing" || !this.key || !this.transcript || this.resumeTried) throw new ChannelError("Unexpected resume message.");
13332
+ this.resumeTried = true;
13333
+ const secret = this.events.lookupResume?.(id) ?? null;
13334
+ const key = secret ? await importResumeKey(secret) : null;
13335
+ const proven = key ? await checkResumeProof(key, "tab", id, this.transcript, proof) : false;
13336
+ if (!key || !proven || this.state !== "pairing" || !this.events.consumeResume?.(id)) {
13337
+ await this.send("resumeFailed", {});
13338
+ return;
13339
+ }
13340
+ clearTimeout(this.pairingTimer);
13341
+ this.state = "paired";
13342
+ const grant = this.newGrant();
13343
+ await this.send("paired", {
13344
+ proof: await resumeProof(key, "connector", id, this.transcript),
13345
+ grant
13346
+ });
13347
+ this.events.onPaired?.(true);
13348
+ }
13349
+ newGrant() {
13350
+ const grant = createResumeGrant();
13351
+ this.events.onGrant?.(grant);
13352
+ return grant;
13353
+ }
13209
13354
  /** Forwards a call to the tab and waits for its result. */
13210
13355
  call(name, args, timeoutMs) {
13211
13356
  return this.request("call", name, args, timeoutMs);
@@ -13300,7 +13445,7 @@ var TabLink = class {
13300
13445
  } catch {}
13301
13446
  this.closed();
13302
13447
  }
13303
- closed() {
13448
+ closed(onPurpose = false) {
13304
13449
  if (this.state === "closed") return;
13305
13450
  this.state = "closed";
13306
13451
  clearInterval(this.heartbeat);
@@ -13317,7 +13462,7 @@ var TabLink = class {
13317
13462
  });
13318
13463
  this.pending.delete(callId);
13319
13464
  }
13320
- this.events.onClose?.();
13465
+ this.events.onClose?.(onPurpose);
13321
13466
  }
13322
13467
  };
13323
13468
  //#endregion
@@ -13335,6 +13480,12 @@ var Bridge = class {
13335
13480
  /** Wrong pairing codes in this run. Too many stop pairing until a restart. */
13336
13481
  failures = 0;
13337
13482
  locked = false;
13483
+ /**
13484
+ * The one resume secret that may be in use: handed to the tab that is (or was) connected, in
13485
+ * memory only, used once. `offlineSince` is when that tab left; it can only resume from then
13486
+ * until the window closes. Never written anywhere, and gone when this connector exits.
13487
+ */
13488
+ resumable = null;
13338
13489
  manifest = toolManifest();
13339
13490
  toolListeners = /* @__PURE__ */ new Set();
13340
13491
  promptListeners = /* @__PURE__ */ new Set();
@@ -13459,10 +13610,27 @@ var Bridge = class {
13459
13610
  this.options.log?.("Too many wrong pairing codes: pairing is off until the AI tool restarts.");
13460
13611
  }
13461
13612
  },
13462
- onPaired: () => this.options.log?.("Approved. Skill Builder is connected."),
13613
+ onPaired: (resumed) => this.options.log?.(resumed ? "Skill Builder came back after a refresh and is connected again." : "Approved. Skill Builder is connected."),
13614
+ onGrant: (grant) => {
13615
+ this.resumable = {
13616
+ ...grant,
13617
+ holder: link,
13618
+ offlineSince: null
13619
+ };
13620
+ },
13621
+ lookupResume: (id) => this.resumableSecret(id),
13622
+ consumeResume: (id) => {
13623
+ if (!this.resumableSecret(id)) return false;
13624
+ this.resumable = null;
13625
+ return true;
13626
+ },
13463
13627
  onTools: (tools) => this.setManifest(tools),
13464
13628
  onPrompts: () => this.promptsMaybeChanged(),
13465
- onClose: () => {
13629
+ onClose: (onPurpose) => {
13630
+ if (this.resumable?.holder === link) {
13631
+ if (onPurpose) this.resumable = null;
13632
+ else if (this.resumable.offlineSince === null) this.resumable.offlineSince = Date.now();
13633
+ }
13466
13634
  if (this.link !== link) return;
13467
13635
  this.link = null;
13468
13636
  this.promptsMaybeChanged();
@@ -13471,6 +13639,13 @@ var Bridge = class {
13471
13639
  });
13472
13640
  this.link = link;
13473
13641
  }
13642
+ /** The secret of a resume id that is still good: the tab left, a short time ago, and has not used it. */
13643
+ resumableSecret(id) {
13644
+ const held = this.resumable;
13645
+ const window = this.options.resumeWindowMs ?? BRIDGE_LIMITS.resumeWindowMs;
13646
+ if (!held || held.id !== id || held.offlineSince === null) return null;
13647
+ return Date.now() - held.offlineSince <= window ? held.secret : null;
13648
+ }
13474
13649
  /**
13475
13650
  * Answers a socket's first message with a refusal and closes it. A socket that stays silent is
13476
13651
  * closed too, so idle ones can not use up the connection limit and block the real tab.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascalcouturier/skill-builder-mcp",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "description": "Connect AI tools (GitHub Copilot, Claude Code, Codex, Cursor, Claude Desktop) to Skill Builder open in your browser, over MCP.",
5
5
  "license": "MIT",
6
6
  "author": "Pascal Couturier (https://pascalcouturier.com)",