@aistoragedepot/mcp 0.1.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/index.mjs +215 -0
  4. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AIStorageDepot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @aistoragedepot/mcp
2
+
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) server that exposes **your
4
+ [AIStorageDepot](https://www.aistoragedepot.com) library** to any MCP-aware AI client —
5
+ Claude Desktop, Cursor, Cline, Windsurf, and others.
6
+
7
+ Your stored content shows up where you actually work:
8
+
9
+ - **Prompts** → your prompt-format items become MCP prompts (slash-commands), with each
10
+ `[field]` surfaced as a prompt argument you fill in.
11
+ - **Resources** → every item (rules, docs, skills, MCP configs, prompts) is readable at
12
+ `aisd://item/<id>`, so an agent can pull "my coding rules" or "my project guidelines"
13
+ into context on demand.
14
+ - **Tools** → `search_library(query)` and `get_item(id)` let an agent find and fetch
15
+ anything in your library programmatically.
16
+
17
+ ## Setup
18
+
19
+ 1. In AIStorageDepot (a **PLUS** plan is required for API tokens), go to **Settings → API
20
+ tokens** and create a token. Copy it.
21
+ 2. Add the server to your MCP client's config. Example (Claude Desktop /
22
+ `claude_desktop_config.json`, Cursor `~/.cursor/mcp.json`, etc.):
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "aistoragedepot": {
28
+ "command": "npx",
29
+ "args": ["-y", "@aistoragedepot/mcp"],
30
+ "env": {
31
+ "AISD_TOKEN": "aisd_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
32
+ }
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ 3. Restart the client. Your prompts appear as slash-commands; your items are available as
39
+ resources; and the `search_library` / `get_item` tools are ready.
40
+
41
+ ## Configuration
42
+
43
+ | Env var | Required | Default | Notes |
44
+ | --- | --- | --- | --- |
45
+ | `AISD_TOKEN` | yes | — | Token from Settings → API tokens. Has full access to your account — keep it secret. |
46
+ | `AISD_BASE_URL` | no | `https://www.aistoragedepot.com` | Point at a self-hosted / dev instance if needed. |
47
+
48
+ ## Notes
49
+
50
+ - Read-only in v0.1 (it surfaces and fetches your content; it doesn't write back).
51
+ - The library snapshot is cached for ~30s, so a burst of calls hits the API once.
52
+ - Revoke a token any time in Settings → API tokens; the server loses access immediately.
53
+
54
+ ## Local development
55
+
56
+ ```bash
57
+ npm install
58
+ AISD_TOKEN=<token> AISD_BASE_URL=http://localhost:3100 npm run test:client
59
+ ```
package/index.mjs ADDED
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env node
2
+ // AIStorageDepot MCP server.
3
+ //
4
+ // Exposes the signed-in user's library to any MCP-aware AI client (Claude Desktop,
5
+ // Cursor, Cline, Windsurf, …) over stdio:
6
+ // • Prompts — prompt-format items, with their [fields] surfaced as prompt arguments.
7
+ // • Resources — every item (rules, docs, skills, configs, prompts) readable by URI.
8
+ // • Tools — search_library(query) and get_item(id).
9
+ //
10
+ // Auth + target are configured via env:
11
+ // AISD_TOKEN (required) a token from AIStorageDepot → Settings → API tokens.
12
+ // AISD_BASE_URL (optional) defaults to https://www.aistoragedepot.com.
13
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import {
16
+ ListResourcesRequestSchema,
17
+ ReadResourceRequestSchema,
18
+ ListPromptsRequestSchema,
19
+ GetPromptRequestSchema,
20
+ ListToolsRequestSchema,
21
+ CallToolRequestSchema,
22
+ } from "@modelcontextprotocol/sdk/types.js";
23
+
24
+ const BASE_URL = (process.env.AISD_BASE_URL || "https://www.aistoragedepot.com").replace(/\/+$/, "");
25
+ const TOKEN = process.env.AISD_TOKEN;
26
+
27
+ if (!TOKEN) {
28
+ console.error("AISD_TOKEN is required. Create one in AIStorageDepot → Settings → API tokens.");
29
+ process.exit(1);
30
+ }
31
+
32
+ async function api(path) {
33
+ const res = await fetch(`${BASE_URL}${path}`, {
34
+ headers: { Authorization: `Bearer ${TOKEN}`, Accept: "application/json" },
35
+ });
36
+ if (res.status === 401) throw new Error("AIStorageDepot rejected the token (401) — check AISD_TOKEN.");
37
+ if (!res.ok) throw new Error(`AIStorageDepot ${path} → HTTP ${res.status}`);
38
+ return res.json();
39
+ }
40
+
41
+ // Library snapshot, cached briefly so a burst of list/read calls hits the API once.
42
+ let cache = { at: 0, workspaces: [], items: [] };
43
+ async function snapshot() {
44
+ if (cache.items.length && Date.now() - cache.at < 30_000) return cache;
45
+ const workspaces = await api("/api/workspaces");
46
+ const items = [];
47
+ for (const ws of workspaces) {
48
+ try {
49
+ const lib = await api(`/api/library?workspace=${encodeURIComponent(ws.id)}`);
50
+ for (const it of lib.items || []) items.push(it);
51
+ } catch {
52
+ // a workspace we can't read (or that errored) — skip it, keep the rest.
53
+ }
54
+ }
55
+ cache = { at: Date.now(), workspaces, items };
56
+ return cache;
57
+ }
58
+
59
+ // --- mapping helpers ---
60
+ const slug = (s) =>
61
+ String(s).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "item";
62
+ const isPrompt = (it) => it.type?.format === "prompt" || (it.placeholders?.length ?? 0) > 0;
63
+ const mimeFor = (it) => (it.type?.format === "json" ? "application/json" : "text/markdown");
64
+
65
+ // Stable, unique prompt name per item (slug of the title, deduped with an id suffix).
66
+ // Rebuilt deterministically from the snapshot so ListPrompts and GetPrompt agree.
67
+ function promptIndex(items) {
68
+ const used = new Set();
69
+ const byName = new Map();
70
+ for (const it of items.filter(isPrompt)) {
71
+ let name = slug(it.title);
72
+ if (used.has(name)) name = `${name}-${it.id.slice(-4)}`;
73
+ used.add(name);
74
+ byName.set(name, it);
75
+ }
76
+ return byName;
77
+ }
78
+
79
+ // MCP argument names should be simple identifiers; map them back to the original
80
+ // "[placeholder]" text so we can substitute into the body.
81
+ function argMap(placeholders) {
82
+ const m = new Map();
83
+ const used = new Set();
84
+ for (const p of placeholders || []) {
85
+ let a = slug(p).replace(/-/g, "_");
86
+ while (used.has(a)) a += "_";
87
+ used.add(a);
88
+ m.set(a, p);
89
+ }
90
+ return m;
91
+ }
92
+
93
+ function fillBody(body, placeholders, args) {
94
+ let out = body;
95
+ for (const [argName, original] of argMap(placeholders)) {
96
+ const val = args?.[argName];
97
+ if (val != null && val !== "") out = out.split(`[${original}]`).join(val);
98
+ }
99
+ return out;
100
+ }
101
+
102
+ const server = new Server(
103
+ { name: "aistoragedepot", version: "0.1.0" },
104
+ { capabilities: { resources: {}, prompts: {}, tools: {} } },
105
+ );
106
+
107
+ // ---- Resources: every item, readable at aisd://item/<id> ----
108
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
109
+ const { items } = await snapshot();
110
+ return {
111
+ resources: items.map((it) => ({
112
+ uri: `aisd://item/${it.id}`,
113
+ name: it.title,
114
+ description: `${it.type?.name || "Item"}${it.tags?.length ? " · " + it.tags.join(", ") : ""}`,
115
+ mimeType: mimeFor(it),
116
+ })),
117
+ };
118
+ });
119
+
120
+ server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
121
+ const { uri } = req.params;
122
+ const id = uri.replace(/^aisd:\/\/item\//, "");
123
+ const { items } = await snapshot();
124
+ const it = items.find((x) => x.id === id);
125
+ if (!it) throw new Error(`Unknown resource: ${uri}`);
126
+ return { contents: [{ uri, mimeType: mimeFor(it), text: it.body }] };
127
+ });
128
+
129
+ // ---- Prompts: prompt-format items, [fields] → arguments ----
130
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
131
+ const { items } = await snapshot();
132
+ const prompts = [];
133
+ for (const [name, it] of promptIndex(items)) {
134
+ const am = argMap(it.placeholders);
135
+ prompts.push({
136
+ name,
137
+ description: `${it.type?.name || "Prompt"}: “${it.title}” (AIStorageDepot)`,
138
+ arguments: [...am.keys()].map((a) => ({
139
+ name: a,
140
+ description: `Value for [${am.get(a)}]`,
141
+ required: false,
142
+ })),
143
+ });
144
+ }
145
+ return { prompts };
146
+ });
147
+
148
+ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
149
+ const { items } = await snapshot();
150
+ const it = promptIndex(items).get(req.params.name);
151
+ if (!it) throw new Error(`Unknown prompt: ${req.params.name}`);
152
+ const text = fillBody(it.body, it.placeholders, req.params.arguments || {});
153
+ return {
154
+ description: it.title,
155
+ messages: [{ role: "user", content: { type: "text", text } }],
156
+ };
157
+ });
158
+
159
+ // ---- Tools: search + fetch ----
160
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
161
+ tools: [
162
+ {
163
+ name: "search_library",
164
+ description:
165
+ "Search your AIStorageDepot library (prompts, rules, docs, skills, configs) by keyword across titles, bodies, tags, and categories. Returns matching items with their aisd://item/<id> resource URIs.",
166
+ inputSchema: {
167
+ type: "object",
168
+ properties: { query: { type: "string", description: "Keyword(s) to search for." } },
169
+ required: ["query"],
170
+ },
171
+ },
172
+ {
173
+ name: "get_item",
174
+ description:
175
+ "Fetch the full content of one library item by its id or aisd://item/<id> URI (e.g. from a search_library result).",
176
+ inputSchema: {
177
+ type: "object",
178
+ properties: { id: { type: "string", description: "The item id, or an aisd://item/<id> URI." } },
179
+ required: ["id"],
180
+ },
181
+ },
182
+ ],
183
+ }));
184
+
185
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
186
+ const { name, arguments: args } = req.params;
187
+ try {
188
+ if (name === "search_library") {
189
+ const q = String(args?.query || "").trim();
190
+ if (q.length < 2) return { content: [{ type: "text", text: "Enter at least 2 characters." }] };
191
+ const { workspaces } = await snapshot();
192
+ const ids = workspaces.map((w) => w.id).join(",");
193
+ const results = await api(`/api/search?q=${encodeURIComponent(q)}&workspaces=${encodeURIComponent(ids)}`);
194
+ if (!results.length) return { content: [{ type: "text", text: `No matches for “${q}”.` }] };
195
+ const lines = results.map(
196
+ (r) => `• ${r.title} — ${r.typeName} (in ${r.workspaceName}) → aisd://item/${r.id}`,
197
+ );
198
+ return { content: [{ type: "text", text: `${results.length} match(es) for “${q}”:\n${lines.join("\n")}` }] };
199
+ }
200
+ if (name === "get_item") {
201
+ const id = String(args?.id || "").replace(/^aisd:\/\/item\//, "");
202
+ const { items } = await snapshot();
203
+ const it = items.find((x) => x.id === id);
204
+ if (!it) return { content: [{ type: "text", text: `No item with id ${id}.` }], isError: true };
205
+ return { content: [{ type: "text", text: it.body }] };
206
+ }
207
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
208
+ } catch (err) {
209
+ return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
210
+ }
211
+ });
212
+
213
+ const transport = new StdioServerTransport();
214
+ await server.connect(transport);
215
+ console.error(`AIStorageDepot MCP server connected → ${BASE_URL}`);
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@aistoragedepot/mcp",
3
+ "version": "0.1.0",
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
+ "type": "module",
6
+ "bin": {
7
+ "aistoragedepot-mcp": "index.mjs"
8
+ },
9
+ "files": [
10
+ "index.mjs",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "scripts": {
18
+ "test:client": "node test-client.mjs"
19
+ },
20
+ "dependencies": {
21
+ "@modelcontextprotocol/sdk": "^1.12.0"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "modelcontextprotocol",
26
+ "model-context-protocol",
27
+ "aistoragedepot",
28
+ "prompts",
29
+ "ai",
30
+ "llm",
31
+ "claude",
32
+ "cursor"
33
+ ],
34
+ "author": "AIStorageDepot",
35
+ "homepage": "https://www.aistoragedepot.com",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/davidmcolvin/aistoragedepot-mcp.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/davidmcolvin/aistoragedepot-mcp/issues"
42
+ },
43
+ "license": "MIT",
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }