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.
- package/README.md +47 -0
- package/lib.mjs +27 -0
- package/package.json +18 -0
- 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})`);
|