@axonity-ai/mcp 0.6.0 → 0.8.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/README.md +251 -14
- package/dist/client.d.ts +15 -2
- package/dist/client.js +8 -1
- package/dist/client.js.map +1 -1
- package/dist/contract.d.ts +109 -0
- package/dist/contract.js +272 -0
- package/dist/contract.js.map +1 -0
- package/dist/errors.d.ts +15 -3
- package/dist/errors.js +27 -3
- package/dist/errors.js.map +1 -1
- package/dist/generated/listFilters.d.ts +19 -0
- package/dist/generated/listFilters.js +78 -0
- package/dist/generated/listFilters.js.map +1 -0
- package/dist/index.js +29 -2
- package/dist/index.js.map +1 -1
- package/dist/tools/authoringSpec.d.ts +76 -0
- package/dist/tools/authoringSpec.js +169 -0
- package/dist/tools/authoringSpec.js.map +1 -0
- package/dist/tools/company.d.ts +5 -2
- package/dist/tools/company.js +79 -2
- package/dist/tools/company.js.map +1 -1
- package/dist/tools/conventions.d.ts +16 -0
- package/dist/tools/conventions.js +299 -19
- package/dist/tools/conventions.js.map +1 -1
- package/dist/tools/credentials.d.ts +20 -0
- package/dist/tools/credentials.js +41 -0
- package/dist/tools/credentials.js.map +1 -1
- package/dist/tools/extras.d.ts +17 -0
- package/dist/tools/extras.js +151 -9
- package/dist/tools/extras.js.map +1 -1
- package/dist/tools/operations.d.ts +28 -0
- package/dist/tools/operations.js +160 -0
- package/dist/tools/operations.js.map +1 -0
- package/dist/tools/register.d.ts +27 -0
- package/dist/tools/register.js +19 -1
- package/dist/tools/register.js.map +1 -1
- package/dist/tools/runs.d.ts +24 -0
- package/dist/tools/runs.js +304 -26
- package/dist/tools/runs.js.map +1 -1
- package/dist/tools/secrets.d.ts +33 -0
- package/dist/tools/secrets.js +69 -0
- package/dist/tools/secrets.js.map +1 -0
- package/dist/tools/subworkflows.d.ts +20 -0
- package/dist/tools/subworkflows.js +42 -0
- package/dist/tools/subworkflows.js.map +1 -0
- package/dist/tools/toolboxes.d.ts +32 -0
- package/dist/tools/toolboxes.js +165 -0
- package/dist/tools/toolboxes.js.map +1 -0
- package/dist/tools/triggers.d.ts +8 -0
- package/dist/tools/triggers.js +65 -7
- package/dist/tools/triggers.js.map +1 -1
- package/dist/tools/validation.js +74 -4
- package/dist/tools/validation.js.map +1 -1
- package/dist/tools/versions.d.ts +9 -0
- package/dist/tools/versions.js +36 -0
- package/dist/tools/versions.js.map +1 -1
- package/dist/tools/workflowMutations.js +53 -12
- package/dist/tools/workflowMutations.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -40,10 +40,16 @@ tenant + scope on every call, so the connector is not a trust boundary.
|
|
|
40
40
|
|
|
41
41
|
## Tools
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
220+ tools total. `axonity_conventions` (read this first) covers the authoring
|
|
44
44
|
rules — drafts vs live, optimistic locking, per-entity fields, delete/restore,
|
|
45
45
|
and how to tell a retryable error from one that will never succeed.
|
|
46
46
|
|
|
47
|
+
What the connector does **not** state is as deliberate as what it does: the
|
|
48
|
+
mutation commands, the step types, the trigger types, the schedule-rule shapes
|
|
49
|
+
and the three value vocabularies are all read live from
|
|
50
|
+
`get_workflow_authoring_spec`, because every one of those lists drifted while it
|
|
51
|
+
was kept here. A conformance test asserts their absence.
|
|
52
|
+
|
|
47
53
|
### The generic entity family
|
|
48
54
|
|
|
49
55
|
Ten entities — **workflow, agent, tool, skill, policy, reference_doc, persona,
|
|
@@ -65,12 +71,45 @@ entity gets every verb (see the per-entity notes below for the exceptions):
|
|
|
65
71
|
Exceptions: `persona` has no `create_persona` (create only via
|
|
66
72
|
`create_agent_persona`).
|
|
67
73
|
|
|
74
|
+
Some list routes narrow in the query, which is where narrowing belongs — the
|
|
75
|
+
backend applies it before the rows come back:
|
|
76
|
+
`list_workflows({ stageId?, capabilityId? })`,
|
|
77
|
+
`list_agents({ includeSystem? })`,
|
|
78
|
+
`list_policies({ scope?, ownerId? })`,
|
|
79
|
+
`list_reference_docs({ scope?, ownerId? })`,
|
|
80
|
+
`list_prompt_snippets({ deleted? })`.
|
|
81
|
+
|
|
82
|
+
That table is **generated** from the pinned schema's own query parameters
|
|
83
|
+
(`npm run generate:filters` → `src/generated/listFilters.ts`), not hand-listed.
|
|
84
|
+
A filter the backend adds is exposed as soon as the snapshot is refreshed, and
|
|
85
|
+
a test fails if the two have parted. The argument names are camelCase; the wire
|
|
86
|
+
keeps whatever spelling each route declares, which is not consistent between
|
|
87
|
+
them and is not something a caller should have to know.
|
|
88
|
+
|
|
68
89
|
Plus:
|
|
90
|
+
- `get_workflow_authoring_spec` — everything **this deploy** can be built from,
|
|
91
|
+
read live from the server (`GET /workflows/operations`): the mutation
|
|
92
|
+
`operations`, the `triggerTypes`, the `stepTypes` (including the ones you may
|
|
93
|
+
not author, each with the reason and what to write instead), the
|
|
94
|
+
`scheduleRuleKinds` (each with an example the backend round-trips through its
|
|
95
|
+
own parser as it serves it), and **three vocabularies for a value's type that
|
|
96
|
+
are not interchangeable** — `parameterTypes` (a trigger parameter's or
|
|
97
|
+
workflow constant's `type`), `outputKinds` (a step output's or input's
|
|
98
|
+
`kind` — narrower, *different key*) and `schemaFieldKinds` (inside a field's
|
|
99
|
+
`schema`, which wins where present). Getting that last group wrong is silent:
|
|
100
|
+
a `kind` written on a trigger parameter is not a 422, it is a key nothing
|
|
101
|
+
reads, so the value falls back to text. Every list is generated from the
|
|
102
|
+
registry that *enforces* it, so the connector states none of them and a new
|
|
103
|
+
value is discoverable without a release here. `operations` is an index by
|
|
104
|
+
default; pass `types` for a command's live payload schema. `rulesVersion` is
|
|
105
|
+
a content hash over all seven — same hash, nothing to re-fetch.
|
|
69
106
|
- `apply_workflow_mutations` for structural workflow edits (add steps, connect
|
|
70
107
|
edges) via mutation commands, sequenced and version-threaded for you.
|
|
71
108
|
- `replace_workflow_document` for one-shot full-document replacement in a single
|
|
72
109
|
atomic PUT.
|
|
73
|
-
- `read_workflow_trigger_parameters` —
|
|
110
|
+
- `read_workflow_trigger_parameters` — how to start the workflow:
|
|
111
|
+
`{ triggers, constants }`, every start with its own parameters and a `pinned`
|
|
112
|
+
flag marking the values the author owns.
|
|
74
113
|
- `bulk_delete_workflows` — soft-delete several at once (each with its own
|
|
75
114
|
`expectedVersion`).
|
|
76
115
|
|
|
@@ -87,7 +126,9 @@ For the ten versioned entities (including `flow`):
|
|
|
87
126
|
| `list_deleted_<entity>_versions` | Restore candidates for the row above. |
|
|
88
127
|
| `restore_deleted_<entity>_version` | Undo the delete above. No version check. |
|
|
89
128
|
| `read_<entity>_published` | The live snapshot, as opposed to the draft. |
|
|
90
|
-
| `
|
|
129
|
+
| `create_<entity>_major_version` | Cut a new **named** major version — "Save As" on the current draft. |
|
|
130
|
+
| `ensure_<entity>_major_version` | Make sure a working draft major version exists. Idempotent. |
|
|
131
|
+
| `name_<entity>_major_version` | Rename an existing major version (label a release). |
|
|
91
132
|
|
|
92
133
|
`{version}` (an int) and `{versionId}` (a UUID) are two different identifiers
|
|
93
134
|
across these routes — the tool parameter names say which.
|
|
@@ -102,12 +143,32 @@ across these routes — the tool parameter names say which.
|
|
|
102
143
|
`update_connector` — `authConfig` must be placeholders only; a human fills real
|
|
103
144
|
secrets in Axonity. (`create_tool`/`update_tool` carry the same guard, so a
|
|
104
145
|
connector authored either way is covered.)
|
|
146
|
+
- **Toolboxes** (the group a tool is filed under): `list_toolboxes`,
|
|
147
|
+
`create_toolbox`, `update_toolbox`, `delete_toolbox`, `set_toolbox_tools`,
|
|
148
|
+
`assign_tool_toolbox`, `set_toolbox_auth`, `list_toolbox_dependent_tools`.
|
|
149
|
+
Read `list_toolboxes` before `create_tool` and pass `toolboxId` — a tool made
|
|
150
|
+
without one is ungrouped. Three things worth knowing before you write:
|
|
151
|
+
`set_toolbox_tools` **declares** the membership (anything you leave out is
|
|
152
|
+
evicted — `assign_tool_toolbox` moves a single tool and takes `null` to
|
|
153
|
+
ungroup); a box never changes what an agent may *call*, only how a tool is
|
|
154
|
+
*advertised* (agents link to individual tools, never to a box); and deleting a
|
|
155
|
+
box leaves its tools alive, ungrouped. `set_toolbox_auth` sets the credential a
|
|
156
|
+
box's tools share and carries the same placeholder guard as a connector.
|
|
105
157
|
- **Attach / detach memory**: `attach_skill_to_agent`,
|
|
106
158
|
`attach_skill_to_workflow`, `attach_policy_to_agent`,
|
|
107
159
|
`attach_reference_to_agent`, and a `detach_*_from_*` for each. Detaching
|
|
108
160
|
removes the link only — the skill or policy itself is untouched. Read the
|
|
109
161
|
links back with `list_agent_skills`, `list_agent_policies`,
|
|
110
|
-
`list_agent_reference_docs`.
|
|
162
|
+
`list_agent_reference_docs` and `list_workflow_skills`. The three agent
|
|
163
|
+
read-backs take an optional `workflowId` for the **composed runtime view** —
|
|
164
|
+
what the agent's prompt actually assembles inside that workflow, with a
|
|
165
|
+
`linkSource` per row saying why each item is there.
|
|
166
|
+
- **What uses this?**: `list_workflows_using({ entityKind, entityId })` names
|
|
167
|
+
the workflows that reference a tool, agent, flow, output schema or workflow,
|
|
168
|
+
**and the steps they reference it in**, with `draft`/`published` per hit.
|
|
169
|
+
`list_dependent_agents({ entityKind, entityId })` is the same question for a
|
|
170
|
+
skill, policy or reference doc. Ask before editing anything shared — the
|
|
171
|
+
alternative is validating every workflow in the tenant.
|
|
111
172
|
- **Prompt elements (placement)**: a `prompt_snippet` is a library item; it only
|
|
112
173
|
takes effect once placed into a flow step's prompt stack.
|
|
113
174
|
`read_workflow_prompt_stacks` / `read_flow_prompt_stacks` resolve a
|
|
@@ -119,13 +180,46 @@ across these routes — the tool parameter names say which.
|
|
|
119
180
|
- **Company** (the tenant's single company document — a singleton, no id):
|
|
120
181
|
`read_company`, `update_company` (whole-document save with `expectedVersion`),
|
|
121
182
|
`list_company_versions`, `read_company_version`, `restore_company_version`,
|
|
122
|
-
`name_company_major_version`, `
|
|
183
|
+
`name_company_major_version`, `create_company_major_version`,
|
|
184
|
+
`ensure_company_major_version`, `read_company_published`,
|
|
185
|
+
`apply_company_mutation` (one validated, version-safe command — preferred
|
|
186
|
+
over the whole-document `update_company`, the same way
|
|
187
|
+
`apply_workflow_mutations` is preferred for a workflow), and
|
|
123
188
|
`request_publish_company` (takes no id — the
|
|
124
189
|
server resolves your tenant's one company; direct company publish is closed to
|
|
125
190
|
service tokens).
|
|
191
|
+
- **Subworkflows**: `list_callable_workflows` — which workflows a `subprocess`
|
|
192
|
+
step may call, each with its `parameters` and `outcomes`, and a
|
|
193
|
+
`blockedReason` for the ones that cannot (never published, or no
|
|
194
|
+
`subprocess-invocation` trigger). Authoring both halves is ordinary
|
|
195
|
+
`apply_workflow_mutations` work — a callable workflow's signature goes into
|
|
196
|
+
the `add_trigger` call itself. `validate_workflow` **does** check a subprocess
|
|
197
|
+
target now (missing, self-call, deleted, unpublished, not callable), but
|
|
198
|
+
`list_callable_workflows` is still what you run first: it is how you pick a
|
|
199
|
+
target and read the interface you are binding to.
|
|
200
|
+
- **The deploy, the tenant and its queues** (read-only): `read_deploy_contract`
|
|
201
|
+
(what this backend actually mounts — read it when a call fails in a way that
|
|
202
|
+
smells like a version mismatch), `list_users` (where an `ownerId` comes from),
|
|
203
|
+
`list_audit_events` (pass `actorKind: "service_token"` to read back what
|
|
204
|
+
external agents — including you — changed), `read_queue_overview`,
|
|
205
|
+
`list_in_flight_runs`, `read_task_queue_summary`, `list_task_queue`,
|
|
206
|
+
`read_task_queue_item`, `export_task_queue`, plus `read_model_tier_map` (what
|
|
207
|
+
`capabilityTier` resolves to), `read_concurrency_status`,
|
|
208
|
+
`read_concurrent_run_cap` and `read_for_each_rate`. Together these answer *why
|
|
209
|
+
is my run not moving* without asking a human to look at a screen. Every write
|
|
210
|
+
in these families — purging or replaying queue work, changing a cap or the
|
|
211
|
+
tier map, importing a tenant bundle, reading someone's notifications — is
|
|
212
|
+
deliberately absent and recorded in `test/exclusions.test.ts`.
|
|
213
|
+
- **Secrets** (read-only): `list_secrets`, `read_secret` — the catalogue a
|
|
214
|
+
connector's `authConfig.secretId` points at. Values are never returned by any
|
|
215
|
+
Axonity route; `valueKeys` says which keys a human has filled in, so you can
|
|
216
|
+
tell an unfinished secret from a finished one before wiring to it. Creating or
|
|
217
|
+
changing a secret is a human act in Axonity (#39).
|
|
126
218
|
- **Catalog & cloning**: `list_system_tools` (read-only catalog — enabling one
|
|
127
219
|
for an agent is `update_agent` with the id added to `systemToolIds`),
|
|
128
|
-
`clone_flow`, `clone_prompt_snippet
|
|
220
|
+
`clone_flow`, `clone_prompt_snippet`, `list_tool_packages` (the import
|
|
221
|
+
allowlist `validate_tool_code` judges against), `list_templates` /
|
|
222
|
+
`read_template`.
|
|
129
223
|
- `list_deleted_prompt_snippets` calls `/api/v1/prompt-snippets/deleted`; the
|
|
130
224
|
backend returns it as `{ items: [... ] }`, and the tool forwards that response
|
|
131
225
|
unchanged.
|
|
@@ -148,6 +242,24 @@ across these routes — the tool parameter names say which.
|
|
|
148
242
|
(plus `update_conditional_trigger`). Trigger deletes are **hard** deletes with
|
|
149
243
|
no restore, and a webhook token is shown **once** at create or rotate.
|
|
150
244
|
|
|
245
|
+
**A schedule is a claim you can now check.** `run_cron_schedule_now` fires one
|
|
246
|
+
immediately *without* moving `nextFireAt` — testing a schedule must not consume
|
|
247
|
+
the run it was going to make. Before it existed, "every weekday at 07:00" could
|
|
248
|
+
only be tested by coming back tomorrow, and what is usually wrong is not the
|
|
249
|
+
timing but whether it starts anything at all.
|
|
250
|
+
|
|
251
|
+
**To pause a schedule, disarm it** — `set_cron_schedule_enabled`, not
|
|
252
|
+
`delete_cron_schedule`. Deleting throws away the rules the author wrote and
|
|
253
|
+
makes "stop this for a week" indistinguishable from "we do not do this any
|
|
254
|
+
more". `list_all_cron_schedules` answers *what runs tonight?* across the tenant;
|
|
255
|
+
`reconcile_cron_schedules` answers *is that actually what runs?* — it reports
|
|
256
|
+
rather than tidying silently, and never arms something someone switched off.
|
|
257
|
+
|
|
258
|
+
`create_cron_schedule` takes either `cronExpr` or the richer `rules`, and the
|
|
259
|
+
trigger must exist in the **published** document. Rule shapes come from
|
|
260
|
+
`get_workflow_authoring_spec` → `scheduleRuleKinds`; this connector names none
|
|
261
|
+
of its own.
|
|
262
|
+
|
|
151
263
|
### Runs — evaluating what you built
|
|
152
264
|
|
|
153
265
|
`start_workflow_run` (test a workflow you built — it runs the **published**
|
|
@@ -157,14 +269,66 @@ workflow and really executes), `cancel_run`, `delete_run`, `list_runs`,
|
|
|
157
269
|
`bulk_delete_runs`. There is no findings endpoint — evaluation means reading a
|
|
158
270
|
run's validator verdicts and its trace.
|
|
159
271
|
|
|
160
|
-
|
|
272
|
+
**Which start, and what it wants.** `read_workflow_trigger_parameters` answers
|
|
273
|
+
`{ triggers, constants }` — every way the workflow can be started, each with its
|
|
274
|
+
own parameters. Pass the one you mean to `start_workflow_run` as `triggerId`;
|
|
275
|
+
omitting it fires the first, which on a workflow with a button *and* a schedule
|
|
276
|
+
is an arbitrary choice. A parameter marked `pinned` is one the **author** owns:
|
|
277
|
+
it is overwritten on every run, so a caller must not send it.
|
|
278
|
+
|
|
279
|
+
**`read_run` omits the workflow snapshot by default.** It is immutable, it is
|
|
280
|
+
never the answer to a question about the run, and it measured 81% of one real
|
|
281
|
+
response — an oversized response turns a call that succeeded into an error.
|
|
282
|
+
Pass `includeSnapshot: true` when you actually want to see what executed.
|
|
283
|
+
|
|
284
|
+
**Start from the outline.** `read_run_outline` is the run's table of contents —
|
|
285
|
+
the run, its steps, and the items a fan-out handed out, flat with parent
|
|
286
|
+
pointers. It carries no bodies, so its size follows the run's *shape* rather
|
|
287
|
+
than its content: a launch over four thousand items costs about what one over
|
|
288
|
+
four costs. `itemCap` bounds the items listed per fan-out step and the remainder
|
|
289
|
+
is counted in `counts.truncated`, never dropped silently. Then open only what
|
|
290
|
+
you want: `read_run_value` for one large step value (by the digest in
|
|
291
|
+
`stepStates`) and `read_run_invocation_messages` for one agent's transcript (by
|
|
292
|
+
the id in `agentInvocations`). Reading a whole run to find one message is the
|
|
293
|
+
habit these replace.
|
|
294
|
+
|
|
295
|
+
**A run can park rather than finish.** `read_run_waiting_on` says what it is
|
|
296
|
+
waiting for; `answer_run_question` answers an `ask_user` step and
|
|
297
|
+
`send_run_message` sends a turn to a conversation run. Both record the input as
|
|
298
|
+
a person's, so use them on runs you started. Deciding a **plan approval** and
|
|
299
|
+
restarting a stuck run are deliberately absent — the first is the human review
|
|
300
|
+
the step exists to get, the second is `require_admin` and a service token is
|
|
301
|
+
always `role="member"`. Both are recorded in `test/exclusions.test.ts`, together
|
|
302
|
+
with stopping runs in bulk (admin), the tenant's storage footprint (admin), an
|
|
303
|
+
inbound channel reply (authenticated by the email/WhatsApp adapter, not by a
|
|
304
|
+
service token) and the retired `workflow-memory` placeholder.
|
|
305
|
+
|
|
306
|
+
**`list_todo_steps`** is the same question across the whole tenant: every step
|
|
307
|
+
waiting on a human, in any run. It is paged over the waiting **runs**, so
|
|
308
|
+
`items` can be longer than `pageSize` — one run may park several steps. Follow
|
|
309
|
+
`nextCursor` to the end before concluding anything about how much is waiting.
|
|
310
|
+
|
|
311
|
+
**What an agent wrote to itself.** `list_run_session_memory` lists the files an
|
|
312
|
+
agent left during a run (metadata only, up to 200 per run) and
|
|
313
|
+
`read_run_session_memory_file` opens one. When the trace shows a decision but
|
|
314
|
+
not what it was reading, the reason is usually here. Workflow-bound reference
|
|
315
|
+
material is not — that lives in `reference_docs`.
|
|
316
|
+
|
|
317
|
+
**Inside a launch**: `read_run_items_summary` is the roll-up ("4,415 processed ·
|
|
318
|
+
12 failed"), `list_run_items({ outcome })` the paged rows — filter in the query,
|
|
319
|
+
because the failures are scattered and sifting page one finds none of them.
|
|
320
|
+
`list_run_tasks` and `read_run_for_each_progress` cover the children a run set
|
|
321
|
+
in motion.
|
|
322
|
+
|
|
323
|
+
`list_workflow_runs({ workflowId, status?, archivedOnly?, limit?, cursor? })` returns one
|
|
161
324
|
**page** — `{ items, nextCursor, pageSize, hasMore }`, 20 by default and 200 at
|
|
162
325
|
most — so follow `nextCursor` while `hasMore` is true rather than treating the
|
|
163
326
|
first page as the answer. It also lists **launches**, not runs: the per-item runs
|
|
164
327
|
a FOR EACH creates stay inside their launch, so a launch over 4,415 people is one
|
|
165
|
-
entry carrying `forEachProgress`. `list_runs` is the tenant-wide list and is
|
|
166
|
-
|
|
167
|
-
`
|
|
328
|
+
entry carrying `forEachProgress`. `list_runs` is the tenant-wide list and is now
|
|
329
|
+
paged the same way — `{ items, nextCursor, pageSize, hasMore }`, walked with
|
|
330
|
+
`cursor`. It stopped answering with a bare array when the backend converted the
|
|
331
|
+
route; the `offset` it used to take is no longer read.
|
|
168
332
|
|
|
169
333
|
### Approvals
|
|
170
334
|
|
|
@@ -173,6 +337,23 @@ entry carrying `forEachProgress`. `list_runs` is the tenant-wide list and is
|
|
|
173
337
|
`request_publish_*` was approved or rejected. Approving and rejecting are
|
|
174
338
|
human-only actions in Axonity.
|
|
175
339
|
|
|
340
|
+
`request_publish_release({ workflowId, changeSummary? })` proposes a **release**:
|
|
341
|
+
a workflow *and everything its run needs* — the agents it runs, their tools and
|
|
342
|
+
personas, the flows it pins, the memory scoped to those agents — as ONE approval.
|
|
343
|
+
Prefer it over a request per entity. Taking a tenant live entity-by-entity means
|
|
344
|
+
a hundred-odd approvals, none of which means anything on its own, and a human
|
|
345
|
+
asked that many times is not reviewing. `list_publish_releases` and
|
|
346
|
+
`get_publish_release` read one back — the release's members, and its readiness
|
|
347
|
+
recomputed as of now.
|
|
348
|
+
|
|
349
|
+
Unlike `request_publish_bulk`, a release is **all-or-nothing and in dependency
|
|
350
|
+
order**: approving it publishes every member or none, so a workflow can never go
|
|
351
|
+
live calling a tool that did not. The response carries the bundle's verdict —
|
|
352
|
+
`ready`, `changedCount` of `totalCount` (unchanged members are already live and
|
|
353
|
+
ride along), `members` with why each is there, and `blockers` that name the
|
|
354
|
+
member in the way. Requesting is ours; deciding stays human, like everywhere
|
|
355
|
+
else here.
|
|
356
|
+
|
|
176
357
|
## What this is for — and what it is not
|
|
177
358
|
|
|
178
359
|
**Authoring.** An agent composing an entity from intent: drafting a workflow,
|
|
@@ -202,19 +383,34 @@ These are enforced by the backend, not merely by convention:
|
|
|
202
383
|
- **Secrets never pass through the agent.** A connector's `authConfig` accepts
|
|
203
384
|
placeholders only; a write carrying something that looks like a real
|
|
204
385
|
credential is rejected before it leaves the connector. Tenant secrets
|
|
205
|
-
(`/api/v1/secrets`)
|
|
206
|
-
|
|
386
|
+
(`/api/v1/secrets`) are **readable and unwritable**: `list_secrets` /
|
|
387
|
+
`read_secret` give the catalogue and `valueKeys` (which keys are filled, never
|
|
388
|
+
their values) so an agent can point `authConfig.secretId` at the right entry,
|
|
389
|
+
and no tool can create, change or delete one — the backend refuses a service
|
|
390
|
+
token there too. A secret's `metadata` is stored unencrypted and readable
|
|
391
|
+
tenant-wide (axonity-flow#908), so credential-shaped entries in it are
|
|
392
|
+
withheld on read and listed under `metadataRedacted`.
|
|
207
393
|
- **Errors carry a machine-readable `code`, not just prose.** A 409 can mean a
|
|
208
394
|
stale write (retry) or a live reference conflict (don't — see
|
|
209
395
|
`axonity_conventions`); the connector tells them apart by `code`, never by
|
|
210
396
|
matching the message text.
|
|
211
397
|
- **No tool crosses the authority boundary.** A test drives the whole registered
|
|
212
|
-
surface and fails the build if any tool targets a publish / approve /
|
|
213
|
-
service-token / deploy route (`test/exclusions.test.ts`).
|
|
398
|
+
surface and fails the build if any tool targets a publish / approve /
|
|
399
|
+
secret-write / service-token / deploy route (`test/exclusions.test.ts`). The
|
|
400
|
+
rules are method-aware: `GET /api/v1/secrets` is allowed, every write verb on
|
|
401
|
+
it is not.
|
|
214
402
|
- **Guidance can't silently drift from the backend.** The field/enum facts the
|
|
215
403
|
connector states are pinned to a vendored snapshot of the backend OpenAPI
|
|
216
404
|
schema; `test/conformance.test.ts` fails if a route or documented enum
|
|
217
405
|
diverges (see `test/fixtures/README.md`).
|
|
406
|
+
- **Every backend route is decided, and the build says so.**
|
|
407
|
+
`test/completeness.test.ts` partitions all 444 operations in the pinned
|
|
408
|
+
snapshot into *covered by a tool* or *excluded by a rule that carries a
|
|
409
|
+
written reason*, and fails on anything in neither. A route nobody has decided
|
|
410
|
+
about is indistinguishable from one somebody is still working on, which is
|
|
411
|
+
what made "is this connector finished?" a question you could only answer with
|
|
412
|
+
an audit. It is now a build status: a new backend route arrives as a red build
|
|
413
|
+
asking **cover it, or exclude it with a reason?**
|
|
218
414
|
|
|
219
415
|
**One known exception, not enforced:** a framework-provided `flow` is meant to
|
|
220
416
|
be read-only to a tenant, but the backend does not actually block
|
|
@@ -236,6 +432,18 @@ Publishing runs from a maintainer's machine, not from CI. npm restricts tokens
|
|
|
236
432
|
that bypass 2FA for direct publishing, so a stored `NPM_TOKEN` cannot ship this
|
|
237
433
|
package; `npm login` is the supported path.
|
|
238
434
|
|
|
435
|
+
**Check the contract first.** Shipping with a stale snapshot is how the
|
|
436
|
+
connector fell nine operations behind the backend without any test noticing:
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
npm run check:contract # expects ../axonity-flow; override with AXONITY_FLOW_PATH
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
It dumps the schema from a local `axonity-flow` checkout and diffs it against
|
|
443
|
+
`test/fixtures/openapi.snapshot.json`, naming every operation that moved. It
|
|
444
|
+
runs here rather than in the release workflow because the publish itself runs
|
|
445
|
+
here — a gate in CI cannot stop a local `npm publish`.
|
|
446
|
+
|
|
239
447
|
Run `npm login` in a real terminal — it prints a URL and waits for you to finish
|
|
240
448
|
in the browser, so it needs a session that stays attached (an editor's 2-minute
|
|
241
449
|
command timeout will kill it mid-flow).
|
|
@@ -257,3 +465,32 @@ was cut from a state CI cannot install.
|
|
|
257
465
|
|
|
258
466
|
Keep npm 11 locally: Node 20 bundles npm 10, whose resolver writes an
|
|
259
467
|
incompatible lockfile tree. CI pins npm 11 for the same reason.
|
|
468
|
+
|
|
469
|
+
## Staying level with the backend
|
|
470
|
+
|
|
471
|
+
The snapshot every drift guard reads is only as fresh as the last time someone
|
|
472
|
+
regenerated it — and once it wasn't: it sat nine operations behind the backend
|
|
473
|
+
while the whole suite stayed green, because a snapshot that has not seen a route
|
|
474
|
+
cannot report it missing.
|
|
475
|
+
|
|
476
|
+
**Before a release, check it** (see §Releasing): `npm run check:contract` dumps
|
|
477
|
+
the schema from a local `axonity-flow` checkout and names every operation that
|
|
478
|
+
moved. It compares the contract surface — query parameters, request body,
|
|
479
|
+
response shape — and not descriptions, so backend docstring churn does not cry
|
|
480
|
+
wolf. This is the gate that counts, because the publish runs here too.
|
|
481
|
+
|
|
482
|
+
**At startup, the connector asks the deploy what it mounts.** It calls
|
|
483
|
+
`GET /api/v1/contract` once before accepting its first tool call and, if this
|
|
484
|
+
backend lacks a route this build needs, says which on stderr — instead of
|
|
485
|
+
failing on the twentieth call, mid-task. It is a diagnostic and never a
|
|
486
|
+
dependency: a backend too old to serve `/contract`, an unreachable one, or a
|
|
487
|
+
slow one all degrade to the previous behaviour and the connector starts
|
|
488
|
+
normally. The result is cached on the `contractHash` the route returns, so an
|
|
489
|
+
unchanged deploy costs one request.
|
|
490
|
+
|
|
491
|
+
**Not here: a scheduled job.** Watching for drift on a timer belongs in
|
|
492
|
+
`axonity-flow`, not in this repository. The schema lives there, its CI already
|
|
493
|
+
has the backend's dependencies installed, and it can read this repository's
|
|
494
|
+
pinned snapshot over plain HTTPS because this repository is public — where the
|
|
495
|
+
reverse needs a credential for a private repo, with an approval policy and an
|
|
496
|
+
expiry behind it. Tracked in axonity-mcp#48.
|
package/dist/client.d.ts
CHANGED
|
@@ -8,8 +8,20 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { AxonityConfig } from "./config.js";
|
|
10
10
|
export type Json = Record<string, unknown>;
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/** One scalar a query parameter may carry. */
|
|
12
|
+
type QueryScalar = string | number | boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Query-string values a route may take. `undefined` entries are omitted.
|
|
15
|
+
*
|
|
16
|
+
* An ARRAY is serialised the way FastAPI reads a `list[…]` query parameter:
|
|
17
|
+
* the key REPEATED, once per element (`?status=pending&status=running`). Not
|
|
18
|
+
* every list-shaped filter on the backend is spelled that way — `GET /runs`
|
|
19
|
+
* takes its statuses as one comma-separated string while
|
|
20
|
+
* `GET /workflows/{id}/runs` repeats the key — so the caller picks the shape
|
|
21
|
+
* its route actually declares. An empty array contributes nothing, which is
|
|
22
|
+
* what "no filter" means.
|
|
23
|
+
*/
|
|
24
|
+
export type Query = Record<string, QueryScalar | QueryScalar[] | undefined>;
|
|
13
25
|
export declare class AxonityClient {
|
|
14
26
|
private readonly config;
|
|
15
27
|
constructor(config: AxonityConfig);
|
|
@@ -31,3 +43,4 @@ export declare class AxonityClient {
|
|
|
31
43
|
*/
|
|
32
44
|
del<T = unknown>(path: string, query?: Query): Promise<T>;
|
|
33
45
|
}
|
|
46
|
+
export {};
|
package/dist/client.js
CHANGED
|
@@ -18,8 +18,15 @@ export class AxonityClient {
|
|
|
18
18
|
return base;
|
|
19
19
|
const params = new URLSearchParams();
|
|
20
20
|
for (const [key, value] of Object.entries(query)) {
|
|
21
|
-
if (value
|
|
21
|
+
if (value === undefined)
|
|
22
|
+
continue;
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
for (const item of value)
|
|
25
|
+
params.append(key, String(item));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
22
28
|
params.set(key, String(value));
|
|
29
|
+
}
|
|
23
30
|
}
|
|
24
31
|
const qs = params.toString();
|
|
25
32
|
return qs ? `${base}?${qs}` : base;
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asking the deploy what it can do, once, at startup (axonity-mcp#48 M2).
|
|
3
|
+
*
|
|
4
|
+
* `src/errors.ts` already turns a 404/405 into a "backend version skew" message
|
|
5
|
+
* — but only AFTER the call has failed, which means a user is mid-task on the
|
|
6
|
+
* one call that mattered. And it decides which 404s count from `ROUTES_ADDED_IN`,
|
|
7
|
+
* a list this repository keeps by hand about routes the server owns. That is the
|
|
8
|
+
* shape of #32 and #44, and it has the same failure mode: a route added here
|
|
9
|
+
* without an entry there reports as "no such id" forever.
|
|
10
|
+
*
|
|
11
|
+
* `GET /api/v1/contract` (axonity-flow#806) is the proactive replacement. It
|
|
12
|
+
* returns every mounted route, derived from the running app so it cannot drift,
|
|
13
|
+
* plus a hash to cache on. It is authenticated but TENANT-AGNOSTIC — a
|
|
14
|
+
* read-only service token is enough, so this check never needs write scope.
|
|
15
|
+
*
|
|
16
|
+
* WHAT THIS DOES NOT DO: fail. A connector that refuses to start because it
|
|
17
|
+
* could not reach a diagnostic endpoint is worse than the drift it reports —
|
|
18
|
+
* every path here degrades to today's behaviour, including a backend too old to
|
|
19
|
+
* serve `/contract` at all. Everything it says goes to STDERR, because stdout is
|
|
20
|
+
* the MCP channel and a stray byte there breaks the protocol.
|
|
21
|
+
*/
|
|
22
|
+
import type { AxonityClient } from "./client.js";
|
|
23
|
+
/** `GET /api/v1/contract`. `routes` are "METHOD /path/{template}" strings. */
|
|
24
|
+
export interface ContractResponse {
|
|
25
|
+
buildVersion?: unknown;
|
|
26
|
+
environment?: unknown;
|
|
27
|
+
routes?: unknown;
|
|
28
|
+
contractHash?: unknown;
|
|
29
|
+
}
|
|
30
|
+
/** The minimal `.tool()` surface the registrar needs — same shape as index.ts. */
|
|
31
|
+
export interface ProbeServer {
|
|
32
|
+
tool: (name: string, description: string, schema: Record<string, unknown>, handler: (args: never) => Promise<unknown>) => void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One filled-in value per argument name any tool takes, so a blind replay
|
|
36
|
+
* reaches as much of the route surface as possible.
|
|
37
|
+
*
|
|
38
|
+
* This is how the connector knows which routes it needs WITHOUT keeping a list
|
|
39
|
+
* of them: it runs its own tools against a recording client and watches. A
|
|
40
|
+
* hand-written route list would be the very thing #48 M2 exists to delete.
|
|
41
|
+
*
|
|
42
|
+
* Shared with `conformance.test.ts` and `exclusions.test.ts` deliberately. They
|
|
43
|
+
* each carried their own copy, the copies had to be edited in lockstep, and a
|
|
44
|
+
* probe fixture that has drifted silently narrows what the sweep reaches.
|
|
45
|
+
*/
|
|
46
|
+
export declare const PROBE_ARGS: Record<string, unknown>;
|
|
47
|
+
/**
|
|
48
|
+
* Argument overlays the replay runs IN ADDITION to `PROBE_ARGS` alone.
|
|
49
|
+
*
|
|
50
|
+
* A blind sweep with one fixed argument set is a LOWER bound on what the tools
|
|
51
|
+
* reach, and the gap is not evenly spread: a handful of tools pick their route
|
|
52
|
+
* FROM an argument, so one probe pass sees one of their routes and misses the
|
|
53
|
+
* rest. `list_templates` chooses between four catalogue paths on `kind`;
|
|
54
|
+
* `list_dependent_agents` and `list_workflows_using` choose on `entityKind`;
|
|
55
|
+
* `validate_workflow` takes EXACTLY ONE of `workflowId`/`document`, so the base
|
|
56
|
+
* set — which supplies both — makes it fire nothing at all.
|
|
57
|
+
*
|
|
58
|
+
* That under-reporting is not theoretical. It made a coverage audit name three
|
|
59
|
+
* template routes as uncovered when `list_templates` had reached them all
|
|
60
|
+
* along. A measurement that quietly reads low is worse than no measurement,
|
|
61
|
+
* because it generates work that did not need doing.
|
|
62
|
+
*
|
|
63
|
+
* Each overlay is merged over `PROBE_ARGS` and replayed as its own pass. An
|
|
64
|
+
* `undefined` value REMOVES an argument, which is how the two shapes of
|
|
65
|
+
* `validate_workflow` are reached.
|
|
66
|
+
*/
|
|
67
|
+
export declare const PROBE_VARIANTS: Record<string, unknown>[];
|
|
68
|
+
/** A route a tool actually called, as observed by the recording client. */
|
|
69
|
+
export interface CalledRoute {
|
|
70
|
+
method: string;
|
|
71
|
+
path: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Every route this build's tools call, found by replaying them.
|
|
75
|
+
*
|
|
76
|
+
* Nothing leaves the process: the client is a recorder. A handler that rejects
|
|
77
|
+
* the probe arguments is simply skipped — it contributes no route, which makes
|
|
78
|
+
* this a LOWER bound on what the connector needs and therefore safe: the check
|
|
79
|
+
* can miss a route, but it cannot invent one.
|
|
80
|
+
*/
|
|
81
|
+
export declare function collectCalledRoutes(register: (server: ProbeServer, client: AxonityClient) => void): Promise<CalledRoute[]>;
|
|
82
|
+
/** Does a concrete path match a "METHOD /a/{b}/c" contract entry? */
|
|
83
|
+
export declare function matchesRoute(call: CalledRoute, entry: string): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Routes this build needs that the deploy does not mount, as "METHOD /path".
|
|
86
|
+
*
|
|
87
|
+
* Deduplicated by template rather than by concrete path: `read_workflow` and
|
|
88
|
+
* `update_workflow` both hit `/workflows/{id}`, and reporting one absence twice
|
|
89
|
+
* makes a short list look like an emergency.
|
|
90
|
+
*/
|
|
91
|
+
export declare function missingFromContract(calls: CalledRoute[], routes: string[]): string[];
|
|
92
|
+
export interface ContractCheckOptions {
|
|
93
|
+
/** Base URL, used to key the cache so two tenants do not share a verdict. */
|
|
94
|
+
apiUrl: string;
|
|
95
|
+
/** Where to write the report. Defaults to stderr — stdout is the MCP channel. */
|
|
96
|
+
log?: (message: string) => void;
|
|
97
|
+
/** How long to wait before giving up on the check. Defaults to 5s. */
|
|
98
|
+
timeoutMs?: number;
|
|
99
|
+
/** Skip the cache. Used by tests, and by a caller that wants a fresh verdict. */
|
|
100
|
+
ignoreCache?: boolean;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Ask the deploy what it mounts, and say — once, at startup — whether this
|
|
104
|
+
* build needs anything it does not have.
|
|
105
|
+
*
|
|
106
|
+
* Returns what it concluded, so a caller (and a test) can tell "checked, all
|
|
107
|
+
* present" from "could not check". Never throws.
|
|
108
|
+
*/
|
|
109
|
+
export declare function reportContractSkew(client: AxonityClient, register: (server: ProbeServer, client: AxonityClient) => void, options: ContractCheckOptions): Promise<"ok" | "missing" | "unavailable" | "cached" | "timeout">;
|