@vardum/mcp 0.3.0 → 0.4.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 +91 -62
- package/dist/index.js +121 -23
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
# @vardum/mcp
|
|
2
2
|
|
|
3
|
-
MCP server for [Vardum](https://vardum.com): lets an AI agent manage assets
|
|
3
|
+
MCP server for [Vardum](https://vardum.com): lets an AI agent manage assets and
|
|
4
4
|
Vards using a key scoped to one workspace. Workspace creation, membership,
|
|
5
5
|
API-key management and domains are managed in the web app with a signed-in session.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
This README describes **MCP 0.4.0**, including revision-guarded Vard editing.
|
|
8
|
+
Check the [0.8 client release notes](https://github.com/f-tuma/vardum/blob/main/docs/client-release-0.8.md)
|
|
9
|
+
and [release status and compatibility](https://github.com/f-tuma/vardum/blob/main/docs/npm-release.md)
|
|
10
|
+
to confirm npm availability before installing this version. Published MCP 0.3.0
|
|
11
|
+
uses the legacy editing contract; installing it does not enable revision guards.
|
|
12
|
+
The published 0.1.0 artifact has a broken workspace dependency.
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
run by hand. Create a workspace API key in **Settings // API keys** first; the
|
|
11
|
-
key is restricted to that workspace and its selected read/write permissions.
|
|
12
|
-
Copy the workspace ID from Settings → General for tool calls.
|
|
14
|
+
## Setup
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
and
|
|
17
|
-
|
|
16
|
+
The server speaks MCP over stdio, so the client starts it. Create a workspace
|
|
17
|
+
API key in **Settings // API keys** first; it is restricted to that workspace
|
|
18
|
+
and its selected permissions. Copy the workspace ID from Settings → General
|
|
19
|
+
for tool calls. Use Node.js 22 or newer.
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
`
|
|
22
|
-
|
|
21
|
+
**These configurations target MCP 0.4.0**; use them once the release-status page
|
|
22
|
+
confirms that exact package is available. Deploy the matching server advertising
|
|
23
|
+
`vardEditRevision: 1` before editing Vards. For current published setup, see the
|
|
24
|
+
[MCP guide](../../docs/guide/mcp.md).
|
|
23
25
|
|
|
24
26
|
Claude Code:
|
|
25
27
|
|
|
26
28
|
```sh
|
|
27
|
-
claude mcp add vardum --env VARDUM_API_KEY=varda_... -- npx -y @vardum/mcp@0.
|
|
29
|
+
claude mcp add vardum --env VARDUM_API_KEY=varda_... -- npx -y @vardum/mcp@0.4.0
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
Any client that reads a JSON config (Claude Desktop, Cursor, …):
|
|
@@ -34,13 +36,17 @@ Any client that reads a JSON config (Claude Desktop, Cursor, …):
|
|
|
34
36
|
"mcpServers": {
|
|
35
37
|
"vardum": {
|
|
36
38
|
"command": "npx",
|
|
37
|
-
"args": ["-y", "@vardum/mcp@0.
|
|
39
|
+
"args": ["-y", "@vardum/mcp@0.4.0"],
|
|
38
40
|
"env": { "VARDUM_API_KEY": "varda_..." }
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
```
|
|
43
45
|
|
|
46
|
+
For a source checkout, build with `pnpm --filter "@vardum/mcp..." build` and
|
|
47
|
+
configure the client to run `node /absolute/path/to/vardum/apps/mcp/dist/index.js`.
|
|
48
|
+
This does not establish npm availability.
|
|
49
|
+
|
|
44
50
|
## Configuration
|
|
45
51
|
|
|
46
52
|
| Setting | Flag | Environment | Default |
|
|
@@ -49,12 +55,11 @@ Any client that reads a JSON config (Claude Desktop, Cursor, …):
|
|
|
49
55
|
| Workspace API key | `--api-key KEY` | `VARDUM_API_KEY` | — (required) |
|
|
50
56
|
|
|
51
57
|
Point `--server` at your own host when self-hosting. `vardum-mcp --version` and
|
|
52
|
-
`--help` answer without needing a key
|
|
53
|
-
client can start the binary at all.
|
|
58
|
+
`--help` answer without needing a key.
|
|
54
59
|
|
|
55
60
|
## Tools
|
|
56
61
|
|
|
57
|
-
The 11 tools
|
|
62
|
+
The 11 tools remain:
|
|
58
63
|
|
|
59
64
|
| Area | Tools |
|
|
60
65
|
| --- | --- |
|
|
@@ -62,74 +67,98 @@ The 11 tools are:
|
|
|
62
67
|
| Vards | `list_vards`, `create_vard`, `get_vard`, `update_vard`, `delete_vard`, `replace_vard_items` |
|
|
63
68
|
|
|
64
69
|
A read key can list/read. A write key also permits create, update, delete and
|
|
65
|
-
publish
|
|
66
|
-
|
|
70
|
+
publish within its scope. `update_vard` and `replace_vard_items` require API read
|
|
71
|
+
and update permission because they return the full definition. `delete_vard`
|
|
72
|
+
requires delete permission. Tools for browser-only administration were removed
|
|
73
|
+
in 0.2.0; an owner-created workspace key is still not an administration credential.
|
|
67
74
|
|
|
68
75
|
`list_assets` returns `{ workspaceSlug, assets, nextCursor }`. It lists ready
|
|
69
76
|
assets in pages of 100 by default (`limit`: 1–500); pass `nextCursor` as `cursor`
|
|
70
|
-
until it is null. Pending/failed versions are not listed.
|
|
71
|
-
|
|
72
|
-
`replace_vard_items` pins with `versionId: "<ready-version-uuid>"` and
|
|
73
|
-
`writable: false`. Omit `versionId` or send null to follow latest. The old
|
|
74
|
-
`pinned` boolean is rejected: it never identified a specific version for the API.
|
|
75
|
-
|
|
76
|
-
Images and video are accepted; video is stored and delivered as-is, and cannot
|
|
77
|
-
be resized or transformed into variants.
|
|
77
|
+
until it is null. Pending/failed versions are not listed. Images and video are
|
|
78
|
+
accepted; video is stored and delivered as-is without variants.
|
|
78
79
|
|
|
79
80
|
## Asset visibility
|
|
80
81
|
|
|
81
82
|
New assets are private. `list_assets` reports `isPublic` for each ready asset.
|
|
82
83
|
Use `set_asset_visibility` with `workspaceId`, `assetId`, and an explicit boolean
|
|
83
|
-
`isPublic` to publish or make an asset private.
|
|
84
|
-
permission; read-only keys cannot change visibility.
|
|
84
|
+
`isPublic` to publish or make an asset private. This requires publish permission.
|
|
85
85
|
|
|
86
86
|
Publishing exposes every ready version and its variants. A public Vard does not
|
|
87
87
|
publish its files: its anonymous manifest remains unavailable while any mapped
|
|
88
88
|
asset is private. Making a file private cannot revoke downloaded or cached
|
|
89
|
-
copies. These tools require the matching visibility-capable API in the
|
|
89
|
+
copies. These tools require the matching visibility-capable API in the
|
|
90
|
+
compatibility table.
|
|
91
|
+
|
|
92
|
+
## Guarded editing
|
|
93
|
+
|
|
94
|
+
Read `get_vard` first. Its editable definition contains `revision`, saved
|
|
95
|
+
`folderRules` and **manual mappings only**, including stable mapping `id` values
|
|
96
|
+
and literal `pinnedVersionId`. A separate `get_vard` with `resolved: true` returns
|
|
97
|
+
a live preview without an editor revision. It can fail on collisions or limits
|
|
98
|
+
without blocking configuration inspection or repair. Never persist resolved
|
|
99
|
+
folder outputs as manual mappings.
|
|
100
|
+
|
|
101
|
+
`update_vard`, `replace_vard_items` and `delete_vard` require the caller's
|
|
102
|
+
**original `expectedRevision`**. It is an opaque `v1:<64 lowercase hex digits>`
|
|
103
|
+
equality token for saved state, not a counter. Keep it unchanged. After a
|
|
104
|
+
successful update or replacement, retain the full returned definition and its
|
|
105
|
+
revision for the next edit.
|
|
106
|
+
|
|
107
|
+
`replace_vard_items` takes the complete intended manual list. Each item must
|
|
108
|
+
contain `id` (its existing manual UUID, or `null` for a new mapping), `assetId`,
|
|
109
|
+
`outputPath`, `writable`, `transformation` (object or `null`) and an explicit
|
|
110
|
+
`versionId` (UUID or `null`). Keep existing IDs; omitting an existing item removes
|
|
111
|
+
it. Copy each saved `pinnedVersionId` into `versionId` literally, including an
|
|
112
|
+
unavailable saved pin. A resolved delivery `versionId` is not the saved pin.
|
|
113
|
+
`null` follows latest; pinned items must be read-only. Omitting `id` or
|
|
114
|
+
`versionId`, or supplying the old `pinned` boolean, is invalid.
|
|
115
|
+
|
|
116
|
+
These editor tools check `vardEditRevision: 1` and use dedicated `/editor`
|
|
117
|
+
endpoints. An unsupported server, missing revision or missing editor endpoint
|
|
118
|
+
stops the operation; there is no fallback to a legacy mutation.
|
|
119
|
+
|
|
120
|
+
A stale write returns a structured MCP tool error (`isError: true`) with
|
|
121
|
+
`status: 409`, `code: "vard_revision_conflict"`,
|
|
122
|
+
`error: "Vard changed; reload before saving."`, `currentRevision` and an
|
|
123
|
+
instruction to reload. Keep the intended edit, read `get_vard` again, review the
|
|
124
|
+
current definition, then reapply the edit with that reviewed definition's
|
|
125
|
+
revision. Do not automatically repeat a stale replacement list with a new token.
|
|
126
|
+
See the [guarded editor protocol](../../docs/vard-editor-concurrency.md).
|
|
90
127
|
|
|
91
128
|
## Folder rules
|
|
92
129
|
|
|
93
|
-
|
|
94
|
-
matching folder-aware API.
|
|
95
|
-
`get_vard` with `workspaceId` and `vardId` returns editable configuration:
|
|
96
|
-
`items` contains **manual mappings only**, and `folderRules` contains saved rules.
|
|
97
|
-
For a separate current live preview use `get_vard` with `resolved: true`; this is
|
|
98
|
-
not a committed frozen snapshot and can fail on collisions or limits without
|
|
99
|
-
blocking configuration inspection or repair.
|
|
100
|
-
|
|
101
|
-
`create_vard` accepts `name`, optional `isPublic` (defaults private), and optional
|
|
130
|
+
`create_vard` accepts `name`, optional `isPublic` (defaults private) and optional
|
|
102
131
|
`folderRules` (defaults empty); it does not require a slug. `update_vard` changes
|
|
103
|
-
only supplied fields.
|
|
132
|
+
only supplied fields. After reading `get_vard`, update rules with:
|
|
104
133
|
|
|
105
134
|
```json
|
|
106
135
|
{
|
|
107
136
|
"workspaceId": "your-workspace-id",
|
|
108
137
|
"vardId": "your-vard-id",
|
|
138
|
+
"expectedRevision": "v1:<copy the original revision from get_vard>",
|
|
109
139
|
"folderRules": [{ "sourcePrefix": "brand/", "outputPrefix": "assets/brand/" }]
|
|
110
140
|
}
|
|
111
141
|
```
|
|
112
142
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
The API remains authoritative for workspace permissions and validation; tools
|
|
117
|
-
forward validation, authorization and resolution errors.
|
|
143
|
+
Replace the illustrative token with the unchanged revision from your read. This
|
|
144
|
+
preserves the Vard's name, public flag and manual mappings. Send `folderRules: []`
|
|
145
|
+
to remove all rules. `replace_vard_items` preserves rules.
|
|
118
146
|
|
|
119
147
|
Rules include ready current originals recursively: `brand/icons/logo.svg` becomes
|
|
120
148
|
`assets/brand/icons/logo.svg`. Generated outputs are read-only, unpinned and
|
|
121
|
-
untransformed; pending files are excluded until ready. Empty prefixes mean
|
|
122
|
-
or project root. Empty/nonexistent folders are valid. Prefixes
|
|
123
|
-
relative directories ending in `/` (or empty), at most 255
|
|
124
|
-
excludes. Maximum 64 rules and 1,000 total manual +
|
|
125
|
-
up to 500 characters. Explicit + rule and rule +
|
|
126
|
-
case/Unicode equivalents and file/directory
|
|
127
|
-
A new private
|
|
128
|
-
|
|
129
|
-
Normal `vardum pull` intentionally adopts membership changes; `--frozen`
|
|
130
|
-
the committed snapshot without live resolution. There is no background
|
|
131
|
-
Removing rules leaves source assets/manual mappings intact. Choose
|
|
132
|
-
the membership-updating pull: a normal no-delete pull keeps stale
|
|
133
|
-
drops their tracking, so a later `--delete` cannot remove them
|
|
134
|
-
|
|
135
|
-
and [operations](https://github.com/f-tuma/vardum/blob/main/docs/operations.md#folder-rule-upgrade)
|
|
149
|
+
untransformed; pending files are excluded until ready. Empty prefixes mean
|
|
150
|
+
workspace or project root. Empty/nonexistent folders are valid. Prefixes are
|
|
151
|
+
safe literal relative directories ending in `/` (or empty), at most 255
|
|
152
|
+
characters; no globs or excludes. Maximum 64 rules and 1,000 total manual +
|
|
153
|
+
generated outputs, with paths up to 500 characters. Explicit + rule and rule +
|
|
154
|
+
rule collisions fail, including case/Unicode equivalents and file/directory
|
|
155
|
+
overlaps. A new private member blocks the entire anonymous public manifest.
|
|
156
|
+
|
|
157
|
+
Normal `vardum pull` intentionally adopts membership changes; `--frozen`
|
|
158
|
+
consumes the committed snapshot without live resolution. There is no background
|
|
159
|
+
sync. Removing rules leaves source assets/manual mappings intact. Choose
|
|
160
|
+
`--delete` on the membership-updating pull: a normal no-delete pull keeps stale
|
|
161
|
+
disk files but drops their tracking, so a later `--delete` cannot remove them.
|
|
162
|
+
See [CLI snapshot limitations](https://github.com/f-tuma/vardum/tree/main/apps/cli#reproducible-asset-consumption)
|
|
163
|
+
and [operations](https://github.com/f-tuma/vardum/blob/main/docs/operations.md#folder-rule-upgrade)
|
|
164
|
+
before upgrading or rolling back.
|
package/dist/index.js
CHANGED
|
@@ -53,6 +53,19 @@ const headers = {
|
|
|
53
53
|
};
|
|
54
54
|
if (API_KEY)
|
|
55
55
|
headers["x-api-key"] = API_KEY;
|
|
56
|
+
class VardumHttpError extends Error {
|
|
57
|
+
responseError;
|
|
58
|
+
status;
|
|
59
|
+
code;
|
|
60
|
+
currentRevision;
|
|
61
|
+
constructor(responseError, status, code, currentRevision) {
|
|
62
|
+
super(`${responseError} (${status})`);
|
|
63
|
+
this.responseError = responseError;
|
|
64
|
+
this.status = status;
|
|
65
|
+
this.code = code;
|
|
66
|
+
this.currentRevision = currentRevision;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
56
69
|
async function api(path, init = {}) {
|
|
57
70
|
const merged = new Headers(init.headers);
|
|
58
71
|
for (const [k, v] of Object.entries(headers))
|
|
@@ -68,14 +81,20 @@ async function api(path, init = {}) {
|
|
|
68
81
|
const text = await res.text();
|
|
69
82
|
if (!res.ok) {
|
|
70
83
|
let msg = text || res.statusText;
|
|
84
|
+
let code;
|
|
85
|
+
let currentRevision;
|
|
71
86
|
try {
|
|
72
87
|
const parsed = JSON.parse(text);
|
|
88
|
+
if (typeof parsed.code === "string")
|
|
89
|
+
code = parsed.code;
|
|
90
|
+
if (typeof parsed.currentRevision === "string")
|
|
91
|
+
currentRevision = parsed.currentRevision;
|
|
73
92
|
msg = parsed.message ?? parsed.error ?? msg;
|
|
74
93
|
}
|
|
75
94
|
catch {
|
|
76
95
|
/* use body text */
|
|
77
96
|
}
|
|
78
|
-
throw new
|
|
97
|
+
throw new VardumHttpError(msg, res.status, code, currentRevision);
|
|
79
98
|
}
|
|
80
99
|
try {
|
|
81
100
|
return JSON.parse(text);
|
|
@@ -84,6 +103,57 @@ async function api(path, init = {}) {
|
|
|
84
103
|
throw new Error("Invalid JSON response");
|
|
85
104
|
}
|
|
86
105
|
}
|
|
106
|
+
const vardRevision = (value) => typeof value === "string" && /^v1:[a-f0-9]{64}$/.test(value);
|
|
107
|
+
const editorUuid = (value) => typeof value === "string" && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(value);
|
|
108
|
+
async function requireVardEditRevision() {
|
|
109
|
+
const capabilities = await api("/api/capabilities");
|
|
110
|
+
if (!capabilities || typeof capabilities !== "object" ||
|
|
111
|
+
capabilities.vardEditRevision !== 1)
|
|
112
|
+
throw new Error("This server does not support revision-guarded Vard editing (vardEditRevision: 1); upgrade the server before editing Vards");
|
|
113
|
+
}
|
|
114
|
+
function editorPath(workspaceId, vardId) {
|
|
115
|
+
return `/api/workspaces/${encodeURIComponent(workspaceId)}/vards/${encodeURIComponent(vardId)}/editor`;
|
|
116
|
+
}
|
|
117
|
+
function validateVardEditor(value) {
|
|
118
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
119
|
+
throw new Error("Invalid Vard editor definition; upgrade the server and reload before editing");
|
|
120
|
+
const definition = value;
|
|
121
|
+
if (definition.schema !== 1 || !vardRevision(definition.revision) ||
|
|
122
|
+
typeof definition.id !== "string" || typeof definition.workspaceId !== "string" ||
|
|
123
|
+
typeof definition.name !== "string" || typeof definition.isPublic !== "boolean" ||
|
|
124
|
+
typeof definition.updatedAt !== "string" || !Array.isArray(definition.folderRules) ||
|
|
125
|
+
!Array.isArray(definition.items) || definition.items.some((item) => {
|
|
126
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
127
|
+
return true;
|
|
128
|
+
const mapping = item;
|
|
129
|
+
return !editorUuid(mapping.id) || !(mapping.pinnedVersionId === null || editorUuid(mapping.pinnedVersionId));
|
|
130
|
+
}))
|
|
131
|
+
throw new Error("Invalid Vard editor definition or missing revision/literal pins; upgrade the server and reload before editing");
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
async function editorResult(action) {
|
|
135
|
+
try {
|
|
136
|
+
const data = await action();
|
|
137
|
+
return { content: [{ type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) }] };
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (error instanceof VardumHttpError && error.status === 409 && error.code === "vard_revision_conflict") {
|
|
141
|
+
const data = {
|
|
142
|
+
status: error.status,
|
|
143
|
+
code: error.code,
|
|
144
|
+
error: error.responseError,
|
|
145
|
+
currentRevision: error.currentRevision,
|
|
146
|
+
instruction: "Reload with get_vard, review the current definition, and reapply the intended edit with its revision. Do not retry the stale save.",
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
isError: true,
|
|
150
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
151
|
+
structuredContent: data,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
87
157
|
// ── Server ─────────────────────────────────────────────────────────────────
|
|
88
158
|
const server = new McpServer({
|
|
89
159
|
name: "Vardum",
|
|
@@ -128,6 +198,12 @@ read-only, unpinned, and untransformed. No globs or excludes. Manual + generated
|
|
|
128
198
|
not collide (including case/Unicode or file/directory overlaps) and total at most 1000.
|
|
129
199
|
get_vard returns editable manual items and folderRules, even if resolution fails. Use
|
|
130
200
|
get_vard with resolved=true for a separate live preview; never persist expanded items.
|
|
201
|
+
get_vard supplies an opaque revision and literal pinnedVersionId for each manual item.
|
|
202
|
+
update_vard, replace_vard_items and delete_vard require the original expectedRevision; a stale
|
|
203
|
+
revision returns vard_revision_conflict. Reload and review before reapplying; never retry blindly.
|
|
204
|
+
replace_vard_items requires each existing manual UUID or null for a new item. Copy the stored
|
|
205
|
+
pinnedVersionId to versionId, including unavailable pins; resolved versionId is not the saved pin.
|
|
206
|
+
These editor tools require vardEditRevision: 1; unsupported servers are refused without legacy writes.
|
|
131
207
|
update_vard replaces only supplied fields; folderRules: [] removes rules without changing manual items.
|
|
132
208
|
A new private folder member makes the whole anonymous public manifest unavailable.
|
|
133
209
|
Normal vardum pull intentionally updates membership; --frozen consumes the committed snapshot.
|
|
@@ -366,50 +442,71 @@ server.tool("create_vard", "Create a Vard with optional recursive folder rules,
|
|
|
366
442
|
});
|
|
367
443
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
368
444
|
});
|
|
369
|
-
|
|
445
|
+
const expectedRevisionInput = z
|
|
446
|
+
.string()
|
|
447
|
+
.regex(/^v1:[a-f0-9]{64}$/)
|
|
448
|
+
.describe("Original opaque revision from get_vard; reload and review after a conflict before reapplying the edit");
|
|
449
|
+
server.tool("get_vard", "Retrieve editable Vard configuration with its revision, manual item IDs and literal pinnedVersionId, even when expansion fails. Requires vardEditRevision: 1. Set resolved=true for a separate live manifest preview without an editing revision; never write resolved items back as manual items.", {
|
|
370
450
|
workspaceId: z.string().describe("Workspace ID"),
|
|
371
451
|
vardId: z.string().describe("Vard ID"),
|
|
372
452
|
resolved: z
|
|
373
453
|
.boolean()
|
|
374
454
|
.optional()
|
|
375
|
-
.describe("True: resolve current outputs, which may fail on collisions/limits. Omitted/false: editable
|
|
455
|
+
.describe("True: resolve current outputs, which may fail on collisions/limits. Omitted/false: editable definition and revision only."),
|
|
376
456
|
}, async ({ workspaceId, vardId, resolved }) => {
|
|
377
|
-
|
|
457
|
+
if (resolved) {
|
|
458
|
+
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/vards/${encodeURIComponent(vardId)}/manifest`);
|
|
459
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
460
|
+
}
|
|
461
|
+
await requireVardEditRevision();
|
|
462
|
+
const data = validateVardEditor(await api(editorPath(workspaceId, vardId)));
|
|
378
463
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
379
464
|
});
|
|
380
|
-
server.tool("update_vard", "Update
|
|
465
|
+
server.tool("update_vard", "Update supplied metadata or folderRules using the caller's original expectedRevision; returns the full manual-only definition and revision. Omitted fields and manual IDs are preserved; folderRules: [] removes rules. Requires API read+update permission and vardEditRevision: 1. On conflict reload, review and reapply the intended edit; never retry a stale save automatically.", {
|
|
381
466
|
workspaceId: z.string().describe("Workspace ID"),
|
|
382
467
|
vardId: z.string().describe("Vard ID"),
|
|
468
|
+
expectedRevision: expectedRevisionInput,
|
|
383
469
|
name: z.string().min(1).max(80).optional().describe("New display name"),
|
|
384
470
|
isPublic: z
|
|
385
471
|
.boolean()
|
|
386
472
|
.optional()
|
|
387
473
|
.describe("Public manifest only; does not publish resolved assets"),
|
|
388
474
|
folderRules: folderRulesInput.optional(),
|
|
389
|
-
}, async ({ workspaceId, vardId, name, isPublic, folderRules }) => {
|
|
390
|
-
const body = {};
|
|
475
|
+
}, async ({ workspaceId, vardId, expectedRevision, name, isPublic, folderRules }) => editorResult(async () => {
|
|
476
|
+
const body = { expectedRevision };
|
|
391
477
|
if (name !== undefined)
|
|
392
478
|
body.name = name;
|
|
393
479
|
if (isPublic !== undefined)
|
|
394
480
|
body.isPublic = isPublic;
|
|
395
481
|
if (folderRules !== undefined)
|
|
396
482
|
body.folderRules = folderRules;
|
|
397
|
-
|
|
398
|
-
return
|
|
399
|
-
|
|
400
|
-
|
|
483
|
+
await requireVardEditRevision();
|
|
484
|
+
return validateVardEditor(await api(editorPath(workspaceId, vardId), {
|
|
485
|
+
method: "PATCH", body: JSON.stringify(body),
|
|
486
|
+
}));
|
|
487
|
+
}));
|
|
488
|
+
server.tool("delete_vard", "Delete a Vard and all output configurations permanently using the caller's original expectedRevision. Requires API delete permission and vardEditRevision: 1. On conflict reload and review before deciding to delete again.", {
|
|
401
489
|
workspaceId: z.string().describe("Workspace ID"),
|
|
402
490
|
vardId: z.string().describe("Vard ID"),
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
});
|
|
407
|
-
|
|
491
|
+
expectedRevision: expectedRevisionInput,
|
|
492
|
+
}, async ({ workspaceId, vardId, expectedRevision }) => editorResult(async () => {
|
|
493
|
+
await requireVardEditRevision();
|
|
494
|
+
const query = new URLSearchParams({ expectedRevision });
|
|
495
|
+
await api(editorPath(workspaceId, vardId) + `?${query}`, { method: "DELETE" });
|
|
496
|
+
return "Vard deleted.";
|
|
497
|
+
}));
|
|
498
|
+
server.tool("replace_vard_items", "Replace manual items using the caller's original expectedRevision; returns the full definition and revision. Keep existing manual IDs, use null for new IDs, and copy pinnedVersionId to versionId literally, including unavailable pins; never infer a pin from resolved versionId or pass expanded folder outputs. Requires API read+update permission and vardEditRevision: 1. On conflict reload, review and reapply the intended edit; never retry a stale list automatically.", {
|
|
408
499
|
workspaceId: z.string().describe("Workspace ID"),
|
|
409
500
|
vardId: z.string().describe("Vard ID"),
|
|
501
|
+
expectedRevision: expectedRevisionInput,
|
|
410
502
|
items: z
|
|
411
503
|
.array(z
|
|
412
504
|
.object({
|
|
505
|
+
id: z
|
|
506
|
+
.string()
|
|
507
|
+
.uuid()
|
|
508
|
+
.nullable()
|
|
509
|
+
.describe("Existing manual item UUID to preserve identity, or null to create a new manual item"),
|
|
413
510
|
assetId: z.string().describe("The asset ID to include"),
|
|
414
511
|
outputPath: z
|
|
415
512
|
.string()
|
|
@@ -421,19 +518,20 @@ server.tool("replace_vard_items", "Replace only manual items in a Vard; folderRu
|
|
|
421
518
|
.string()
|
|
422
519
|
.uuid()
|
|
423
520
|
.nullable()
|
|
424
|
-
.
|
|
425
|
-
.describe("Exact ready version ID to pin; null/omitted follows latest. Pinned outputs must not be writable."),
|
|
521
|
+
.describe("Literal saved pin: copy pinnedVersionId from get_vard, never resolved versionId. Null follows latest. Pinned outputs must not be writable."),
|
|
426
522
|
transformation: z
|
|
427
523
|
.record(z.unknown())
|
|
428
524
|
.nullable()
|
|
429
525
|
.describe("Optional transformation parameters (w, h, fit, format, q) or null"),
|
|
430
526
|
})
|
|
431
527
|
.strict())
|
|
432
|
-
.describe("Complete
|
|
433
|
-
}, async ({ workspaceId, vardId, items }) => {
|
|
434
|
-
|
|
435
|
-
return
|
|
436
|
-
})
|
|
528
|
+
.describe("Complete manual item list only; folderRules are preserved"),
|
|
529
|
+
}, async ({ workspaceId, vardId, expectedRevision, items }) => editorResult(async () => {
|
|
530
|
+
await requireVardEditRevision();
|
|
531
|
+
return validateVardEditor(await api(editorPath(workspaceId, vardId) + "/items", {
|
|
532
|
+
method: "PUT", body: JSON.stringify({ expectedRevision, items }),
|
|
533
|
+
}));
|
|
534
|
+
}));
|
|
437
535
|
// ── Main ────────────────────────────────────────────────────────────────────
|
|
438
536
|
async function main() {
|
|
439
537
|
if (!API_KEY) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vardum/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MCP server for workspace-scoped Vardum assets and Vards",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@modelcontextprotocol/sdk": "1.31.0",
|
|
22
|
-
"@vardum/shared": "^0.
|
|
22
|
+
"@vardum/shared": "^0.4.0",
|
|
23
23
|
"zod": "^3.24.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|