@vardum/mcp 0.1.0 → 0.3.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/LICENSE +21 -0
- package/README.md +92 -10
- package/dist/index.js +117 -171
- package/package.json +19 -10
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vardum contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
# @vardum/mcp
|
|
2
2
|
|
|
3
3
|
MCP server for [Vardum](https://vardum.com): lets an AI agent manage assets,
|
|
4
|
-
Vards
|
|
5
|
-
|
|
4
|
+
Vards using a key scoped to one workspace. Workspace creation, membership,
|
|
5
|
+
API-key management and domains are managed in the web app with a signed-in session.
|
|
6
6
|
|
|
7
7
|
## Setup
|
|
8
8
|
|
|
9
9
|
The server speaks MCP over stdio, so the client starts it — there is nothing to
|
|
10
10
|
run by hand. Create a workspace API key in **Settings // API keys** first; the
|
|
11
|
-
key
|
|
11
|
+
key is restricted to that workspace and its selected read/write permissions.
|
|
12
|
+
Copy the workspace ID from Settings → General for tool calls.
|
|
13
|
+
|
|
14
|
+
This README describes **MCP 0.3.0**. The 11-tool surface is unchanged. Check the
|
|
15
|
+
[release notes](https://github.com/f-tuma/vardum/blob/main/docs/client-release-0.7.md)
|
|
16
|
+
and [release status and compatibility](https://github.com/f-tuma/vardum/blob/main/docs/npm-release.md).
|
|
17
|
+
The published 0.1.0 artifact has a broken workspace dependency.
|
|
18
|
+
|
|
19
|
+
The configurations below pin **0.3.0**. Verify its availability in your registry
|
|
20
|
+
before installing, or build from source with
|
|
21
|
+
`pnpm --filter "@vardum/mcp..." build` and configure the client to run
|
|
22
|
+
`node /absolute/path/to/vardum/apps/mcp/dist/index.js`.
|
|
12
23
|
|
|
13
24
|
Claude Code:
|
|
14
25
|
|
|
15
26
|
```sh
|
|
16
|
-
claude mcp add vardum --env VARDUM_API_KEY=varda_... -- npx -y @vardum/mcp
|
|
27
|
+
claude mcp add vardum --env VARDUM_API_KEY=varda_... -- npx -y @vardum/mcp@0.3.0
|
|
17
28
|
```
|
|
18
29
|
|
|
19
30
|
Any client that reads a JSON config (Claude Desktop, Cursor, …):
|
|
@@ -23,7 +34,7 @@ Any client that reads a JSON config (Claude Desktop, Cursor, …):
|
|
|
23
34
|
"mcpServers": {
|
|
24
35
|
"vardum": {
|
|
25
36
|
"command": "npx",
|
|
26
|
-
"args": ["-y", "@vardum/mcp"],
|
|
37
|
+
"args": ["-y", "@vardum/mcp@0.3.0"],
|
|
27
38
|
"env": { "VARDUM_API_KEY": "varda_..." }
|
|
28
39
|
}
|
|
29
40
|
}
|
|
@@ -43,11 +54,82 @@ client can start the binary at all.
|
|
|
43
54
|
|
|
44
55
|
## Tools
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
The 11 tools are:
|
|
58
|
+
|
|
59
|
+
| Area | Tools |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| Assets | `list_assets`, `create_asset`, `upload_asset_version`, `set_asset_visibility`, `delete_asset` |
|
|
62
|
+
| Vards | `list_vards`, `create_vard`, `get_vard`, `update_vard`, `delete_vard`, `replace_vard_items` |
|
|
63
|
+
|
|
64
|
+
A read key can list/read. A write key also permits create, update, delete and
|
|
65
|
+
publish. Tools for browser-only administration were removed in 0.2.0; creating
|
|
66
|
+
an asset key as an owner does not make it an administration credential.
|
|
67
|
+
|
|
68
|
+
`list_assets` returns `{ workspaceSlug, assets, nextCursor }`. It lists ready
|
|
69
|
+
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.
|
|
51
75
|
|
|
52
76
|
Images and video are accepted; video is stored and delivered as-is, and cannot
|
|
53
77
|
be resized or transformed into variants.
|
|
78
|
+
|
|
79
|
+
## Asset visibility
|
|
80
|
+
|
|
81
|
+
New assets are private. `list_assets` reports `isPublic` for each ready asset.
|
|
82
|
+
Use `set_asset_visibility` with `workspaceId`, `assetId`, and an explicit boolean
|
|
83
|
+
`isPublic` to publish or make an asset private. The API requires asset publish
|
|
84
|
+
permission; read-only keys cannot change visibility.
|
|
85
|
+
|
|
86
|
+
Publishing exposes every ready version and its variants. A public Vard does not
|
|
87
|
+
publish its files: its anonymous manifest remains unavailable while any mapped
|
|
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 compatibility table.
|
|
90
|
+
|
|
91
|
+
## Folder rules
|
|
92
|
+
|
|
93
|
+
MCP 0.3.0 retains the folder-rule behavior introduced in 0.2.0 and requires the
|
|
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
|
|
102
|
+
`folderRules` (defaults empty); it does not require a slug. `update_vard` changes
|
|
103
|
+
only supplied fields. For example, call it with:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"workspaceId": "your-workspace-id",
|
|
108
|
+
"vardId": "your-vard-id",
|
|
109
|
+
"folderRules": [{ "sourcePrefix": "brand/", "outputPrefix": "assets/brand/" }]
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This preserves the Vard's name, public flag and manual item IDs. Send
|
|
114
|
+
`folderRules: []` to remove all rules. `replace_vard_items` replaces only manual
|
|
115
|
+
mappings and does not change rules: **never copy resolved folder outputs into it**.
|
|
116
|
+
The API remains authoritative for workspace permissions and validation; tools
|
|
117
|
+
forward validation, authorization and resolution errors.
|
|
118
|
+
|
|
119
|
+
Rules include ready current originals recursively: `brand/icons/logo.svg` becomes
|
|
120
|
+
`assets/brand/icons/logo.svg`. Generated outputs are read-only, unpinned and
|
|
121
|
+
untransformed; pending files are excluded until ready. Empty prefixes mean workspace
|
|
122
|
+
or project root. Empty/nonexistent folders are valid. Prefixes must be safe literal
|
|
123
|
+
relative directories ending in `/` (or empty), at most 255 characters; no globs or
|
|
124
|
+
excludes. Maximum 64 rules and 1,000 total manual + generated outputs, with paths
|
|
125
|
+
up to 500 characters. Explicit + rule and rule + rule collisions fail, including
|
|
126
|
+
case/Unicode equivalents and file/directory overlaps; no precedence or truncation.
|
|
127
|
+
A new private folder member makes the entire anonymous public manifest unavailable.
|
|
128
|
+
|
|
129
|
+
Normal `vardum pull` intentionally adopts membership changes; `--frozen` consumes
|
|
130
|
+
the committed snapshot without live resolution. There is no background sync.
|
|
131
|
+
Removing rules leaves source assets/manual mappings intact. Choose `--delete` on
|
|
132
|
+
the membership-updating pull: a normal no-delete pull keeps stale disk files but
|
|
133
|
+
drops their tracking, so a later `--delete` cannot remove them; deliberate manual
|
|
134
|
+
cleanup is then required. See [CLI snapshot limitations](https://github.com/f-tuma/vardum/tree/main/apps/cli#reproducible-asset-consumption)
|
|
135
|
+
and [operations](https://github.com/f-tuma/vardum/blob/main/docs/operations.md#folder-rule-upgrade) before upgrading or rolling back.
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { supportedMimeTypes } from "@vardum/shared";
|
|
5
|
+
import { supportedMimeTypes, } from "@vardum/shared";
|
|
6
6
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { z } from "zod";
|
|
@@ -87,7 +87,7 @@ async function api(path, init = {}) {
|
|
|
87
87
|
// ── Server ─────────────────────────────────────────────────────────────────
|
|
88
88
|
const server = new McpServer({
|
|
89
89
|
name: "Vardum",
|
|
90
|
-
version:
|
|
90
|
+
version: VERSION,
|
|
91
91
|
});
|
|
92
92
|
// ── Resources ──────────────────────────────────────────────────────────────
|
|
93
93
|
server.resource("constraints", "vardum://constraints", () => ({
|
|
@@ -97,7 +97,7 @@ server.resource("constraints", "vardum://constraints", () => ({
|
|
|
97
97
|
text: `# Vardum Constraints
|
|
98
98
|
|
|
99
99
|
## Slug immutability
|
|
100
|
-
Organization
|
|
100
|
+
Organization and workspace slugs are set at creation time and **cannot be changed**.
|
|
101
101
|
Each slug must match \`[a-z0-9][a-z0-9._-]*\`.
|
|
102
102
|
|
|
103
103
|
## Asset key rules
|
|
@@ -111,9 +111,29 @@ Every asset has a unique key scoped to a workspace. Rules:
|
|
|
111
111
|
- Image types: AVIF, GIF, JPEG, PNG, SVG, TIFF, WebP
|
|
112
112
|
- Video types: MP4, WebM, MOV, AVI, MPEG
|
|
113
113
|
- Max 100 megapixels per image; videos are stored as-is (no transcode)
|
|
114
|
-
-
|
|
114
|
+
- Per-upload size ceiling is set by the server, and differs for images and videos (the API rejects anything over it)
|
|
115
115
|
- Video assets cannot be resized or transformed into variants (images only)
|
|
116
116
|
|
|
117
|
+
## Asset visibility
|
|
118
|
+
New assets are private. Use set_asset_visibility with publish permission to change visibility.
|
|
119
|
+
Public assets expose every ready version and its variants without authentication.
|
|
120
|
+
A public Vard does not publish its assets: anonymous manifests require all mapped assets to be public.
|
|
121
|
+
Making an asset private cannot revoke previously downloaded or cached copies.
|
|
122
|
+
|
|
123
|
+
## Folder inclusion
|
|
124
|
+
Vards support up to 64 literal recursive sourcePrefix → outputPrefix rules (255 characters each).
|
|
125
|
+
Example: brand/ → assets/brand/. Empty prefixes mean root; empty/nonexistent folders are valid.
|
|
126
|
+
Only ready current originals are included; pending files wait until ready. Generated outputs are
|
|
127
|
+
read-only, unpinned, and untransformed. No globs or excludes. Manual + generated outputs must
|
|
128
|
+
not collide (including case/Unicode or file/directory overlaps) and total at most 1000.
|
|
129
|
+
get_vard returns editable manual items and folderRules, even if resolution fails. Use
|
|
130
|
+
get_vard with resolved=true for a separate live preview; never persist expanded items.
|
|
131
|
+
update_vard replaces only supplied fields; folderRules: [] removes rules without changing manual items.
|
|
132
|
+
A new private folder member makes the whole anonymous public manifest unavailable.
|
|
133
|
+
Normal vardum pull intentionally updates membership; --frozen consumes the committed snapshot.
|
|
134
|
+
There is no background sync. Choose --delete on the membership-updating pull: without it,
|
|
135
|
+
stale files remain but lose tracking, requiring deliberate manual cleanup later.
|
|
136
|
+
|
|
117
137
|
## Writable outputs
|
|
118
138
|
Only Vard items explicitly marked "Writable" in the UI can be pushed to.
|
|
119
139
|
Pinned and transformed outputs are always read-only.`,
|
|
@@ -156,7 +176,7 @@ Folders exist only as key prefixes — no need to "create" them.
|
|
|
156
176
|
- **editor**: full CRUD on assets, Vards, and presets
|
|
157
177
|
- **owner**: workspace administration (members, API keys, domain, deletion)
|
|
158
178
|
|
|
159
|
-
|
|
179
|
+
MCP uses a workspace-scoped asset key. Members, invitations, keys, domains and workspace administration require a browser session and are managed in the web app.
|
|
160
180
|
|
|
161
181
|
## Video assets
|
|
162
182
|
MP4, WebM, MOV, AVI and MPEG are accepted. They are stored and delivered as-is —
|
|
@@ -196,142 +216,42 @@ Everything an editor can do, plus:
|
|
|
196
216
|
- Full audit log access
|
|
197
217
|
|
|
198
218
|
## API key scoping
|
|
199
|
-
|
|
200
|
-
|
|
219
|
+
MCP exposes asset and Vard tools only. A read key can list/read; a write key adds
|
|
220
|
+
create, update, delete and publish. Keys are restricted to one workspace and do
|
|
221
|
+
not grant browser-session administration, even when created by an owner. Self-revocation
|
|
201
222
|
(\`vardum logout\`) is available to CLI keys without workspace admin access.`,
|
|
202
223
|
},
|
|
203
224
|
],
|
|
204
225
|
}));
|
|
205
|
-
// ── Tools: Workspace ────────────────────────────────────────────────────────
|
|
206
|
-
server.tool("list_organizations", "List all organizations the authenticated user belongs to.", {}, async () => {
|
|
207
|
-
const data = await api("/api/organizations");
|
|
208
|
-
return {
|
|
209
|
-
content: [
|
|
210
|
-
{ type: "text", text: JSON.stringify(data.organizations, null, 2) },
|
|
211
|
-
],
|
|
212
|
-
};
|
|
213
|
-
});
|
|
214
|
-
server.tool("create_workspace", "Create a new workspace inside an organization.", {
|
|
215
|
-
organizationId: z.string().describe("Organization ID"),
|
|
216
|
-
name: z.string().min(1).max(64).describe("Display name for the workspace"),
|
|
217
|
-
slug: z
|
|
218
|
-
.string()
|
|
219
|
-
.regex(/^[a-z0-9][a-z0-9._-]*$/)
|
|
220
|
-
.describe("URL-safe slug (cannot be changed later)"),
|
|
221
|
-
}, async ({ organizationId, name, slug }) => {
|
|
222
|
-
const data = await api(`/api/organizations/${encodeURIComponent(organizationId)}/workspaces`, { method: "POST", body: JSON.stringify({ name, slug }) });
|
|
223
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
224
|
-
});
|
|
225
|
-
server.tool("get_workspace", "Retrieve workspace details including name, slug, and member count.", { workspaceId: z.string().describe("Workspace ID") }, async ({ workspaceId }) => {
|
|
226
|
-
const [members, overview] = await Promise.all([
|
|
227
|
-
api(`/api/workspaces/${encodeURIComponent(workspaceId)}/members`),
|
|
228
|
-
api(`/api/workspaces/${encodeURIComponent(workspaceId)}/overview`),
|
|
229
|
-
]);
|
|
230
|
-
return {
|
|
231
|
-
content: [
|
|
232
|
-
{
|
|
233
|
-
type: "text",
|
|
234
|
-
text: JSON.stringify({ ...overview, members: members.members }, null, 2),
|
|
235
|
-
},
|
|
236
|
-
],
|
|
237
|
-
};
|
|
238
|
-
});
|
|
239
|
-
server.tool("update_workspace", "Update the display name of a workspace. The slug cannot be changed.", {
|
|
240
|
-
workspaceId: z.string().describe("Workspace ID"),
|
|
241
|
-
name: z.string().min(1).max(64).describe("New display name"),
|
|
242
|
-
}, async ({ workspaceId, name }) => {
|
|
243
|
-
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}`, { method: "PATCH", body: JSON.stringify({ name }) });
|
|
244
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
245
|
-
});
|
|
246
|
-
// ── Tools: Members & Invitations ────────────────────────────────────────────
|
|
247
|
-
server.tool("list_members", "List all members of a workspace with their roles.", { workspaceId: z.string().describe("Workspace ID") }, async ({ workspaceId }) => {
|
|
248
|
-
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/members`);
|
|
249
|
-
return {
|
|
250
|
-
content: [{ type: "text", text: JSON.stringify(data.members, null, 2) }],
|
|
251
|
-
};
|
|
252
|
-
});
|
|
253
|
-
server.tool("change_member_role", "Change a member's role in the workspace.", {
|
|
254
|
-
workspaceId: z.string().describe("Workspace ID"),
|
|
255
|
-
memberId: z.string().describe("Member ID"),
|
|
256
|
-
role: z.enum(["owner", "editor", "viewer"]).describe("New role"),
|
|
257
|
-
}, async ({ workspaceId, memberId, role }) => {
|
|
258
|
-
await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(memberId)}`, { method: "PATCH", body: JSON.stringify({ role }) });
|
|
259
|
-
return { content: [{ type: "text", text: `Role changed to ${role}.` }] };
|
|
260
|
-
});
|
|
261
|
-
server.tool("list_invitations", "List pending invitations for a workspace.", { workspaceId: z.string().describe("Workspace ID") }, async ({ workspaceId }) => {
|
|
262
|
-
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/invitations`);
|
|
263
|
-
return {
|
|
264
|
-
content: [
|
|
265
|
-
{ type: "text", text: JSON.stringify(data.invitations, null, 2) },
|
|
266
|
-
],
|
|
267
|
-
};
|
|
268
|
-
});
|
|
269
|
-
server.tool("create_invitation", "Invite someone by email to join a workspace with a specific role.", {
|
|
270
|
-
workspaceId: z.string().describe("Workspace ID"),
|
|
271
|
-
email: z.string().email().describe("Recipient email address"),
|
|
272
|
-
role: z
|
|
273
|
-
.enum(["editor", "viewer"])
|
|
274
|
-
.describe("Role to assign (owner cannot be invited directly)"),
|
|
275
|
-
}, async ({ workspaceId, email, role }) => {
|
|
276
|
-
await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/invitations`, { method: "POST", body: JSON.stringify({ email, role }) });
|
|
277
|
-
return {
|
|
278
|
-
content: [
|
|
279
|
-
{ type: "text", text: `Invitation sent to ${email} (${role}).` },
|
|
280
|
-
],
|
|
281
|
-
};
|
|
282
|
-
});
|
|
283
|
-
server.tool("revoke_invitation", "Revoke a pending invitation before it expires.", {
|
|
284
|
-
workspaceId: z.string().describe("Workspace ID"),
|
|
285
|
-
invitationId: z.string().describe("Invitation ID"),
|
|
286
|
-
}, async ({ workspaceId, invitationId }) => {
|
|
287
|
-
await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/invitations/${encodeURIComponent(invitationId)}`, { method: "DELETE" });
|
|
288
|
-
return { content: [{ type: "text", text: "Invitation revoked." }] };
|
|
289
|
-
});
|
|
290
|
-
// ── Tools: API Keys ─────────────────────────────────────────────────────────
|
|
291
|
-
server.tool("list_api_keys", "List all API keys for a workspace.", { workspaceId: z.string().describe("Workspace ID") }, async ({ workspaceId }) => {
|
|
292
|
-
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/api-keys`);
|
|
293
|
-
return {
|
|
294
|
-
content: [{ type: "text", text: JSON.stringify(data.keys, null, 2) }],
|
|
295
|
-
};
|
|
296
|
-
});
|
|
297
|
-
server.tool("create_api_key", "Create a new API key for a workspace. The full key is shown only once.", {
|
|
298
|
-
workspaceId: z.string().describe("Workspace ID"),
|
|
299
|
-
name: z
|
|
300
|
-
.string()
|
|
301
|
-
.min(1)
|
|
302
|
-
.max(32)
|
|
303
|
-
.describe("Human-readable label for the key"),
|
|
304
|
-
access: z
|
|
305
|
-
.enum(["read", "write"])
|
|
306
|
-
.describe("Access level: read-only or read-write"),
|
|
307
|
-
}, async ({ workspaceId, name, access }) => {
|
|
308
|
-
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/api-keys`, {
|
|
309
|
-
method: "POST",
|
|
310
|
-
body: JSON.stringify({ name, access }),
|
|
311
|
-
});
|
|
312
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
313
|
-
});
|
|
314
|
-
server.tool("revoke_api_key", "Revoke (delete) an API key by ID.", {
|
|
315
|
-
workspaceId: z.string().describe("Workspace ID"),
|
|
316
|
-
keyId: z.string().describe("API key ID to revoke"),
|
|
317
|
-
}, async ({ workspaceId, keyId }) => {
|
|
318
|
-
await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/api-keys/${encodeURIComponent(keyId)}`, { method: "DELETE" });
|
|
319
|
-
return { content: [{ type: "text", text: "API key revoked." }] };
|
|
320
|
-
});
|
|
321
226
|
// ── Tools: Assets ───────────────────────────────────────────────────────────
|
|
322
|
-
server.tool("list_assets", "List assets
|
|
227
|
+
server.tool("list_assets", "List one page of ready assets and visibility, optionally filtered by prefix. Pass nextCursor as cursor to continue; null means the end.", {
|
|
323
228
|
workspaceId: z.string().describe("Workspace ID"),
|
|
324
229
|
prefix: z
|
|
325
230
|
.string()
|
|
326
231
|
.optional()
|
|
327
232
|
.describe("Prefix filter (e.g. 'icons/' to list only that folder)"),
|
|
328
|
-
|
|
329
|
-
|
|
233
|
+
cursor: z
|
|
234
|
+
.string()
|
|
235
|
+
.max(255)
|
|
236
|
+
.optional()
|
|
237
|
+
.describe("nextCursor from the previous page"),
|
|
238
|
+
limit: z
|
|
239
|
+
.number()
|
|
240
|
+
.int()
|
|
241
|
+
.min(1)
|
|
242
|
+
.max(500)
|
|
243
|
+
.optional()
|
|
244
|
+
.describe("Page size, default 100"),
|
|
245
|
+
}, async ({ workspaceId, prefix = "", cursor, limit = 100 }) => {
|
|
246
|
+
const query = new URLSearchParams({ prefix, limit: String(limit) });
|
|
247
|
+
if (cursor !== undefined)
|
|
248
|
+
query.set("cursor", cursor);
|
|
249
|
+
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/sync/assets?${query}`);
|
|
330
250
|
return {
|
|
331
|
-
content: [{ type: "text", text: JSON.stringify(data
|
|
251
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
332
252
|
};
|
|
333
253
|
});
|
|
334
|
-
server.tool("create_asset", "Create a
|
|
254
|
+
server.tool("create_asset", "Create a private asset record with a key. Use upload_asset_version to attach media, then set_asset_visibility to publish explicitly.", {
|
|
335
255
|
workspaceId: z.string().describe("Workspace ID"),
|
|
336
256
|
key: z
|
|
337
257
|
.string()
|
|
@@ -340,7 +260,20 @@ server.tool("create_asset", "Create a new asset record with a key. Use upload_ve
|
|
|
340
260
|
.describe("Asset key (use / for folder structure)"),
|
|
341
261
|
displayName: z.string().min(1).describe("Human-readable filename"),
|
|
342
262
|
}, async ({ workspaceId, key, displayName }) => {
|
|
343
|
-
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/assets`, {
|
|
263
|
+
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/assets`, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
body: JSON.stringify({ key, displayName }),
|
|
266
|
+
});
|
|
267
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
268
|
+
});
|
|
269
|
+
server.tool("set_asset_visibility", "Set asset visibility with publish permission. Public exposes every ready version and variant. Private requires workspace read access but cannot revoke downloaded or cached copies. Vard visibility is independent.", {
|
|
270
|
+
workspaceId: z.string().min(1).describe("Workspace ID"),
|
|
271
|
+
assetId: z.string().uuid().describe("Asset ID"),
|
|
272
|
+
isPublic: z
|
|
273
|
+
.boolean()
|
|
274
|
+
.describe("True to publish; false to require workspace read access"),
|
|
275
|
+
}, async ({ workspaceId, assetId, isPublic }) => {
|
|
276
|
+
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/assets/${encodeURIComponent(assetId)}/visibility`, { method: "PATCH", body: JSON.stringify({ isPublic }) });
|
|
344
277
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
345
278
|
});
|
|
346
279
|
server.tool("delete_asset", "Delete an asset and all its versions permanently.", {
|
|
@@ -398,42 +331,69 @@ server.tool("list_vards", "List all Vards in a workspace.", { workspaceId: z.str
|
|
|
398
331
|
content: [{ type: "text", text: JSON.stringify(data.vards, null, 2) }],
|
|
399
332
|
};
|
|
400
333
|
});
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
334
|
+
const folderRulesInput = z
|
|
335
|
+
.array(z
|
|
336
|
+
.object({
|
|
337
|
+
sourcePrefix: z
|
|
338
|
+
.string()
|
|
339
|
+
.max(255)
|
|
340
|
+
.describe("Literal recursive source directory, slash-terminated; empty means workspace root"),
|
|
341
|
+
outputPrefix: z
|
|
405
342
|
.string()
|
|
406
|
-
.
|
|
407
|
-
.describe("
|
|
343
|
+
.max(255)
|
|
344
|
+
.describe("Relative destination directory, slash-terminated; empty means project root"),
|
|
345
|
+
})
|
|
346
|
+
.strict())
|
|
347
|
+
.max(64)
|
|
348
|
+
.describe("Complete folder rule list; [] removes all rules. API validates safe prefixes. Ready current originals only, no globs; 1000 combined outputs maximum.");
|
|
349
|
+
server.tool("create_vard", "Create a Vard with optional recursive folder rules, private by default. Rules generate read-only originals separately from manual items.", {
|
|
350
|
+
workspaceId: z.string().describe("Workspace ID"),
|
|
351
|
+
name: z.string().min(1).max(80).describe("Display name"),
|
|
408
352
|
isPublic: z
|
|
409
353
|
.boolean()
|
|
410
354
|
.optional()
|
|
411
|
-
.describe("
|
|
412
|
-
|
|
355
|
+
.describe("Public manifest; every resolved asset must also be public for anonymous access"),
|
|
356
|
+
folderRules: folderRulesInput.optional(),
|
|
357
|
+
}, async ({ workspaceId, name, isPublic, folderRules }) => {
|
|
358
|
+
const body = { name };
|
|
359
|
+
if (isPublic !== undefined)
|
|
360
|
+
body.isPublic = isPublic;
|
|
361
|
+
if (folderRules !== undefined)
|
|
362
|
+
body.folderRules = folderRules;
|
|
413
363
|
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/vards`, {
|
|
414
364
|
method: "POST",
|
|
415
|
-
body: JSON.stringify(
|
|
365
|
+
body: JSON.stringify(body),
|
|
416
366
|
});
|
|
417
367
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
418
368
|
});
|
|
419
|
-
server.tool("get_vard", "Retrieve
|
|
369
|
+
server.tool("get_vard", "Retrieve editable Vard configuration (manual items and folderRules), even when expansion fails. Set resolved=true for a separate live manifest preview, not a committed frozen snapshot. Never write resolved items back as manual items.", {
|
|
420
370
|
workspaceId: z.string().describe("Workspace ID"),
|
|
421
371
|
vardId: z.string().describe("Vard ID"),
|
|
422
|
-
|
|
423
|
-
|
|
372
|
+
resolved: z
|
|
373
|
+
.boolean()
|
|
374
|
+
.optional()
|
|
375
|
+
.describe("True: resolve current outputs, which may fail on collisions/limits. Omitted/false: editable configuration only."),
|
|
376
|
+
}, async ({ workspaceId, vardId, resolved }) => {
|
|
377
|
+
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/vards/${encodeURIComponent(vardId)}${resolved ? "/manifest" : ""}`);
|
|
424
378
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
425
379
|
});
|
|
426
|
-
server.tool("update_vard", "Update
|
|
380
|
+
server.tool("update_vard", "Update only supplied metadata or folderRules; omitted fields and manual item IDs are preserved. folderRules: [] removes rules. Saving valid configuration may still leave a collision/limit error in the live preview; repair rules or manual paths. Requires API update permission.", {
|
|
427
381
|
workspaceId: z.string().describe("Workspace ID"),
|
|
428
382
|
vardId: z.string().describe("Vard ID"),
|
|
429
|
-
name: z.string().min(1).max(
|
|
430
|
-
isPublic: z
|
|
431
|
-
|
|
383
|
+
name: z.string().min(1).max(80).optional().describe("New display name"),
|
|
384
|
+
isPublic: z
|
|
385
|
+
.boolean()
|
|
386
|
+
.optional()
|
|
387
|
+
.describe("Public manifest only; does not publish resolved assets"),
|
|
388
|
+
folderRules: folderRulesInput.optional(),
|
|
389
|
+
}, async ({ workspaceId, vardId, name, isPublic, folderRules }) => {
|
|
432
390
|
const body = {};
|
|
433
391
|
if (name !== undefined)
|
|
434
392
|
body.name = name;
|
|
435
393
|
if (isPublic !== undefined)
|
|
436
394
|
body.isPublic = isPublic;
|
|
395
|
+
if (folderRules !== undefined)
|
|
396
|
+
body.folderRules = folderRules;
|
|
437
397
|
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/vards/${encodeURIComponent(vardId)}`, { method: "PATCH", body: JSON.stringify(body) });
|
|
438
398
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
439
399
|
});
|
|
@@ -444,11 +404,12 @@ server.tool("delete_vard", "Delete a Vard and all its output configurations perm
|
|
|
444
404
|
await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/vards/${encodeURIComponent(vardId)}`, { method: "DELETE" });
|
|
445
405
|
return { content: [{ type: "text", text: "Vard deleted." }] };
|
|
446
406
|
});
|
|
447
|
-
server.tool("replace_vard_items", "Replace
|
|
407
|
+
server.tool("replace_vard_items", "Replace only manual items in a Vard; folderRules are unchanged. Never pass expanded folder outputs here. Each manual item maps an asset to an output path and optional transformation settings.", {
|
|
448
408
|
workspaceId: z.string().describe("Workspace ID"),
|
|
449
409
|
vardId: z.string().describe("Vard ID"),
|
|
450
410
|
items: z
|
|
451
|
-
.array(z
|
|
411
|
+
.array(z
|
|
412
|
+
.object({
|
|
452
413
|
assetId: z.string().describe("The asset ID to include"),
|
|
453
414
|
outputPath: z
|
|
454
415
|
.string()
|
|
@@ -456,38 +417,23 @@ server.tool("replace_vard_items", "Replace every item in a Vard with a new set o
|
|
|
456
417
|
writable: z
|
|
457
418
|
.boolean()
|
|
458
419
|
.describe("Whether this output can be pushed back"),
|
|
459
|
-
|
|
460
|
-
.
|
|
461
|
-
.
|
|
420
|
+
versionId: z
|
|
421
|
+
.string()
|
|
422
|
+
.uuid()
|
|
423
|
+
.nullable()
|
|
424
|
+
.optional()
|
|
425
|
+
.describe("Exact ready version ID to pin; null/omitted follows latest. Pinned outputs must not be writable."),
|
|
462
426
|
transformation: z
|
|
463
427
|
.record(z.unknown())
|
|
464
428
|
.nullable()
|
|
465
429
|
.describe("Optional transformation parameters (w, h, fit, format, q) or null"),
|
|
466
|
-
})
|
|
467
|
-
.
|
|
430
|
+
})
|
|
431
|
+
.strict())
|
|
432
|
+
.describe("Complete list of manual items to set (replaces manual items only)"),
|
|
468
433
|
}, async ({ workspaceId, vardId, items }) => {
|
|
469
434
|
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/vards/${encodeURIComponent(vardId)}/items`, { method: "PUT", body: JSON.stringify({ items }) });
|
|
470
435
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
471
436
|
});
|
|
472
|
-
// ── Domain ─────────────────────────────────────────────────────────────────
|
|
473
|
-
server.tool("get_domain", "Get the current custom domain configuration for a workspace.", { workspaceId: z.string().describe("Workspace ID") }, async ({ workspaceId }) => {
|
|
474
|
-
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/domain`);
|
|
475
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
476
|
-
});
|
|
477
|
-
server.tool("configure_domain", "Configure a custom domain for a workspace. Requires DNS verification (TXT + CNAME).", {
|
|
478
|
-
workspaceId: z.string().describe("Workspace ID"),
|
|
479
|
-
domain: z.string().describe("Domain name (e.g. assets.example.com)"),
|
|
480
|
-
}, async ({ workspaceId, domain }) => {
|
|
481
|
-
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/domain`, {
|
|
482
|
-
method: "PUT",
|
|
483
|
-
body: JSON.stringify({ domain }),
|
|
484
|
-
});
|
|
485
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
486
|
-
});
|
|
487
|
-
server.tool("verify_domain", "Trigger domain verification. The agent should first read the current domain config to show the required DNS records, then call this once they are in place.", { workspaceId: z.string().describe("Workspace ID") }, async ({ workspaceId }) => {
|
|
488
|
-
const data = await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/domain/verify`, { method: "POST" });
|
|
489
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
490
|
-
});
|
|
491
437
|
// ── Main ────────────────────────────────────────────────────────────────────
|
|
492
438
|
async function main() {
|
|
493
439
|
if (!API_KEY) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vardum/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Vardum
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "MCP server for workspace-scoped Vardum assets and Vards",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -14,21 +14,30 @@
|
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=22"
|
|
16
16
|
},
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "tsc -p tsconfig.json",
|
|
19
|
-
"prepublishOnly": "pnpm build",
|
|
20
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
21
|
-
},
|
|
22
17
|
"publishConfig": {
|
|
23
18
|
"access": "public"
|
|
24
19
|
},
|
|
25
20
|
"dependencies": {
|
|
26
|
-
"@modelcontextprotocol/sdk": "
|
|
27
|
-
"@vardum/shared": "
|
|
21
|
+
"@modelcontextprotocol/sdk": "1.31.0",
|
|
22
|
+
"@vardum/shared": "^0.3.0",
|
|
28
23
|
"zod": "^3.24.0"
|
|
29
24
|
},
|
|
30
25
|
"devDependencies": {
|
|
31
26
|
"@types/node": "^26.2.0",
|
|
32
27
|
"typescript": "5.9.3"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/f-tuma/vardum.git",
|
|
32
|
+
"directory": "apps/mcp"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/f-tuma/vardum/tree/main/apps/mcp#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/f-tuma/vardum/issues"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.json",
|
|
40
|
+
"test": "node --import tsx --test test/*.test.ts",
|
|
41
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
33
42
|
}
|
|
34
|
-
}
|
|
43
|
+
}
|