@aistoragedepot/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 +11 -3
- package/index.mjs +14 -4
- package/package.json +1 -1
- package/pull.mjs +52 -12
package/README.md
CHANGED
|
@@ -52,19 +52,27 @@ Your stored content shows up where you actually work:
|
|
|
52
52
|
## Slash-commands (`pull`)
|
|
53
53
|
|
|
54
54
|
Not every client turns MCP prompts into `/` slash-commands (some only use the tools). To get
|
|
55
|
-
your
|
|
55
|
+
your library as **native** slash-commands that always work, sync it to command files:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
58
|
npx -y @aistoragedepot/mcp pull --token=aisd_your_token
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
This writes `~/.claude/commands/aistoragedepot/*.md` — start a new chat and type
|
|
62
|
-
**`/aistoragedepot`** to see them.
|
|
63
|
-
|
|
62
|
+
**`/aistoragedepot`** to see them. **Skills** run as-is. **Prompts** take input: their
|
|
63
|
+
`[fields]` show as an argument hint, and whatever you type after the command fills them —
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
/aistoragedepot:cold-outreach Jane at Globex, about our payments API
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
— with Claude asking for any value you left out. Re-run any time; it only clears and
|
|
70
|
+
rewrites its own namespaced folder.
|
|
64
71
|
|
|
65
72
|
| Option | Default | |
|
|
66
73
|
| --- | --- | --- |
|
|
67
74
|
| `--token=…` | `AISD_TOKEN` env | Your API token. |
|
|
75
|
+
| `--types=…` | `skill,prompt` | Which item types become commands (e.g. `skill` for skills only). |
|
|
68
76
|
| `--workspaces="A,B"` | your personal library | Which workspaces to pull (names, or `all`). |
|
|
69
77
|
| `--project` | off | Write to `./.claude/commands/…` (this repo) instead of your home dir. |
|
|
70
78
|
| `--dir=<path>` | `~/.claude/commands/aistoragedepot` | Write somewhere else entirely. |
|
package/index.mjs
CHANGED
|
@@ -23,11 +23,21 @@ import {
|
|
|
23
23
|
|
|
24
24
|
const BASE_URL = (process.env.AISD_BASE_URL || "https://www.aistoragedepot.com").replace(/\/+$/, "");
|
|
25
25
|
|
|
26
|
-
// CLI mode: `aistoragedepot-mcp pull` syncs
|
|
26
|
+
// CLI mode: `aistoragedepot-mcp pull` syncs the library to native slash-command files, then
|
|
27
|
+
// exits. The brief settle delay lets undici finish closing its sockets (Connection: close)
|
|
28
|
+
// before process.exit — a hard exit mid-teardown trips a libuv assert on Windows (Node 24)
|
|
29
|
+
// that prints a scary-but-harmless message after the sync succeeds.
|
|
27
30
|
if (process.argv[2] === "pull") {
|
|
28
31
|
const { pull } = await import("./pull.mjs");
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
let code = 0;
|
|
33
|
+
try {
|
|
34
|
+
await pull({ BASE_URL, argv: process.argv.slice(3) });
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error(err?.message || err);
|
|
37
|
+
code = 1;
|
|
38
|
+
}
|
|
39
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
40
|
+
process.exit(code);
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
const TOKEN = process.env.AISD_TOKEN;
|
|
@@ -131,7 +141,7 @@ function fillBody(body, placeholders, args) {
|
|
|
131
141
|
}
|
|
132
142
|
|
|
133
143
|
const server = new Server(
|
|
134
|
-
{ name: "aistoragedepot", version: "0.
|
|
144
|
+
{ name: "aistoragedepot", version: "0.4.0" },
|
|
135
145
|
{ capabilities: { resources: {}, prompts: {}, tools: {} } },
|
|
136
146
|
);
|
|
137
147
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aistoragedepot/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
//
|
|
3
3
|
// MCP prompts-as-slash-commands are supported inconsistently across clients, so this writes
|
|
4
4
|
// real command files that any Claude Code client picks up: ~/.claude/commands/aistoragedepot/*.md
|
|
5
|
-
// (a /aistoragedepot:<name> command each). Defaults to the **skills** in your
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// (a /aistoragedepot:<name> command each). Defaults to the **skills and prompts** in your
|
|
6
|
+
// personal library. Skills run as-is; prompts become commands that take input — their [fields]
|
|
7
|
+
// are declared as an argument-hint and filled from whatever you type after the command
|
|
8
|
+
// ($ARGUMENTS), with Claude asking for anything missing. Narrow with --types / widen with
|
|
9
|
+
// --workspaces. Re-run any time — it clears and rewrites only its own namespaced folder.
|
|
8
10
|
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
9
11
|
import { homedir } from "node:os";
|
|
10
12
|
import { join, resolve } from "node:path";
|
|
@@ -12,6 +14,30 @@ import { join, resolve } from "node:path";
|
|
|
12
14
|
const slug = (s) =>
|
|
13
15
|
String(s).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "item";
|
|
14
16
|
const typeOf = (it) => (it.type?.slug || it.type?.name || "").toLowerCase();
|
|
17
|
+
const isPromptItem = (it) => it.type?.format === "prompt" || typeOf(it) === "prompt";
|
|
18
|
+
|
|
19
|
+
// Prompts become commands that take input: the [fields] in the body are declared as an
|
|
20
|
+
// argument-hint (shown in the / picker) and filled from whatever the user types after the
|
|
21
|
+
// command ($ARGUMENTS); Claude asks for any value that's missing.
|
|
22
|
+
const FIELD_RE = /\[([A-Za-z0-9 ]+)\]/g;
|
|
23
|
+
const yq = (s) => String(s).replace(/"/g, "'").replace(/\s+/g, " ").trim(); // YAML-safe one-liner
|
|
24
|
+
|
|
25
|
+
function promptCommand(it) {
|
|
26
|
+
const body = String(it.body || "").trim();
|
|
27
|
+
const fields = [...new Set([...body.matchAll(FIELD_RE)].map((m) => m[1]))];
|
|
28
|
+
const head = ["---", `description: "${yq(`Prompt — ${it.title} (AIStorageDepot)`)}"`];
|
|
29
|
+
if (fields.length) head.push(`argument-hint: "${yq(fields.map((f) => `[${f}]`).join(" "))}"`);
|
|
30
|
+
head.push("---");
|
|
31
|
+
const tail = fields.length
|
|
32
|
+
? [
|
|
33
|
+
"",
|
|
34
|
+
"Input: $ARGUMENTS",
|
|
35
|
+
"",
|
|
36
|
+
`Fill 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.`,
|
|
37
|
+
]
|
|
38
|
+
: ["", "$ARGUMENTS"]; // no fields: whatever the user types just flows in after the prompt
|
|
39
|
+
return [...head, "", body, ...tail, ""].join("\n");
|
|
40
|
+
}
|
|
15
41
|
|
|
16
42
|
function parseArgs(argv) {
|
|
17
43
|
const o = {};
|
|
@@ -32,18 +58,22 @@ export async function pull({ BASE_URL, argv }) {
|
|
|
32
58
|
process.exit(1);
|
|
33
59
|
}
|
|
34
60
|
const api = async (p) => {
|
|
35
|
-
|
|
61
|
+
// Connection: close — keep-alive sockets left open at exit trip a libuv assert on
|
|
62
|
+
// Windows (Node 24), printing a scary (but harmless) message after the sync finishes.
|
|
63
|
+
const r = await fetch(BASE_URL + p, {
|
|
64
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/json", Connection: "close" },
|
|
65
|
+
});
|
|
36
66
|
if (r.status === 401) throw new Error("Token rejected (401) — check it in Settings → API tokens.");
|
|
37
67
|
if (!r.ok) throw new Error(`${p} → HTTP ${r.status}`);
|
|
38
68
|
return r.json();
|
|
39
69
|
};
|
|
40
70
|
|
|
41
|
-
// Which item types to pull. Default: skills
|
|
42
|
-
const TYPES = String(o.types || "skill").toLowerCase().split(",").map((s) => s.trim()).filter(Boolean);
|
|
71
|
+
// Which item types to pull. Default: skills + prompts. --types=skill | prompt | all.
|
|
72
|
+
const TYPES = String(o.types || "skill,prompt").toLowerCase().split(",").map((s) => s.trim()).filter(Boolean);
|
|
43
73
|
const wantType = (it) => {
|
|
44
74
|
const t = typeOf(it);
|
|
45
75
|
const isSkill = t === "skill";
|
|
46
|
-
const isPrompt = it
|
|
76
|
+
const isPrompt = isPromptItem(it);
|
|
47
77
|
if (TYPES.includes("all")) return isSkill || isPrompt;
|
|
48
78
|
return (TYPES.includes("skill") && isSkill) || (TYPES.includes("prompt") && isPrompt) || TYPES.includes(t);
|
|
49
79
|
};
|
|
@@ -72,19 +102,29 @@ export async function pull({ BASE_URL, argv }) {
|
|
|
72
102
|
const used = new Set();
|
|
73
103
|
let n = 0;
|
|
74
104
|
for (const it of items) {
|
|
105
|
+
// Clean names by default; on a title collision, try the type as a tiebreaker
|
|
106
|
+
// (code-review vs code-review-prompt) before falling back to an id suffix.
|
|
75
107
|
let name = slug(it.title);
|
|
108
|
+
if (used.has(name)) {
|
|
109
|
+
const alt = slug(`${it.title} ${typeOf(it) || "item"}`);
|
|
110
|
+
name = used.has(alt) ? `${slug(it.title)}-${it.id.slice(-4)}` : alt;
|
|
111
|
+
}
|
|
76
112
|
while (used.has(name)) name = `${name}-${it.id.slice(-4)}`;
|
|
77
113
|
used.add(name);
|
|
78
114
|
const body = it.body || "";
|
|
79
|
-
// Skills already carry their own YAML frontmatter
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
115
|
+
// Prompts get the argument treatment. Skills already carry their own YAML frontmatter
|
|
116
|
+
// (name/description) — keep them as-is; anything else without frontmatter gets a
|
|
117
|
+
// description so it shows nicely in the / menu.
|
|
118
|
+
const content = isPromptItem(it)
|
|
119
|
+
? promptCommand(it)
|
|
120
|
+
: /^---\r?\n/.test(body)
|
|
121
|
+
? body.endsWith("\n") ? body : `${body}\n`
|
|
122
|
+
: `---\ndescription: "${yq(`${it.title} (AIStorageDepot)`)}"\n---\n\n${body}\n`;
|
|
84
123
|
await writeFile(join(dir, `${name}.md`), content, "utf8");
|
|
85
124
|
n++;
|
|
86
125
|
}
|
|
87
126
|
|
|
88
127
|
console.log(`✓ Synced ${n} item${n === 1 ? "" : "s"} (${TYPES.join(", ")}) → ${dir}`);
|
|
89
128
|
console.log(` Start a new chat and type /aistoragedepot to use them.`);
|
|
129
|
+
console.log(` Skills run as-is; prompts take input — type values right after the command.`);
|
|
90
130
|
}
|