@zibby/skills 2.0.4 → 2.0.6

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 (55) hide show
  1. package/dist/artifact.js +7 -7
  2. package/dist/chat-notify.js +4 -4
  3. package/dist/chatProgress.js +4 -4
  4. package/dist/datasetStore.js +2 -2
  5. package/dist/discord.js +3 -3
  6. package/dist/figma.js +2 -2
  7. package/dist/git-write.js +9 -9
  8. package/dist/github.js +5 -5
  9. package/dist/gitlab.js +5 -5
  10. package/dist/googleDocs.js +9 -9
  11. package/dist/hubspot.js +1 -1
  12. package/dist/index.js +179 -181
  13. package/dist/jira.d.ts +36 -1
  14. package/dist/jira.js +22 -20
  15. package/dist/kvMemory.js +2 -2
  16. package/dist/lark.js +2 -2
  17. package/dist/larkAttendance.js +1 -1
  18. package/dist/larkDocs.js +3 -3
  19. package/dist/lib/http-deadline.d.ts +117 -0
  20. package/dist/lib/http-deadline.js +1 -0
  21. package/dist/lib/markup.d.ts +158 -0
  22. package/dist/lib/markup.js +22 -0
  23. package/dist/linear.js +15 -15
  24. package/dist/linkedin.js +2 -2
  25. package/dist/llm-billing.js +1 -1
  26. package/dist/notion.js +7 -7
  27. package/dist/opendesign.js +2 -2
  28. package/dist/package.json +6 -5
  29. package/dist/report.d.ts +42 -42
  30. package/dist/review.js +4 -4
  31. package/dist/reviewMemoryIo.js +1 -1
  32. package/dist/sentry.js +2 -2
  33. package/dist/skill-installer.js +3 -3
  34. package/dist/slack.js +2 -2
  35. package/dist/trackers/github-adapter.js +5 -5
  36. package/dist/trackers/index.js +46 -44
  37. package/dist/trackers/jira-adapter.js +24 -22
  38. package/dist/trackers/linear-adapter.js +19 -19
  39. package/dist/trackers/plane-adapter.js +1 -1
  40. package/dist/triggerAgent.js +1 -1
  41. package/dist/vikunja.d.ts +8 -1
  42. package/dist/vikunja.js +22 -7
  43. package/docs/templates/catalog-metadata.md +101 -0
  44. package/docs/templates/composition.md +97 -0
  45. package/docs/templates/deploy-time-config.md +228 -0
  46. package/docs/templates/index.md +138 -0
  47. package/docs/templates/models.md +157 -0
  48. package/docs/templates/node-declarations.md +149 -0
  49. package/docs/templates/remote-mcp.md +105 -0
  50. package/docs/templates/requires.md +183 -0
  51. package/docs/templates/sidecars.md +163 -0
  52. package/docs/templates/stores.md +113 -0
  53. package/docs/templates/surfaces.md +184 -0
  54. package/docs/templates/update-behavior.md +108 -0
  55. package/package.json +6 -5
@@ -0,0 +1,138 @@
1
+ ---
2
+ sidebar_position: 1
3
+ title: The declaration model
4
+ ---
5
+
6
+ # Agent templates — the declaration model
7
+
8
+ An **agent template** is the source of a reusable agent: its graph, its state
9
+ schema, and a **declaration block** that says what the platform must do when
10
+ somebody installs it.
11
+
12
+ If you have written CloudFormation, you already know the shape:
13
+
14
+ | CloudFormation | Zibby |
15
+ |---|---|
16
+ | stack template | the template |
17
+ | `Resources` | the declaration blocks (`models`, `stores`, `requires`, …) |
18
+ | `create-stack` | deploying the template from the catalog |
19
+ | `Ref` / logical names | a declared **name** the platform binds at deploy |
20
+ | *Update requires: Replacement* | fields frozen at deploy — see [Update behavior](./update-behavior.md) |
21
+
22
+ The rule the whole system is built around:
23
+
24
+ > **Adding a new agent means writing only a declaration.** No new UI branch, no
25
+ > new gate, no code that knows this agent's name.
26
+
27
+ Everything the platform does for your agent — which model keys it asks you for,
28
+ which stores it provisions, which service containers it pulls, which endpoints
29
+ it publishes, which other agents it installs alongside — is computed from the
30
+ declaration. One renderer, one provisioner, one deploy path, for every agent
31
+ that exists or ever will.
32
+
33
+ ## Where a declaration lives
34
+
35
+ ```
36
+ my-agent/
37
+ ├── graph.mjs ← the graph; NODES carry their own declarations
38
+ ├── state.js ← the input schema; fields can carry deploy-time annotations
39
+ ├── workflow.json ← optional: which events start a run
40
+ ├── package.json ← the versions this agent will be FROZEN at
41
+ └── icon.png ← optional
42
+ ```
43
+
44
+ plus the **`spec` block** that declares everything else:
45
+
46
+ ```js
47
+ spec: {
48
+ slug: 'my-agent',
49
+ tagline: 'One clamped line that IS the card header.',
50
+ tags: ['Coding'],
51
+ capabilities: [
52
+ 'Each bullet is one thing, starting with a verb',
53
+ ],
54
+ models: [ /* … */ ],
55
+ surfaces: ['cli', 'webhook', 'schedule', 'mcp'],
56
+ releaseNotes: 'What changed in THIS version.',
57
+ }
58
+ ```
59
+
60
+ A template with no `spec` block is not in the catalog at all. It is still
61
+ usable as a local scaffold, but nothing syncs, nothing renders, nothing deploys.
62
+
63
+ :::note `marketplace:` is the same block
64
+ Older templates spell this block `marketplace:`. That spelling still works and
65
+ always will — `spec:` is simply the neutral name for the same thing. You never
66
+ need to rename an existing template.
67
+ :::
68
+
69
+ ## What happens at deploy
70
+
71
+ ```
72
+ your declaration
73
+
74
+
75
+ ┌────────────────────┐
76
+ │ the catalog card │ models → credential slots
77
+ └────────────────────┘ surfaces → which sections the page shows
78
+ │ requires → "installs a team of N"
79
+
80
+ you press Deploy
81
+
82
+ ┌───────────┼───────────┬──────────────┬─────────────┐
83
+ ▼ ▼ ▼ ▼ ▼
84
+ stores sidecars member remote MCP selections
85
+ created pulled + agents servers written to
86
+ + bound verified installed bound the Env bag
87
+ + started + attached
88
+ ```
89
+
90
+ Every step is idempotent. Deploying the same version twice changes nothing;
91
+ deploying a newer version re-runs the steps and keeps what you configured by
92
+ hand.
93
+
94
+ ## Backward compatibility by synthesis
95
+
96
+ A block you do not declare is not "missing" — the platform **synthesises the
97
+ equivalent**, so an old template behaves exactly as it always did:
98
+
99
+ - no `models` → one ordinary bring-your-own-key model block;
100
+ - no `surfaces` → the four standard channels (CLI, webhook, schedule, MCP);
101
+ - no `entryPoints` → the platform serves each surface itself.
102
+
103
+ :::tip Empty is not the same as absent
104
+ `models: []` is a **real declaration**: "this agent runs no model at all". The
105
+ deploy will not ask you for an AI key. Leaving `models` out entirely means
106
+ something different — "give me the normal one".
107
+ :::
108
+
109
+ ## Names, not addresses
110
+
111
+ No template ever writes down a concrete id, host or URL. It declares a **name**;
112
+ the platform binds that name to a real resource at deploy; and whatever consumes
113
+ it resolves the name late — differently on the cloud and on your own box.
114
+
115
+ That is why the same template works in both places, and why nothing breaks when
116
+ a resource is re-created.
117
+
118
+ :::danger Never put a credential in a declaration
119
+ A declaration is public data that travels with the card. Tokens, keys and
120
+ passwords reach an agent **only** through its encrypted Env bag — never through
121
+ a template, a prompt, a trigger payload or a chat message.
122
+ :::
123
+
124
+ ## The blocks
125
+
126
+ | Block | What it declares |
127
+ |---|---|
128
+ | [`models`](./models.md) | which model(s) the agent runs, and who supplies the key |
129
+ | [`stores`](./stores.md) | durable storage, provisioned at deploy and injected by name |
130
+ | [`requires`](./requires.md) | an endpoint another agent publishes, installed and attached for you |
131
+ | [`dispatchesWorkflow` / `kind`](./composition.md) | other agents this one runs as sub-graphs — a fleet |
132
+ | [`surfaces` / `entryPoints`](./surfaces.md) | which channels the agent offers, and where each one lives |
133
+ | [`sidecars` / `sidecarSpecs`](./sidecars.md) | a long-lived service container the agent needs |
134
+ | [`remoteMcp`](./remote-mcp.md) | a hosted third-party MCP server the agent is for |
135
+ | [deploy-time configuration](./deploy-time-config.md) | pickers, inputs, a default schedule, trigger events |
136
+ | [node declarations](./node-declarations.md) | skills, integrations and vendor pins, per node |
137
+ | [catalog metadata](./catalog-metadata.md) | slug, tagline, tags, release notes, availability |
138
+ | [Update behavior](./update-behavior.md) | what changes on re-deploy, what is seeded once, what is frozen |
@@ -0,0 +1,157 @@
1
+ ---
2
+ sidebar_position: 2
3
+ title: models
4
+ ---
5
+
6
+ # `models`
7
+
8
+ ## What it is
9
+
10
+ At deploy the platform has to decide two things: which model credentials to ask
11
+ you for, and whether a missing one should block the install. `models` is the
12
+ declaration those decisions are computed from.
13
+
14
+ Each block says **what kind** of model, **which node** it belongs to, **where**
15
+ the key is consumed, and **whether you supply it**. The deploy dialog and the
16
+ model disc on the agent's graph are generic renderings of this array — there is
17
+ no per-agent special case anywhere.
18
+
19
+ ## Syntax
20
+
21
+ ```js
22
+ spec: {
23
+ models: [
24
+ {
25
+ kind: 'llm', // 'llm' | 'embedding'
26
+ target: 'agent', // 'agent' | 'sidecar'
27
+ role: 'review', // the graph NODE this block belongs to
28
+ label: 'Review', // the row heading in the deploy dialog
29
+ byok: true, // true → a real picker; false → a read-only row
30
+ options: ['claude', 'codex', 'gemini'],
31
+ required: true, // does a missing key block the deploy?
32
+ note: 'Reads the diff and writes the review.',
33
+ },
34
+ {
35
+ kind: 'llm', target: 'agent', role: 'triage', label: 'Triage',
36
+ byok: false, required: true,
37
+ followsVendorOf: 'review', // tracks another block's vendor
38
+ fixedModelByVendor: { // …and shows what it then runs
39
+ claude: 'haiku-4.5',
40
+ codex: 'gpt-4o-mini',
41
+ gemini: 'gemini-3-flash',
42
+ },
43
+ note: 'Fixed — a cheap fast-tier model decides how much review depth the change needs.',
44
+ },
45
+ ],
46
+ }
47
+ ```
48
+
49
+ ## Properties
50
+
51
+ | Name | Type | Required | Allowed values | Default | Update behavior |
52
+ |---|---|---|---|---|---|
53
+ | `kind` | string | No | `llm`, `embedding` | `llm` | Re-applied on the next deploy |
54
+ | `target` | string | No | `agent` (the run container), `sidecar` (a service reads it per request) | `agent` | Re-applied |
55
+ | `role` | string | No | a node id in this agent's graph | none | Re-applied |
56
+ | `label` | string | No | any | the vendor name | Card only |
57
+ | `byok` | boolean | No | `true`, `false` | `true` | Re-applied |
58
+ | `provider` | string | No | e.g. `openai` | none | Re-applied |
59
+ | `options` | string[] | No | vendor ids for `kind: 'llm'`; **model ids** for `kind: 'embedding'` | `[]` | Re-applied |
60
+ | `default` | string | No | one of `options` | none | Re-applied |
61
+ | `required` | boolean | No | `true`, `false` | `true` | Re-applied — **this is the deploy gate** |
62
+ | `followsVendorOf` | string | No | another block's `role` | none | Re-applied |
63
+ | `fixedModelByVendor` | object | No | `{ vendor: modelId }` | none | Re-applied |
64
+ | `note` | string | No | one honest sentence | none | Card only |
65
+
66
+ ## The three decisions
67
+
68
+ **`target` — where the key is consumed.**
69
+ `agent` puts it in the run container's environment. `sidecar` routes it to a
70
+ service container that reads it **per request**, from the declaring agent's own
71
+ encrypted Env bag. One shared service, many isolated tenants.
72
+
73
+ **`byok` — what the dialog renders.**
74
+ `true` gives you a real picker that collects a key. `false` gives you an honest,
75
+ read-only line describing what actually runs. It is never a text box that does
76
+ nothing.
77
+
78
+ **`required` — whether a missing key blocks the install.**
79
+ `required: false` is how an agent says "I need no AI key". Use it for a block
80
+ that names an identity rather than adding a dependency — a deterministic node
81
+ that never invokes a model.
82
+
83
+ ## Use cases
84
+
85
+ | You want | Declare |
86
+ |---|---|
87
+ | An ordinary bring-your-own-key agent | nothing — it is synthesised for you |
88
+ | An agent that runs no model at all | `models: []` |
89
+ | A different model per node, chosen by the operator | one block per model-running node, each with its own `role` |
90
+ | A cheap fixed tier behind an operator-chosen one | `byok: false` + `followsVendorOf` + `fixedModelByVendor` |
91
+ | An embedding model used by a knowledge-base service | `kind: 'embedding', target: 'sidecar', provider: 'openai', options: [<model ids>]` |
92
+ | A locked identity badge with no key | `byok: false, required: false` |
93
+
94
+ ### Example — one block per model-running node
95
+
96
+ The shipped **Product Owner** agent runs three separate judgements, each on its
97
+ own node, each with its own picker:
98
+
99
+ ```js
100
+ models: [
101
+ { kind: 'llm', target: 'agent', role: 'plan', label: 'Planner',
102
+ byok: true, options: ['claude','codex','gemini'], required: true,
103
+ note: 'Reads the PRD and the repository, then writes the tickets.' },
104
+ { kind: 'llm', target: 'agent', role: 'review', label: 'Plan reviewer',
105
+ byok: true, options: ['claude','codex','gemini'], required: true,
106
+ note: 'The second opinion that fails a weak plan back to the planner.' },
107
+ { kind: 'llm', target: 'agent', role: 'judge', label: 'Acceptance judge',
108
+ byok: true, options: ['claude','codex','gemini'], required: true,
109
+ note: 'Judges a pull request against the ticket\'s acceptance criteria.' },
110
+ ]
111
+ ```
112
+
113
+ ### Example — an embedding model consumed by a service
114
+
115
+ ```js
116
+ models: [{
117
+ kind: 'embedding',
118
+ role: 'kb',
119
+ target: 'sidecar',
120
+ byok: true,
121
+ provider: 'openai',
122
+ options: ['text-embedding-3-small', 'text-embedding-3-large'],
123
+ default: 'text-embedding-3-small',
124
+ required: true,
125
+ }]
126
+ ```
127
+
128
+ ## Gotchas
129
+
130
+ **`options` means different things per `kind`.** For `kind: 'llm'` they are
131
+ vendor ids. For `kind: 'embedding'` they are model ids, and the picker offers
132
+ them directly.
133
+
134
+ **`role` must match a node id exactly.** A block whose `role` names no node
135
+ binds to nothing: the pinned tier stops applying, and the node renders a free
136
+ picker whose choice the node's own code then ignores. If your node ids live in
137
+ a constant, import it — do not retype the string.
138
+
139
+ **A composite agent collects its children's keys too.** If your agent dispatches
140
+ another agent, that child's model blocks are appended to yours automatically,
141
+ marked non-blocking and labelled with whose they are. You do not declare them.
142
+
143
+ **Probe a model id before you declare it.** An id that does not exist makes the
144
+ vendor's API reject the call, and the turn comes back empty rather than with an
145
+ error.
146
+
147
+ **Credentials are one per vendor.** The key in the "Codex · OpenAI" slot is the
148
+ same key OpenAI embeddings use. At run time the order is: a key supplied for
149
+ this agent, then the project's credential for that vendor, then (self-hosted
150
+ only) the box's own environment. Nobody should ever paste the same key twice.
151
+
152
+ ## See also
153
+
154
+ - [Node declarations](./node-declarations.md) — a node's `agent:` pin is what
155
+ *locks* a vendor; `models` describes it.
156
+ - [Sidecars](./sidecars.md) — `target: 'sidecar'` needs a service container.
157
+ - [Update behavior](./update-behavior.md)
@@ -0,0 +1,149 @@
1
+ ---
2
+ sidebar_position: 10
3
+ title: Node declarations
4
+ ---
5
+
6
+ # Node declarations
7
+
8
+ Some declarations live on a **graph node**, not in the `spec` block,
9
+ because they are about what that node needs.
10
+
11
+ | Field | Declares |
12
+ |---|---|
13
+ | `skills` | the tools this node gets, and the integrations the agent requires |
14
+ | `optionalSkills` | integrations that improve the node but never block a deploy |
15
+ | `agent` | a vendor this node is locked to |
16
+ | `stores` | durable storage — see [`stores`](./stores.md) |
17
+ | `dispatchesWorkflow` | a member agent — see [Composition](./composition.md) |
18
+
19
+ ---
20
+
21
+ ## `skills` and `optionalSkills`
22
+
23
+ ### What it is
24
+
25
+ A node's single "what this node needs" list. It drives four things at once:
26
+
27
+ 1. the MCP tools the model gets **at that node**;
28
+ 2. the skill's prompt fragment, appended to the node's prompt;
29
+ 3. the required/optional integrations shown on the card and enforced at deploy;
30
+ 4. for a no-connection skill, the on/off switch in the agent's Settings.
31
+
32
+ ### Syntax
33
+
34
+ ```js
35
+ import { SKILLS } from '@zibby/core';
36
+
37
+ graph.addNode('review', {
38
+ prompt: …,
39
+ outputSchema: Review,
40
+ skills: [SKILLS.GITHUB, SKILLS.ARTIFACT],
41
+ optionalSkills: [SKILLS.SENTRY],
42
+ });
43
+ ```
44
+
45
+ ### Required vs optional
46
+
47
+ | Declared in | Surfaces as | Blocks Deploy? |
48
+ |---|---|---|
49
+ | `skills`, and the skill needs a connection | **required** | **Yes** — the Deploy button is blocked until it is connected |
50
+ | `skills`, and the skill is marked optional | optional | No |
51
+ | `optionalSkills` | optional | **Never** |
52
+ | the same skill in **both** arrays on one node | optional **for that node only** | No |
53
+
54
+ That last row is the useful one. A skill can be required by one node and merely
55
+ nice-to-have on another; listing it in a node's `optionalSkills` demotes it for
56
+ **that node** and cannot cancel a different node's requirement.
57
+
58
+ The card shows required and optional in separate groups — "Connect" versus
59
+ "Connect (optional)" — and only the required group can stop an install.
60
+
61
+ ### One-of groups
62
+
63
+ Some entries in the array are not skills at all: they are markers that resolve
64
+ to *any one of* a set of providers.
65
+
66
+ | Marker | Satisfied by |
67
+ |---|---|
68
+ | `board_tracker` | Jira **or** Vikunja |
69
+ | `doc_source` | Google Docs **or** Notion **or** Lark Docs |
70
+ | `chat_notify` | Slack **or** Lark |
71
+
72
+ Connect any member and the requirement is met. Entries the platform does not
73
+ recognise are skipped with a warning, so an unknown id costs you nothing.
74
+
75
+ ### Toggleable skills
76
+
77
+ A few skills need no connection at all — codebase memory, code scanning,
78
+ artifacts. They render as a simple on/off switch, default on, and the operator
79
+ can turn them off per agent.
80
+
81
+ :::danger Bind a toggleable skill on `skills`, not `optionalSkills`
82
+ The switch will appear either way — but **the tools will not**. Skills reach the
83
+ model only through a node's `skills`; `optionalSkills` is an integration-surface
84
+ declaration that the engine never reads. Put a toggleable skill in
85
+ `optionalSkills` and you ship a switch that controls nothing.
86
+ :::
87
+
88
+ ### Gotchas
89
+
90
+ **`optionalSkills` is not part of the serialised graph.** It is read from your
91
+ source when the catalog is built, so it is visible on the card — but anything
92
+ that re-derives from a stored graph will not see it. Treat it as a card-level
93
+ declaration, never as a runtime one.
94
+
95
+ **A store skill is a precondition for [`stores`](./stores.md).** Without it, a
96
+ `stores` block on that node provisions nothing, silently.
97
+
98
+ ---
99
+
100
+ ## `agent` — pinning a node to a vendor
101
+
102
+ ### What it is
103
+
104
+ Locks one node to one coding-agent vendor, regardless of what the operator picks
105
+ elsewhere.
106
+
107
+ ### Syntax
108
+
109
+ ```js
110
+ graph.addNode('plan', { prompt, outputSchema: Plan, agent: 'claude' });
111
+ ```
112
+
113
+ ### Properties
114
+
115
+ | Name | Type | Required | Allowed values | Default | Update behavior |
116
+ |---|---|---|---|---|---|
117
+ | `agent` | string | No | `claude`, `codex`, `gemini` | unpinned — the operator's choice applies | Frozen into the published graph; the credential slots are re-applied |
118
+
119
+ ### What it does at deploy
120
+
121
+ The distinct set of pins across your graph becomes the agent's vendor
122
+ requirement:
123
+
124
+ | Pins found | Deploy asks for |
125
+ |---|---|
126
+ | none | any vendor — the picker offers all of them |
127
+ | one | that vendor only |
128
+ | several | one credential per vendor |
129
+
130
+ Children you dispatch contribute their pins too, so a parent on Claude that
131
+ dispatches a Codex child collects both keys.
132
+
133
+ ### Gotchas
134
+
135
+ **A pinned node is invisible to "apply this model to every node".** It is
136
+ *locked* — that is the point — so it never receives an operator's model choice.
137
+ If every model-running node is locked, there is nothing left for the operator to
138
+ pick.
139
+
140
+ **There is no node-level `model:` declaration.** A model comes from one of three
141
+ places: the operator's per-node pick, a literal in your node's own code, or —
142
+ best — [`models[].fixedModelByVendor`](./models.md) joined to the node by
143
+ `role`. Only the third is visible on the card and the canvas. Prefer it.
144
+
145
+ ## See also
146
+
147
+ - [`models`](./models.md), [`stores`](./stores.md),
148
+ [Composition](./composition.md)
149
+ - [Skills](../concepts/skills.md)
@@ -0,0 +1,105 @@
1
+ ---
2
+ sidebar_position: 8
3
+ title: remoteMcp
4
+ ---
5
+
6
+ # `remoteMcp`
7
+
8
+ ## What it is
9
+
10
+ The hosted, third-party MCP servers this agent is **for**. Declaring one means a
11
+ customer who installs the card never has to know an API call exists: deploy
12
+ binds the server onto the agent, the owner authorises it once from the agent's
13
+ page, and the platform holds the grant encrypted from then on.
14
+
15
+ The declaration is pure data — a name, the server's own public URL, and which
16
+ authentication **the server** uses. It can never carry a credential.
17
+
18
+ ## Syntax
19
+
20
+ ```js
21
+ spec: {
22
+ remoteMcp: [{
23
+ name: 'figma',
24
+ url: 'https://mcp.figma.com/mcp',
25
+ auth: 'oauth',
26
+ }],
27
+ }
28
+ ```
29
+
30
+ ## Properties
31
+
32
+ | Name | Type | Required | Allowed values | Default | Update behavior |
33
+ |---|---|---|---|---|---|
34
+ | `name` | string | **Yes** | any — this is the handle `entryPoints.connect.server` refers to | — | Re-applied (matched by URL) |
35
+ | `url` | string | **Yes** | must parse; credential-shaped query parameters are stripped | — | Re-applied |
36
+ | `auth` | string | No | `oauth`, `bearer` — **anything else drops the whole entry** | absent | Re-applied |
37
+
38
+ `bearer` and "no `auth`" produce the same stored entry, on purpose: a template
39
+ carries no token, so there is nothing to distinguish.
40
+
41
+ ## What deploy does
42
+
43
+ **Without `auth: 'oauth'`** — the server is bound onto the agent immediately.
44
+
45
+ **With `auth: 'oauth'`** — deploy binds **nothing**. The agent carries the
46
+ declaration; the server entry is created **when the sign-in completes**. A
47
+ freshly deployed agent therefore shows no server under its custom MCP list,
48
+ because nothing is connected yet — that list holds exactly what is connected.
49
+
50
+ Binding is **idempotent by URL and additive**. Re-deploying keeps the existing
51
+ entry byte-for-byte, which is what keeps the OAuth grant intact. Removing the
52
+ declaration never removes a server somebody connected — that stays an explicit
53
+ action.
54
+
55
+ ## Pairing it with a Connect card
56
+
57
+ `remoteMcp` says *what the server is*. [`entryPoints.connect`](./surfaces.md)
58
+ says *what the sign-in looks like*, and refers to the server by **name**:
59
+
60
+ ```js
61
+ spec: {
62
+ remoteMcp: [{ name: 'figma', url: 'https://mcp.figma.com/mcp', auth: 'oauth' }],
63
+ surfaces: ['mcp', 'connect'],
64
+ entryPoints: {
65
+ connect: {
66
+ kind: 'oauth',
67
+ server: 'figma',
68
+ label: 'Connect your Figma account',
69
+ button: 'Connect',
70
+ buttonDisconnect: 'Disconnect',
71
+ connectedLine: 'Connected to Figma as {account}.',
72
+ },
73
+ },
74
+ }
75
+ ```
76
+
77
+ Because the card comes from the declaration rather than from a live server row,
78
+ the Connect button is on the page from the moment you deploy and stays there
79
+ whatever happens to the connection.
80
+
81
+ ## Gotchas
82
+
83
+ :::warning The identity is the owner's
84
+ An agent runs with its own role and resolves nothing about whoever is talking to
85
+ it. Anyone who can reach an authorised agent's chat surface acts with the
86
+ owner's third-party account. Say so in your card copy, and do not put an
87
+ authorised agent in a shared channel.
88
+ :::
89
+
90
+ **Never give one agent two MCP addresses.** An agent that both declares a remote
91
+ server *and* hosts its own MCP sidecar will have the sidecar win the address and
92
+ answer every call with its own "missing token", while the OAuth grant sits
93
+ unused on the other one. One card, one address, one sign-in.
94
+
95
+ **Credential-shaped query parameters are stripped from the URL** before it is
96
+ stored. A template must never carry a credential — and the strip is also what
97
+ stops a duplicate server being attached on every re-deploy.
98
+
99
+ **Servers are matched by URL, not by name**, because the owner may rename an
100
+ entry on their own page.
101
+
102
+ ## See also
103
+
104
+ - [`surfaces` and `entryPoints`](./surfaces.md)
105
+ - [`requires`](./requires.md) — the same shape, for one of your own agents.