@heyclaude/mcp 0.1.2 → 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/CHANGELOG.md +25 -0
- package/README.md +71 -11
- package/package.json +1 -1
- package/scripts/validate-endpoint.mjs +236 -12
- package/src/package-metadata.js +4 -7
- package/src/registry.d.ts +139 -0
- package/src/registry.js +2154 -61
- package/src/remote-proxy.d.ts +20 -0
- package/src/remote-proxy.js +155 -42
- package/src/schemas.d.ts +15 -0
- package/src/schemas.js +181 -1
- package/src/server.js +40 -1
- package/src/submissions.d.ts +14 -2
- package/src/submissions.js +464 -67
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @heyclaude/mcp Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - Safety Metadata and Submission Policy
|
|
4
|
+
|
|
5
|
+
- Expose registry `safetyNotes` and `privacyNotes` in MCP search, detail,
|
|
6
|
+
copyable asset, comparison, and install guidance responses.
|
|
7
|
+
- Accept `safety_notes` and `privacy_notes` in submission draft helpers with
|
|
8
|
+
the same short-note limits used by HeyClaude intake.
|
|
9
|
+
- Support source-backed and copyable-content skill submissions without requiring
|
|
10
|
+
community ZIP/MCPB package hosting.
|
|
11
|
+
- Reflect the review-gated import policy: submission helpers can prepare issues
|
|
12
|
+
and local checks, but never create issues, open PRs, merge, publish, or mirror
|
|
13
|
+
package artifacts.
|
|
14
|
+
|
|
15
|
+
## 0.2.0 - Discovery and Submission Drafting
|
|
16
|
+
|
|
17
|
+
- Add read-only discovery tools for server metadata, paginated category
|
|
18
|
+
browsing, recent updates, and related entries.
|
|
19
|
+
- Add copyable asset, entry comparison, registry stats, and client setup tools
|
|
20
|
+
for richer MCP client workflows.
|
|
21
|
+
- Add read-only MCP resources and workflow prompts for discovery, submission
|
|
22
|
+
drafting, pre-issue review, and safe install guidance.
|
|
23
|
+
- Add submission helper tools for examples, canonical issue drafts, duplicate
|
|
24
|
+
review, and maintainer checklist guidance.
|
|
25
|
+
- Document the public no-key access model and the dedicated MCP rate-limit
|
|
26
|
+
policy.
|
|
27
|
+
|
|
3
28
|
## 0.1.2 - Repository Rename
|
|
4
29
|
|
|
5
30
|
- Update package metadata, README links, and release provenance for the
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<a href="https://github.com/JSONbored/awesome-claude">GitHub</a> •
|
|
12
12
|
<a href="https://www.npmjs.com/package/@heyclaude/mcp">npm</a> •
|
|
13
13
|
<a href="https://heyclau.de/api/mcp">MCP endpoint</a> •
|
|
14
|
-
<a href="https://github.com/JSONbored/awesome-claude/releases/tag/mcp-v0.
|
|
14
|
+
<a href="https://github.com/JSONbored/awesome-claude/releases/tag/mcp-v0.3.0">v0.3.0 release</a>
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
17
|
Read-only Model Context Protocol server for the HeyClaude registry.
|
|
@@ -19,14 +19,35 @@ Read-only Model Context Protocol server for the HeyClaude registry.
|
|
|
19
19
|
It exposes the same public registry surface used by the website and Raycast:
|
|
20
20
|
search, entry details, platform compatibility, install guidance, generated
|
|
21
21
|
adapters, feed discovery, and safe submission-draft helpers. It does not create
|
|
22
|
-
GitHub issues, open pull requests, write local files, publish content,
|
|
23
|
-
accounts.
|
|
22
|
+
GitHub issues, open pull requests, write local files, publish content, host
|
|
23
|
+
packages, or manage accounts.
|
|
24
|
+
|
|
25
|
+
No API key is required for the public endpoint. Abuse controls are handled with
|
|
26
|
+
strict request validation, a 64 KiB body limit, and a dedicated Cloudflare
|
|
27
|
+
`API_MCP_RATE_LIMIT` binding capped at 60 requests/minute/IP in production.
|
|
24
28
|
|
|
25
29
|
## Tools
|
|
26
30
|
|
|
27
31
|
- `search_registry` - search public registry entries by query, category, and
|
|
28
32
|
platform.
|
|
33
|
+
- `server_info` - fetch package version, registry generation, tool list, public
|
|
34
|
+
access policy, and rate-limit metadata.
|
|
35
|
+
- `list_category_entries` - browse entries with bounded pagination and optional
|
|
36
|
+
category, platform, tag, and query filters.
|
|
37
|
+
- `get_recent_updates` - list recently added or upstream-updated entries from
|
|
38
|
+
generated registry metadata, optionally filtered with `since`.
|
|
39
|
+
- `get_related_entries` - find related entries based on category, tags,
|
|
40
|
+
platforms, keywords, and source metadata.
|
|
29
41
|
- `get_entry_detail` - fetch an entry detail payload by category and slug.
|
|
42
|
+
- `get_copyable_asset` - fetch the category-aware copy/install asset for an
|
|
43
|
+
entry, such as full prompt text, config snippets, commands, scripts, or
|
|
44
|
+
collection items.
|
|
45
|
+
- `compare_entries` - compare 2-5 entries by fit, category, platform support,
|
|
46
|
+
install complexity, and source metadata.
|
|
47
|
+
- `get_registry_stats` - fetch aggregate counts, freshness metadata, and real
|
|
48
|
+
source-signal coverage without implying popularity when stats are absent.
|
|
49
|
+
- `get_client_setup` - fetch tested setup snippets for Codex, Claude Desktop,
|
|
50
|
+
Cursor, Windsurf, and raw Streamable HTTP clients.
|
|
30
51
|
- `get_compatibility` - fetch skill platform compatibility metadata.
|
|
31
52
|
- `get_install_guidance` - fetch install commands, config, package, and platform
|
|
32
53
|
guidance.
|
|
@@ -34,15 +55,45 @@ accounts.
|
|
|
34
55
|
rule adapters for skill packages.
|
|
35
56
|
- `list_distribution_feeds` - discover public JSON, RSS, Atom, and platform
|
|
36
57
|
feeds.
|
|
37
|
-
- `get_submission_schema` - fetch category submission fields
|
|
38
|
-
|
|
58
|
+
- `get_submission_schema` - fetch category submission fields for PR-first
|
|
59
|
+
intake.
|
|
39
60
|
- `validate_submission_draft` - validate a content submission draft locally.
|
|
40
61
|
- `search_duplicate_entries` - check generated registry artifacts for likely
|
|
41
62
|
duplicates before opening a submission.
|
|
42
|
-
- `build_submission_urls` - build prefilled HeyClaude submit and
|
|
43
|
-
|
|
63
|
+
- `build_submission_urls` - build prefilled HeyClaude submit and review URLs for human
|
|
64
|
+
review.
|
|
44
65
|
- `get_category_submission_guidance` - fetch category-specific contribution
|
|
45
66
|
guidance and required fields.
|
|
67
|
+
- `prepare_submission_draft` - normalize and validate fields, then return a
|
|
68
|
+
canonical PR draft plus prefilled submit URL.
|
|
69
|
+
- `get_submission_examples` - fetch category-specific example fields and
|
|
70
|
+
templates for more complete submissions.
|
|
71
|
+
- `review_submission_draft` - review schema errors, duplicate risk, and
|
|
72
|
+
maintainer checklist items before a submission PR is opened.
|
|
73
|
+
- `get_submission_policy` - fetch the read-only submission, artifact, import,
|
|
74
|
+
and maintainer-review policy.
|
|
75
|
+
- `explain_entry_trust` - explain source, package, safety, privacy, and review
|
|
76
|
+
metadata signals for one entry. This is a metadata review only and does not
|
|
77
|
+
provide malware scanning, automatic safety guarantees, or installation approval.
|
|
78
|
+
- `review_entry_safety` - compare 1-5 entries for source, package, safety, and
|
|
79
|
+
privacy metadata fit before install or recommendation. This is a metadata review
|
|
80
|
+
only and does not provide malware scanning, automatic safety guarantees, or
|
|
81
|
+
installation approval.
|
|
82
|
+
|
|
83
|
+
## Resources and Prompts
|
|
84
|
+
|
|
85
|
+
The server also exposes read-only MCP resources:
|
|
86
|
+
|
|
87
|
+
- `heyclaude://feeds/directory`
|
|
88
|
+
- `heyclaude://category/{category}`
|
|
89
|
+
- `heyclaude://entry/{category}/{slug}`
|
|
90
|
+
|
|
91
|
+
Workflow prompts are available for common client flows:
|
|
92
|
+
|
|
93
|
+
- `find_best_asset`
|
|
94
|
+
- `prepare_submission`
|
|
95
|
+
- `review_submission_before_pr`
|
|
96
|
+
- `install_asset_safely`
|
|
46
97
|
|
|
47
98
|
## Local Stdio
|
|
48
99
|
|
|
@@ -123,8 +174,17 @@ checks the HTTP guards used by the remote route.
|
|
|
123
174
|
- Submission helpers generate URLs and validation reports only.
|
|
124
175
|
- No GitHub OAuth, tokens, issue creation, PR creation, or repo writes.
|
|
125
176
|
- No local project-file writes or config mutations.
|
|
126
|
-
- Remote endpoint
|
|
127
|
-
|
|
177
|
+
- Remote endpoint requires JSON POST bodies, rejects payloads above 64 KiB, and
|
|
178
|
+
uses the dedicated `API_MCP_RATE_LIMIT` Cloudflare binding at
|
|
179
|
+
60 requests/minute/IP in production.
|
|
180
|
+
- Submission tools prepare review drafts only; the MCP server does not perform
|
|
181
|
+
GitHub writes or publish submitted content.
|
|
182
|
+
- Source-backed, content-only PRs may be merged automatically after content
|
|
183
|
+
validation, Superagent, and private maintainer-agent review pass. Platform,
|
|
184
|
+
workflow, package, and generated-artifact changes are never auto-merged by
|
|
185
|
+
this path.
|
|
186
|
+
- Community ZIP/MCPB artifacts are review/quarantine material only. Public
|
|
187
|
+
HeyClaude-hosted downloads are maintainer-built package artifacts.
|
|
128
188
|
|
|
129
189
|
## npm Release Prep
|
|
130
190
|
|
|
@@ -139,10 +199,10 @@ Do not publish until the web branch has shipped, the production endpoint has
|
|
|
139
199
|
been verified, and the package smoke test passes. The release checklist is:
|
|
140
200
|
|
|
141
201
|
```bash
|
|
142
|
-
pnpm validate:mcp-endpoint -- --url https://heyclau.de/api/mcp
|
|
202
|
+
MCP_ENDPOINT_REQUIRE_SAFETY_METADATA=1 pnpm validate:mcp-endpoint -- --url https://heyclau.de/api/mcp --strict-tools
|
|
143
203
|
pnpm --filter @heyclaude/mcp test
|
|
144
204
|
pnpm --filter @heyclaude/mcp pack --dry-run
|
|
145
|
-
MCP_PACKAGE_REMOTE_SMOKE_URL=https://heyclau.de/api/mcp pnpm validate:mcp-package
|
|
205
|
+
MCP_PACKAGE_REQUIRE_SAFETY_METADATA=1 MCP_PACKAGE_REMOTE_SMOKE_URL=https://heyclau.de/api/mcp pnpm validate:mcp-package
|
|
146
206
|
```
|
|
147
207
|
|
|
148
208
|
Publishing should happen through the manual `Publish MCP Package` GitHub
|
package/package.json
CHANGED
|
@@ -6,12 +6,34 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
6
6
|
import { normalizeEndpointUrl } from "../src/endpoint-url.js";
|
|
7
7
|
import { READ_ONLY_TOOL_NAMES } from "../src/registry.js";
|
|
8
8
|
|
|
9
|
+
const baselineToolNames = [
|
|
10
|
+
"search_registry",
|
|
11
|
+
"get_entry_detail",
|
|
12
|
+
"get_compatibility",
|
|
13
|
+
"get_install_guidance",
|
|
14
|
+
"get_platform_adapter",
|
|
15
|
+
"list_distribution_feeds",
|
|
16
|
+
"get_submission_schema",
|
|
17
|
+
"validate_submission_draft",
|
|
18
|
+
"search_duplicate_entries",
|
|
19
|
+
"build_submission_urls",
|
|
20
|
+
"get_category_submission_guidance",
|
|
21
|
+
];
|
|
22
|
+
|
|
9
23
|
function parseArgs(argv) {
|
|
10
24
|
const args = new Map();
|
|
11
25
|
for (let index = 0; index < argv.length; index += 1) {
|
|
12
26
|
const value = argv[index];
|
|
13
27
|
if (value === "--") continue;
|
|
14
28
|
if (!value.startsWith("--")) continue;
|
|
29
|
+
if (value === "--strict-tools") {
|
|
30
|
+
args.set("strict-tools", "1");
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (value === "--require-safety-metadata") {
|
|
34
|
+
args.set("require-safety-metadata", "1");
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
15
37
|
args.set(value.slice(2), argv[index + 1] ?? "");
|
|
16
38
|
index += 1;
|
|
17
39
|
}
|
|
@@ -28,6 +50,40 @@ function assert(condition, message) {
|
|
|
28
50
|
if (!condition) throw new Error(message);
|
|
29
51
|
}
|
|
30
52
|
|
|
53
|
+
function assertSubmitUrl(value) {
|
|
54
|
+
let url;
|
|
55
|
+
try {
|
|
56
|
+
url = new URL(String(value || ""));
|
|
57
|
+
} catch {
|
|
58
|
+
throw new Error(
|
|
59
|
+
"build_submission_urls did not return an absolute submit URL.",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
assert(
|
|
63
|
+
url.protocol === "https:",
|
|
64
|
+
"build_submission_urls submit URL must use HTTPS.",
|
|
65
|
+
);
|
|
66
|
+
assert(
|
|
67
|
+
url.origin === "https://heyclau.de",
|
|
68
|
+
"build_submission_urls submit URL used the wrong origin.",
|
|
69
|
+
);
|
|
70
|
+
assert(
|
|
71
|
+
url.pathname === "/submit",
|
|
72
|
+
"build_submission_urls did not return the submit URL.",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function assertSafetyMetadataShape(payload, label) {
|
|
77
|
+
assert(
|
|
78
|
+
Array.isArray(payload?.safetyNotes),
|
|
79
|
+
`${label} did not expose safetyNotes as an array.`,
|
|
80
|
+
);
|
|
81
|
+
assert(
|
|
82
|
+
Array.isArray(payload?.privacyNotes),
|
|
83
|
+
`${label} did not expose privacyNotes as an array.`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
31
87
|
async function validateHttpGuards(endpointUrl) {
|
|
32
88
|
const options = await fetch(endpointUrl, { method: "OPTIONS" });
|
|
33
89
|
assert(options.status === 204, `OPTIONS returned ${options.status}`);
|
|
@@ -75,7 +131,24 @@ async function validateHttpGuards(endpointUrl) {
|
|
|
75
131
|
);
|
|
76
132
|
}
|
|
77
133
|
|
|
78
|
-
|
|
134
|
+
function validateToolList(toolNames, strictTools) {
|
|
135
|
+
if (strictTools) {
|
|
136
|
+
assert(
|
|
137
|
+
JSON.stringify(toolNames) === JSON.stringify(READ_ONLY_TOOL_NAMES),
|
|
138
|
+
`Unexpected tool list: ${toolNames.join(", ")}`,
|
|
139
|
+
);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const toolName of baselineToolNames) {
|
|
144
|
+
assert(
|
|
145
|
+
toolNames.includes(toolName),
|
|
146
|
+
`Deployed MCP endpoint is missing baseline tool: ${toolName}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function validateMcpTools(endpointUrl, options = {}) {
|
|
79
152
|
const client = new Client({
|
|
80
153
|
name: "heyclaude-endpoint-validator",
|
|
81
154
|
version: "0.1.0",
|
|
@@ -87,10 +160,49 @@ async function validateMcpTools(endpointUrl) {
|
|
|
87
160
|
|
|
88
161
|
const tools = await client.listTools();
|
|
89
162
|
const toolNames = tools.tools.map((tool) => tool.name);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
163
|
+
validateToolList(toolNames, options.strictTools);
|
|
164
|
+
const hasVersionTwoSurface = toolNames.includes("get_registry_stats");
|
|
165
|
+
if (hasVersionTwoSurface) {
|
|
166
|
+
assert(
|
|
167
|
+
tools.tools.every((tool) => tool.annotations?.readOnlyHint === true),
|
|
168
|
+
"All HeyClaude MCP tools must advertise read-only annotations.",
|
|
169
|
+
);
|
|
170
|
+
assert(
|
|
171
|
+
tools.tools.every((tool) => tool.outputSchema?.type === "object"),
|
|
172
|
+
"All HeyClaude MCP tools must expose object output schemas.",
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const resources = await client.listResources();
|
|
176
|
+
assert(
|
|
177
|
+
resources.resources.some(
|
|
178
|
+
(resource) => resource.uri === "heyclaude://feeds/directory",
|
|
179
|
+
),
|
|
180
|
+
"MCP resources did not expose the directory feed resource.",
|
|
181
|
+
);
|
|
182
|
+
const directoryResource = await client.readResource({
|
|
183
|
+
uri: "heyclaude://feeds/directory",
|
|
184
|
+
});
|
|
185
|
+
assert(
|
|
186
|
+
directoryResource.contents?.[0]?.mimeType === "application/json",
|
|
187
|
+
"Directory resource did not return JSON content.",
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const prompts = await client.listPrompts();
|
|
191
|
+
assert(
|
|
192
|
+
prompts.prompts.some((prompt) => prompt.name === "find_best_asset"),
|
|
193
|
+
"MCP prompts did not expose find_best_asset.",
|
|
194
|
+
);
|
|
195
|
+
const installPrompt = await client.getPrompt({
|
|
196
|
+
name: "install_asset_safely",
|
|
197
|
+
arguments: { category: "mcp", slug: "example", platform: "Codex" },
|
|
198
|
+
});
|
|
199
|
+
assert(
|
|
200
|
+
installPrompt.messages?.[0]?.content?.text?.includes(
|
|
201
|
+
"get_install_guidance",
|
|
202
|
+
),
|
|
203
|
+
"install_asset_safely prompt did not mention install guidance.",
|
|
204
|
+
);
|
|
205
|
+
}
|
|
94
206
|
|
|
95
207
|
const search = parseToolResult(
|
|
96
208
|
await client.callTool({
|
|
@@ -103,6 +215,58 @@ async function validateMcpTools(endpointUrl) {
|
|
|
103
215
|
Array.isArray(search.entries) && search.entries.length > 0,
|
|
104
216
|
"search_registry did not return entries.",
|
|
105
217
|
);
|
|
218
|
+
if (options.requireSafetyMetadata) {
|
|
219
|
+
assertSafetyMetadataShape(
|
|
220
|
+
search.entries[0],
|
|
221
|
+
"search_registry first entry",
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (toolNames.includes("server_info")) {
|
|
226
|
+
const info = parseToolResult(
|
|
227
|
+
await client.callTool({
|
|
228
|
+
name: "server_info",
|
|
229
|
+
arguments: {},
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
assert(info.ok === true, "server_info did not return ok.");
|
|
233
|
+
assert(
|
|
234
|
+
info.endpoint?.auth === "none",
|
|
235
|
+
"server_info did not expose the public no-key access model.",
|
|
236
|
+
);
|
|
237
|
+
assert(
|
|
238
|
+
info.endpoint?.rateLimit?.binding === "API_MCP_RATE_LIMIT",
|
|
239
|
+
"server_info did not expose the MCP rate-limit binding.",
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (toolNames.includes("list_category_entries")) {
|
|
244
|
+
const listed = parseToolResult(
|
|
245
|
+
await client.callTool({
|
|
246
|
+
name: "list_category_entries",
|
|
247
|
+
arguments: { category: "mcp", limit: 2 },
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
assert(listed.ok === true, "list_category_entries did not return ok.");
|
|
251
|
+
assert(
|
|
252
|
+
Array.isArray(listed.entries) && listed.entries.length > 0,
|
|
253
|
+
"list_category_entries did not return entries.",
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (toolNames.includes("get_registry_stats")) {
|
|
258
|
+
const stats = parseToolResult(
|
|
259
|
+
await client.callTool({
|
|
260
|
+
name: "get_registry_stats",
|
|
261
|
+
arguments: {},
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
assert(stats.ok === true, "get_registry_stats did not return ok.");
|
|
265
|
+
assert(
|
|
266
|
+
stats.policy?.readOnly === true,
|
|
267
|
+
"get_registry_stats did not expose the no-write policy.",
|
|
268
|
+
);
|
|
269
|
+
}
|
|
106
270
|
|
|
107
271
|
const first = search.entries[0];
|
|
108
272
|
const detail = parseToolResult(
|
|
@@ -116,6 +280,23 @@ async function validateMcpTools(endpointUrl) {
|
|
|
116
280
|
detail.key === `${first.category}:${first.slug}`,
|
|
117
281
|
"get_entry_detail returned the wrong entry.",
|
|
118
282
|
);
|
|
283
|
+
if (options.requireSafetyMetadata) {
|
|
284
|
+
assertSafetyMetadataShape(detail.entry, "get_entry_detail entry");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (toolNames.includes("get_copyable_asset")) {
|
|
288
|
+
const asset = parseToolResult(
|
|
289
|
+
await client.callTool({
|
|
290
|
+
name: "get_copyable_asset",
|
|
291
|
+
arguments: { category: first.category, slug: first.slug },
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
294
|
+
assert(asset.ok === true, "get_copyable_asset did not return ok.");
|
|
295
|
+
assert(
|
|
296
|
+
asset.primaryAsset || asset.assets?.length,
|
|
297
|
+
"get_copyable_asset did not return any copyable asset.",
|
|
298
|
+
);
|
|
299
|
+
}
|
|
119
300
|
|
|
120
301
|
const feeds = parseToolResult(
|
|
121
302
|
await client.callTool({
|
|
@@ -137,9 +318,20 @@ async function validateMcpTools(endpointUrl) {
|
|
|
137
318
|
);
|
|
138
319
|
assert(schema.ok === true, "get_submission_schema did not return ok.");
|
|
139
320
|
assert(
|
|
140
|
-
schema.
|
|
141
|
-
"get_submission_schema did not return
|
|
321
|
+
schema.prIntake?.mode === "github_app_user_fork_pr",
|
|
322
|
+
"get_submission_schema did not return PR-first intake metadata.",
|
|
142
323
|
);
|
|
324
|
+
if (options.requireSafetyMetadata) {
|
|
325
|
+
const fieldIds = schema.schema?.fields?.map((field) => field.id) || [];
|
|
326
|
+
assert(
|
|
327
|
+
fieldIds.includes("safety_notes"),
|
|
328
|
+
"get_submission_schema did not expose safety_notes.",
|
|
329
|
+
);
|
|
330
|
+
assert(
|
|
331
|
+
fieldIds.includes("privacy_notes"),
|
|
332
|
+
"get_submission_schema did not expose privacy_notes.",
|
|
333
|
+
);
|
|
334
|
+
}
|
|
143
335
|
|
|
144
336
|
const urls = parseToolResult(
|
|
145
337
|
await client.callTool({
|
|
@@ -158,10 +350,36 @@ async function validateMcpTools(endpointUrl) {
|
|
|
158
350
|
}),
|
|
159
351
|
);
|
|
160
352
|
assert(urls.ok === true, "build_submission_urls did not return ok.");
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
353
|
+
assertSubmitUrl(urls.submitUrl);
|
|
354
|
+
|
|
355
|
+
if (toolNames.includes("prepare_submission_draft")) {
|
|
356
|
+
const prepared = parseToolResult(
|
|
357
|
+
await client.callTool({
|
|
358
|
+
name: "prepare_submission_draft",
|
|
359
|
+
arguments: {
|
|
360
|
+
fields: {
|
|
361
|
+
category: "mcp",
|
|
362
|
+
name: "Endpoint Validation MCP",
|
|
363
|
+
docs_url: "https://example.com/docs",
|
|
364
|
+
description:
|
|
365
|
+
"Endpoint validation draft for the HeyClaude MCP submission helpers.",
|
|
366
|
+
install_command: "npx -y endpoint-validation-mcp",
|
|
367
|
+
usage_snippet: "Use this draft to validate MCP route behavior.",
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
}),
|
|
371
|
+
);
|
|
372
|
+
assert(
|
|
373
|
+
prepared.prDraft?.body,
|
|
374
|
+
"prepare_submission_draft did not return a canonical PR draft body.",
|
|
375
|
+
);
|
|
376
|
+
assert(
|
|
377
|
+
String(prepared.submissionPolicy || "").includes(
|
|
378
|
+
"may be merged automatically",
|
|
379
|
+
),
|
|
380
|
+
"prepare_submission_draft did not expose the maintainer-reviewed policy.",
|
|
381
|
+
);
|
|
382
|
+
}
|
|
165
383
|
|
|
166
384
|
const invalid = parseToolResult(
|
|
167
385
|
await client.callTool({
|
|
@@ -182,6 +400,12 @@ async function validateMcpTools(endpointUrl) {
|
|
|
182
400
|
const args = parseArgs(process.argv.slice(2));
|
|
183
401
|
const endpointUrlRaw = args.get("url") || process.env.MCP_ENDPOINT_URL;
|
|
184
402
|
const endpointUrl = endpointUrlRaw ? normalizeEndpointUrl(endpointUrlRaw) : "";
|
|
403
|
+
const strictTools =
|
|
404
|
+
args.get("strict-tools") === "1" ||
|
|
405
|
+
process.env.MCP_ENDPOINT_STRICT_TOOLS === "1";
|
|
406
|
+
const requireSafetyMetadata =
|
|
407
|
+
args.get("require-safety-metadata") === "1" ||
|
|
408
|
+
process.env.MCP_ENDPOINT_REQUIRE_SAFETY_METADATA === "1";
|
|
185
409
|
|
|
186
410
|
if (!endpointUrl) {
|
|
187
411
|
console.error(
|
|
@@ -192,7 +416,7 @@ if (!endpointUrl) {
|
|
|
192
416
|
|
|
193
417
|
try {
|
|
194
418
|
await validateHttpGuards(endpointUrl);
|
|
195
|
-
await validateMcpTools(endpointUrl);
|
|
419
|
+
await validateMcpTools(endpointUrl, { strictTools, requireSafetyMetadata });
|
|
196
420
|
console.log(`Validated HeyClaude MCP endpoint at ${endpointUrl.toString()}`);
|
|
197
421
|
} catch (error) {
|
|
198
422
|
console.error(error instanceof Error ? error.message : String(error));
|
package/src/package-metadata.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
export const packageName = String(packageJson.name || "@heyclaude/mcp");
|
|
7
|
-
export const packageVersion = String(packageJson.version || "0.0.0");
|
|
1
|
+
// Keep this Worker-safe: Cloudflare's bundle loader rejects runtime
|
|
2
|
+
// package.json specifiers inside the SSR/MCP route bundle.
|
|
3
|
+
export const packageName = "@heyclaude/mcp";
|
|
4
|
+
export const packageVersion = "0.3.0";
|
package/src/registry.d.ts
CHANGED
|
@@ -14,18 +14,90 @@ export const TOOL_DEFINITIONS: Array<{
|
|
|
14
14
|
name: string;
|
|
15
15
|
description: string;
|
|
16
16
|
inputSchema: Record<string, unknown>;
|
|
17
|
+
outputSchema: Record<string, unknown>;
|
|
18
|
+
annotations: Record<string, unknown>;
|
|
17
19
|
}>;
|
|
18
20
|
|
|
21
|
+
export const MCP_PUBLIC_POLICY: Record<string, unknown>;
|
|
22
|
+
export const RESOURCE_TEMPLATES: Array<Record<string, unknown>>;
|
|
23
|
+
export const PROMPT_DEFINITIONS: Array<Record<string, unknown>>;
|
|
24
|
+
|
|
19
25
|
export function searchRegistry(
|
|
20
26
|
args?: Record<string, unknown>,
|
|
21
27
|
options?: RegistryArtifactLoaders,
|
|
22
28
|
): Promise<RegistryToolResult>;
|
|
23
29
|
|
|
30
|
+
export function planWorkflowToolbox(
|
|
31
|
+
args?: Record<string, unknown>,
|
|
32
|
+
options?: RegistryArtifactLoaders,
|
|
33
|
+
): Promise<RegistryToolResult>;
|
|
34
|
+
|
|
35
|
+
export function getServerInfo(
|
|
36
|
+
args?: Record<string, unknown>,
|
|
37
|
+
options?: RegistryArtifactLoaders,
|
|
38
|
+
): Promise<RegistryToolResult>;
|
|
39
|
+
|
|
40
|
+
export function listCategoryEntries(
|
|
41
|
+
args?: Record<string, unknown>,
|
|
42
|
+
options?: RegistryArtifactLoaders,
|
|
43
|
+
): Promise<RegistryToolResult>;
|
|
44
|
+
|
|
45
|
+
export function getRecentUpdates(
|
|
46
|
+
args?: Record<string, unknown>,
|
|
47
|
+
options?: RegistryArtifactLoaders,
|
|
48
|
+
): Promise<RegistryToolResult>;
|
|
49
|
+
|
|
50
|
+
export function getRelatedEntries(
|
|
51
|
+
args?: Record<string, unknown>,
|
|
52
|
+
options?: RegistryArtifactLoaders,
|
|
53
|
+
): Promise<RegistryToolResult>;
|
|
54
|
+
|
|
24
55
|
export function getEntryDetail(
|
|
25
56
|
args?: Record<string, unknown>,
|
|
26
57
|
options?: RegistryArtifactLoaders,
|
|
27
58
|
): Promise<RegistryToolResult>;
|
|
28
59
|
|
|
60
|
+
export function getCopyableAsset(
|
|
61
|
+
args?: Record<string, unknown>,
|
|
62
|
+
options?: RegistryArtifactLoaders,
|
|
63
|
+
): Promise<RegistryToolResult>;
|
|
64
|
+
|
|
65
|
+
export function compareEntries(
|
|
66
|
+
args?: Record<string, unknown>,
|
|
67
|
+
options?: RegistryArtifactLoaders,
|
|
68
|
+
): Promise<RegistryToolResult>;
|
|
69
|
+
|
|
70
|
+
export function getRegistryStats(
|
|
71
|
+
args?: Record<string, unknown>,
|
|
72
|
+
options?: RegistryArtifactLoaders,
|
|
73
|
+
): Promise<RegistryToolResult>;
|
|
74
|
+
|
|
75
|
+
export function getClientSetup(
|
|
76
|
+
args?: Record<string, unknown>,
|
|
77
|
+
options?: RegistryArtifactLoaders,
|
|
78
|
+
): Promise<RegistryToolResult>;
|
|
79
|
+
|
|
80
|
+
export function listRegistryResources(
|
|
81
|
+
args?: Record<string, unknown>,
|
|
82
|
+
options?: RegistryArtifactLoaders,
|
|
83
|
+
): Promise<Record<string, unknown>>;
|
|
84
|
+
|
|
85
|
+
export function listRegistryResourceTemplates(): Record<string, unknown>;
|
|
86
|
+
|
|
87
|
+
export function readRegistryResource(
|
|
88
|
+
args?: Record<string, unknown>,
|
|
89
|
+
options?: RegistryArtifactLoaders & {
|
|
90
|
+
publicApiBaseUrl?: string;
|
|
91
|
+
fetchPublicApi?: (apiPath: string) => Promise<unknown>;
|
|
92
|
+
},
|
|
93
|
+
): Promise<Record<string, unknown>>;
|
|
94
|
+
|
|
95
|
+
export function listRegistryPrompts(): Record<string, unknown>;
|
|
96
|
+
|
|
97
|
+
export function getRegistryPrompt(
|
|
98
|
+
args?: Record<string, unknown>,
|
|
99
|
+
): Record<string, unknown>;
|
|
100
|
+
|
|
29
101
|
export function getCompatibility(
|
|
30
102
|
args?: Record<string, unknown>,
|
|
31
103
|
options?: RegistryArtifactLoaders,
|
|
@@ -71,6 +143,57 @@ export function getCategorySubmissionGuidance(
|
|
|
71
143
|
options?: RegistryArtifactLoaders,
|
|
72
144
|
): Promise<RegistryToolResult>;
|
|
73
145
|
|
|
146
|
+
export function prepareSubmissionDraft(
|
|
147
|
+
args?: Record<string, unknown>,
|
|
148
|
+
options?: RegistryArtifactLoaders,
|
|
149
|
+
): Promise<RegistryToolResult>;
|
|
150
|
+
|
|
151
|
+
export function getSubmissionExamples(
|
|
152
|
+
args?: Record<string, unknown>,
|
|
153
|
+
options?: RegistryArtifactLoaders,
|
|
154
|
+
): Promise<RegistryToolResult>;
|
|
155
|
+
|
|
156
|
+
export function reviewSubmissionDraft(
|
|
157
|
+
args?: Record<string, unknown>,
|
|
158
|
+
options?: RegistryArtifactLoaders,
|
|
159
|
+
): Promise<RegistryToolResult>;
|
|
160
|
+
|
|
161
|
+
export function getSubmissionPolicy(
|
|
162
|
+
args?: Record<string, unknown>,
|
|
163
|
+
options?: RegistryArtifactLoaders,
|
|
164
|
+
): Promise<RegistryToolResult>;
|
|
165
|
+
|
|
166
|
+
export function explainEntryTrust(
|
|
167
|
+
args?: Record<string, unknown>,
|
|
168
|
+
options?: RegistryArtifactLoaders,
|
|
169
|
+
): Promise<RegistryToolResult>;
|
|
170
|
+
|
|
171
|
+
export function reviewEntrySafety(
|
|
172
|
+
args?: Record<string, unknown>,
|
|
173
|
+
options?: RegistryArtifactLoaders,
|
|
174
|
+
): Promise<RegistryToolResult>;
|
|
175
|
+
|
|
176
|
+
export function listRegistryRecent(
|
|
177
|
+
options?: RegistryArtifactLoaders & {
|
|
178
|
+
publicApiBaseUrl?: string;
|
|
179
|
+
fetchPublicApi?: (apiPath: string) => Promise<unknown>;
|
|
180
|
+
},
|
|
181
|
+
): Promise<RegistryToolResult>;
|
|
182
|
+
|
|
183
|
+
export function listRegistryTrending(
|
|
184
|
+
options?: RegistryArtifactLoaders & {
|
|
185
|
+
publicApiBaseUrl?: string;
|
|
186
|
+
fetchPublicApi?: (apiPath: string) => Promise<unknown>;
|
|
187
|
+
},
|
|
188
|
+
): Promise<RegistryToolResult>;
|
|
189
|
+
|
|
190
|
+
export function listJobsActive(
|
|
191
|
+
options?: RegistryArtifactLoaders & {
|
|
192
|
+
publicApiBaseUrl?: string;
|
|
193
|
+
fetchPublicApi?: (apiPath: string) => Promise<unknown>;
|
|
194
|
+
},
|
|
195
|
+
): Promise<RegistryToolResult>;
|
|
196
|
+
|
|
74
197
|
export function callRegistryTool(
|
|
75
198
|
name: string,
|
|
76
199
|
args?: Record<string, unknown>,
|
|
@@ -79,7 +202,16 @@ export function callRegistryTool(
|
|
|
79
202
|
|
|
80
203
|
export {
|
|
81
204
|
SearchRegistryInputSchema,
|
|
205
|
+
PlanWorkflowToolboxInputSchema,
|
|
206
|
+
ServerInfoInputSchema,
|
|
207
|
+
ListCategoryEntriesInputSchema,
|
|
208
|
+
RecentUpdatesInputSchema,
|
|
209
|
+
RelatedEntriesInputSchema,
|
|
82
210
|
EntryDetailInputSchema,
|
|
211
|
+
CopyableAssetInputSchema,
|
|
212
|
+
CompareEntriesInputSchema,
|
|
213
|
+
RegistryStatsInputSchema,
|
|
214
|
+
ClientSetupInputSchema,
|
|
83
215
|
CompatibilityInputSchema,
|
|
84
216
|
InstallGuidanceInputSchema,
|
|
85
217
|
PlatformAdapterInputSchema,
|
|
@@ -90,8 +222,15 @@ export {
|
|
|
90
222
|
SearchDuplicateEntriesInputSchema,
|
|
91
223
|
BuildSubmissionUrlsInputSchema,
|
|
92
224
|
CategorySubmissionGuidanceInputSchema,
|
|
225
|
+
PrepareSubmissionDraftInputSchema,
|
|
226
|
+
GetSubmissionExamplesInputSchema,
|
|
227
|
+
ReviewSubmissionDraftInputSchema,
|
|
228
|
+
SubmissionPolicyInputSchema,
|
|
229
|
+
ExplainEntryTrustInputSchema,
|
|
230
|
+
ReviewEntrySafetyInputSchema,
|
|
93
231
|
TOOL_INPUT_SCHEMAS,
|
|
94
232
|
jsonSchemaForTool,
|
|
233
|
+
jsonSchemaForToolOutput,
|
|
95
234
|
parseToolArguments,
|
|
96
235
|
formatZodError,
|
|
97
236
|
} from "./schemas.js";
|