@bevel-software/platform-core-backend 0.34.0 → 0.35.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 (179) hide show
  1. package/agent-guide/access-control.md +146 -300
  2. package/agent-guide/conventions.md +23 -25
  3. package/agent-guide/directory-structure.md +72 -120
  4. package/agent-guide/drafts-and-change-requests.md +92 -0
  5. package/agent-guide/for-git-users.md +47 -0
  6. package/agent-guide/glossary.md +36 -0
  7. package/agent-guide/how-agents-find-things.md +49 -0
  8. package/agent-guide/html-views.md +16 -11
  9. package/agent-guide/new-knowledge-base.md +9 -8
  10. package/agent-guide/skills.md +67 -42
  11. package/agent-guide/start-here.md +65 -0
  12. package/agent-guide/tool-manuals.md +132 -154
  13. package/agent-guide/tools.md +67 -0
  14. package/agent-guide/where-a-new-file-goes.md +45 -28
  15. package/dist/core/create-core-server.js +1 -1
  16. package/dist/core/create-core-server.js.map +1 -1
  17. package/dist/core/create-core-services.d.ts +6 -0
  18. package/dist/core/create-core-services.d.ts.map +1 -1
  19. package/dist/core/create-core-services.js +7 -4
  20. package/dist/core/create-core-services.js.map +1 -1
  21. package/dist/modules/access/access-control.interface.d.ts +22 -0
  22. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  23. package/dist/modules/access/access-control.service.d.ts +3 -1
  24. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  25. package/dist/modules/access/access-control.service.js +75 -0
  26. package/dist/modules/access/access-control.service.js.map +1 -1
  27. package/dist/modules/access/access.routes.d.ts.map +1 -1
  28. package/dist/modules/access/access.routes.js +23 -4
  29. package/dist/modules/access/access.routes.js.map +1 -1
  30. package/dist/modules/access/index.d.ts +1 -1
  31. package/dist/modules/access/index.d.ts.map +1 -1
  32. package/dist/modules/agent-guide/agent-guide.d.ts +47 -7
  33. package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
  34. package/dist/modules/agent-guide/agent-guide.js +103 -13
  35. package/dist/modules/agent-guide/agent-guide.js.map +1 -1
  36. package/dist/modules/agent-guide/agent-guide.tools.d.ts +6 -2
  37. package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
  38. package/dist/modules/agent-guide/agent-guide.tools.js +45 -31
  39. package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
  40. package/dist/modules/agent-guide/guide-budget.d.ts +48 -0
  41. package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
  42. package/dist/modules/agent-guide/guide-budget.js +67 -0
  43. package/dist/modules/agent-guide/guide-budget.js.map +1 -0
  44. package/dist/modules/agent-guide/index.d.ts +2 -1
  45. package/dist/modules/agent-guide/index.d.ts.map +1 -1
  46. package/dist/modules/agent-guide/index.js +2 -1
  47. package/dist/modules/agent-guide/index.js.map +1 -1
  48. package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
  49. package/dist/modules/agent-instructions/compose.js +4 -0
  50. package/dist/modules/agent-instructions/compose.js.map +1 -1
  51. package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
  52. package/dist/modules/agent-instructions/shared-file-rules.js +10 -8
  53. package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
  54. package/dist/modules/code-mode/code-mode.tool.js +2 -2
  55. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  56. package/dist/modules/connection-probe/connection-probe.service.d.ts +0 -12
  57. package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
  58. package/dist/modules/connection-probe/connection-probe.service.js +1 -38
  59. package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
  60. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  61. package/dist/modules/mcp/mcp.service.js +6 -35
  62. package/dist/modules/mcp/mcp.service.js.map +1 -1
  63. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +11 -1
  64. package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
  65. package/dist/modules/secrets-vault/db-secrets-vault.service.js +159 -24
  66. package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
  67. package/dist/modules/secrets-vault/index.d.ts +1 -1
  68. package/dist/modules/secrets-vault/index.d.ts.map +1 -1
  69. package/dist/modules/secrets-vault/index.js +1 -1
  70. package/dist/modules/secrets-vault/index.js.map +1 -1
  71. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +53 -11
  72. package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
  73. package/dist/modules/secrets-vault/secrets-vault.contract.js +27 -0
  74. package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
  75. package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
  76. package/dist/modules/secrets-vault/secrets-vault.routes.js +85 -39
  77. package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
  78. package/dist/modules/settings/oidc-check.d.ts.map +1 -1
  79. package/dist/modules/settings/oidc-check.js +5 -17
  80. package/dist/modules/settings/oidc-check.js.map +1 -1
  81. package/dist/modules/skills/skills.tools.js +1 -1
  82. package/dist/modules/skills/skills.tools.js.map +1 -1
  83. package/dist/modules/tool-helpers/tool-def.js +1 -1
  84. package/dist/modules/tool-helpers/tool-def.js.map +1 -1
  85. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +7 -1
  86. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
  87. package/dist/modules/tool-manuals/mcp-json-discovery.js +69 -38
  88. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
  89. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +27 -0
  90. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  91. package/dist/modules/tool-manuals/tool-manuals.service.d.ts +12 -8
  92. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  93. package/dist/modules/tool-manuals/tool-manuals.service.js +115 -44
  94. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  95. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +8 -2
  96. package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
  97. package/dist/modules/tool-manuals/tool-manuals.tools.js +31 -10
  98. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  99. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  100. package/dist/modules/workflow/agent-tools/workflow.tools.js +22 -18
  101. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  102. package/dist/modules/workspace/agent-access.gate.d.ts +1 -1
  103. package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
  104. package/dist/modules/workspace/agent-access.gate.js +1 -1
  105. package/dist/modules/workspace/agent-access.gate.js.map +1 -1
  106. package/dist/modules/workspace/workspace.tools.d.ts +3 -2
  107. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  108. package/dist/modules/workspace/workspace.tools.js +71 -21
  109. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  110. package/dist/shared/oauth-token-error.d.ts +23 -0
  111. package/dist/shared/oauth-token-error.d.ts.map +1 -0
  112. package/dist/shared/oauth-token-error.js +47 -0
  113. package/dist/shared/oauth-token-error.js.map +1 -0
  114. package/dist/shared/read-capped.d.ts +19 -0
  115. package/dist/shared/read-capped.d.ts.map +1 -0
  116. package/dist/shared/read-capped.js +45 -0
  117. package/dist/shared/read-capped.js.map +1 -0
  118. package/package.json +4 -3
  119. package/src/__tests__/hexis-catalog.ts +144 -0
  120. package/src/__tests__/real-disk.ts +21 -0
  121. package/src/__tests__/tokens.ts +19 -0
  122. package/src/__tests__/tool-manuals.ts +32 -0
  123. package/src/__tests__/tool-wiring.ts +25 -0
  124. package/src/core/__tests__/oauth-sign-in-real-file.test.ts +179 -0
  125. package/src/core/create-core-server.ts +1 -1
  126. package/src/core/create-core-services.ts +13 -3
  127. package/src/modules/access/__tests__/access.routes.prospective.test.ts +52 -12
  128. package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
  129. package/src/modules/access/access-control.interface.ts +26 -0
  130. package/src/modules/access/access-control.service.ts +92 -0
  131. package/src/modules/access/access.routes.ts +23 -4
  132. package/src/modules/access/index.ts +1 -1
  133. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +79 -25
  134. package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +38 -11
  135. package/src/modules/agent-guide/__tests__/guide-budget.test.ts +95 -0
  136. package/src/modules/agent-guide/__tests__/guide-eval.test.ts +182 -0
  137. package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +263 -0
  138. package/src/modules/agent-guide/__tests__/tool-surface.json +514 -0
  139. package/src/modules/agent-guide/__tests__/tool-surface.test.ts +75 -0
  140. package/src/modules/agent-guide/agent-guide.tools.ts +49 -31
  141. package/src/modules/agent-guide/agent-guide.ts +120 -13
  142. package/src/modules/agent-guide/guide-budget.ts +73 -0
  143. package/src/modules/agent-guide/index.ts +12 -0
  144. package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +4 -4
  145. package/src/modules/agent-instructions/compose.ts +4 -0
  146. package/src/modules/agent-instructions/shared-file-rules.ts +10 -8
  147. package/src/modules/code-mode/code-mode.tool.ts +2 -2
  148. package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +5 -24
  149. package/src/modules/connection-probe/connection-probe.service.ts +1 -34
  150. package/src/modules/mcp/mcp.service.ts +5 -35
  151. package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +430 -3
  152. package/src/modules/secrets-vault/__tests__/oauth-live-declaration.route.test.ts +218 -0
  153. package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +39 -6
  154. package/src/modules/secrets-vault/db-secrets-vault.service.ts +170 -27
  155. package/src/modules/secrets-vault/index.ts +1 -1
  156. package/src/modules/secrets-vault/secrets-vault.contract.ts +61 -11
  157. package/src/modules/secrets-vault/secrets-vault.routes.ts +91 -39
  158. package/src/modules/settings/oidc-check.ts +5 -18
  159. package/src/modules/skills/skills.tools.ts +1 -1
  160. package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +19 -11
  161. package/src/modules/tool-helpers/__tests__/route-tool-schemas.catalog.test.ts +38 -0
  162. package/src/modules/tool-helpers/tool-def.ts +1 -1
  163. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +197 -11
  164. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +96 -5
  165. package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
  166. package/src/modules/tool-manuals/tool-manuals.contract.ts +22 -0
  167. package/src/modules/tool-manuals/tool-manuals.service.ts +138 -46
  168. package/src/modules/tool-manuals/tool-manuals.tools.ts +53 -14
  169. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -113
  170. package/src/modules/workflow/agent-tools/workflow.tools.ts +22 -18
  171. package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +8 -10
  172. package/src/modules/workspace/__tests__/workspace.tools.test.ts +156 -49
  173. package/src/modules/workspace/agent-access.gate.ts +1 -1
  174. package/src/modules/workspace/workspace.tools.ts +74 -21
  175. package/src/shared/__tests__/read-capped.test.ts +68 -0
  176. package/src/shared/oauth-token-error.ts +46 -0
  177. package/src/shared/read-capped.ts +40 -0
  178. package/agent-guide/finding-things.md +0 -7
  179. package/agent-guide/introduction.md +0 -27
@@ -1,55 +1,80 @@
1
1
  ## Skills (`{{skillsDir}}/<scope>/…/<skill>/SKILL.md`, or `{{pluginsDir}}/<Plugin>/skills/<skill>/SKILL.md`)
2
2
 
3
3
  A skill is a folder holding a `SKILL.md` and whatever files it needs. Shared
4
- skills live under `{{skillsDir}}/`, organised by ownership; a skill that belongs to
5
- exactly one plugin may live inside that plugin's `skills/` folder instead.
6
- Skill names are unique across the whole catalog, whichever home they have.
7
- The frontmatter names it, declares which tools it may use, and may carry a
8
- version:
4
+ skills live under `{{skillsDir}}/`, organised by ownership; a skill that
5
+ belongs to one plugin alone may live in that plugin's `skills/` folder. Skill
6
+ names are unique across the catalog. The frontmatter names the skill, says
7
+ when to use it, and may list tools and a version:
9
8
 
10
9
  ```yaml
11
10
  ---
12
11
  name: weekly-newsletter
13
- description: Drafts the Friday newsletter for review.
12
+ description: "Use when someone asks for the Friday newsletter: drafts it for review."
14
13
  allowed-tools: [slack_post_message]
15
14
  metadata:
16
15
  version: "1.4.0"
17
16
  ---
18
17
  ```
19
18
 
20
- The body is the instructions, in plain markdown. `allowed-tools` entries are
21
- tool names from the `.tool` manuals and MCP servers of the plugins that hold
22
- the skill, and the agent client's own tools beside them (`Bash`, `Read`,
23
- `Bash(git:*)` and the like, which the platform leaves to the client).
24
- `metadata.version` is semver; `list_skills` reports it, and
25
- `get_skill` with a `version` loads the skill as it was when it last declared
26
- that version (omit `version` for the latest). Any other `metadata` keys are
27
- the author's own notes — the catalog carries the file as it is and acts on
28
- none of them.
29
-
30
- Before offering to change a skill, read its `canWrite`: `list_skills` judges
31
- the `SKILL.md`; `get_skill` judges every file of the skill unless `file` is
32
- passed, in which case it judges only that file — a bundled file's `true` is
33
- not the skill's; both by the default branch's rules whichever branch you
34
- read. `canWrite: false` means use a branch and a change request; do not
35
- offer a direct write (see *Direct writes vs change requests* under **Access
36
- control**).
37
-
38
- A `SKILL.md` committed on the default branch is listed and loadable from the
39
- very next `list_skills` or `get_skill`, on the connection you already have:
40
- the released catalog is cached briefly and dropped the moment the default
41
- branch changes, so the next request reads the workspace again.
42
- See *A released tool or skill is live within ten seconds* under **Tool
43
- Manuals** for the one caveat (an MCP client that caches the prompt list it
44
- was given at connect time must re-list — the prompt-list-changed notification
45
- that tells it to arrives with the connection's next catalog check, which is
46
- within ten seconds on a connection in use and at its next use on an idle
47
- one).
48
-
49
- **How skills reach agents.** Through the MCP server (`list_skills`,
50
- `get_skill`), or as native plugins: every user can clone a git remote from
51
- the app's external-agent page that holds a plugin marketplace compiled from
52
- exactly the skills they may read — one plugin per plugin here, a
53
- `skills-and-knowledge` plugin for the rest plus this knowledge base's MCP
54
- server, and `hexis-all`, one plugin holding every skill they may read and
55
- the MCP server, for a single install.
19
+ - **The frontmatter is YAML.** Quote a value that contains `: ` or starts
20
+ with a special character, as above, or the description comes out empty and
21
+ nobody finds the skill. Read it back with `get_skill` after writing.
22
+ - **`description` is how the skill is found**: agents load a skill when its
23
+ description fits the task. Say when to use it, in the words people use.
24
+ - **`allowed-tools`** names the tools the skill uses: any of the platform's
25
+ tools, a plugin's (as `<server>_<tool>`), and the client's own (`Bash`,
26
+ `Read`, `Bash(git:*)`). The platform warns about a name it does not know,
27
+ and enforces nothing.
28
+ - **`metadata.version`** (or a top-level `version`) is reported by
29
+ `list_skills`; `get_skill` with `version` loads the skill as it was when it
30
+ last declared that version. Other `metadata` keys are the author's notes.
31
+
32
+ The body is the instructions, in plain markdown.
33
+
34
+ **A skill on a draft is not live.** The app's skills page, the prompts and
35
+ `list_skills` without `branch` show only the released catalog, read from
36
+ `{{defaultBranch}}`, so a skill on a draft "doesn't exist" there until it is
37
+ merged. To try it, pass the draft: `list_skills({ body: { branch: "<draft>" } })`,
38
+ then `get_skill({ body: { name: "weekly-newsletter", branch: "<draft>" } })`.
39
+ A skill that differs from the released one comes back with `unmerged: true`,
40
+ and its body opens with a line saying it is not approved. Once merged, it is
41
+ in the next `list_skills` or `get_skill`; a client that cached the prompt list
42
+ when it connected must reconnect to show it as a prompt.
43
+
44
+ Before offering to change a skill, check its `canWrite` (see `access-control`).
45
+
46
+ **How skills reach agents.** Through this server (prompts, `list_skills`,
47
+ `get_skill`), or as native plugins: each person can install a plugin
48
+ marketplace from the app's external agent page, compiled from exactly the
49
+ skills they may read.
50
+
51
+ ### A skill that needs tools checks them first
52
+
53
+ A skill that calls another service fails for anyone who has not signed in, or
54
+ before the tool is merged. Make its first step a check, and stop with a plain
55
+ instruction rather than a failed call:
56
+
57
+ ```markdown
58
+ ## Before you start
59
+ Call `list_tool_setup`. Find the Jira tool. If a variable with `scope: user`
60
+ shows `userConfigured: false` (or `authorized: false` for a sign-in), stop and
61
+ say: "Open the Connect page in <app> and sign in to Jira, then ask me again."
62
+ If the tool is missing altogether, say it is not available to them yet and who
63
+ to ask.
64
+ ```
65
+
66
+ ### A guided skill for people who are not technical
67
+
68
+ When a skill is for someone who is not an engineer, such as turning an idea
69
+ into a ticket, write it to lead them:
70
+
71
+ - **One question per message**, the most important first. Never a list of
72
+ five.
73
+ - **Propose, then ask them to confirm.** "I would call it 'Search: filter by
74
+ date'. Is that right?" is easier to answer than "What is the title?".
75
+ - **A checklist for "specified enough"** in the skill (who it is for, what
76
+ should happen, how we know it works), and stop asking once it is met.
77
+ - **Plain words.** No platform terms (branch, change request, frontmatter,
78
+ tool names); show the result in their language before you create anything.
79
+ - **Check its tools first** (above), so the conversation does not end in an
80
+ error after ten questions.
@@ -0,0 +1,65 @@
1
+ ## Start here
2
+
3
+ You are connected to this organisation's knowledge base: its documents, and
4
+ the skills and tools its teams have approved, with access set per folder. To
5
+ the people you work with it is not a Git repository: say "knowledge base",
6
+ "draft" and "change request", and do not explain it as "a Git repo". If they
7
+ ask how it compares, see `for-git-users`.
8
+
9
+ **Read this once per conversation.** Then fetch only the section a task needs,
10
+ `get_agent_guide({ body: { section: "<id>" } })`, and do not reload the whole
11
+ guide. When you start a sub-agent, pass it the rules below and the one section
12
+ its task needs.
13
+
14
+ ### Before your first call
15
+
16
+ - **Every file tool takes `branch`.** `{{defaultBranch}}` is the default
17
+ branch: what is live for everyone. Work in progress lives on a draft branch.
18
+ For example:
19
+ `read_file({ body: { branch: "{{defaultBranch}}", path: "{{kbDirName}}/{{knowledgeBaseDir}}/Overview.md" } })`.
20
+ - **Copy each tool's `Call:` line.** It is generated from the tool's own
21
+ schema and shows the shape that tool takes, with every required argument;
22
+ optional ones are in the schema. A call with a missing or unknown argument
23
+ is refused, and the refusal shows the tool's interface.
24
+ - **Pass `sessionId` only where a tool lists it.** Over MCP, call
25
+ `start_session` once and pass its id to the tools whose arguments include
26
+ `sessionId`. Other tools refuse it.
27
+ - **`grep` takes RE2-style regular expressions** (ripgrep): no look-around
28
+ and no backreferences.
29
+ - **Paths sit under `{{kbDirName}}/`**, as in
30
+ `{{kbDirName}}/{{knowledgeBaseDir}}/Overview.md`. A path without that prefix
31
+ is placed under it.
32
+
33
+ ### What is live, and what is only on a draft
34
+
35
+ Work on a draft is visible to you, and to anyone who opens that draft in the
36
+ app: its change request, once you open one, shows every changed file.
37
+ **Skills and tools go live only once they are on `{{defaultBranch}}`**,
38
+ merged through a change request or written there directly by someone allowed
39
+ to. Until then the app's skills page, `list_skills` without `branch` and the
40
+ tool list do not show them, and "doesn't exist" means "not merged yet". To see
41
+ a skill on a draft, pass the draft as `branch` to `list_skills` or
42
+ `get_skill`.
43
+
44
+ ### Which section answers what
45
+
46
+ | Question | Section |
47
+ |---|---|
48
+ | What does my agent see when it connects, and how does it find the rest? | `how-agents-find-things` |
49
+ | How is this like Git, and how is it not? Why can't I delete this? | `for-git-users` |
50
+ | What is a plugin, a skill, a tool, a secret, a role, a group? | `glossary` |
51
+ | What do the platform's folders hold? | `directory-structure` |
52
+ | Where does a new file go? How do I lay out a team's first project? | `where-a-new-file-goes` |
53
+ | What may the file tools do: content kinds, write modes, moves, deletes? | `working-with-files` |
54
+ | Who may read or change what: roles, groups, `access.md`? | `access-control` |
55
+ | How do I propose, sync or delete a draft, or settle a conflict? | `drafts-and-change-requests` |
56
+ | How do I write a skill, or one for people who are not technical? | `skills` |
57
+ | When can I use a tool, and what does it need set up? | `tools` |
58
+ | How do I add a tool: `.tool` files, `mcp.json`, sign-ins? | `tool-manuals` |
59
+ | How do I build an interactive HTML page? | `html-views` |
60
+ | How are pages named, linked and sourced? | `conventions` |
61
+ | The knowledge base is empty: what now? | `new-knowledge-base` |
62
+
63
+ A deployment may add sections of its own: `sections` in this answer lists
64
+ every one. `get_agent_guide({ body: { section: "all" } })` returns the whole
65
+ guide.
@@ -1,114 +1,95 @@
1
1
  ## Tool Manuals (`{{pluginsDir}}/<Plugin>/software.bevel.hexis/tools/*.tool`)
2
2
 
3
- Each plugin folder holds `*.tool` files — reusable **tool manuals** that let agents call external APIs. They are **not part of the knowledge graph** (never modelled as nodes) and are access-controlled like any other file via `access.md`. Any user who can *read* a `.tool` can use its tools; anyone who can *write* it sets its shared (admin) secrets (see below). Put each manual in the plugin's `software.bevel.hexis/tools/` directory, beside
4
- the skills that use it. The same integration may exist in several plugins as
5
- separate files (`Everyone/…/serper.tool` and `Finance/…/serper.tool`), each
6
- with its own credentials and access rule — a plugin is a folder, not a registry
7
- of unique names. The manual's **id** is, though: two files that resolve to
8
- the same id (an explicit `id`, else `name`, else the file name) collide and
9
- the second is refused, so give each copy its own `id` (`serper_everyone`,
10
- `serper_finance`). Remember: `.tool` files are for `http` and `inline` manuals
11
- only; MCP servers belong in `mcp.json`.
12
-
13
- **Calling a tool: read its `Call:` line.** Tools do not share one calling
14
- shape — the knowledge-base tools take their arguments under a `body` object, a
15
- tool that calls another service takes them flat — so every tool description
16
- opens with one generated line showing how that tool is called, e.g.
17
- `Call: KNOWLEDGE_BASE.read_file({ body: { branch: "...", path: "..." } })` or
18
- `Call: SERPER.search({ query: "..." })`. Copy that line and fill it in, inside
19
- `call_tool_chain` or directly. Arguments that do not match a tool's input
20
- schema are refused before anything is sent, and the refusal lists what is
21
- wrong and gives the tool's whole interface.
22
-
23
- A `.tool` file is JSON or YAML. Its `type` decides how tools are discovered:
24
-
25
- - **`inline`** — the tools are embedded in the file (no network round-trip to list them).
26
- - **`http`** — `url` points to an endpoint that returns a UTCP manual.
27
-
28
- (`type: mcp` is the LEGACY spelling of an MCP server as a `.tool`. The boot
29
- migration converts such files into `mcp.json` entries; do not write new ones.)
30
-
31
- **The tool is the frontmatter.** A `.tool` is one `---` YAML block holding *everything* — its `id`, its access verbs (`read:`/`write:`/`owner:`/`download:`), and its config (`type`/`url`/`variables`/…) — all in the same object. Anything after the closing `---` is free-form notes the parser ignores (like a `SKILL.md` body):
3
+ A `.tool` file is a **tool manual**: it lets agents call a service's HTTP API.
4
+ It lives in a plugin's `software.bevel.hexis/tools/` folder, beside the skills
5
+ that use it. `.tool` files are for `http` and `inline` manuals only: an MCP
6
+ server belongs in the plugin's `mcp.json` (see `directory-structure`). Who may
7
+ use and who manages a tool is in `tools`.
8
+
9
+ **The tool is the frontmatter.** A `.tool` is one `---` YAML block holding
10
+ everything: its `id`, its access verbs (`read`/`write`/`owner`/`download`,
11
+ which override the folder's `access.md` for this file) and its config
12
+ (`type`, `url`, `variables`, …). Anything after the closing `---` is notes the
13
+ parser ignores. A file without the fence is the older form: the whole file is
14
+ the object, so a bare JSON `.tool` still works.
32
15
 
33
16
  ```yaml
34
17
  ---
35
18
  id: my_tool
36
19
  write:
37
20
  - group/Product Team
38
- owner:
39
- - Jane Doe <jane@x.com>
40
21
  type: http
41
22
  url: https://api.example.com/utcp
42
23
  ---
43
24
  ```
44
25
 
45
- (A file with no `---` fence is the legacy form — the whole file is the object, so a bare JSON `.tool` still works.)
26
+ `type` decides how the tools are found: **`inline`** embeds them in the file,
27
+ **`http`** points `url` at an endpoint that returns a UTCP manual.
28
+ (`type: mcp` is an older way to declare an MCP server; startup moves such files
29
+ into `mcp.json` where it can. Do not write new ones.)
46
30
 
47
- **`id` = variable namespace.** The `id` is the manual's stable identity: it's the UTCP namespace secrets bind to (`<id>_<VAR>`) and its route slug. It must be lowercase `snake_case` and **unique** across all `.tool` files. Resolution is `id` → `name` → the file name (so a `name:` alone works, same as the id system uses for every file). If two files collide, the second is REFUSED, not renamed: a suffix would silently bind a configured secret to a different file, so the catalog keeps one and names the other as a duplicate until it is renamed by hand. **Access** declared here gates who can use and edit that tool, exactly like a node's own frontmatter (most specific; overrides the folder `access.md`).
31
+ **`id` is the manual's identity**: the namespace its secrets bind to and its
32
+ address. Use lowercase `snake_case`. Without an `id`, the `name`, then the file
33
+ name, is used. It must be unique among all `.tool` files and `mcp.json` server
34
+ names, and names differing only in `-` and `_` collide: the second is refused
35
+ (an `mcp.json` server wins over a `.tool`), not renamed, and `list_tool_setup`
36
+ names it under `invalid`. The same service in two plugins needs two ids
37
+ (`serper_everyone`, `serper_finance`), each with its own secrets and access.
48
38
 
49
- **Frontmatter `id` = address.** This is generic, not tool-specific: ANY `.md` or `.tool` file whose frontmatter declares an `id` (or a lowercase snake_case/kebab `name`) is addressable at `/workspace/<branch>/<id>` in the app, exactly like a knowledge node — tools, skills (`SKILL.md`), and plain notes alike. Graph nodes win an id collision; files without frontmatter stay path-addressed.
39
+ ### Secrets: `${VAR}` and `variables`
50
40
 
51
- **Remote vs local (`remote`).** A tool is available to remote agents by default. Add `remote: false` for a tool that only works on the user's own machine (e.g. an `http` manual whose `url` is on `localhost`): the hosted remote MCP endpoint cannot reach it, so it skips the tool and advertises it through the `list_local_tools` tool instead. (An MCP server that is local-only declares `local: true` in the plugin.json extensions block instead — see above.)
41
+ Wherever a manual needs a credential, write a placeholder such as
42
+ `${API_KEY}`. At call time it is filled from the platform's Secrets Vault,
43
+ under a key made from the manual's id and the variable's name, so a secret
44
+ belongs to one manual. The app's Secrets page shows the exact key; do not spell
45
+ it by hand.
52
46
 
53
- To actually USE those tools, run the workspace as a local MCP server:
47
+ Declare who provides each variable in an optional `variables` list of
48
+ `{ name, scope, label? }`:
54
49
 
55
- ```
56
- npx @bevel-software/hexis-mcp --url <workspace-url> --key <connection-key>
57
- ```
58
-
59
- It serves everything the hosted endpoint serves **plus** the local-only tools, because it runs on the machine where they exist. Remote tools still execute on the server, so their shared keys and OAuth sign-ins keep working untouched; a local-only tool's own `${VAR}`s are fetched by `hexis-mcp` from this workspace's Secrets Vault with the connection key, and from the environment of whatever launched the command (your MCP client's config) for any the vault does not hold. Reading the `.tool` and wiring the server into your client by hand still works and is the fallback when the command is unavailable.
60
-
61
- ### Referencing secrets — `${VAR}` and the `variables` block
62
-
63
- Anywhere a `.tool` needs a credential (an API key, a token) write a placeholder like `${API_KEY}`. At call time it is filled from the **Secrets Vault** under a key derived from the manual's resolved id (the same `id` → `name` → file-name resolution described above) and the variable's name: for a plain id the key is `<id>_<VAR>` — a manual whose id is `weather` referencing `${API_KEY}` reads the secret `weather_API_KEY` — while an id that contains `_` or punctuation is namespaced with every such character turned into `_` and then doubled (`serper_everyone` + `API_KEY` → `serper__everyone_API_KEY`). `list_tool_setup` and the Secrets page show the exact key each manual reads, so read it there rather than spelling it by hand. A secret is therefore bound to exactly one manual; another manual cannot read it.
50
+ - **`scope: admin`** (the default): set once by a writer of the file and shared
51
+ by everyone who uses the tool. Prefer it.
52
+ - **`scope: user`**: each person sets their own.
64
53
 
65
- Declare who provisions each variable with an optional top-level `variables` array. Each entry is `{ name, scope, label? }`:
54
+ `name` matches `[A-Za-z0-9_]+`. A `${VAR}` you do not declare is `admin` and
55
+ still shows in the app, so the list is needed only for `user` scope, a label,
56
+ or a sign-in. Values are entered in the app, never in the file. A malformed
57
+ entry makes the whole file fail to load (it then shows under `invalid`).
66
58
 
67
- - **`scope: admin`** (the **default**) — set **once by a writer** of this `.tool` file; the same value is shared by everyone who uses the tool. Prefer this: keep as much as possible owned by the tool author.
68
- - **`scope: user`** — set by **each end user** for themselves (their own value, never shared).
59
+ ### Sign-ins: the `oauth` block
69
60
 
70
- `name` must match `[A-Za-z0-9_]+`. A referenced `${VAR}` that you don't declare defaults to `admin` — and it still SURFACES automatically: the app detects every `${VAR}` the file actually references and shows it in the secrets UI, so the `variables` block is only needed to change a variable's scope to `user`, give it a label, or declare an OAuth sign-in. Values are entered in the Secrets Vault UI (or the `.tool` editor's sidebar), never in the file itself. A malformed `variables` entry makes the whole file fail to load, so it is never silently mis-scoped.
71
-
72
- ### Declaring an OAuth sign-in — the `oauth` block
73
-
74
- A `user`-scoped variable can be filled by **signing in** instead of by a typed value: add an `oauth` block and each member authorizes with the provider; the token then rides in whatever header references `${VAR}`. The block carries PUBLIC config only:
61
+ A `user` variable can be filled by signing in instead of typing a value: add
62
+ an `oauth` block, and the token rides in whatever header uses `${VAR}`. The
63
+ block carries public settings only:
75
64
 
76
65
  | field | | |
77
66
  |---|---|---|
78
- | `clientId` | required | the OAuth app's client id — the tool owner registers the app with the provider, using the redirect URI `<backend>/api/secrets/oauth/callback` |
79
- | `authorizationUrl`, `tokenUrl` | **optional on an `mcp.json` server**, required in a `.tool` | leave both out on an MCP server: they are discovered from the server's own OAuth metadata. Give both or neither. |
80
- | `scopes` | optional | `string[]`, requested at sign-in and required back from the token |
81
- | `pkce` | optional, default **on** | PKCE S256 — MCP servers require it; providers without it ignore it. Only `false` is meaningful. |
82
- | `resource` | optional | RFC 8707 resource indicator (the MCP server URL); discovered on an `mcp.json` server |
83
- | `authParams` | optional | extra static authorize params, e.g. Google's `access_type: offline` |
84
-
85
- **Never** a `clientSecret` — a `.tool` carrying one fails to load, and an `mcp.json` server whose plugin.json entry carries one is dropped from the catalog. The secret is pasted once by a tool writer on the tool's page, then every member signs in on the Connect page.
86
-
87
- For an `mcp.json` server the declaration lives in `plugin.json`, in the same extensions entry as the auth header that uses it:
67
+ | `clientId` | required | the OAuth app's client id; its owner registers the app with the redirect URI `<backend>/api/secrets/oauth/callback` |
68
+ | `authorizationUrl`, `tokenUrl` | required in a `.tool`; optional on an `mcp.json` server | an MCP server's are discovered from its own OAuth metadata, so give them only when that discovery fails. Both or neither |
69
+ | `scopes` | optional | `string[]`, requested at sign-in |
70
+ | `pkce` | optional, default on | only `false` means anything |
71
+ | `resource` | optional | the RFC 8707 resource indicator; discovered on an `mcp.json` server |
72
+ | `authParams` | optional | extra fixed parameters, such as Google's `access_type: offline` |
73
+
74
+ **Never** a `clientSecret`: a `.tool` with one fails to load, and an `mcp.json`
75
+ server whose entry has one is dropped. The secret is pasted once on the tool's
76
+ page. For an `mcp.json` server, the declaration goes in `plugin.json`, beside
77
+ the header that uses it:
88
78
 
89
79
  ```json
90
- {
91
- "extensions": {
92
- "software.bevel.hexis": {
93
- "mcpServers": {
94
- "hubspot": {
95
- "headers": { "Authorization": "Bearer ${HUBSPOT_TOKEN}" },
96
- "variables": [
97
- { "name": "HUBSPOT_TOKEN", "scope": "user", "label": "HubSpot sign-in",
98
- "oauth": { "clientId": "<the app's client id>" } }
99
- ]
100
- }
101
- }
102
- }
103
- }
104
- }
80
+ { "extensions": { "software.bevel.hexis": { "mcpServers": { "hubspot": {
81
+ "headers": { "Authorization": "Bearer ${HUBSPOT_TOKEN}" },
82
+ "variables": [{ "name": "HUBSPOT_TOKEN", "scope": "user", "label": "HubSpot sign-in",
83
+ "oauth": { "clientId": "<the app's client id>" } }]
84
+ } } } } }
105
85
  ```
106
86
 
107
- That is the whole declaration: endpoints, PKCE and the resource indicator come from the server. Add `authorizationUrl`/`tokenUrl` only when `list_tool_setup` reports in `setup.reason` that they could not be discovered.
108
-
109
- ### Calling a Google API as a service account: `auth_type: google_service_account`
87
+ ### Google APIs as a service account
110
88
 
111
- Some Google APIs (Google Ads, Tag Manager, BigQuery, Sheets, …) are called as a **service account**: one shared identity, no sign-in per person. Google does not accept the service account's key on a call. It accepts a short-lived token that has to be minted from the key, so a header holding `${VAR}` cannot do it. Name the key in an `auth` block on an inline tool's `tool_call_template` instead, and the platform mints the token at call time, keeps it until shortly before it expires, and sends it as `Authorization: Bearer <token>`:
89
+ Some Google APIs (Ads, Tag Manager, BigQuery, Sheets) are called as one shared
90
+ service account, with a short-lived token minted from its key. Name the key in
91
+ an `auth` block on an inline tool's `tool_call_template` (with
92
+ `call_template_type: http`), and the platform mints, caches and sends the token:
112
93
 
113
94
  ```yaml
114
95
  ---
@@ -134,94 +115,91 @@ tools:
134
115
  ---
135
116
  ```
136
117
 
137
- | field | requirement | notes |
138
- |---|---|---|
139
- | `credentials` | required | always a `${VAR}`: the vault variable holding the key JSON Google issued for the service account (the whole file, or the file in base64). Admin-scoped, so a writer of the `.tool` stores it once on the tool's page. Never the key itself. |
140
- | `scopes` | required | the OAuth scopes the API needs: one scope, a space-separated list, or a list |
141
- | `subject` | optional | a user's email to act as, for a service account granted domain-wide delegation |
142
-
143
- The token always comes from Google's own token endpoint; a `token_uri` inside the key is ignored. Give the service account access in the Google product itself (for example add its email as a user of the Google Ads account or the Tag Manager container), or that product refuses every call with an error of its own. When Google refuses the key itself (a revoked key, a scope the account may not have, a subject without delegation), the error names the service account and Google's reason, never the key.
144
-
145
- The block works in one place: an inline tool's `tool_call_template` with `call_template_type: http`. Anywhere else (an `sse`, `streamable_http` or `mcp` template, or a `type: http` / `type: mcp` tool that discovers its tools from a `url`) no token would be sent, so the `.tool` is refused and `list_tool_setup` names it under `invalid`, saying where the block was found. It works the same for a `remote: false` tool run by the local `hexis-mcp` server, which mints the token on the machine it runs on.
146
-
147
- A service account is one shared identity. To have each person call Google as themselves instead, do not use this block: declare a sign-in variable (`oauth`, above) and send it as `Authorization: Bearer ${VAR}`.
118
+ `credentials` is always a `${VAR}` holding the key JSON (whole, or base64);
119
+ `scopes` is required; `subject` (optional) is a user to act as, for a service
120
+ account with domain-wide delegation. The block anywhere else is refused and
121
+ named under `invalid`. Give the service account access in the Google product
122
+ itself. To have each person call Google as themselves, use a sign-in
123
+ (`oauth`) instead.
148
124
 
149
125
  ### Examples
150
126
 
151
- An `http` manual that authenticates with a shared org key and a per-user key:
127
+ An `http` manual with a shared and a personal key:
152
128
 
153
129
  ```yaml
154
- name: weather
130
+ ---
131
+ id: weather
155
132
  type: http
156
133
  url: https://api.weather.example/utcp
157
134
  headers:
158
135
  Authorization: Bearer ${ORG_KEY}
159
136
  X-User-Key: ${USER_KEY}
160
137
  variables:
161
- - { name: ORG_KEY, scope: admin, label: "Org-wide weather.com key" }
162
- - { name: USER_KEY, scope: user, label: "Your personal weather.com key" }
138
+ - { name: ORG_KEY, scope: admin, label: "Org-wide weather key" }
139
+ - { name: USER_KEY, scope: user, label: "Your personal weather key" }
140
+ ---
163
141
  ```
164
142
 
165
143
  An `inline` manual with one tool:
166
144
 
167
- ```json
168
- {
169
- "name": "billing",
170
- "type": "inline",
171
- "variables": [{ "name": "BILLING_KEY", "scope": "admin" }],
172
- "tools": [
173
- {
174
- "name": "create_invoice",
175
- "description": "Create an invoice.",
176
- "inputs": { "type": "object", "properties": {} },
177
- "outputs": { "type": "object", "properties": {} },
178
- "tool_call_template": {
179
- "call_template_type": "http",
180
- "http_method": "POST",
181
- "url": "https://api.billing.example/invoices",
182
- "headers": { "Authorization": "Bearer ${BILLING_KEY}" }
183
- }
184
- }
185
- ]
186
- }
145
+ ```yaml
146
+ ---
147
+ id: billing
148
+ type: inline
149
+ variables:
150
+ - { name: BILLING_KEY, scope: admin }
151
+ tools:
152
+ - name: create_invoice
153
+ description: Create an invoice.
154
+ inputs: { type: object, properties: {} }
155
+ outputs: { type: object, properties: {} }
156
+ tool_call_template:
157
+ call_template_type: http
158
+ http_method: POST
159
+ url: https://api.billing.example/invoices
160
+ headers: { Authorization: "Bearer ${BILLING_KEY}" }
161
+ ---
187
162
  ```
188
163
 
189
164
  ### Adding a third-party tool
190
165
 
191
- When asked to add/integrate a product as a tool (e.g. "add Notion", "wire up Linear"), **never invent an endpoint or write a placeholder URL** — a `.tool` pointing at a made-up host is useless:
192
-
193
- 1. **Find the real endpoint from the vendor's own docs.** Prefer the vendor's official **remote MCP server** if one exists; otherwise fall back to their **REST API** base. No endpoint is named here on purpose — a URL copied into this file would be asserted long after it stopped being true, which is the failure this step exists to prevent. Use web search/extract to confirm the exact URL, transport, and auth scheme — don't answer from memory. If you have no web access or genuinely can't find it, **ask the user** for the endpoint URL and auth instead of guessing.
194
- 2. **Pick the home from what you found.** An MCP server → an entry in the plugin's `mcp.json` (`type: "streamable-http"` with the official `url` — use the `https://…` URL, **never** `ws://`/`wss://`). A plain REST/HTTP endpoint → a `.tool` with `type: http`. Use `type: inline` only when hand-authoring the individual HTTP calls.
195
- 3. **An OAuth-protected MCP server usually needs NOTHING beyond its `mcp.json` entry.** Write just those two and let the app probe the server: it discovers the sign-in provider (MCP authorization spec), registers itself, and surfaces a per-user sign-in on the Connect page. That is the `oauth-auto` case, and for it you must NOT declare `variables` or `headers`.
196
-
197
- Some providers do not support automatic registration (`oauth-manual` — HubSpot, Google; see the walkthrough below). Those DO need a sign-in variable holding the client id of an app the owner registers, and an admin pastes the client secret on the tool's page. You do not have to guess which kind you are facing: write the two lines, then run `list_tool_setup` and read `setup.kind` — and `setup.reason`, which spells out the next step (including the redirect URI to register).
198
- 4. **For key-based auth, wire it as `variables`, never a hard-coded secret.** Reference credentials as `${VAR}` in `headers` (e.g. `Authorization: Bearer ${NOTION_TOKEN}`) and declare each in the `variables` block with a scope (`admin` = one shared value; `user` = per-user). Users fill the values in the Secrets Vault.
199
- 5. **Say so when a tool is reachable ONLY from the user's own machine.** For an MCP server (e.g. one on `localhost`), declare `local: true` on its entry in the plugin.json extensions block — `remote: false` is a `.tool` frontmatter field and means nothing in `mcp.json`. For an `http`/`inline` `.tool`, set `remote: false`. Otherwise leave the tool remote-capable.
200
-
201
- ### Checking what an admin still needs to configure
202
-
203
- Call the **`list_tool_setup`** tool to see, for every accessible tool — `.tool` manuals and `mcp.json` servers alike — what is configured and what is still missing. Use it whenever a tool isn't working, after adding a tool, or when asked "what do I need to set up?" — then EXPLAIN the remaining steps to the user rather than guessing. Per tool it reports:
204
-
205
- - **`setup.kind`** (for MCP servers): `open` = no credentials needed; `oauth-auto` = the platform registered itself with the server automatically and users just authorize on the **Connect page**; `oauth-manual` = the sign-in uses an OAuth app the owner registers (the provider offers no automatic registration, or the declaration already names a client id). `setup.reason` is present only while something still blocks that sign-in — no declaration yet, or endpoints that could not be discovered — and says what to do.
206
- - **Per variable**: `adminConfigured` (the shared value — or, for a sign-in, the owner-side provider setup — is done), `userConfigured` / `authorized` (the CURRENT user's own value / sign-in), and `canWrite` (whether the current user may set the tool's shared config).
207
-
208
- **Tools are served from the default branch only.** An `mcp.json` entry or `.tool` you write on a draft is committed to that draft and nowhere else: it is not listed, not callable and has no sign-in on the Connect page until the draft is merged. After declaring a tool on a draft, call `list_tool_setup` with `branch` set to that draft — `onBranchOnly` names what is still waiting there — and tell the user it goes live once the change request is merged. A tool that stays in `tools` is released, and a restart does not remove it or its sign-ins; if one disappears, check the caller's read access to the file that declares it.
209
-
210
- **A released tool or skill is live within ten seconds — no reconnect.** A commit on the default branch that adds, changes or removes a `.tool`, an `mcp.json`, a `plugin.json` or a `SKILL.md` drops the catalogs at once, whichever way the commit arrived: the app, the file tools, a git push, or an approved change request being applied. The hosted endpoint is stateless — it reads the live catalog on every request, so the very next call sees the change. The local `hexis-mcp` server checks the workspace's catalog whenever its connection is USED — when a tool call finishes, and when a client lists the tools — and re-registers what changed, local-only servers included; on a connection in use the change is there within ten seconds of the commit — unless a tool call is still running on that connection, which holds the refresh for as long as that call runs, to a limit of fifteen seconds (see the caveat below) — and `list_tools`, `list_tool_setup` and `list_local_tools` then answer with the new state on the connection you already have. A listing runs a check and waits for it, so what you are handed is never a list a refresh is halfway through replacing; checks are collapsed to at most one every two seconds, so a listing arriving inside that window is answered from what the last check confirmed rather than from a fresh read — up to two of those ten seconds are that window alone, before the workspace has been asked anything. After a call, the change lands once that call finishes, so a new tool is callable from the call after that one. An IDLE connection is deliberately outside that window: it asks the workspace nothing about its catalog, holds nothing open beyond the one MCP session it serves tools through, and is told nothing — no catalog timer, no extra socket parked per laptop, so an unused connection costs the workspace nothing more than being connected (a browser-signed-in server still renews its own sign-in shortly before it expires, a single request every few hours) — and it catches up at its next use. Skills need no check at all: the released skill catalog is cached briefly and dropped the moment the default branch changes, so a committed `SKILL.md` is in the very next `list_skills` or `get_skill` answer, and the two resolve a skill the same way, so a skill you can load by name is a skill the listing shows.
211
-
212
- The platform also sends the MCP tool-list-changed and prompt-list-changed notifications when it can. **A client that CACHES the list it got at connect time — rather than honouring those notifications — will not see the change: it must re-list, or reconnect.** That is a property of the client, not of the workspace; if a tool you just wrote is missing, call `list_tools` again before assuming anything is wrong. One caveat on the local `hexis-mcp` server: a refresh there waits for a tool call that is still running, but only for fifteen seconds, so a commit made mid-call lands once that call finishes — or, if the call is still running after those fifteen seconds, while it is still running — and a LOCAL-only server (`local: true`, or a `type: "stdio"` command) that changed is restarted by that refresh, so a call to it made in the same moment may see it come back.
213
-
214
- The listing is scoped by the same access controls as everything else: a tool the caller can't READ doesn't appear at all, and `canWrite` means write access **on the file that declares it** — the `.tool` file itself (via its frontmatter `write:`/`owner:` verbs or the `access.md` chain), or the plugin's `mcp.json` for an MCP server (via the plugin's `access.md` chain — `mcp.json` carries no verb list of its own) — NOT any platform role. The people who manage that file are exactly the people who configure its shared secrets. To delegate a `.tool` to someone, add them to that file's `write:`/`owner:` list; to delegate an MCP server, grant them `write` on the plugin in its `access.md` (both are edits you can make via change request). That alone lets them configure it.
215
-
216
- **Agents never handle secret VALUES.** Never ask for an API key, token, or client secret in the conversation, and there is no tool to set one. Point the right person at the right surface instead:
217
-
218
- - **Shared (admin) values and OAuth client secrets** → a tool writer pastes them into the fields on the tool's page in the app (the "Your connection" section; for a `.tool` file, the setup panel is also in its editor sidebar).
219
- - **Per-user values and sign-ins** → each user enters/authorizes on the **Connect page**.
220
-
221
- For **`oauth-manual`** (e.g. HubSpot, Google, GitHub, Slack — no dynamic client registration), walk the admin through the one-time setup:
222
-
223
- 1. Register an OAuth app in the provider's console, with redirect URI `<backend>/api/secrets/oauth/callback` (the exact URI is in `setup.reason`).
224
- 2. Ask for the app's **client id** (public — fine to receive in chat) and write the sign-in declaration yourself: for an `mcp.json` server, the `variables` entry with `oauth: { clientId }` plus the `Authorization: Bearer ${VAR}` header in the plugin.json extensions entry (see "Declaring an OAuth sign-in" above — no URLs needed); for a `.tool`, the same entry with `authorizationUrl` and `tokenUrl` as well. You can do this edit for them via a change request. A human can do the same under "Edit server" on the tool's page — the form's fields are exactly this block.
225
- 3. Run `list_tool_setup` again: `setup.reason` must be gone. If it says the endpoints could not be discovered, add `authorizationUrl`/`tokenUrl` from the provider's docs.
226
- 4. The admin pastes the app's **client secret** into the "Client secret" field on the tool's page — never into the file, never into the chat.
227
- 5. Every user then authorizes on the Connect page.
166
+ When asked to add a product as a tool ("add Notion", "wire up Linear"), never
167
+ invent an endpoint or write a placeholder URL:
168
+
169
+ 1. **Find the real endpoint in the vendor's own docs**, preferring their
170
+ official remote MCP server, else their REST API. Confirm the URL, transport
171
+ and auth with a web search, not from memory; without web access, ask the
172
+ person.
173
+ 2. **Pick the home.** An MCP server is an entry in the plugin's `mcp.json`
174
+ (`type: "streamable-http"` and the `https://` URL, never `ws://`). A REST
175
+ API is a `.tool` with `type: http`, or `type: inline` when you write each
176
+ call by hand.
177
+ 3. **An OAuth-protected MCP server usually needs only its `mcp.json` entry.**
178
+ The app probes it, registers itself and offers a sign-in on the Connect page
179
+ (`oauth-auto`). An `Authorization` header or an `oauth` variable turns that
180
+ discovery off, so add neither. Some providers do not support it
181
+ (`oauth-manual`, such as HubSpot or Google): write the entry; once it is
182
+ live, `list_tool_setup` answers `setup.kind` and `setup.reason` for it.
183
+ 4. **For a key, use `variables`**, never a key in the file:
184
+ `Authorization: Bearer ${NOTION_TOKEN}` and a `variables` entry with its
185
+ scope.
186
+ 5. **Mark tools that only work on the person's machine**: `local: true` on an
187
+ MCP server's `plugin.json` entry, `remote: false` on a `.tool`.
188
+
189
+ All of it lands on a draft first, and goes live once merged (see `tools`).
190
+
191
+ **Walking an admin through `oauth-manual`:**
192
+
193
+ 1. Register an OAuth app with the provider, with the redirect URI that
194
+ `setup.reason` gives.
195
+ 2. Ask for the app's **client id** (public, fine in the chat) and write the
196
+ declaration: for an `mcp.json` server, the `variables` entry with
197
+ `oauth: { clientId }` and the `Authorization: Bearer ${VAR}` header in its
198
+ `plugin.json` entry; for a `.tool`, the same with `authorizationUrl` and
199
+ `tokenUrl`. A person can do the same under "Edit server" on the tool's page.
200
+ 3. Once the declaration is on `{{defaultBranch}}` (a draft's entry shows in
201
+ `onBranchOnly` with no `setup`), run `list_tool_setup`: `setup.reason` must
202
+ be gone. If the endpoints could not be discovered, add `authorizationUrl`
203
+ and `tokenUrl` from the provider's docs.
204
+ 4. The admin pastes the **client secret** into the tool's page, never into
205
+ the file or the chat. Then everyone signs in on the Connect page.