@aistoragedepot/mcp 0.2.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/README.md +20 -0
- package/index.mjs +9 -1
- package/package.json +3 -2
- package/pull.mjs +90 -0
package/README.md
CHANGED
|
@@ -49,6 +49,26 @@ Your stored content shows up where you actually work:
|
|
|
49
49
|
| `AISD_BASE_URL` | no | `https://www.aistoragedepot.com` | Point at a self-hosted / dev instance if needed. |
|
|
50
50
|
| `AISD_WORKSPACES` | no | your personal library | Which workspaces expose **prompts** (slash-commands). Comma-separated workspace names (e.g. `My Library,Engineering`), or `all`. Keeps the slash menu short; **search and resources still cover every workspace** regardless. |
|
|
51
51
|
|
|
52
|
+
## Slash-commands (`pull`)
|
|
53
|
+
|
|
54
|
+
Not every client turns MCP prompts into `/` slash-commands (some only use the tools). To get
|
|
55
|
+
your prompts as **native** slash-commands that always work, sync them to command files:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx -y @aistoragedepot/mcp pull --token=aisd_your_token
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This writes `~/.claude/commands/aistoragedepot/*.md` — start a new chat and type
|
|
62
|
+
**`/aistoragedepot`** to see them. Re-run any time; it only clears and rewrites its own
|
|
63
|
+
namespaced folder.
|
|
64
|
+
|
|
65
|
+
| Option | Default | |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `--token=…` | `AISD_TOKEN` env | Your API token. |
|
|
68
|
+
| `--workspaces="A,B"` | your personal library | Which workspaces to pull (names, or `all`). |
|
|
69
|
+
| `--project` | off | Write to `./.claude/commands/…` (this repo) instead of your home dir. |
|
|
70
|
+
| `--dir=<path>` | `~/.claude/commands/aistoragedepot` | Write somewhere else entirely. |
|
|
71
|
+
|
|
52
72
|
## Notes
|
|
53
73
|
|
|
54
74
|
- Read-only in v0.1 (it surfaces and fetches your content; it doesn't write back).
|
package/index.mjs
CHANGED
|
@@ -22,6 +22,14 @@ import {
|
|
|
22
22
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
23
23
|
|
|
24
24
|
const BASE_URL = (process.env.AISD_BASE_URL || "https://www.aistoragedepot.com").replace(/\/+$/, "");
|
|
25
|
+
|
|
26
|
+
// CLI mode: `aistoragedepot-mcp pull` syncs prompts to native slash-command files, then exits.
|
|
27
|
+
if (process.argv[2] === "pull") {
|
|
28
|
+
const { pull } = await import("./pull.mjs");
|
|
29
|
+
await pull({ BASE_URL, argv: process.argv.slice(3) });
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
const TOKEN = process.env.AISD_TOKEN;
|
|
26
34
|
|
|
27
35
|
if (!TOKEN) {
|
|
@@ -123,7 +131,7 @@ function fillBody(body, placeholders, args) {
|
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
const server = new Server(
|
|
126
|
-
{ name: "aistoragedepot", version: "0.
|
|
134
|
+
{ name: "aistoragedepot", version: "0.3.0" },
|
|
127
135
|
{ capabilities: { resources: {}, prompts: {}, tools: {} } },
|
|
128
136
|
);
|
|
129
137
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aistoragedepot/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"index.mjs",
|
|
11
11
|
"README.md",
|
|
12
|
-
"LICENSE"
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"pull.mjs"
|
|
13
14
|
],
|
|
14
15
|
"engines": {
|
|
15
16
|
"node": ">=18"
|
package/pull.mjs
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// `aistoragedepot-mcp pull` — sync your library into native slash-command files.
|
|
2
|
+
//
|
|
3
|
+
// MCP prompts-as-slash-commands are supported inconsistently across clients, so this writes
|
|
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 personal library
|
|
6
|
+
// (self-contained instructions that run cleanly as commands); widen with --workspaces / include
|
|
7
|
+
// prompts with --types. Re-run any time — it clears and rewrites only its own namespaced folder.
|
|
8
|
+
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
|
|
12
|
+
const slug = (s) =>
|
|
13
|
+
String(s).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "item";
|
|
14
|
+
const typeOf = (it) => (it.type?.slug || it.type?.name || "").toLowerCase();
|
|
15
|
+
|
|
16
|
+
function parseArgs(argv) {
|
|
17
|
+
const o = {};
|
|
18
|
+
for (const a of argv) {
|
|
19
|
+
const m = a.match(/^--([^=]+)(?:=(.*))?$/);
|
|
20
|
+
if (m) o[m[1]] = m[2] ?? true;
|
|
21
|
+
}
|
|
22
|
+
return o;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function pull({ BASE_URL, argv }) {
|
|
26
|
+
const o = parseArgs(argv);
|
|
27
|
+
const token = o.token || process.env.AISD_TOKEN;
|
|
28
|
+
if (!token) {
|
|
29
|
+
console.error(
|
|
30
|
+
"A token is required. Pass --token=aisd_… (create one in AIStorageDepot → Settings → API tokens), or set AISD_TOKEN.",
|
|
31
|
+
);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const api = async (p) => {
|
|
35
|
+
const r = await fetch(BASE_URL + p, { headers: { Authorization: `Bearer ${token}`, Accept: "application/json" } });
|
|
36
|
+
if (r.status === 401) throw new Error("Token rejected (401) — check it in Settings → API tokens.");
|
|
37
|
+
if (!r.ok) throw new Error(`${p} → HTTP ${r.status}`);
|
|
38
|
+
return r.json();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Which item types to pull. Default: skills only. --types=skill,prompt | all.
|
|
42
|
+
const TYPES = String(o.types || "skill").toLowerCase().split(",").map((s) => s.trim()).filter(Boolean);
|
|
43
|
+
const wantType = (it) => {
|
|
44
|
+
const t = typeOf(it);
|
|
45
|
+
const isSkill = t === "skill";
|
|
46
|
+
const isPrompt = it.type?.format === "prompt" || t === "prompt";
|
|
47
|
+
if (TYPES.includes("all")) return isSkill || isPrompt;
|
|
48
|
+
return (TYPES.includes("skill") && isSkill) || (TYPES.includes("prompt") && isPrompt) || TYPES.includes(t);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const workspaces = await api("/api/workspaces");
|
|
52
|
+
// Scope: default every workspace; --workspaces="A,B" | "personal" | "all".
|
|
53
|
+
const names = String(o.workspaces || "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
54
|
+
let scope;
|
|
55
|
+
if (names.includes("all") || names.includes("*")) scope = workspaces;
|
|
56
|
+
else if (names.length) scope = workspaces.filter((w) => names.includes((w.name || "").toLowerCase()));
|
|
57
|
+
else scope = workspaces.filter((w) => w.type === "PERSONAL"); // default: just your personal library
|
|
58
|
+
|
|
59
|
+
const libs = await Promise.all(
|
|
60
|
+
scope.map((w) => api(`/api/library?workspace=${encodeURIComponent(w.id)}`).catch(() => ({ items: [] }))),
|
|
61
|
+
);
|
|
62
|
+
const items = libs.flatMap((l) => (l.items || []).filter(wantType));
|
|
63
|
+
|
|
64
|
+
const dir = o.dir
|
|
65
|
+
? resolve(String(o.dir))
|
|
66
|
+
: o.project
|
|
67
|
+
? resolve(".claude", "commands", "aistoragedepot")
|
|
68
|
+
: join(homedir(), ".claude", "commands", "aistoragedepot");
|
|
69
|
+
await rm(dir, { recursive: true, force: true }); // clean re-sync of our namespaced folder only
|
|
70
|
+
await mkdir(dir, { recursive: true });
|
|
71
|
+
|
|
72
|
+
const used = new Set();
|
|
73
|
+
let n = 0;
|
|
74
|
+
for (const it of items) {
|
|
75
|
+
let name = slug(it.title);
|
|
76
|
+
while (used.has(name)) name = `${name}-${it.id.slice(-4)}`;
|
|
77
|
+
used.add(name);
|
|
78
|
+
const body = it.body || "";
|
|
79
|
+
// Skills already carry their own YAML frontmatter (name/description) — keep it as-is; for anything
|
|
80
|
+
// without frontmatter (e.g. prompts), add a description so it shows nicely in the / menu.
|
|
81
|
+
const content = /^---\r?\n/.test(body)
|
|
82
|
+
? body.endsWith("\n") ? body : `${body}\n`
|
|
83
|
+
: `---\ndescription: "${`${it.title} (AIStorageDepot)`.replace(/"/g, "'").replace(/\s+/g, " ").trim()}"\n---\n\n${body}\n`;
|
|
84
|
+
await writeFile(join(dir, `${name}.md`), content, "utf8");
|
|
85
|
+
n++;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(`✓ Synced ${n} item${n === 1 ? "" : "s"} (${TYPES.join(", ")}) → ${dir}`);
|
|
89
|
+
console.log(` Start a new chat and type /aistoragedepot to use them.`);
|
|
90
|
+
}
|