@heyclaude/mcp 0.1.1 → 0.2.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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # @heyclaude/mcp Changelog
2
2
 
3
+ ## 0.2.0 - Discovery and Submission Drafting
4
+
5
+ - Add read-only discovery tools for server metadata, paginated category
6
+ browsing, recent updates, and related entries.
7
+ - Add copyable asset, entry comparison, registry stats, and client setup tools
8
+ for richer MCP client workflows.
9
+ - Add read-only MCP resources and workflow prompts for discovery, submission
10
+ drafting, pre-issue review, and safe install guidance.
11
+ - Add submission helper tools for examples, canonical issue drafts, duplicate
12
+ review, and maintainer checklist guidance.
13
+ - Document the public no-key access model and the dedicated MCP rate-limit
14
+ policy.
15
+
16
+ ## 0.1.2 - Repository Rename
17
+
18
+ - Update package metadata, README links, and release provenance for the
19
+ `JSONbored/awesome-claude` GitHub repository.
20
+ - Keep published package behavior unchanged.
21
+
3
22
  ## 0.1.1 - Package Page Polish
4
23
 
5
24
  - Add npm-facing package README branding, repository links, npm links, and
package/README.md CHANGED
@@ -8,10 +8,10 @@
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://heyclau.de">Website</a> •
11
- <a href="https://github.com/JSONbored/claudepro-directory">GitHub</a> •
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/claudepro-directory/releases/tag/mcp-v0.1.1">v0.1.1 release</a>
14
+ <a href="https://github.com/JSONbored/awesome-claude/releases/tag/mcp-v0.2.0">v0.2.0 release</a>
15
15
  </p>
16
16
 
17
17
  Read-only Model Context Protocol server for the HeyClaude registry.
@@ -22,11 +22,32 @@ adapters, feed discovery, and safe submission-draft helpers. It does not create
22
22
  GitHub issues, open pull requests, write local files, publish content, or manage
23
23
  accounts.
24
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.
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.
@@ -43,6 +64,27 @@ accounts.
43
64
  URLs for human 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 issue title/body plus prefilled URLs.
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 issue is opened.
73
+
74
+ ## Resources and Prompts
75
+
76
+ The server also exposes read-only MCP resources:
77
+
78
+ - `heyclaude://feeds/directory`
79
+ - `heyclaude://category/{category}`
80
+ - `heyclaude://entry/{category}/{slug}`
81
+
82
+ Workflow prompts are available for common client flows:
83
+
84
+ - `find_best_asset`
85
+ - `prepare_submission`
86
+ - `review_submission_before_issue`
87
+ - `install_asset_safely`
46
88
 
47
89
  ## Local Stdio
48
90
 
@@ -123,8 +165,11 @@ checks the HTTP guards used by the remote route.
123
165
  - Submission helpers generate URLs and validation reports only.
124
166
  - No GitHub OAuth, tokens, issue creation, PR creation, or repo writes.
125
167
  - No local project-file writes or config mutations.
126
- - Remote endpoint uses route-level rate limits and Cloudflare rate-limit bindings
127
- when available.
168
+ - Remote endpoint requires JSON POST bodies, rejects payloads above 64 KiB, and
169
+ uses the dedicated `API_MCP_RATE_LIMIT` Cloudflare binding at
170
+ 60 requests/minute/IP in production.
171
+ - Submission tools prepare review drafts only; HeyClaude does not auto-publish
172
+ MCP-submitted content.
128
173
 
129
174
  ## npm Release Prep
130
175
 
@@ -133,13 +178,13 @@ semver releases. The initial public package version is `0.1.0`, and GitHub
133
178
  release tags use `mcp-vX.Y.Z`.
134
179
 
135
180
  The npm package artifact is hosted on npmjs.com. GitHub Releases track the
136
- matching package-scoped source tag and release notes, such as `mcp-v0.1.1`.
181
+ matching package-scoped source tag and release notes, such as `mcp-v0.1.2`.
137
182
 
138
183
  Do not publish until the web branch has shipped, the production endpoint has
139
184
  been verified, and the package smoke test passes. The release checklist is:
140
185
 
141
186
  ```bash
142
- pnpm validate:mcp-endpoint -- --url https://heyclau.de/api/mcp
187
+ pnpm validate:mcp-endpoint -- --url https://heyclau.de/api/mcp --strict-tools
143
188
  pnpm --filter @heyclaude/mcp test
144
189
  pnpm --filter @heyclaude/mcp pack --dry-run
145
190
  MCP_PACKAGE_REMOTE_SMOKE_URL=https://heyclau.de/api/mcp pnpm validate:mcp-package
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyclaude/mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Read-only MCP server for the HeyClaude registry.",
6
6
  "license": "MIT",
@@ -9,14 +9,15 @@
9
9
  "homepage": "https://heyclau.de",
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "git+https://github.com/JSONbored/claudepro-directory.git",
12
+ "url": "git+https://github.com/JSONbored/awesome-claude.git",
13
13
  "directory": "packages/mcp"
14
14
  },
15
15
  "bugs": {
16
- "url": "https://github.com/JSONbored/claudepro-directory/issues"
16
+ "url": "https://github.com/JSONbored/awesome-claude/issues"
17
17
  },
18
18
  "keywords": [
19
19
  "heyclaude",
20
+ "awesome-claude",
20
21
  "mcp",
21
22
  "model-context-protocol",
22
23
  "claude",
@@ -6,12 +6,30 @@ 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
+ }
15
33
  args.set(value.slice(2), argv[index + 1] ?? "");
16
34
  index += 1;
17
35
  }
@@ -75,7 +93,24 @@ async function validateHttpGuards(endpointUrl) {
75
93
  );
76
94
  }
77
95
 
78
- async function validateMcpTools(endpointUrl) {
96
+ function validateToolList(toolNames, strictTools) {
97
+ if (strictTools) {
98
+ assert(
99
+ JSON.stringify(toolNames) === JSON.stringify(READ_ONLY_TOOL_NAMES),
100
+ `Unexpected tool list: ${toolNames.join(", ")}`,
101
+ );
102
+ return;
103
+ }
104
+
105
+ for (const toolName of baselineToolNames) {
106
+ assert(
107
+ toolNames.includes(toolName),
108
+ `Deployed MCP endpoint is missing baseline tool: ${toolName}`,
109
+ );
110
+ }
111
+ }
112
+
113
+ async function validateMcpTools(endpointUrl, options = {}) {
79
114
  const client = new Client({
80
115
  name: "heyclaude-endpoint-validator",
81
116
  version: "0.1.0",
@@ -87,10 +122,49 @@ async function validateMcpTools(endpointUrl) {
87
122
 
88
123
  const tools = await client.listTools();
89
124
  const toolNames = tools.tools.map((tool) => tool.name);
90
- assert(
91
- JSON.stringify(toolNames) === JSON.stringify(READ_ONLY_TOOL_NAMES),
92
- `Unexpected tool list: ${toolNames.join(", ")}`,
93
- );
125
+ validateToolList(toolNames, options.strictTools);
126
+ const hasVersionTwoSurface = toolNames.includes("get_registry_stats");
127
+ if (hasVersionTwoSurface) {
128
+ assert(
129
+ tools.tools.every((tool) => tool.annotations?.readOnlyHint === true),
130
+ "All HeyClaude MCP tools must advertise read-only annotations.",
131
+ );
132
+ assert(
133
+ tools.tools.every((tool) => tool.outputSchema?.type === "object"),
134
+ "All HeyClaude MCP tools must expose object output schemas.",
135
+ );
136
+
137
+ const resources = await client.listResources();
138
+ assert(
139
+ resources.resources.some(
140
+ (resource) => resource.uri === "heyclaude://feeds/directory",
141
+ ),
142
+ "MCP resources did not expose the directory feed resource.",
143
+ );
144
+ const directoryResource = await client.readResource({
145
+ uri: "heyclaude://feeds/directory",
146
+ });
147
+ assert(
148
+ directoryResource.contents?.[0]?.mimeType === "application/json",
149
+ "Directory resource did not return JSON content.",
150
+ );
151
+
152
+ const prompts = await client.listPrompts();
153
+ assert(
154
+ prompts.prompts.some((prompt) => prompt.name === "find_best_asset"),
155
+ "MCP prompts did not expose find_best_asset.",
156
+ );
157
+ const installPrompt = await client.getPrompt({
158
+ name: "install_asset_safely",
159
+ arguments: { category: "mcp", slug: "example", platform: "Codex" },
160
+ });
161
+ assert(
162
+ installPrompt.messages?.[0]?.content?.text?.includes(
163
+ "get_install_guidance",
164
+ ),
165
+ "install_asset_safely prompt did not mention install guidance.",
166
+ );
167
+ }
94
168
 
95
169
  const search = parseToolResult(
96
170
  await client.callTool({
@@ -104,6 +178,52 @@ async function validateMcpTools(endpointUrl) {
104
178
  "search_registry did not return entries.",
105
179
  );
106
180
 
181
+ if (toolNames.includes("server_info")) {
182
+ const info = parseToolResult(
183
+ await client.callTool({
184
+ name: "server_info",
185
+ arguments: {},
186
+ }),
187
+ );
188
+ assert(info.ok === true, "server_info did not return ok.");
189
+ assert(
190
+ info.endpoint?.auth === "none",
191
+ "server_info did not expose the public no-key access model.",
192
+ );
193
+ assert(
194
+ info.endpoint?.rateLimit?.binding === "API_MCP_RATE_LIMIT",
195
+ "server_info did not expose the MCP rate-limit binding.",
196
+ );
197
+ }
198
+
199
+ if (toolNames.includes("list_category_entries")) {
200
+ const listed = parseToolResult(
201
+ await client.callTool({
202
+ name: "list_category_entries",
203
+ arguments: { category: "mcp", limit: 2 },
204
+ }),
205
+ );
206
+ assert(listed.ok === true, "list_category_entries did not return ok.");
207
+ assert(
208
+ Array.isArray(listed.entries) && listed.entries.length > 0,
209
+ "list_category_entries did not return entries.",
210
+ );
211
+ }
212
+
213
+ if (toolNames.includes("get_registry_stats")) {
214
+ const stats = parseToolResult(
215
+ await client.callTool({
216
+ name: "get_registry_stats",
217
+ arguments: {},
218
+ }),
219
+ );
220
+ assert(stats.ok === true, "get_registry_stats did not return ok.");
221
+ assert(
222
+ stats.policy?.readOnly === true,
223
+ "get_registry_stats did not expose the no-write policy.",
224
+ );
225
+ }
226
+
107
227
  const first = search.entries[0];
108
228
  const detail = parseToolResult(
109
229
  await client.callTool({
@@ -117,6 +237,20 @@ async function validateMcpTools(endpointUrl) {
117
237
  "get_entry_detail returned the wrong entry.",
118
238
  );
119
239
 
240
+ if (toolNames.includes("get_copyable_asset")) {
241
+ const asset = parseToolResult(
242
+ await client.callTool({
243
+ name: "get_copyable_asset",
244
+ arguments: { category: first.category, slug: first.slug },
245
+ }),
246
+ );
247
+ assert(asset.ok === true, "get_copyable_asset did not return ok.");
248
+ assert(
249
+ asset.primaryAsset || asset.assets?.length,
250
+ "get_copyable_asset did not return any copyable asset.",
251
+ );
252
+ }
253
+
120
254
  const feeds = parseToolResult(
121
255
  await client.callTool({
122
256
  name: "list_distribution_feeds",
@@ -163,6 +297,35 @@ async function validateMcpTools(endpointUrl) {
163
297
  "build_submission_urls did not return an MCP issue URL.",
164
298
  );
165
299
 
300
+ if (toolNames.includes("prepare_submission_draft")) {
301
+ const prepared = parseToolResult(
302
+ await client.callTool({
303
+ name: "prepare_submission_draft",
304
+ arguments: {
305
+ fields: {
306
+ category: "mcp",
307
+ name: "Endpoint Validation MCP",
308
+ docs_url: "https://example.com/docs",
309
+ description:
310
+ "Endpoint validation draft for the HeyClaude MCP submission helpers.",
311
+ install_command: "npx -y endpoint-validation-mcp",
312
+ usage_snippet: "Use this draft to validate MCP route behavior.",
313
+ },
314
+ },
315
+ }),
316
+ );
317
+ assert(
318
+ prepared.issueDraft?.body,
319
+ "prepare_submission_draft did not return a canonical issue body.",
320
+ );
321
+ assert(
322
+ String(prepared.submissionPolicy || "").includes(
323
+ "does not auto-publish",
324
+ ),
325
+ "prepare_submission_draft did not expose the maintainer-reviewed policy.",
326
+ );
327
+ }
328
+
166
329
  const invalid = parseToolResult(
167
330
  await client.callTool({
168
331
  name: "search_registry",
@@ -182,6 +345,9 @@ async function validateMcpTools(endpointUrl) {
182
345
  const args = parseArgs(process.argv.slice(2));
183
346
  const endpointUrlRaw = args.get("url") || process.env.MCP_ENDPOINT_URL;
184
347
  const endpointUrl = endpointUrlRaw ? normalizeEndpointUrl(endpointUrlRaw) : "";
348
+ const strictTools =
349
+ args.get("strict-tools") === "1" ||
350
+ process.env.MCP_ENDPOINT_STRICT_TOOLS === "1";
185
351
 
186
352
  if (!endpointUrl) {
187
353
  console.error(
@@ -192,7 +358,7 @@ if (!endpointUrl) {
192
358
 
193
359
  try {
194
360
  await validateHttpGuards(endpointUrl);
195
- await validateMcpTools(endpointUrl);
361
+ await validateMcpTools(endpointUrl, { strictTools });
196
362
  console.log(`Validated HeyClaude MCP endpoint at ${endpointUrl.toString()}`);
197
363
  } catch (error) {
198
364
  console.error(error instanceof Error ? error.message : String(error));
package/src/registry.d.ts CHANGED
@@ -14,18 +14,82 @@ 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 getServerInfo(
31
+ args?: Record<string, unknown>,
32
+ options?: RegistryArtifactLoaders,
33
+ ): Promise<RegistryToolResult>;
34
+
35
+ export function listCategoryEntries(
36
+ args?: Record<string, unknown>,
37
+ options?: RegistryArtifactLoaders,
38
+ ): Promise<RegistryToolResult>;
39
+
40
+ export function getRecentUpdates(
41
+ args?: Record<string, unknown>,
42
+ options?: RegistryArtifactLoaders,
43
+ ): Promise<RegistryToolResult>;
44
+
45
+ export function getRelatedEntries(
46
+ args?: Record<string, unknown>,
47
+ options?: RegistryArtifactLoaders,
48
+ ): Promise<RegistryToolResult>;
49
+
24
50
  export function getEntryDetail(
25
51
  args?: Record<string, unknown>,
26
52
  options?: RegistryArtifactLoaders,
27
53
  ): Promise<RegistryToolResult>;
28
54
 
55
+ export function getCopyableAsset(
56
+ args?: Record<string, unknown>,
57
+ options?: RegistryArtifactLoaders,
58
+ ): Promise<RegistryToolResult>;
59
+
60
+ export function compareEntries(
61
+ args?: Record<string, unknown>,
62
+ options?: RegistryArtifactLoaders,
63
+ ): Promise<RegistryToolResult>;
64
+
65
+ export function getRegistryStats(
66
+ args?: Record<string, unknown>,
67
+ options?: RegistryArtifactLoaders,
68
+ ): Promise<RegistryToolResult>;
69
+
70
+ export function getClientSetup(
71
+ args?: Record<string, unknown>,
72
+ options?: RegistryArtifactLoaders,
73
+ ): Promise<RegistryToolResult>;
74
+
75
+ export function listRegistryResources(
76
+ args?: Record<string, unknown>,
77
+ options?: RegistryArtifactLoaders,
78
+ ): Promise<Record<string, unknown>>;
79
+
80
+ export function listRegistryResourceTemplates(): Record<string, unknown>;
81
+
82
+ export function readRegistryResource(
83
+ args?: Record<string, unknown>,
84
+ options?: RegistryArtifactLoaders,
85
+ ): Promise<Record<string, unknown>>;
86
+
87
+ export function listRegistryPrompts(): Record<string, unknown>;
88
+
89
+ export function getRegistryPrompt(
90
+ args?: Record<string, unknown>,
91
+ ): Record<string, unknown>;
92
+
29
93
  export function getCompatibility(
30
94
  args?: Record<string, unknown>,
31
95
  options?: RegistryArtifactLoaders,
@@ -71,6 +135,21 @@ export function getCategorySubmissionGuidance(
71
135
  options?: RegistryArtifactLoaders,
72
136
  ): Promise<RegistryToolResult>;
73
137
 
138
+ export function prepareSubmissionDraft(
139
+ args?: Record<string, unknown>,
140
+ options?: RegistryArtifactLoaders,
141
+ ): Promise<RegistryToolResult>;
142
+
143
+ export function getSubmissionExamples(
144
+ args?: Record<string, unknown>,
145
+ options?: RegistryArtifactLoaders,
146
+ ): Promise<RegistryToolResult>;
147
+
148
+ export function reviewSubmissionDraft(
149
+ args?: Record<string, unknown>,
150
+ options?: RegistryArtifactLoaders,
151
+ ): Promise<RegistryToolResult>;
152
+
74
153
  export function callRegistryTool(
75
154
  name: string,
76
155
  args?: Record<string, unknown>,
@@ -79,7 +158,15 @@ export function callRegistryTool(
79
158
 
80
159
  export {
81
160
  SearchRegistryInputSchema,
161
+ ServerInfoInputSchema,
162
+ ListCategoryEntriesInputSchema,
163
+ RecentUpdatesInputSchema,
164
+ RelatedEntriesInputSchema,
82
165
  EntryDetailInputSchema,
166
+ CopyableAssetInputSchema,
167
+ CompareEntriesInputSchema,
168
+ RegistryStatsInputSchema,
169
+ ClientSetupInputSchema,
83
170
  CompatibilityInputSchema,
84
171
  InstallGuidanceInputSchema,
85
172
  PlatformAdapterInputSchema,
@@ -90,8 +177,12 @@ export {
90
177
  SearchDuplicateEntriesInputSchema,
91
178
  BuildSubmissionUrlsInputSchema,
92
179
  CategorySubmissionGuidanceInputSchema,
180
+ PrepareSubmissionDraftInputSchema,
181
+ GetSubmissionExamplesInputSchema,
182
+ ReviewSubmissionDraftInputSchema,
93
183
  TOOL_INPUT_SCHEMAS,
94
184
  jsonSchemaForTool,
185
+ jsonSchemaForToolOutput,
95
186
  parseToolArguments,
96
187
  formatZodError,
97
188
  } from "./schemas.js";