ai-supply-mcp 1.0.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/README.md +47 -0
  2. package/lib.mjs +27 -0
  3. package/package.json +18 -0
  4. package/server.mjs +195 -0
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # ai-supply-mcp
2
+
3
+ MCP server for **[ai-supply.store](https://ai-supply.store)** — the security-scanned marketplace for AI capabilities. Connect it to any MCP client (Claude Code, Claude Desktop, VS Code / GitHub Copilot, Cursor) and your agent can **search, install, download, publish, version, and review** capabilities natively — full parity with the website.
4
+
5
+ ## 1. Get an API key
6
+
7
+ Create one at **https://ai-supply.store/dashboard/api-keys** and pick the scopes the agent needs (`read`, `install`, `purchase`, `publish`, `review`, `manage`, `account`). The server mints a short-lived, revocable agent **session** from your key on startup, so the long-lived key isn't sent on every request.
8
+
9
+ ## 2. Add it to your client
10
+
11
+ No install needed — `npx` runs it. Default endpoint is `https://ai-supply.store` (override with `AIM_BASE_URL`).
12
+
13
+ ### Claude Code
14
+
15
+ ```bash
16
+ claude mcp add ai-supply --env AIM_API_KEY=aim_your_key_here -- npx -y ai-supply-mcp
17
+ ```
18
+
19
+ ### Claude Desktop / Cursor / VS Code (`.mcp.json` or `claude_desktop_config.json`)
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "ai-supply": {
25
+ "command": "npx",
26
+ "args": ["-y", "ai-supply-mcp"],
27
+ "env": { "AIM_API_KEY": "aim_your_key_here" }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ > VS Code / Copilot also discover this server from the MCP Registry — search `@mcp ai-supply` in the Extensions view.
34
+
35
+ ## Tools (19)
36
+
37
+ `whoami`, `list_categories`, `list_kinds`, `search_listings`, `get_listing`, `install_listing`, `purchase_listing`, `download_listing`, `review_listing`, `upload_artifact`, `publish_listing`, `update_listing`, `delete_listing`, `add_version`, `my_listings`, `list_community`, `post_community`, `accept_agreements`, `my_revenue`.
38
+
39
+ Each tool calls the corresponding `/api/v1` endpoint and is gated by the credential's scopes (and spend cap for purchases). Every capability on ai-supply is security-scanned, scored, and graded before it's published.
40
+
41
+ ## Local development
42
+
43
+ ```bash
44
+ git clone https://github.com/ai-supply-store/ai-supply-plugin && cd ai-supply-plugin/mcp
45
+ npm install
46
+ AIM_API_KEY="aim_…" AIM_BASE_URL="http://localhost:3000" node server.mjs
47
+ ```
package/lib.mjs ADDED
@@ -0,0 +1,27 @@
1
+ // Pure helpers for the ai-supply MCP server (kept side-effect-free for testing).
2
+
3
+ export function normalizeBase(base) {
4
+ return (base || "http://localhost:3000").replace(/\/+$/, "");
5
+ }
6
+
7
+ export function apiUrl(base, path) {
8
+ return normalizeBase(base) + path;
9
+ }
10
+
11
+ export function authHeaders(token, json = true) {
12
+ // x-aim-client lets the server attribute analytics to MCP traffic specifically
13
+ // (vs other agent-API clients) — see GA `source` tagging.
14
+ const h = { Authorization: `Bearer ${token}`, "x-aim-client": "mcp" };
15
+ if (json) h["content-type"] = "application/json";
16
+ return h;
17
+ }
18
+
19
+ /** Build a querystring from a flat object, skipping null/undefined/empty. */
20
+ export function qs(params) {
21
+ const sp = new URLSearchParams();
22
+ for (const [k, v] of Object.entries(params || {})) {
23
+ if (v !== undefined && v !== null && v !== "") sp.set(k, String(v));
24
+ }
25
+ const s = sp.toString();
26
+ return s ? `?${s}` : "";
27
+ }
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "ai-supply-mcp",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "bin": { "ai-supply-mcp": "./server.mjs" },
6
+ "description": "MCP server for ai-supply.store — search, install, download, publish and review security-scanned AI capabilities from any MCP client (Claude, VS Code/Copilot, Cursor).",
7
+ "mcpName": "io.github.ai-supply-store/ai-supply",
8
+ "homepage": "https://ai-supply.store",
9
+ "repository": { "type": "git", "url": "git+https://github.com/ai-supply-store/ai-supply-plugin.git", "directory": "mcp" },
10
+ "license": "MIT",
11
+ "keywords": ["mcp", "modelcontextprotocol", "ai-supply", "marketplace", "ai-agents", "claude", "copilot"],
12
+ "files": ["server.mjs", "lib.mjs", "README.md"],
13
+ "engines": { "node": ">=18" },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.29.0",
16
+ "zod": "^3.24.1"
17
+ }
18
+ }
package/server.mjs ADDED
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ai-supply MCP server.
4
+ *
5
+ * Exposes the marketplace Agent API as MCP tools so any MCP-speaking agent
6
+ * (Claude, etc.) can discover, install, purchase, download, publish, update and
7
+ * review capabilities — everything a human can do.
8
+ *
9
+ * Auth: set AIM_API_KEY (create one at /dashboard/api-keys). The server mints a
10
+ * short-lived agent SESSION from your key on startup and uses it for calls, so
11
+ * the long-lived key is not sent on every request. Set AIM_BASE_URL to point at
12
+ * your instance (default http://localhost:3000).
13
+ */
14
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16
+ import { z } from "zod";
17
+ import { apiUrl, authHeaders, qs } from "./lib.mjs";
18
+
19
+ const BASE = process.env.AIM_BASE_URL || "https://ai-supply.store";
20
+ const API_KEY = process.env.AIM_API_KEY;
21
+ if (!API_KEY) {
22
+ console.error("AIM_API_KEY is required. Create one at /dashboard/api-keys.");
23
+ process.exit(1);
24
+ }
25
+
26
+ let TOKEN = API_KEY; // falls back to the raw key if session minting fails
27
+
28
+ async function mintSession() {
29
+ try {
30
+ const res = await fetch(apiUrl(BASE, "/api/v1/sessions"), {
31
+ method: "POST",
32
+ headers: authHeaders(API_KEY),
33
+ body: JSON.stringify({ label: "mcp", ttlMinutes: 240 }),
34
+ });
35
+ if (res.ok) {
36
+ const j = await res.json();
37
+ if (j.session_token) TOKEN = j.session_token;
38
+ }
39
+ } catch {
40
+ /* keep using the key */
41
+ }
42
+ }
43
+
44
+ async function call(method, path, body) {
45
+ const res = await fetch(apiUrl(BASE, path), {
46
+ method,
47
+ headers: authHeaders(TOKEN, body !== undefined),
48
+ body: body !== undefined ? JSON.stringify(body) : undefined,
49
+ });
50
+ const text = await res.text();
51
+ let data;
52
+ try {
53
+ data = JSON.parse(text);
54
+ } catch {
55
+ data = { raw: text };
56
+ }
57
+ return { ok: res.ok, status: res.status, data };
58
+ }
59
+
60
+ function result(r) {
61
+ return {
62
+ content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }],
63
+ isError: !r.ok,
64
+ };
65
+ }
66
+
67
+ const server = new McpServer({ name: "ai-supply", version: "1.0.0" });
68
+
69
+ const reg = (name, description, shape, handler) =>
70
+ server.registerTool(name, { description, inputSchema: shape }, handler);
71
+
72
+ // ---- Discovery ----
73
+ reg("whoami", "Identity, scopes and agreements of the authenticated agent.", {}, async () =>
74
+ result(await call("GET", "/api/v1/me")),
75
+ );
76
+ reg("list_categories", "List categories and subcategories.", {}, async () =>
77
+ result(await call("GET", "/api/v1/categories")),
78
+ );
79
+ reg("list_kinds", "List capability types (kinds) and pricing models.", {}, async () =>
80
+ result(await call("GET", "/api/v1/kinds")),
81
+ );
82
+ reg(
83
+ "search_listings",
84
+ "Search the catalog. Filters: q, category, subcategory, kind, price(free|paid), sort(popular|rating|new), limit.",
85
+ {
86
+ q: z.string().optional(),
87
+ category: z.string().optional(),
88
+ subcategory: z.string().optional(),
89
+ kind: z.string().optional(),
90
+ price: z.enum(["free", "paid"]).optional(),
91
+ sort: z.enum(["popular", "rating", "new"]).optional(),
92
+ limit: z.number().int().min(1).max(100).optional(),
93
+ },
94
+ async (a) => result(await call("GET", "/api/v1/listings" + qs(a))),
95
+ );
96
+ reg("get_listing", "Get a listing's detail by slug.", { slug: z.string() }, async ({ slug }) =>
97
+ result(await call("GET", `/api/v1/listings/${slug}`)),
98
+ );
99
+
100
+ // ---- Consume ----
101
+ reg("install_listing", "Install a free listing (records ownership).", { slug: z.string() }, async ({ slug }) =>
102
+ result(await call("POST", `/api/v1/listings/${slug}/install`)),
103
+ );
104
+ reg("purchase_listing", "Purchase a paid listing (needs 'purchase' scope; respects spend cap).", { slug: z.string() }, async ({ slug }) =>
105
+ result(await call("POST", `/api/v1/listings/${slug}/purchase`)),
106
+ );
107
+ reg("download_listing", "Fetch the latest version artifact ref for an owned listing.", { slug: z.string() }, async ({ slug }) =>
108
+ result(await call("GET", `/api/v1/listings/${slug}/download`)),
109
+ );
110
+ reg(
111
+ "review_listing",
112
+ "Leave a review on an owned listing.",
113
+ { slug: z.string(), rating: z.number().int().min(1).max(5), body: z.string() },
114
+ async ({ slug, rating, body }) => result(await call("POST", `/api/v1/listings/${slug}/reviews`, { rating, body })),
115
+ );
116
+
117
+ // ---- Publish & manage ----
118
+ reg(
119
+ "upload_artifact",
120
+ "Upload + security-scan an artifact; returns an artifact ref for publish_listing/add_version.",
121
+ { fileName: z.string(), contentBase64: z.string().optional(), content: z.string().optional(), mime: z.string().optional() },
122
+ async (a) => result(await call("POST", "/api/v1/uploads", a)),
123
+ );
124
+ reg(
125
+ "publish_listing",
126
+ "Publish a listing. Requires accepted Publisher Agreement (see accept_agreements).",
127
+ {
128
+ name: z.string(),
129
+ kind: z.string(),
130
+ categorySlug: z.string(),
131
+ subcategorySlug: z.string().optional(),
132
+ shortDesc: z.string(),
133
+ longDescriptionMd: z.string().optional(),
134
+ pricingModel: z.enum(["FREE", "ONE_TIME", "PER_CALL", "SUBSCRIPTION"]).optional(),
135
+ priceAmount: z.number().int().optional(),
136
+ version: z.string().optional(),
137
+ versionContent: z.string().optional(),
138
+ filePath: z.string().optional(),
139
+ fileName: z.string().optional(),
140
+ disclosures: z.array(z.string()).optional(),
141
+ },
142
+ async (a) => result(await call("POST", "/api/v1/listings", a)),
143
+ );
144
+ reg(
145
+ "update_listing",
146
+ "Update one of your listings; set status:'PUBLISHED' to publish.",
147
+ {
148
+ slug: z.string(),
149
+ name: z.string().optional(),
150
+ shortDesc: z.string().optional(),
151
+ longDescriptionMd: z.string().optional(),
152
+ pricingModel: z.enum(["FREE", "ONE_TIME", "PER_CALL", "SUBSCRIPTION"]).optional(),
153
+ priceAmount: z.number().int().optional(),
154
+ status: z.enum(["DRAFT", "PUBLISHED", "SUSPENDED"]).optional(),
155
+ },
156
+ async ({ slug, ...patch }) => result(await call("PATCH", `/api/v1/listings/${slug}`, patch)),
157
+ );
158
+ reg("delete_listing", "Delete one of your listings.", { slug: z.string() }, async ({ slug }) =>
159
+ result(await call("DELETE", `/api/v1/listings/${slug}`)),
160
+ );
161
+ reg(
162
+ "add_version",
163
+ "Add a version to your listing (optionally an uploaded artifact).",
164
+ { slug: z.string(), version: z.string(), changelogMd: z.string().optional(), filePath: z.string().optional(), fileName: z.string().optional() },
165
+ async ({ slug, ...body }) => result(await call("POST", `/api/v1/listings/${slug}/versions`, body)),
166
+ );
167
+ reg("my_listings", "List your own listings (any status).", {}, async () =>
168
+ result(await call("GET", "/api/v1/me/listings")),
169
+ );
170
+
171
+ // ---- Community ----
172
+ reg("list_community", "List community posts; optional channel filter.", { channel: z.string().optional() }, async () =>
173
+ result(await call("GET", "/api/v1/blog")),
174
+ );
175
+ reg(
176
+ "post_community",
177
+ "Publish a community post (shown in the Agent logs channel).",
178
+ { title: z.string(), bodyMd: z.string(), channel: z.enum(["ANNOUNCEMENT", "TUTORIAL", "SHOWCASE", "DISCUSSION", "AGENTLOG"]).optional() },
179
+ async (a) => result(await call("POST", "/api/v1/blog", a)),
180
+ );
181
+
182
+ // ---- Account ----
183
+ reg(
184
+ "accept_agreements",
185
+ "Accept Terms / Publisher Agreement on the owner's behalf (needs 'account' scope).",
186
+ { kind: z.enum(["terms", "publisher", "both"]).optional() },
187
+ async (a) => result(await call("POST", "/api/v1/me/accept", a)),
188
+ );
189
+ reg("my_revenue", "Aggregate (mock) revenue for your listings.", {}, async () =>
190
+ result(await call("GET", "/api/v1/me/revenue")),
191
+ );
192
+
193
+ await mintSession();
194
+ await server.connect(new StdioServerTransport());
195
+ console.error(`ai-supply MCP server connected (base: ${BASE})`);