@bevel-software/platform-core-backend 0.25.2 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-guide/access-control.md +234 -0
- package/agent-guide/conventions.md +27 -0
- package/agent-guide/directory-structure.md +145 -0
- package/agent-guide/finding-things.md +7 -0
- package/agent-guide/introduction.md +27 -0
- package/agent-guide/skills.md +47 -0
- package/agent-guide/tool-manuals.md +217 -0
- package/agent-guide/where-a-new-file-goes.md +36 -0
- package/dist/assets.d.ts +7 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +9 -0
- package/dist/assets.js.map +1 -1
- package/dist/core/core-ports.d.ts +11 -0
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +13 -2
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +9 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +14 -4
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +9 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +1 -0
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +16 -0
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +139 -0
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -0
- package/dist/modules/agent-guide/agent-guide.js +191 -0
- package/dist/modules/agent-guide/agent-guide.js.map +1 -0
- package/dist/modules/agent-guide/agent-guide.tools.d.ts +24 -0
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -0
- package/dist/modules/agent-guide/agent-guide.tools.js +100 -0
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -0
- package/dist/modules/agent-guide/index.d.ts +4 -0
- package/dist/modules/agent-guide/index.d.ts.map +1 -0
- package/dist/modules/agent-guide/index.js +4 -0
- package/dist/modules/agent-guide/index.js.map +1 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +3 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.js +3 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +9 -6
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +9 -6
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/index.d.ts +1 -1
- package/dist/modules/agent-instructions/index.d.ts.map +1 -1
- package/dist/modules/agent-instructions/index.js +1 -1
- package/dist/modules/agent-instructions/index.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +10 -50
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +32 -85
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +29 -2
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +113 -16
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/tool-schema-guard.d.ts +105 -0
- package/dist/modules/mcp/tool-schema-guard.d.ts.map +1 -0
- package/dist/modules/mcp/tool-schema-guard.js +171 -0
- package/dist/modules/mcp/tool-schema-guard.js.map +1 -0
- package/dist/modules/plugins/plugins.tools.d.ts +36 -2
- package/dist/modules/plugins/plugins.tools.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.tools.js +71 -14
- package/dist/modules/plugins/plugins.tools.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +0 -7
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +14 -53
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +3 -6
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +58 -16
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +23 -4
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +4 -0
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +14 -0
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +7 -0
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +66 -36
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +14 -14
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
- package/dist/modules/tool-registry/description-length.js +24 -26
- package/dist/modules/tool-registry/description-length.js.map +1 -1
- package/dist/modules/tool-registry/guide-first.d.ts +23 -0
- package/dist/modules/tool-registry/guide-first.d.ts.map +1 -0
- package/dist/modules/tool-registry/guide-first.js +32 -0
- package/dist/modules/tool-registry/guide-first.js.map +1 -0
- package/dist/modules/tool-registry/tool-registry.d.ts +6 -0
- package/dist/modules/tool-registry/tool-registry.d.ts.map +1 -1
- package/dist/modules/tool-registry/tool-registry.js +9 -2
- package/dist/modules/tool-registry/tool-registry.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts +449 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.js +481 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +73 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js +582 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +12 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.js +5 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +9 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +210 -13
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +456 -91
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/merge-commit.d.ts +73 -0
- package/dist/modules/workflow/git/merge-commit.d.ts.map +1 -0
- package/dist/modules/workflow/git/merge-commit.js +89 -0
- package/dist/modules/workflow/git/merge-commit.js.map +1 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts +94 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +332 -37
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +35 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +178 -12
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts +6 -2
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +7 -2
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +4 -0
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +3 -0
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +22 -27
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +58 -52
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +209 -223
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +5 -3
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.js +5 -3
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +10 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +211 -18
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +11 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +14 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/hidden-tools.d.ts +44 -0
- package/dist/shared/hidden-tools.d.ts.map +1 -0
- package/dist/shared/hidden-tools.js +13 -0
- package/dist/shared/hidden-tools.js.map +1 -0
- package/kb-template/.bevelignore +0 -5
- package/package.json +4 -3
- package/src/__tests__/kb-layout-config.test.ts +10 -100
- package/src/__tests__/packaged-assets-ship.test.ts +54 -0
- package/src/assets.ts +10 -0
- package/src/core/core-ports.ts +11 -0
- package/src/core/create-core-server.ts +13 -2
- package/src/core/create-core-services.ts +28 -4
- package/src/index.ts +2 -2
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +58 -0
- package/src/modules/access/__tests__/access-control.platform-restore.test.ts +8 -7
- package/src/modules/access/__tests__/access-personal-plugin.test.ts +1 -18
- package/src/modules/access/access-control.interface.ts +15 -0
- package/src/modules/access/access-control.service.ts +21 -0
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +328 -0
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +189 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +122 -0
- package/src/modules/agent-guide/agent-guide.ts +291 -0
- package/src/modules/agent-guide/index.ts +21 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +28 -121
- package/src/modules/agent-instructions/agent-instructions.routes.ts +3 -2
- package/src/modules/agent-instructions/compose.ts +9 -6
- package/src/modules/agent-instructions/index.ts +0 -3
- package/src/modules/agent-instructions/shared-file-rules.ts +31 -93
- package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +14 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +250 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +31 -23
- package/src/modules/mcp/__tests__/tool-schema-guard.test.ts +266 -0
- package/src/modules/mcp/mcp.service.ts +137 -19
- package/src/modules/mcp/tool-schema-guard.ts +196 -0
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +154 -4
- package/src/modules/plugins/plugins.tools.ts +75 -15
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +26 -55
- package/src/modules/settings/deployment-settings.service.ts +13 -54
- package/src/modules/settings/setup.routes.ts +3 -6
- package/src/modules/skills/__tests__/skills.tools.description.test.ts +91 -0
- package/src/modules/skills/skills.tools.ts +62 -16
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +57 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +73 -4
- package/src/modules/tool-manuals/tool-manuals.contract.ts +24 -4
- package/src/modules/tool-manuals/tool-manuals.service.ts +17 -0
- package/src/modules/tool-manuals/tool-manuals.tools.ts +74 -36
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +160 -0
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +61 -59
- package/src/modules/tool-registry/description-length.ts +24 -26
- package/src/modules/tool-registry/guide-first.ts +34 -0
- package/src/modules/tool-registry/tool-registry.ts +9 -2
- package/src/modules/workflow/__tests__/apply-failure.test.ts +6 -1
- package/src/modules/workflow/agent-tools/__tests__/change-request-read-shape.test.ts +705 -0
- package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +1518 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +23 -2
- package/src/modules/workflow/agent-tools/change-request-read-shape.ts +712 -0
- package/src/modules/workflow/agent-tools/change-request-read.tools.ts +724 -0
- package/src/modules/workflow/agent-tools/change-request-summary.ts +5 -1
- package/src/modules/workflow/agent-tools/workflow.tools.ts +8 -0
- package/src/modules/workflow/git/__tests__/git.service.appliedChange.test.ts +285 -0
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +124 -0
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +334 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +72 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +24 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +620 -1
- package/src/modules/workflow/git/git.service.ts +537 -94
- package/src/modules/workflow/git/merge-commit.ts +88 -0
- package/src/modules/workflow/git/pull-request.service.ts +380 -54
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +7 -1
- package/src/modules/workflow/review-workflow/__tests__/merge-records-own-commit.test.ts +407 -0
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +189 -11
- package/src/modules/workflow/workflow.routes.ts +7 -2
- package/src/modules/workflow/workflow.service.ts +7 -0
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +4 -3
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +21 -10
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +33 -55
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +255 -22
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +191 -489
- package/src/modules/workspace/startup/steps/seed-tree.ts +21 -27
- package/src/modules/workspace/startup/steps/template-files.step.ts +217 -249
- package/src/modules/workspace/startup/steps/template-source.ts +5 -3
- package/src/modules/workspace/workspace.tools.ts +226 -16
- package/src/shared/domain-errors.ts +15 -0
- package/src/shared/hidden-tools.ts +45 -0
- package/kb-template/AGENTS.md +0 -730
package/kb-template/AGENTS.md
DELETED
|
@@ -1,730 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
# This file's own access rule: readable by every signed-in person and their
|
|
3
|
-
# agents, whatever the root access.md says. Agents are told to read this file
|
|
4
|
-
# before their first action, and the root rules grant read to nobody by
|
|
5
|
-
# default — without this line a non-admin's agent would fail on step one.
|
|
6
|
-
read:
|
|
7
|
-
- everyone
|
|
8
|
-
---
|
|
9
|
-
# Knowledge base
|
|
10
|
-
|
|
11
|
-
This is a git-backed knowledge base. You are the primary agent responsible for
|
|
12
|
-
maintaining it.
|
|
13
|
-
|
|
14
|
-
> **This file is managed by the platform.** It lives at the repository root as
|
|
15
|
-
> `{{agentsFile}}`, and every server restart replaces it with the current
|
|
16
|
-
> template, so edits made here are overwritten. Deployment- or team-specific
|
|
17
|
-
> conventions belong in files of your own — anywhere under
|
|
18
|
-
> `{{knowledgeBaseDir}}/`, linked from wherever they are needed.
|
|
19
|
-
|
|
20
|
-
**Read `mcp-description.md` at the repository root first.** It says what this
|
|
21
|
-
knowledge base contains and when to consult it. Agents connected over MCP
|
|
22
|
-
receive the default branch's copy inline at the start of every session; a
|
|
23
|
-
clone like this one reads the copy on its own branch.
|
|
24
|
-
|
|
25
|
-
**There is no required format for knowledge.** Write markdown the way the
|
|
26
|
-
subject wants to be written: prose, tables, checklists, diagrams, whatever
|
|
27
|
-
serves the reader. Nothing here parses your files into a schema or rejects a
|
|
28
|
-
document for having the wrong shape. If a deployment layers a structured
|
|
29
|
-
knowledge graph on top, it brings its own conventions and its own guide; this
|
|
30
|
-
one describes the platform underneath, which stores files and controls who may
|
|
31
|
-
change them.
|
|
32
|
-
|
|
33
|
-
## Directory Structure
|
|
34
|
-
|
|
35
|
-
```text
|
|
36
|
-
knowledge-base/
|
|
37
|
-
├── {{knowledgeBaseDir}}/ ← the knowledge itself; organise it however suits you
|
|
38
|
-
├── {{skillsDir}}/ ← shared skills, organised by who owns them
|
|
39
|
-
├── {{pluginsDir}}/ ← one folder per plugin: its tools, and links to skills
|
|
40
|
-
├── roles.yaml ← identity → role mapping (Admin-only edits)
|
|
41
|
-
└── access.md ← repo-root access-control rules
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
(The three root names above are this deployment's own — a deployment may
|
|
45
|
-
rename them in its setup screen, and this guide is rendered with the names in
|
|
46
|
-
effect each time it is written.)
|
|
47
|
-
|
|
48
|
-
Tool paths are workspace-relative, and the workspace root holds this
|
|
49
|
-
repository as the `knowledge-base/` folder: a file in it is
|
|
50
|
-
`knowledge-base/{{knowledgeBaseDir}}/Foo.md`. Write the prefix where you can —
|
|
51
|
-
it is the path every tool reports back — but a path without it is PLACED under
|
|
52
|
-
`knowledge-base/` rather than refused, so `{{knowledgeBaseDir}}/Foo.md` names that
|
|
53
|
-
same file, and so does the root-anchored `/knowledge-base/{{knowledgeBaseDir}}/Foo.md`
|
|
54
|
-
the app's Copy path gives you. Nothing you send can land beside the repository,
|
|
55
|
-
where git would never see it. `.` or `..` segments, backslashes and every other
|
|
56
|
-
absolute path are refused.
|
|
57
|
-
|
|
58
|
-
Only those three folders are structural. `{{skillsDir}}/` holds shared skills at any
|
|
59
|
-
depth — the folder that holds a `SKILL.md` is the skill, and everything above
|
|
60
|
-
it is ownership (`{{skillsDir}}/<scope>/…/<skill>/SKILL.md`, with an `access.md` in
|
|
61
|
-
any scope folder that needs its own rules). `{{pluginsDir}}/` has a layout the
|
|
62
|
-
platform reads:
|
|
63
|
-
|
|
64
|
-
```text
|
|
65
|
-
{{pluginsDir}}/<Plugin>/plugin.json the manifest (Agent Plugins) — what makes the folder a plugin; its `name` is the plugin's identity
|
|
66
|
-
{{pluginsDir}}/<Plugin>/skills/<skill>/SKILL.md a skill that lives inside the plugin
|
|
67
|
-
{{pluginsDir}}/<Plugin>/mcp.json MCP servers (authoritative)
|
|
68
|
-
{{pluginsDir}}/<Plugin>/software.bevel.hexis/tools/ `.tool` manuals
|
|
69
|
-
{{pluginsDir}}/<Plugin>/access.md who can read/write the plugin
|
|
70
|
-
{{pluginsDir}}/personal-<user-id>/… one per person: private
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
**The manifest's `name` is the plugin.** It is a kebab-case identifier
|
|
74
|
-
(`sales-team`), and it is what every grant spells (`plugin/sales-team/read`),
|
|
75
|
-
what the URLs and the catalog key on, and what the compiled marketplace
|
|
76
|
-
publishes the plugin as. `displayName` is what people see it called ("Sales
|
|
77
|
-
Team"); absent, the folder name is shown. Rename a plugin from its page in the
|
|
78
|
-
app: an identifier change rewrites every grant that names it, in one commit —
|
|
79
|
-
editing `name` by hand leaves those grants pointing at a plugin that no longer
|
|
80
|
-
exists.
|
|
81
|
-
|
|
82
|
-
**A plugin LINKS shared skills rather than containing them.** Its manifest
|
|
83
|
-
lists skill paths under `extensions["software.bevel.hexis"].skills` — each
|
|
84
|
-
entry is one skill folder or a folder of skills under `{{skillsDir}}/`:
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
{ "extensions": { "software.bevel.hexis": { "skills": ["{{skillsDir}}/Engineering/deploy", "{{skillsDir}}/Sales"] } } }
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
One skill, stored once, can be listed by many plugins. A plugin's effective
|
|
91
|
-
skills are the ones inside its folder plus everything its links resolve to.
|
|
92
|
-
Do not edit that list by hand: linking is done from the plugin's page in the
|
|
93
|
-
app, because it is two edits at once — the manifest entry AND a grant on the
|
|
94
|
-
skill (see *Access control* below). A manifest entry without the grant lists
|
|
95
|
-
a skill the plugin's members cannot read; the app shows such a link as
|
|
96
|
-
needing setup and offers Repair.
|
|
97
|
-
|
|
98
|
-
**Ownership decides who may read a skill, never the plugin.** A shared
|
|
99
|
-
skill's readability comes from the `access.md` rules on its own folder and
|
|
100
|
-
the scopes above it. A plugin that links a skill someone cannot read simply
|
|
101
|
-
does not show it to them.
|
|
102
|
-
|
|
103
|
-
**Symlinks are not supported anywhere under `{{pluginsDir}}/`.** Access control
|
|
104
|
-
resolves rules by path, and a symlink is a second path to the same content —
|
|
105
|
-
the two can disagree about who may read what. The platform never creates
|
|
106
|
-
them and ignores any it finds (they can only arrive via a direct git push).
|
|
107
|
-
|
|
108
|
-
**A plugin follows the [Agent Plugins](https://agent-plugins.org) specification**
|
|
109
|
-
(v1.0.0), so another conformant client can load one: it reads `plugin.json`, the
|
|
110
|
-
skills under `skills/`, and the servers in `mcp.json`, and ignores everything
|
|
111
|
-
else. Two things here are ours and sit outside that portable core. `access.md`
|
|
112
|
-
stays at the plugin root because access resolution walks root → file, so the
|
|
113
|
-
same rules one level down would govern only that subtree. And `http`/`inline` `.tool`
|
|
114
|
-
manuals live under the reverse-DNS `software.bevel.hexis/` namespace, because
|
|
115
|
-
the specification describes MCP servers only and has no way to express them.
|
|
116
|
-
|
|
117
|
-
**MCP servers belong in `mcp.json` — do not write `.tool` files for them.**
|
|
118
|
-
Each `mcpServers` key is the server's identity: it is the namespace its vault
|
|
119
|
-
secrets bind to (`<name>_<VAR>`), so renaming a key unbinds every configured
|
|
120
|
-
secret and sign-in. The portable entry carries only where the server is
|
|
121
|
-
(`type`, `url`, literal headers). Anything this platform needs beyond that —
|
|
122
|
-
auth headers carrying `${VAR}` vault references, `variables` declarations,
|
|
123
|
-
a `description`, or `local: true` for a server only reachable from a user's
|
|
124
|
-
machine — goes in `plugin.json` under
|
|
125
|
-
`extensions["software.bevel.hexis"].mcpServers[<name>]`, which other clients
|
|
126
|
-
ignore by design. A `type: "stdio"` entry (a command run on the user's own
|
|
127
|
-
machine) is always local: the hosted endpoint never spawns it; the local
|
|
128
|
-
`hexis-mcp` server fetches the plugin's files to a local directory and runs it
|
|
129
|
-
per the Agent Plugins runtime contract (`PLUGIN_ROOT`/`PLUGIN_DATA`, `./`
|
|
130
|
-
commands contained to the plugin). A stdio server SHOULD exit when its stdin
|
|
131
|
-
reaches EOF — the client also terminates it on shutdown, but a server that
|
|
132
|
-
ignores EOF outlives crashes as an orphan whose working directory blocks the
|
|
133
|
-
plugin folder from ever refreshing.
|
|
134
|
-
|
|
135
|
-
**Secrets are never written into a plugin's portable files.** The specification
|
|
136
|
-
defines no portable credential mechanism on purpose: authorization and
|
|
137
|
-
credential storage are the client's business, header and `env` values are
|
|
138
|
-
"visible package data", and a client must not expand anything except
|
|
139
|
-
`${PLUGIN_ROOT}` and `${PLUGIN_DATA}`. So the Secrets Vault IS this platform's
|
|
140
|
-
answer to that — and `mcp.json` carries only where a server is, never a
|
|
141
|
-
`${VAR}` reference to how to authenticate with it. Those live in `plugin.json`
|
|
142
|
-
under `extensions["software.bevel.hexis"].mcpServers[<name>]`, which is ours
|
|
143
|
-
to interpret and which other clients ignore by design.
|
|
144
|
-
|
|
145
|
-
**Plugin folders are made through the platform, not by writing files.** A
|
|
146
|
-
folder is a plugin exactly when it carries a `plugin.json` (the platform
|
|
147
|
-
writes one into every legacy plugin folder at startup), and it is LISTED only
|
|
148
|
-
when it also carries an `access.md` — a bare directory under `{{pluginsDir}}/` is
|
|
149
|
-
neither. Plugins may sit at any depth under `{{pluginsDir}}/`; a folder that holds
|
|
150
|
-
plugins deeper down is a grouping folder, not a plugin. A new plugin needs an
|
|
151
|
-
`access.md` naming who runs it, and the write gate refuses a plain write
|
|
152
|
-
into an unused name there — so do not try to create a plugin by writing a
|
|
153
|
-
skill into `{{pluginsDir}}/<new-name>/…`; it will be denied. Use the two tools
|
|
154
|
-
instead:
|
|
155
|
-
|
|
156
|
-
- `my_plugin` — your user's own private space, created on first use:
|
|
157
|
-
`{{pluginsDir}}/personal-<id>/`. Readable only by its owner — not even
|
|
158
|
-
admins — and never listed as a plugin. Their personal skills go under its `skills/`,
|
|
159
|
-
each in its own folder with a `SKILL.md`; write there with the file tools.
|
|
160
|
-
- `create_plugin` — a shared plugin, named, optionally inside a grouping
|
|
161
|
-
folder under `{{pluginsDir}}/` (`parent`). The caller runs it; others join
|
|
162
|
-
through the app or are granted in its `access.md`.
|
|
163
|
-
|
|
164
|
-
The app's **New plugin** button and `POST /api/plugins` do the same. A skill
|
|
165
|
-
moves from a personal space into a plugin by moving its folder.
|
|
166
|
-
|
|
167
|
-
Everything under `{{knowledgeBaseDir}}/` is yours to arrange. Subfolders, naming,
|
|
168
|
-
whether a topic is one file or twenty — all of it is a judgement call about
|
|
169
|
-
what the next reader needs, not a rule the platform enforces.
|
|
170
|
-
|
|
171
|
-
A deployment may reserve further root folders of its own — `Data/`, `Agents/`
|
|
172
|
-
and `Pipelines/` scaffold an agentic execution layer in some installations.
|
|
173
|
-
They are not part of this template and are not created here; where they exist,
|
|
174
|
-
each carries its own `README.md` describing what belongs in it.
|
|
175
|
-
|
|
176
|
-
## Where a new file goes
|
|
177
|
-
|
|
178
|
-
Decide by what the file IS, not by which folder you already hold rights in.
|
|
179
|
-
Write access is not evidence that a file belongs somewhere.
|
|
180
|
-
|
|
181
|
-
- **Any document goes under `{{knowledgeBaseDir}}/`.** Knowledge, notes,
|
|
182
|
-
reports, tickets, specifications, plans, meeting minutes — anything written
|
|
183
|
-
to be read by a person. That is what the root is for, and its shape inside
|
|
184
|
-
is yours to choose.
|
|
185
|
-
- **A shared skill goes under `{{skillsDir}}/`**, or under
|
|
186
|
-
`{{pluginsDir}}/<Plugin>/skills/<skill>/SKILL.md` when it belongs to one
|
|
187
|
-
plugin alone. A person's private skill goes in their own space (`my_plugin`).
|
|
188
|
-
- **Tool manuals, MCP server declarations and manifests go inside a plugin:**
|
|
189
|
-
`.tool` manuals under `{{pluginsDir}}/<Plugin>/software.bevel.hexis/tools/`,
|
|
190
|
-
servers in that plugin's `mcp.json`, and `plugin.json` at its root.
|
|
191
|
-
- **A plugin folder never holds a document.** `{{pluginsDir}}/` carries
|
|
192
|
-
machinery — manifests, tool manuals, server declarations, access rules, and
|
|
193
|
-
the skills a plugin owns. A ticket or a report written there is filed where
|
|
194
|
-
nobody will look for it, under rules written for tools.
|
|
195
|
-
- **When the place named does not exist, or nothing fits, ask.** If the user
|
|
196
|
-
names a folder that is not there, or the file is of a kind this deployment
|
|
197
|
-
has made no home for, say so and ask where it should go. Do not settle for a
|
|
198
|
-
folder you happen to be able to write to; a wrong guess is discovered much
|
|
199
|
-
later than a question.
|
|
200
|
-
|
|
201
|
-
{{sharedFileRules}}
|
|
202
|
-
|
|
203
|
-
## Access control
|
|
204
|
-
|
|
205
|
-
Access to any path — reading it as much as writing it — is governed by
|
|
206
|
-
`roles.yaml` (who has which role), `groups.yaml` (who is in which group) and
|
|
207
|
-
`access.md` files (who may do what, where).
|
|
208
|
-
|
|
209
|
-
- **Roles** in `roles.yaml` map a role name to a list of members: emails, and
|
|
210
|
-
`group:<Name>` entries that give the role to a whole group (see *Giving a
|
|
211
|
-
role to a group* below). Role names are
|
|
212
|
-
case- and whitespace-insensitive (`Admin` = `admin` = `ADMIN`; `Product Team`
|
|
213
|
-
= `product team`). The reserved name `deny` cannot be used, and neither can
|
|
214
|
-
names starting with `role/` or `plugin/` — those spellings are tokens in
|
|
215
|
-
access entries (below). One exception to the file's authority: the
|
|
216
|
-
**deployment admin** — the address the server configuration sets as
|
|
217
|
-
`ADMIN_EMAIL` — is **always an Admin**, whether or not `roles.yaml` lists
|
|
218
|
-
it, and taking it out of the file does not change that. It is the rescue
|
|
219
|
-
path for a `roles.yaml` that has lost its last Admin. The App roles page
|
|
220
|
-
shows that account under Admin as a fixed member that cannot be added or
|
|
221
|
-
removed there; every other Admin membership is exactly what the file says,
|
|
222
|
-
and removing one takes effect on that person's next request.
|
|
223
|
-
- **Plugins are grantable principals.** `plugin/<name>/read`,
|
|
224
|
-
`plugin/<name>/write` and `plugin/<name>/owner` in any access file mean
|
|
225
|
-
everyone who currently holds that verb on the plugin whose manifest `name`
|
|
226
|
-
is `<name>`, derived live from the plugin's own `access.md`. Any spelling
|
|
227
|
-
folds to the identifier (`plugin/GTM/read` and `plugin/gtm/read` are one
|
|
228
|
-
principal). This is how a shared skill is made visible to a plugin's
|
|
229
|
-
members: `read: plugin/gtm/read` on the skill's folder.
|
|
230
|
-
Adding or removing someone on the plugin changes what they can read
|
|
231
|
-
everywhere the token is granted, with no copying.
|
|
232
|
-
- **Access rules** live in `access.md` files, which carry **two blocks with two
|
|
233
|
-
scopes**: the BODY (below the closing `---`) declares the rules for the
|
|
234
|
-
folder the file sits in, and the FRONTMATTER declares who may read and
|
|
235
|
-
write that `access.md` itself. Each block names verbs (`read`, `write`,
|
|
236
|
-
`download`, `owner`) whose entries are either grants (a bare principal) or
|
|
237
|
-
denials (the lowercase word `deny`, a space, then the principal).
|
|
238
|
-
Capitalised forms like `Deny` are *not* triggers; they are treated as part
|
|
239
|
-
of a name.
|
|
240
|
-
- **Principals** are a role name from `roles.yaml`, a group name from
|
|
241
|
-
`groups.yaml`, a person as `Name <email>`, a plugin token (above), or
|
|
242
|
-
**`everyone`** — the built-in org-wide principal: every signed-in person and
|
|
243
|
-
their agents. `read: everyone` in a folder's BODY opens that folder to the
|
|
244
|
-
whole organisation; it is how an organisation-wide skill or plugin is
|
|
245
|
-
shared. The same line in a file's FRONTMATTER only makes that one file
|
|
246
|
-
visible — a plugin's `access.md` ships with `read: everyone` in its
|
|
247
|
-
frontmatter so the plugin can be found and joined, and that admits nobody
|
|
248
|
-
to the plugin itself. A person's own space (`{{pluginsDir}}/personal-<id>/`)
|
|
249
|
-
denies `everyone` outright, so opening a parent folder never opens it.
|
|
250
|
-
When a group and a role share a name, the bare name means the GROUP;
|
|
251
|
-
`role/<Name>` (for example `deny role/Reviewer`) always means the role.
|
|
252
|
-
- **Keep an `access.md` body pure YAML**, with any explanation in `#` comments.
|
|
253
|
-
A body that does not parse as YAML naming at least one verb is read in the
|
|
254
|
-
older format instead, where the FRONTMATTER carried the folder's rules — so a
|
|
255
|
-
stray line of prose silently changes which block governs the folder.
|
|
256
|
-
- **The verbs nest.** `owner` sits over `write` and `download`; `write` and
|
|
257
|
-
`download` each sit over `read` — anyone who may edit a node, or save a copy
|
|
258
|
-
of it, may also view it. `write` and `download` say nothing about each other.
|
|
259
|
-
The nesting is GRANT-ONLY: a grant of a higher verb confers the lower ones,
|
|
260
|
-
but `deny write` or `deny download` says nothing about `read` and never
|
|
261
|
-
strips a separate read grant. So `download: Ana <ana@x.io>` alone lets Ana
|
|
262
|
-
open the node as well as download it, and a `deny download` beside an
|
|
263
|
-
inherited read leaves her able to open it but not save it.
|
|
264
|
-
- **You can only change what you can read.** Nothing is created, changed,
|
|
265
|
-
moved into or removed from a place the caller cannot read — on every
|
|
266
|
-
branch, drafts included, whatever `write:` rules say. A write tool refused
|
|
267
|
-
for this says so (`write-denied`, naming the unreadable folder), and
|
|
268
|
-
proposing is not offered either: a proposal into a folder its author cannot
|
|
269
|
-
see would vanish from them the moment it landed. Two exceptions. A NEW
|
|
270
|
-
FOLDER directly under `{{knowledgeBaseDir}}/`, `{{skillsDir}}/` or
|
|
271
|
-
`{{pluginsDir}}/`: anyone may start one, whatever the root's rules grant
|
|
272
|
-
them, and the new folder's `access.md` is seeded with the creator's own
|
|
273
|
-
`read:` grant so what they put there is visible to them (a loose FILE
|
|
274
|
-
directly at a root has no folder to carry that grant and is not excepted).
|
|
275
|
-
And an Admin — or the deployment owner — may change the files directly in
|
|
276
|
-
the repository root (`roles.yaml`, `access.md`, `groups.yaml`, `{{agentsFile}}`,
|
|
277
|
-
…) even when the root grants read to nobody: the same rescue the write
|
|
278
|
-
floor gives them, so a tree whose root rules lock everyone out stays
|
|
279
|
-
repairable from inside the app. That rescue stops at the root; a subfolder
|
|
280
|
-
an admin cannot read is closed to them like to anyone else.
|
|
281
|
-
- **Resolution** walks repo root → file directory, accumulating per-principal
|
|
282
|
-
state. User-level entries trump role-level entries. A role denial removes
|
|
283
|
-
only that role's contribution; it does not undo grants from other roles.
|
|
284
|
-
- **`roles.yaml` is editable only by Admin** — hard-coded in the resolver,
|
|
285
|
-
never overridable by an `access.md`.
|
|
286
|
-
- **`access.md` files are picked up at any depth**, so a folder can tighten or
|
|
287
|
-
widen what it inherited from its parent.
|
|
288
|
-
- **Per-file rules exist for Markdown notes only.** A note (`.md`, lowercase)
|
|
289
|
-
may name verbs in its own frontmatter, and those rules apply to that one
|
|
290
|
-
note. (A `.tool` definition keeps the access verbs in its own YAML the same
|
|
291
|
-
way.) Every other file (a PDF, a presentation, a spreadsheet, an image, any
|
|
292
|
-
binary, a `.markdown` or `.MD` file, or binary content saved as `.md`)
|
|
293
|
-
takes its folder's rules: sharing it on its own is refused with
|
|
294
|
-
`folder-governs-access`, naming the folder. To change who
|
|
295
|
-
can open such a file, change its folder's `access.md`, or move the file to a
|
|
296
|
-
folder whose rules fit.
|
|
297
|
-
|
|
298
|
-
Rules are enforced at runtime; a malformed `roles.yaml` or `access.md` surfaces
|
|
299
|
-
when access is resolved.
|
|
300
|
-
|
|
301
|
-
### Roles are pre-set — a "new role" is usually a group
|
|
302
|
-
|
|
303
|
-
**What a role is.** A role in `roles.yaml` is an app role: a capability the
|
|
304
|
-
platform defines and acts on (`Admin` is one), listed with the people who hold
|
|
305
|
-
it. The set of roles is pre-set by the platform. A role is not a way to name a
|
|
306
|
-
team.
|
|
307
|
-
|
|
308
|
-
**Agents never create roles.** Add people to a role that already exists, or
|
|
309
|
-
remove them, and nothing more: never add a role name to `roles.yaml`, and never
|
|
310
|
-
rename one — a rename is a delete plus a create. Such a write is refused with a
|
|
311
|
-
422 that names the role and says: app roles are pre-set — add people to
|
|
312
|
-
existing roles, and use a GROUP for a task- or team-scoped set of people.
|
|
313
|
-
Relay that refusal to your user as it stands; do not look for another way to
|
|
314
|
-
write the file.
|
|
315
|
-
|
|
316
|
-
**Is it really a group?** When someone asks for a "new role", it almost always
|
|
317
|
-
is. It is a group when any of these hold:
|
|
318
|
-
|
|
319
|
-
- the name says who the people are — a team, a project, a customer, a
|
|
320
|
-
committee — rather than a capability the platform already has;
|
|
321
|
-
- it would change or disappear when the project ends or the team reshuffles;
|
|
322
|
-
- its purpose is to give those people access to some folders or files.
|
|
323
|
-
|
|
324
|
-
A request that matches a role that already exists is membership, not a new
|
|
325
|
-
role.
|
|
326
|
-
|
|
327
|
-
**What to do instead.**
|
|
328
|
-
|
|
329
|
-
1. If an existing role already carries the capability, add the people to it.
|
|
330
|
-
2. Otherwise make it a group: add or extend the group in `groups.yaml` (or
|
|
331
|
-
point your user at the app's Groups page), then grant the group in the
|
|
332
|
-
`access.md` of the folders it should reach.
|
|
333
|
-
3. If your user still needs a role the platform does not have, that is not an
|
|
334
|
-
edit you can make — say so, and leave the decision to an admin.
|
|
335
|
-
|
|
336
|
-
### Giving a role to a group
|
|
337
|
-
|
|
338
|
-
A role's member list takes a group as well as individual emails. Write the
|
|
339
|
-
entry as `- group:<Name>`, where `<Name>` is a group in the active group
|
|
340
|
-
source — `synced-groups.yaml` when the deployment syncs groups from an
|
|
341
|
-
identity provider, `groups.yaml` otherwise. Here a `Reviewer` role the
|
|
342
|
-
deployment already has goes to a whole group:
|
|
343
|
-
|
|
344
|
-
```yaml
|
|
345
|
-
roles:
|
|
346
|
-
Admin:
|
|
347
|
-
- dana@example.com
|
|
348
|
-
Reviewer:
|
|
349
|
-
- lee@example.com
|
|
350
|
-
- group:Platform Team
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
- **Matching.** The name is matched case- and whitespace-insensitively against
|
|
354
|
-
the active group source, like role names: `group:platform team` and
|
|
355
|
-
`group:Platform Team` are the same entry as `group:Platform Team`.
|
|
356
|
-
- **Unknown groups are refused.** An entry naming a group the active source
|
|
357
|
-
does not declare is a validation error: the write is refused with a 422
|
|
358
|
-
that names the entry and its role (`'- group:Platfrom Team' under role
|
|
359
|
-
'Reviewer'`), and nothing is saved. Create the group first, or fix the name.
|
|
360
|
-
- **A group under `Admin` makes every member a full admin** — including anyone
|
|
361
|
-
added to the group later, and including the right to edit `roles.yaml`
|
|
362
|
-
itself. Only make that edit when your user is an Admin and explicitly asks
|
|
363
|
-
for exactly that, and say so in the commit summary; for anyone else, tell
|
|
364
|
-
them what it would mean and who can do it (below). `Admin` must also always keep at least one
|
|
365
|
-
direct email member; a group entry alone is not enough, so a broken
|
|
366
|
-
directory can never leave the deployment without an admin.
|
|
367
|
-
- **With direct emails.** Group entries and emails add up: the role's members
|
|
368
|
-
are everyone listed by email plus everyone currently in each listed group.
|
|
369
|
-
A person in both is simply a member; adding or removing someone from the
|
|
370
|
-
group changes the role with no edit to `roles.yaml`.
|
|
371
|
-
- **With denials.** Group members hold the role's grants exactly as if they
|
|
372
|
-
were listed by email. A denial of the role in an `access.md`
|
|
373
|
-
(`deny role/Reviewer`) therefore removes the role's contribution for
|
|
374
|
-
everyone in the group, as it does for the emails. Write the `role/` form:
|
|
375
|
-
a bare `deny Reviewer` would deny a group named `Reviewer` instead, if one
|
|
376
|
-
exists. The nearest `access.md` that says
|
|
377
|
-
anything about the person decides: a person granted by name
|
|
378
|
-
(`Name <email>`) in the SAME `access.md` as the denial keeps that access,
|
|
379
|
-
because within one file a person's own entry beats a role entry. A grant by
|
|
380
|
-
name in a folder further up does not survive a role denial closer to the
|
|
381
|
-
file.
|
|
382
|
-
|
|
383
|
-
**Only an Admin changes `roles.yaml`, and only on the default branch.** A
|
|
384
|
-
change request cannot carry the edit: when a request is merged, `roles.yaml`
|
|
385
|
-
is restored to what the default branch has, so a role edit drafted on a
|
|
386
|
-
branch is dropped at the merge without a word. Do not propose one. If your
|
|
387
|
-
user is an Admin, `edit_file` the file on the default branch directly — for
|
|
388
|
-
example, to give the Reviewer role to a group, add the entry under the
|
|
389
|
-
existing role:
|
|
390
|
-
|
|
391
|
-
```yaml
|
|
392
|
-
roles:
|
|
393
|
-
Admin:
|
|
394
|
-
- dana@example.com
|
|
395
|
-
Reviewer:
|
|
396
|
-
- lee@example.com
|
|
397
|
-
- group:Platform Team # added
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
If your user is not an Admin, tell them who is (the `Admin` entries in
|
|
401
|
-
`roles.yaml`) and that the change is made in the app's Roles page or by an
|
|
402
|
-
admin editing the file; do not open a change request for it.
|
|
403
|
-
|
|
404
|
-
### Direct writes vs change requests
|
|
405
|
-
|
|
406
|
-
File-level write access decides how a change lands on the default branch:
|
|
407
|
-
|
|
408
|
-
- A user — or an agent acting as that user — whose access resolution grants
|
|
409
|
-
**write or owner on every file the change touches** may commit **directly**
|
|
410
|
-
to the default branch.
|
|
411
|
-
- Without that access, the change goes through a **branch + change request**,
|
|
412
|
-
approved by an owner / write-access holder of the affected files — every
|
|
413
|
-
affected file with an eligible approver needs that approval, whatever its
|
|
414
|
-
type (notes, binary files, files without an extension).
|
|
415
|
-
- Agents carry exactly their user's access, never more. Before writing to the
|
|
416
|
-
default branch, **ask the user** whether to write directly or go through the
|
|
417
|
-
review flow — and prefer a change request when in doubt, when the change is
|
|
418
|
-
large, or when it touches content the user does not own.
|
|
419
|
-
|
|
420
|
-
### An agent proposes and syncs; a person merges
|
|
421
|
-
|
|
422
|
-
- **Propose** with `open_change_request`, then give the user the request's
|
|
423
|
-
`url`. Reviewing, approving and merging a change request happen in the app,
|
|
424
|
-
by a person — no agent tool approves a file, bypasses approval, or merges a
|
|
425
|
-
request. `merge_change_request` no longer exists.
|
|
426
|
-
- **Sync** a draft with `merge_branch`, `source` = the branch the request
|
|
427
|
-
targets, `target` = the draft. This is allowed while the draft's request is
|
|
428
|
-
open, and is how you bring it up to date or surface conflicts to resolve on
|
|
429
|
-
the draft.
|
|
430
|
-
- `merge_branch` refuses to merge a draft into the branch its open change
|
|
431
|
-
request targets — it names the request; ask the user to review it in the
|
|
432
|
-
app. Into a protected branch it merges only what you could commit there
|
|
433
|
-
directly, under the rule above — and never a change to `roles.yaml`, whoever
|
|
434
|
-
you are: roles are changed in the app, not merged in from a draft.
|
|
435
|
-
|
|
436
|
-
## Skills (`{{skillsDir}}/<scope>/…/<skill>/SKILL.md`, or `{{pluginsDir}}/<Plugin>/skills/<skill>/SKILL.md`)
|
|
437
|
-
|
|
438
|
-
A skill is a folder holding a `SKILL.md` and whatever files it needs. Shared
|
|
439
|
-
skills live under `{{skillsDir}}/`, organised by ownership; a skill that belongs to
|
|
440
|
-
exactly one plugin may live inside that plugin's `skills/` folder instead.
|
|
441
|
-
Skill names are unique across the whole catalog, whichever home they have.
|
|
442
|
-
The frontmatter names it, declares which tools it may use, and may carry a
|
|
443
|
-
version:
|
|
444
|
-
|
|
445
|
-
```yaml
|
|
446
|
-
---
|
|
447
|
-
name: weekly-newsletter
|
|
448
|
-
description: Drafts the Friday newsletter for review.
|
|
449
|
-
allowed-tools: [slack_post_message]
|
|
450
|
-
metadata:
|
|
451
|
-
version: "1.4.0"
|
|
452
|
-
---
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
The body is the instructions, in plain markdown. `allowed-tools` entries are
|
|
456
|
-
tool names from the `.tool` manuals and MCP servers of the plugins that hold
|
|
457
|
-
the skill. `metadata.version` is semver; `list_skills` reports it, and
|
|
458
|
-
`get_skill` with a `version` loads the skill as it was when it last declared
|
|
459
|
-
that version (omit `version` for the latest). Any other `metadata` keys are
|
|
460
|
-
the author's own notes — the catalog carries the file as it is and acts on
|
|
461
|
-
none of them.
|
|
462
|
-
|
|
463
|
-
A `SKILL.md` committed on the default branch is listed and loadable from the
|
|
464
|
-
very next `list_skills` or `get_skill`, on the connection you already have:
|
|
465
|
-
skills are read from the workspace on every request, on either connection.
|
|
466
|
-
See *A released tool or skill is live within ten seconds* under **Tool
|
|
467
|
-
Manuals** for the one caveat (an MCP client that caches the prompt list it
|
|
468
|
-
was given at connect time must re-list — the prompt-list-changed notification
|
|
469
|
-
that tells it to arrives with the connection's next catalog check, which is
|
|
470
|
-
within ten seconds on a connection in use and at its next use on an idle
|
|
471
|
-
one).
|
|
472
|
-
|
|
473
|
-
**How skills reach agents.** Through the MCP server (`list_skills`,
|
|
474
|
-
`get_skill`), or as native plugins: every user can clone a git remote from
|
|
475
|
-
the app's external-agent page that holds a plugin marketplace compiled from
|
|
476
|
-
exactly the skills they may read — one plugin per plugin here, a
|
|
477
|
-
`skills-and-knowledge` plugin for the rest plus this knowledge base's MCP
|
|
478
|
-
server, and `hexis-all`, one plugin holding every skill they may read and
|
|
479
|
-
the MCP server, for a single install.
|
|
480
|
-
|
|
481
|
-
## Tool Manuals (`{{pluginsDir}}/<Plugin>/software.bevel.hexis/tools/*.tool`)
|
|
482
|
-
|
|
483
|
-
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
|
|
484
|
-
the skills that use it. The same integration may exist in several plugins as
|
|
485
|
-
separate files (`Everyone/…/serper.tool` and `Finance/…/serper.tool`), each
|
|
486
|
-
with its own credentials and access rule — a plugin is a folder, not a registry
|
|
487
|
-
of unique names. Remember: `.tool` files are for `http` and `inline` manuals
|
|
488
|
-
only; MCP servers belong in `mcp.json`.
|
|
489
|
-
|
|
490
|
-
A `.tool` file is JSON or YAML. Its `type` decides how tools are discovered:
|
|
491
|
-
|
|
492
|
-
- **`inline`** — the tools are embedded in the file (no network round-trip to list them).
|
|
493
|
-
- **`http`** — `url` points to an endpoint that returns a UTCP manual.
|
|
494
|
-
|
|
495
|
-
(`type: mcp` is the LEGACY spelling of an MCP server as a `.tool`. The boot
|
|
496
|
-
migration converts such files into `mcp.json` entries; do not write new ones.)
|
|
497
|
-
|
|
498
|
-
**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):
|
|
499
|
-
|
|
500
|
-
```yaml
|
|
501
|
-
---
|
|
502
|
-
id: my_tool
|
|
503
|
-
write:
|
|
504
|
-
- Product Team
|
|
505
|
-
owner:
|
|
506
|
-
- Jane Doe <jane@x.com>
|
|
507
|
-
type: http
|
|
508
|
-
url: https://api.example.com/utcp
|
|
509
|
-
---
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
(A file with no `---` fence is the legacy form — the whole file is the object, so a bare JSON `.tool` still works.)
|
|
513
|
-
|
|
514
|
-
**`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 one saved most recently through the app is auto-suffixed (`my_tool` → `my_tool2`). **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`).
|
|
515
|
-
|
|
516
|
-
**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.
|
|
517
|
-
|
|
518
|
-
**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.)
|
|
519
|
-
|
|
520
|
-
To actually USE those tools, run the workspace as a local MCP server:
|
|
521
|
-
|
|
522
|
-
```
|
|
523
|
-
npx @bevel-software/hexis-mcp --url <workspace-url> --key <connection-key>
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
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 come from the environment of whatever launched the command (your MCP client's config), since the Secrets Vault never leaves the server. Reading the `.tool` and wiring the server into your client by hand still works and is the fallback when the command is unavailable.
|
|
527
|
-
|
|
528
|
-
### Referencing secrets — `${VAR}` and the `variables` block
|
|
529
|
-
|
|
530
|
-
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 the key `<id>_<VAR>`, where `<id>` is the manual's resolved id (the same `id` → `name` → file-name resolution described above) — so a manual whose id is `weather` referencing `${API_KEY}` reads the secret `weather_API_KEY`. A secret is therefore bound to exactly one manual; another manual cannot read it.
|
|
531
|
-
|
|
532
|
-
Declare who provisions each variable with an optional top-level `variables` array. Each entry is `{ name, scope, label? }`:
|
|
533
|
-
|
|
534
|
-
- **`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.
|
|
535
|
-
- **`scope: user`** — set by **each end user** for themselves (their own value, never shared).
|
|
536
|
-
|
|
537
|
-
`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.
|
|
538
|
-
|
|
539
|
-
### Declaring an OAuth sign-in — the `oauth` block
|
|
540
|
-
|
|
541
|
-
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:
|
|
542
|
-
|
|
543
|
-
| field | | |
|
|
544
|
-
|---|---|---|
|
|
545
|
-
| `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` |
|
|
546
|
-
| `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. |
|
|
547
|
-
| `scopes` | optional | `string[]`, requested at sign-in and required back from the token |
|
|
548
|
-
| `pkce` | optional, default **on** | PKCE S256 — MCP servers require it; providers without it ignore it. Only `false` is meaningful. |
|
|
549
|
-
| `resource` | optional | RFC 8707 resource indicator (the MCP server URL); discovered on an `mcp.json` server |
|
|
550
|
-
| `authParams` | optional | extra static authorize params, e.g. Google's `access_type: offline` |
|
|
551
|
-
|
|
552
|
-
**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.
|
|
553
|
-
|
|
554
|
-
For an `mcp.json` server the declaration lives in `plugin.json`, in the same extensions entry as the auth header that uses it:
|
|
555
|
-
|
|
556
|
-
```json
|
|
557
|
-
{
|
|
558
|
-
"extensions": {
|
|
559
|
-
"software.bevel.hexis": {
|
|
560
|
-
"mcpServers": {
|
|
561
|
-
"hubspot": {
|
|
562
|
-
"headers": { "Authorization": "Bearer ${HUBSPOT_TOKEN}" },
|
|
563
|
-
"variables": [
|
|
564
|
-
{ "name": "HUBSPOT_TOKEN", "scope": "user", "label": "HubSpot sign-in",
|
|
565
|
-
"oauth": { "clientId": "<the app's client id>" } }
|
|
566
|
-
]
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
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.
|
|
575
|
-
|
|
576
|
-
### Calling a Google API as a service account: `auth_type: google_service_account`
|
|
577
|
-
|
|
578
|
-
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>`:
|
|
579
|
-
|
|
580
|
-
```yaml
|
|
581
|
-
---
|
|
582
|
-
id: google_ads
|
|
583
|
-
type: inline
|
|
584
|
-
variables:
|
|
585
|
-
- { name: GOOGLE_SA_KEY, scope: admin, label: "Service-account key JSON" }
|
|
586
|
-
- { name: DEVELOPER_TOKEN, scope: admin, label: "Google Ads developer token" }
|
|
587
|
-
tools:
|
|
588
|
-
- name: list_accessible_customers
|
|
589
|
-
description: List the Google Ads customers the service account can reach.
|
|
590
|
-
inputs: { type: object, properties: {} }
|
|
591
|
-
outputs: { type: object, properties: {} }
|
|
592
|
-
tool_call_template:
|
|
593
|
-
call_template_type: http
|
|
594
|
-
http_method: GET
|
|
595
|
-
url: https://googleads.googleapis.com/v22/customers:listAccessibleCustomers
|
|
596
|
-
headers: { developer-token: "${DEVELOPER_TOKEN}" }
|
|
597
|
-
auth:
|
|
598
|
-
auth_type: google_service_account
|
|
599
|
-
credentials: ${GOOGLE_SA_KEY}
|
|
600
|
-
scopes: https://www.googleapis.com/auth/adwords
|
|
601
|
-
---
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
| field | requirement | notes |
|
|
605
|
-
|---|---|---|
|
|
606
|
-
| `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. |
|
|
607
|
-
| `scopes` | required | the OAuth scopes the API needs: one scope, a space-separated list, or a list |
|
|
608
|
-
| `subject` | optional | a user's email to act as, for a service account granted domain-wide delegation |
|
|
609
|
-
|
|
610
|
-
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.
|
|
611
|
-
|
|
612
|
-
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.
|
|
613
|
-
|
|
614
|
-
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}`.
|
|
615
|
-
|
|
616
|
-
### Examples
|
|
617
|
-
|
|
618
|
-
An `http` manual that authenticates with a shared org key and a per-user key:
|
|
619
|
-
|
|
620
|
-
```yaml
|
|
621
|
-
name: weather
|
|
622
|
-
type: http
|
|
623
|
-
url: https://api.weather.example/utcp
|
|
624
|
-
headers:
|
|
625
|
-
Authorization: Bearer ${ORG_KEY}
|
|
626
|
-
X-User-Key: ${USER_KEY}
|
|
627
|
-
variables:
|
|
628
|
-
- { name: ORG_KEY, scope: admin, label: "Org-wide weather.com key" }
|
|
629
|
-
- { name: USER_KEY, scope: user, label: "Your personal weather.com key" }
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
An `inline` manual with one tool:
|
|
633
|
-
|
|
634
|
-
```json
|
|
635
|
-
{
|
|
636
|
-
"name": "billing",
|
|
637
|
-
"type": "inline",
|
|
638
|
-
"variables": [{ "name": "BILLING_KEY", "scope": "admin" }],
|
|
639
|
-
"tools": [
|
|
640
|
-
{
|
|
641
|
-
"name": "create_invoice",
|
|
642
|
-
"description": "Create an invoice.",
|
|
643
|
-
"inputs": { "type": "object", "properties": {} },
|
|
644
|
-
"outputs": { "type": "object", "properties": {} },
|
|
645
|
-
"tool_call_template": {
|
|
646
|
-
"call_template_type": "http",
|
|
647
|
-
"http_method": "POST",
|
|
648
|
-
"url": "https://api.billing.example/invoices",
|
|
649
|
-
"headers": { "Authorization": "Bearer ${BILLING_KEY}" }
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
]
|
|
653
|
-
}
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
### Adding a third-party tool
|
|
657
|
-
|
|
658
|
-
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:
|
|
659
|
-
|
|
660
|
-
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.
|
|
661
|
-
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.
|
|
662
|
-
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`.
|
|
663
|
-
|
|
664
|
-
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).
|
|
665
|
-
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.
|
|
666
|
-
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.
|
|
667
|
-
|
|
668
|
-
### Checking what an admin still needs to configure
|
|
669
|
-
|
|
670
|
-
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:
|
|
671
|
-
|
|
672
|
-
- **`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.
|
|
673
|
-
- **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).
|
|
674
|
-
|
|
675
|
-
**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.
|
|
676
|
-
|
|
677
|
-
**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: `list_skills` and `get_skill` read the workspace on every request, so a committed `SKILL.md` is in the very next answer, and the two resolve a skill the same way, so a skill you can load by name is a skill the listing shows.
|
|
678
|
-
|
|
679
|
-
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.
|
|
680
|
-
|
|
681
|
-
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.
|
|
682
|
-
|
|
683
|
-
**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:
|
|
684
|
-
|
|
685
|
-
- **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).
|
|
686
|
-
- **Per-user values and sign-ins** → each user enters/authorizes on the **Connect page**.
|
|
687
|
-
|
|
688
|
-
For **`oauth-manual`** (e.g. HubSpot, Google, GitHub, Slack — no dynamic client registration), walk the admin through the one-time setup:
|
|
689
|
-
|
|
690
|
-
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`).
|
|
691
|
-
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.
|
|
692
|
-
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.
|
|
693
|
-
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.
|
|
694
|
-
5. Every user then authorizes on the Connect page.
|
|
695
|
-
|
|
696
|
-
## Conventions
|
|
697
|
-
|
|
698
|
-
These are conventions, not validations — nothing rejects a file for breaking
|
|
699
|
-
them. They exist because a knowledge base people can navigate beats one that is
|
|
700
|
-
merely correct.
|
|
701
|
-
|
|
702
|
-
1. **Descriptive file names.** `Weekly-Sync-2026-03-14.md` beats `notes3.md`.
|
|
703
|
-
Avoid spaces; they survive git fine but make links noisier to read.
|
|
704
|
-
|
|
705
|
-
2. **Markdown links between documents.** Use
|
|
706
|
-
`[Page Name](relative/path/to/Page.md)`, relative to the LINKING file's
|
|
707
|
-
directory rather than the repo root, so links resolve both in the app and on
|
|
708
|
-
the git host.
|
|
709
|
-
|
|
710
|
-
3. **Absolute dates.** `YYYY-MM-DD`, never "last Tuesday" — a saved file
|
|
711
|
-
outlives the moment it was written.
|
|
712
|
-
|
|
713
|
-
4. **Search before creating.** If a document on the subject exists, extend it
|
|
714
|
-
rather than starting a rival.
|
|
715
|
-
|
|
716
|
-
5. **Preserve what is there.** Append or edit sections; do not overwrite a file
|
|
717
|
-
wholesale unless asked to.
|
|
718
|
-
|
|
719
|
-
6. **Say where it came from.** When a claim rests on a specific source — a
|
|
720
|
-
person, a ticket, a document, a URL — name it inline near the claim, with
|
|
721
|
-
the date it was true. The next reader's first question is "says who, and is
|
|
722
|
-
it still true?".
|
|
723
|
-
|
|
724
|
-
## Finding things
|
|
725
|
-
|
|
726
|
-
- `grep` for keywords across `{{knowledgeBaseDir}}/`.
|
|
727
|
-
- Follow markdown links: when you read `[Some Page](relative/path/Some Page.md)`,
|
|
728
|
-
that path is relative to the file you are reading.
|
|
729
|
-
- `list_files` to see the shape of a folder before assuming where something
|
|
730
|
-
lives.
|