impreza-mcp 0.6.0 → 0.7.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 +25 -1
- package/dist/server.js +137 -1
- package/dist/server.js.map +1 -1
- package/dist/ui-assets.js +2455 -2455
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ ticket away from being linked to a legal name.
|
|
|
39
39
|
|
|
40
40
|
## Status
|
|
41
41
|
|
|
42
|
-
**Full surface live.** All
|
|
42
|
+
**Full surface live.** All 102 tools shipped — app deployment plus account +
|
|
43
43
|
crypto balance, catalog + ordering, domains/DNS + registration, invoices, VPS
|
|
44
44
|
lifecycle with snapshots and backups, dedicated / bare-metal servers, plan
|
|
45
45
|
upgrades, and Titan / Google Workspace mailboxes — with a setup wizard that
|
|
@@ -48,6 +48,30 @@ generates ready-to-paste config snippets for 5 AI tools.
|
|
|
48
48
|
The local (`npx`) server and the hosted OAuth connector expose the **same 97
|
|
49
49
|
tools**, so nothing is lost by picking either path.
|
|
50
50
|
|
|
51
|
+
### New in 0.6.1
|
|
52
|
+
|
|
53
|
+
**`tools/list` now reflects what your account actually owns.** About a third of
|
|
54
|
+
the tools only make sense if you have the machine behind them — VPS power
|
|
55
|
+
controls with no VPS can only ever answer "not found" — so those are left out
|
|
56
|
+
of the listing until you own one. Typical accounts see around 70 tools instead
|
|
57
|
+
of 97, which is roughly six thousand fewer tokens of context spent before you
|
|
58
|
+
ask anything.
|
|
59
|
+
|
|
60
|
+
Three things worth knowing about how it behaves:
|
|
61
|
+
|
|
62
|
+
- **The purchase path is never filtered.** An account that owns nothing is the
|
|
63
|
+
one that needs to buy something, so ordering, top-up, invoices and the
|
|
64
|
+
catalogue are always listed.
|
|
65
|
+
- **Buying something grows the list mid-session.** The server sends
|
|
66
|
+
`notifications/tools/list_changed` on the same response as the order, so a
|
|
67
|
+
client that honours it picks up the new tools without reconnecting.
|
|
68
|
+
- **It fails open.** If this server cannot reach the API to ask, it lists
|
|
69
|
+
everything rather than guess.
|
|
70
|
+
|
|
71
|
+
Hiding a tool is not an authorization boundary — the API still refuses anything
|
|
72
|
+
your account does not own. This only stops the listing from carrying tools that
|
|
73
|
+
could never work for you.
|
|
74
|
+
|
|
51
75
|
### New in 0.6.0
|
|
52
76
|
|
|
53
77
|
Four things that only make sense on a host built for anonymity:
|
package/dist/server.js
CHANGED
|
@@ -214,6 +214,68 @@ const TOOLS = [
|
|
|
214
214
|
additionalProperties: false,
|
|
215
215
|
},
|
|
216
216
|
},
|
|
217
|
+
{
|
|
218
|
+
name: 'impreza_backup_app',
|
|
219
|
+
description: "Back up a running deployment's data into the customer's OWN Impreza S3 bucket. Packs the app's data directory and uploads it in chunks with a per-chunk SHA-256 manifest written alongside it, so the backup stays verifiable with the customer's own S3 credentials and without us. Returns immediately with a backup_id — a large app takes minutes, so poll impreza_list_backups for the outcome. Needs an active S3 service on the account; without one it answers NO_STORAGE. One job at a time per app.",
|
|
220
|
+
inputSchema: {
|
|
221
|
+
type: 'object',
|
|
222
|
+
properties: {
|
|
223
|
+
deployment_id: { type: 'string', description: 'The dpl_... id to back up.' },
|
|
224
|
+
},
|
|
225
|
+
required: ['deployment_id'],
|
|
226
|
+
additionalProperties: false,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'impreza_list_backups',
|
|
231
|
+
description: "List this account's app backups and restores, newest first — id, kind, status, chunk count, size, and any error. This is how you find out whether a job started with impreza_backup_app or impreza_restore_app has finished. A `replaced_path` on a restore means the data it displaced is STILL on the server using disk until impreza_discard_replaced removes it. Pass deployment_id to narrow to one app. Not the same as impreza_vps_list_backups, which is Proxmox snapshots of a whole VPS.",
|
|
232
|
+
inputSchema: {
|
|
233
|
+
type: 'object',
|
|
234
|
+
properties: {
|
|
235
|
+
deployment_id: { type: 'string', description: 'Optional — only this app\'s jobs.' },
|
|
236
|
+
},
|
|
237
|
+
additionalProperties: false,
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: 'impreza_restore_app',
|
|
242
|
+
description: "Restore a completed backup over its app's current data, then restart the app. Every chunk's SHA-256 is checked BEFORE anything is touched, and the data being replaced is moved aside rather than deleted — so restoring the wrong backup is recoverable. That saved copy keeps using disk until impreza_discard_replaced removes it. Returns a job id; poll impreza_list_backups. This overwrites the app's current state: confirm which backup the customer means before calling.",
|
|
243
|
+
inputSchema: {
|
|
244
|
+
type: 'object',
|
|
245
|
+
properties: {
|
|
246
|
+
backup_id: { type: 'string', description: 'The completed backup to restore, from impreza_list_backups.' },
|
|
247
|
+
},
|
|
248
|
+
required: ['backup_id'],
|
|
249
|
+
additionalProperties: false,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'impreza_discard_replaced',
|
|
254
|
+
description: "Permanently delete the copy of the old data that a restore set aside, freeing that disk on the server. Pass the RESTORE job's id — the one whose replaced_path is set. Irreversible, and that copy is the customer's way back from a restore they may not have wanted, so only call it once they confirm the restored app is the one they want.",
|
|
255
|
+
inputSchema: {
|
|
256
|
+
type: 'object',
|
|
257
|
+
properties: {
|
|
258
|
+
backup_id: { type: 'string', description: 'The restore job that set data aside.' },
|
|
259
|
+
},
|
|
260
|
+
required: ['backup_id'],
|
|
261
|
+
additionalProperties: false,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: 'impreza_backup_schedule',
|
|
266
|
+
description: "Change how often an app is backed up automatically and how many copies are kept. Every running app is already on a schedule by DEFAULT — daily, keeping 3 — even with nothing configured, so this changes an existing policy rather than creating one; to read the current setting, call impreza_list_backups with that deployment_id. Set enabled=false to stop automatic backups for the app (manual ones still work). Old copies are removed only after a NEWER one has completed and been verified, and only automatic copies are ever pruned — a backup taken by hand stays until someone deletes it.",
|
|
267
|
+
inputSchema: {
|
|
268
|
+
type: 'object',
|
|
269
|
+
properties: {
|
|
270
|
+
deployment_id: { type: 'string', description: 'The dpl_... id whose schedule changes.' },
|
|
271
|
+
enabled: { type: 'boolean', description: 'Whether automatic backups run at all. Default true.' },
|
|
272
|
+
frequency: { type: 'string', enum: ['daily', 'weekly'], description: 'How often. Default daily.' },
|
|
273
|
+
keep: { type: 'integer', description: 'How many automatic copies to keep, 1-30. Default 3.' },
|
|
274
|
+
},
|
|
275
|
+
required: ['deployment_id'],
|
|
276
|
+
additionalProperties: false,
|
|
277
|
+
},
|
|
278
|
+
},
|
|
217
279
|
{
|
|
218
280
|
name: 'impreza_restart_deployment',
|
|
219
281
|
description: 'Restart a deployment\'s docker-compose stack (non-destructive). The container is stopped + started; data volumes preserved. Status flips to `installing` briefly then back to `running`. Works for both catalog and custom deployments.',
|
|
@@ -1616,6 +1678,11 @@ const TOOL_ANNOTATIONS = {
|
|
|
1616
1678
|
// since HEAD moves under it.
|
|
1617
1679
|
impreza_redeploy_deployment: A_WRITE_EXT,
|
|
1618
1680
|
impreza_restart_deployment: A_WRITE_IDEM,
|
|
1681
|
+
impreza_backup_app: A_WRITE_EXT,
|
|
1682
|
+
impreza_list_backups: A_READ,
|
|
1683
|
+
impreza_restore_app: A_DESTRUCTIVE,
|
|
1684
|
+
impreza_discard_replaced: A_DESTRUCTIVE,
|
|
1685
|
+
impreza_backup_schedule: A_WRITE_IDEM,
|
|
1619
1686
|
impreza_change_domain: A_WRITE_EXT_IDEM,
|
|
1620
1687
|
impreza_add_onion: A_WRITE_EXT_IDEM,
|
|
1621
1688
|
// Both touch the hook on GitHub; disconnect documents itself as idempotent
|
|
@@ -1871,9 +1938,45 @@ const ANNOTATED_TOOLS = TOOLS.map((tool) => {
|
|
|
1871
1938
|
throw new Error(`Tool ${tool.name} has no entry in TOOL_ANNOTATIONS`);
|
|
1872
1939
|
return { ...tool, annotations };
|
|
1873
1940
|
});
|
|
1941
|
+
/**
|
|
1942
|
+
* Which tools this account should not be shown.
|
|
1943
|
+
*
|
|
1944
|
+
* The hosted server filters `tools/list` by what the account owns — about 42
|
|
1945
|
+
* of the tools only make sense if you have the product behind them, and an
|
|
1946
|
+
* account with one VPS was carrying twenty-five that could only ever answer
|
|
1947
|
+
* "not found".
|
|
1948
|
+
*
|
|
1949
|
+
* This process has no database, so it asks for the ANSWER rather than the
|
|
1950
|
+
* rules. A copy of the prefix-to-family map here would drift the first time a
|
|
1951
|
+
* family is added server-side, and would need an npm release to catch up.
|
|
1952
|
+
*
|
|
1953
|
+
* Fails open in every direction — a request that errors, times out, or comes
|
|
1954
|
+
* back malformed hides nothing. Too many tools is the behaviour we shipped
|
|
1955
|
+
* yesterday; too few silently removes capability the customer pays for, and
|
|
1956
|
+
* they would have no way to tell why.
|
|
1957
|
+
*/
|
|
1958
|
+
async function hiddenTools() {
|
|
1959
|
+
try {
|
|
1960
|
+
const res = await impreza.get('/v1/entitlements');
|
|
1961
|
+
if (res?.known !== true || !Array.isArray(res.hidden)) {
|
|
1962
|
+
return new Set();
|
|
1963
|
+
}
|
|
1964
|
+
return new Set(res.hidden.filter((n) => typeof n === 'string'));
|
|
1965
|
+
}
|
|
1966
|
+
catch {
|
|
1967
|
+
return new Set();
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1874
1970
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1875
1971
|
const offer = wantsUi();
|
|
1876
|
-
|
|
1972
|
+
// Fetched per call rather than cached for the process: a stdio session
|
|
1973
|
+
// outlives a purchase, and a customer who buys a VPS mid-session should see
|
|
1974
|
+
// its tools on the next listing rather than after a restart. tools/list is
|
|
1975
|
+
// called rarely enough that the extra round trip does not matter.
|
|
1976
|
+
const hidden = await hiddenTools();
|
|
1977
|
+
const tools = ANNOTATED_TOOLS
|
|
1978
|
+
.filter((tool) => !hidden.has(tool.name))
|
|
1979
|
+
.map((tool) => {
|
|
1877
1980
|
const uri = offer ? panelFor(tool.name) : undefined;
|
|
1878
1981
|
// The nested `ui.resourceUri`, not the flat `_meta["ui/resourceUri"]`:
|
|
1879
1982
|
// the spec deprecated the flat key and removes it before GA.
|
|
@@ -1940,6 +2043,39 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1940
2043
|
body.since_seconds = args.since_seconds;
|
|
1941
2044
|
return toResult(await impreza.post(`/v1/platform/deployments/${encodeURIComponent(dep)}/logs`, body));
|
|
1942
2045
|
}
|
|
2046
|
+
case 'impreza_backup_app': {
|
|
2047
|
+
const dep = String(args.deployment_id ?? '');
|
|
2048
|
+
if (!dep)
|
|
2049
|
+
return toError('deployment_id is required');
|
|
2050
|
+
return toResult(await impreza.post('/v1/backups', { deployment_id: dep }));
|
|
2051
|
+
}
|
|
2052
|
+
case 'impreza_backup_schedule': {
|
|
2053
|
+
const dep = String(args.deployment_id ?? '');
|
|
2054
|
+
if (!dep)
|
|
2055
|
+
return toError('deployment_id is required');
|
|
2056
|
+
const body = {};
|
|
2057
|
+
for (const k of ['enabled', 'frequency', 'keep']) {
|
|
2058
|
+
if (k in args)
|
|
2059
|
+
body[k] = args[k];
|
|
2060
|
+
}
|
|
2061
|
+
return toResult(await impreza.post(`/v1/backups/schedule/${encodeURIComponent(dep)}`, body));
|
|
2062
|
+
}
|
|
2063
|
+
case 'impreza_list_backups': {
|
|
2064
|
+
const dep = String(args.deployment_id ?? '');
|
|
2065
|
+
return toResult(await impreza.get(`/v1/backups${dep ? `?deployment_id=${encodeURIComponent(dep)}` : ''}`));
|
|
2066
|
+
}
|
|
2067
|
+
case 'impreza_restore_app': {
|
|
2068
|
+
const bid = String(args.backup_id ?? '');
|
|
2069
|
+
if (!bid)
|
|
2070
|
+
return toError('backup_id is required');
|
|
2071
|
+
return toResult(await impreza.post(`/v1/backups/${encodeURIComponent(bid)}/restore`, {}));
|
|
2072
|
+
}
|
|
2073
|
+
case 'impreza_discard_replaced': {
|
|
2074
|
+
const bid = String(args.backup_id ?? '');
|
|
2075
|
+
if (!bid)
|
|
2076
|
+
return toError('backup_id is required');
|
|
2077
|
+
return toResult(await impreza.post(`/v1/backups/${encodeURIComponent(bid)}/discard-previous`, {}));
|
|
2078
|
+
}
|
|
1943
2079
|
case 'impreza_restart_deployment': {
|
|
1944
2080
|
const dep = String(args.deployment_id ?? '');
|
|
1945
2081
|
if (!dep)
|