@aistoragedepot/mcp 0.5.2 → 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.
Files changed (3) hide show
  1. package/index.mjs +32 -2
  2. package/package.json +1 -1
  3. package/pull.mjs +17 -9
package/index.mjs CHANGED
@@ -145,6 +145,34 @@ function fillBody(body, placeholders, args) {
145
145
  return out;
146
146
  }
147
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
+
148
176
  const server = new Server(
149
177
  { name: "aistoragedepot", version: "0.5.0" },
150
178
  { capabilities: { resources: {}, prompts: {}, tools: {} } },
@@ -177,13 +205,14 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
177
205
  const prompts = [];
178
206
  for (const [name, it] of scopedPromptIndex(await snapshot())) {
179
207
  const am = argMap(it.placeholders);
208
+ const req = requiredArgNames(it.placeholders, it.requiredFields);
180
209
  prompts.push({
181
210
  name,
182
211
  description: `${it.type?.name || "Prompt"}: “${it.title}” (AIStorageDepot)`,
183
212
  arguments: [...am.keys()].map((a) => ({
184
213
  name: a,
185
214
  description: `Value for [${am.get(a)}]`,
186
- required: false,
215
+ required: req.has(a),
187
216
  })),
188
217
  });
189
218
  }
@@ -193,7 +222,8 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
193
222
  server.setRequestHandler(GetPromptRequestSchema, async (req) => {
194
223
  const it = scopedPromptIndex(await snapshot()).get(req.params.name);
195
224
  if (!it) throw new Error(`Unknown prompt: ${req.params.name}`);
196
- const text = fillBody(it.body, it.placeholders, req.params.arguments || {});
225
+ const args = req.params.arguments || {};
226
+ const text = missingRequiredPreamble(it.placeholders, it.requiredFields, args) + fillBody(it.body, it.placeholders, args);
197
227
  return {
198
228
  description: it.title,
199
229
  messages: [{ role: "user", content: { type: "text", text } }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aistoragedepot/mcp",
3
- "version": "0.5.2",
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": {
package/pull.mjs CHANGED
@@ -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 ask me for any value that's missing — then carry out the prompt.`
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 ask me for any value that's missing — then carry out the prompt.`;
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 ──────────────────────────────────────────────────────────────────