@aistoragedepot/mcp 0.5.0 → 0.5.3
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 +1 -1
- package/index.mjs +39 -4
- package/package.json +2 -1
- package/pull.mjs +23 -11
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ your library as **native** slash-commands, sync it to command files — for ever
|
|
|
56
56
|
use, not just Claude:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
npx -y @aistoragedepot/mcp pull --token=aisd_your_token --to=all
|
|
59
|
+
npx -y -p @aistoragedepot/mcp aistoragedepot-mcp pull --token=aisd_your_token --to=all
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
| Target | Writes to | Commands appear as |
|
package/index.mjs
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
|
|
24
24
|
const BASE_URL = (process.env.AISD_BASE_URL || "https://www.aistoragedepot.com").replace(/\/+$/, "");
|
|
25
25
|
|
|
26
|
-
// CLI mode: `aistoragedepot
|
|
26
|
+
// CLI mode: `npx -y @aistoragedepot/mcp pull` syncs the library to native slash-command files, then
|
|
27
27
|
// exits. The brief settle delay lets undici finish closing its sockets (Connection: close)
|
|
28
28
|
// before process.exit — a hard exit mid-teardown trips a libuv assert on Windows (Node 24)
|
|
29
29
|
// that prints a scary-but-harmless message after the sync succeeds.
|
|
@@ -52,7 +52,12 @@ async function api(path) {
|
|
|
52
52
|
headers: { Authorization: `Bearer ${TOKEN}`, Accept: "application/json" },
|
|
53
53
|
});
|
|
54
54
|
if (res.status === 401) throw new Error("AIStorageDepot rejected the token (401) — check AISD_TOKEN.");
|
|
55
|
-
if (!res.ok)
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
// Surface the server's own message when it sends one (e.g. the free-plan
|
|
57
|
+
// monthly pull limit returns 402 with an upgrade note) instead of a bare code.
|
|
58
|
+
const detail = await res.json().then((d) => d?.error).catch(() => null);
|
|
59
|
+
throw new Error(detail || `AIStorageDepot ${path} → HTTP ${res.status}`);
|
|
60
|
+
}
|
|
56
61
|
return res.json();
|
|
57
62
|
}
|
|
58
63
|
|
|
@@ -140,6 +145,34 @@ function fillBody(body, placeholders, args) {
|
|
|
140
145
|
return out;
|
|
141
146
|
}
|
|
142
147
|
|
|
148
|
+
// Which MCP arg-names are required (their [placeholder] was marked required by the author).
|
|
149
|
+
function requiredArgNames(placeholders, requiredFields = []) {
|
|
150
|
+
const req = new Set(requiredFields);
|
|
151
|
+
const out = new Set();
|
|
152
|
+
for (const [argName, original] of argMap(placeholders)) if (req.has(original)) out.add(argName);
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// A "stop and ask first" instruction for any required field missing from the args — prepended to the
|
|
157
|
+
// prompt so even clients that ignore `required` don't run half-blank. "" when nothing's missing.
|
|
158
|
+
function missingRequiredPreamble(placeholders, requiredFields = [], args = {}) {
|
|
159
|
+
const req = new Set(requiredFields);
|
|
160
|
+
const missing = [];
|
|
161
|
+
for (const [argName, original] of argMap(placeholders)) {
|
|
162
|
+
if (!req.has(original)) continue;
|
|
163
|
+
const v = args?.[argName];
|
|
164
|
+
if (v == null || v === "") missing.push(original);
|
|
165
|
+
}
|
|
166
|
+
if (!missing.length) return "";
|
|
167
|
+
const list = missing.map((f) => `[${f}]`).join(", ");
|
|
168
|
+
const one = missing.length === 1;
|
|
169
|
+
return (
|
|
170
|
+
`⚠ Before doing anything else, ask me for a value for ${one ? "this field" : "these fields"}: ${list}. ` +
|
|
171
|
+
`Do not explore, run, generate, or take any action until I answer. Then substitute my ${one ? "answer" : "answers"} ` +
|
|
172
|
+
`for ${one ? "that field" : "those fields"} and carry out the prompt below.\n\n`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
143
176
|
const server = new Server(
|
|
144
177
|
{ name: "aistoragedepot", version: "0.5.0" },
|
|
145
178
|
{ capabilities: { resources: {}, prompts: {}, tools: {} } },
|
|
@@ -172,13 +205,14 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
|
172
205
|
const prompts = [];
|
|
173
206
|
for (const [name, it] of scopedPromptIndex(await snapshot())) {
|
|
174
207
|
const am = argMap(it.placeholders);
|
|
208
|
+
const req = requiredArgNames(it.placeholders, it.requiredFields);
|
|
175
209
|
prompts.push({
|
|
176
210
|
name,
|
|
177
211
|
description: `${it.type?.name || "Prompt"}: “${it.title}” (AIStorageDepot)`,
|
|
178
212
|
arguments: [...am.keys()].map((a) => ({
|
|
179
213
|
name: a,
|
|
180
214
|
description: `Value for [${am.get(a)}]`,
|
|
181
|
-
required:
|
|
215
|
+
required: req.has(a),
|
|
182
216
|
})),
|
|
183
217
|
});
|
|
184
218
|
}
|
|
@@ -188,7 +222,8 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
|
188
222
|
server.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
189
223
|
const it = scopedPromptIndex(await snapshot()).get(req.params.name);
|
|
190
224
|
if (!it) throw new Error(`Unknown prompt: ${req.params.name}`);
|
|
191
|
-
const
|
|
225
|
+
const args = req.params.arguments || {};
|
|
226
|
+
const text = missingRequiredPreamble(it.placeholders, it.requiredFields, args) + fillBody(it.body, it.placeholders, args);
|
|
192
227
|
return {
|
|
193
228
|
description: it.title,
|
|
194
229
|
messages: [{ role: "user", content: { type: "text", text } }],
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aistoragedepot/mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Model Context Protocol server for your AIStorageDepot library — exposes your prompts, rules, docs, and skills to MCP-aware AI clients (Claude, Cursor, Cline, …).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
+
"mcp": "index.mjs",
|
|
7
8
|
"aistoragedepot-mcp": "index.mjs"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
package/pull.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// `aistoragedepot
|
|
1
|
+
// `npx @aistoragedepot/mcp pull` — sync your library into native slash-command files for the
|
|
2
2
|
// AI tools you use.
|
|
3
3
|
//
|
|
4
4
|
// Every major client now reads commands from a folder of markdown files; only the folder
|
|
@@ -52,14 +52,22 @@ function fmFile(fm, body) {
|
|
|
52
52
|
return ["---", ...Object.entries(fm).map(([k, v]) => `${k}: "${yq(v)}"`), "---", "", String(body).trim(), ""].join("\n");
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Required fields the author marked (intersected with the fields actually in this body).
|
|
56
|
+
const reqOf = (it, fields) => (it.requiredFields || []).filter((f) => fields.includes(f));
|
|
57
|
+
// A forceful "these are required — ask first" clause, or the gentle version when none are required.
|
|
58
|
+
const askClause = (required) =>
|
|
59
|
+
required.length
|
|
60
|
+
? ` ${required.length === 1 ? "This field is" : "These fields are"} REQUIRED: ${required.map((f) => `[${f}]`).join(", ")} — if a value wasn't provided, STOP and ask me first; don't explore, run, or generate anything until I answer.`
|
|
61
|
+
: ` Ask me for any value that's missing.`;
|
|
62
|
+
|
|
55
63
|
// Tail for tools that substitute $ARGUMENTS (claude, codex).
|
|
56
|
-
const argsTail = (fields) =>
|
|
64
|
+
const argsTail = (fields, required = []) =>
|
|
57
65
|
fields.length
|
|
58
|
-
? `\n\nInput: $ARGUMENTS\n\nFill the bracketed ${fields.length === 1 ? "field" : "fields"} in the prompt above from the input
|
|
66
|
+
? `\n\nInput: $ARGUMENTS\n\nFill the bracketed ${fields.length === 1 ? "field" : "fields"} in the prompt above from the input.${askClause(required)} Then carry out the prompt.`
|
|
59
67
|
: "\n\n$ARGUMENTS";
|
|
60
68
|
// Tail for tools that just pass the typed text along with the inserted prompt.
|
|
61
|
-
const fillTail = (fields) =>
|
|
62
|
-
`\n\nFill the bracketed ${fields.length === 1 ? "field" : "fields"} (${fields.map((f) => `[${f}]`).join(", ")}) from anything I typed along with this command
|
|
69
|
+
const fillTail = (fields, required = []) =>
|
|
70
|
+
`\n\nFill the bracketed ${fields.length === 1 ? "field" : "fields"} (${fields.map((f) => `[${f}]`).join(", ")}) from anything I typed along with this command.${askClause(required)} Then carry out the prompt.`;
|
|
63
71
|
|
|
64
72
|
// ── per-target renderers ─────────────────────────────────────────────────────
|
|
65
73
|
|
|
@@ -72,7 +80,7 @@ function renderClaude(it) {
|
|
|
72
80
|
const fields = fieldsOf(content);
|
|
73
81
|
const fm = { description: `Prompt — ${it.title} (AIStorageDepot)` };
|
|
74
82
|
if (fields.length) fm["argument-hint"] = fields.map((f) => `[${f}]`).join(" ");
|
|
75
|
-
return fmFile(fm, content.trim() + argsTail(fields));
|
|
83
|
+
return fmFile(fm, content.trim() + argsTail(fields, reqOf(it, fields)));
|
|
76
84
|
}
|
|
77
85
|
if (/^---\r?\n/.test(body)) return body.endsWith("\n") ? body : `${body}\n`;
|
|
78
86
|
return fmFile({ description: `${it.title} (AIStorageDepot)` }, body);
|
|
@@ -85,7 +93,7 @@ function renderCodex(it) {
|
|
|
85
93
|
const fields = isPromptItem(it) ? fieldsOf(content) : [];
|
|
86
94
|
const fm = { description: descFor(it, meta) };
|
|
87
95
|
if (isPromptItem(it) && fields.length) fm["argument-hint"] = fields.map((f) => `[${f}]`).join(" ");
|
|
88
|
-
return fmFile(fm, content.trim() + (isPromptItem(it) ? argsTail(fields) : ""));
|
|
96
|
+
return fmFile(fm, content.trim() + (isPromptItem(it) ? argsTail(fields, reqOf(it, fields)) : ""));
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
// VS Code prompt files: description + argument-hint frontmatter; typed extras arrive as
|
|
@@ -95,21 +103,21 @@ function renderVscode(it) {
|
|
|
95
103
|
const fields = isPromptItem(it) ? fieldsOf(content) : [];
|
|
96
104
|
const fm = { description: descFor(it, meta) };
|
|
97
105
|
if (fields.length) fm["argument-hint"] = fields.map((f) => `[${f}]`).join(" ");
|
|
98
|
-
return fmFile(fm, content.trim() + (fields.length ? fillTail(fields) : ""));
|
|
106
|
+
return fmFile(fm, content.trim() + (fields.length ? fillTail(fields, reqOf(it, fields)) : ""));
|
|
99
107
|
}
|
|
100
108
|
|
|
101
109
|
// Windsurf workflows: description frontmatter; 12k char cap per file (enforced by caller).
|
|
102
110
|
function renderWindsurf(it) {
|
|
103
111
|
const { meta, content } = splitFrontmatter(it.body || "");
|
|
104
112
|
const fields = isPromptItem(it) ? fieldsOf(content) : [];
|
|
105
|
-
return fmFile({ description: descFor(it, meta) }, content.trim() + (fields.length ? fillTail(fields) : ""));
|
|
113
|
+
return fmFile({ description: descFor(it, meta) }, content.trim() + (fields.length ? fillTail(fields, reqOf(it, fields)) : ""));
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
// Cursor commands: plain markdown — no frontmatter dialect; the body IS the prompt.
|
|
109
117
|
function renderCursor(it) {
|
|
110
118
|
const { content } = splitFrontmatter(it.body || "");
|
|
111
119
|
const fields = isPromptItem(it) ? fieldsOf(content) : [];
|
|
112
|
-
return content.trim() + (fields.length ? fillTail(fields) : "") + "\n";
|
|
120
|
+
return content.trim() + (fields.length ? fillTail(fields, reqOf(it, fields)) : "") + "\n";
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
// ── targets ──────────────────────────────────────────────────────────────────
|
|
@@ -196,7 +204,11 @@ export async function pull({ BASE_URL, argv }) {
|
|
|
196
204
|
headers: { Authorization: `Bearer ${token}`, Accept: "application/json", Connection: "close" },
|
|
197
205
|
});
|
|
198
206
|
if (r.status === 401) throw new Error("Token rejected (401) — check it in Settings → API tokens.");
|
|
199
|
-
if (!r.ok)
|
|
207
|
+
if (!r.ok) {
|
|
208
|
+
// Prefer the server's message (e.g. the free-plan pull limit's 402 upgrade note).
|
|
209
|
+
const detail = await r.json().then((d) => d?.error).catch(() => null);
|
|
210
|
+
throw new Error(detail || `${p} → HTTP ${r.status}`);
|
|
211
|
+
}
|
|
200
212
|
return r.json();
|
|
201
213
|
};
|
|
202
214
|
|