@heyclaude/mcp 0.1.2 → 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.
@@ -14,6 +14,26 @@ export function createRemoteMcpProxyServer(
14
14
  timeoutMs: number;
15
15
  }>;
16
16
 
17
+ export function createRemoteMcpProxyServerFromClient(
18
+ client: {
19
+ getServerCapabilities: () => Record<string, unknown> | undefined;
20
+ listTools: (...args: unknown[]) => Promise<{ tools: Array<unknown> }>;
21
+ callTool: (...args: unknown[]) => Promise<unknown>;
22
+ listResources?: (...args: unknown[]) => Promise<unknown>;
23
+ listResourceTemplates?: (...args: unknown[]) => Promise<unknown>;
24
+ readResource?: (...args: unknown[]) => Promise<unknown>;
25
+ listPrompts?: (...args: unknown[]) => Promise<unknown>;
26
+ getPrompt?: (...args: unknown[]) => Promise<unknown>;
27
+ close?: () => Promise<void>;
28
+ },
29
+ options?: RemoteProxyOptions,
30
+ ): Promise<{
31
+ server: Server;
32
+ client: unknown;
33
+ endpointUrl: URL;
34
+ timeoutMs: number;
35
+ }>;
36
+
17
37
  export function runRemoteStdioProxy(
18
38
  options?: RemoteProxyOptions,
19
39
  ): Promise<void>;
@@ -4,12 +4,17 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
  import {
6
6
  CallToolRequestSchema,
7
+ GetPromptRequestSchema,
7
8
  ListToolsRequestSchema,
9
+ ListPromptsRequestSchema,
10
+ ListResourcesRequestSchema,
11
+ ListResourceTemplatesRequestSchema,
12
+ ReadResourceRequestSchema,
8
13
  } from "@modelcontextprotocol/sdk/types.js";
9
14
 
10
15
  import { normalizeEndpointUrl, normalizeTimeoutMs } from "./endpoint-url.js";
11
16
  import { packageVersion } from "./package-metadata.js";
12
- import { READ_ONLY_TOOL_NAMES, TOOL_DEFINITIONS } from "./registry.js";
17
+ import { MCP_PUBLIC_POLICY, READ_ONLY_TOOL_NAMES } from "./registry.js";
13
18
 
14
19
  function toError(error) {
15
20
  if (error instanceof Error) return error;
@@ -34,7 +39,11 @@ function createTimeoutFetch(timeoutMs) {
34
39
  }
35
40
 
36
41
  try {
37
- return await fetch(url, { ...init, signal: controller.signal });
42
+ return await fetch(url, {
43
+ ...init,
44
+ redirect: "error",
45
+ signal: controller.signal,
46
+ });
38
47
  } finally {
39
48
  clearTimeout(timeout);
40
49
  if (inputSignal) {
@@ -45,86 +54,146 @@ function createTimeoutFetch(timeoutMs) {
45
54
  }
46
55
 
47
56
  function invalidToolResult(name) {
57
+ const structuredContent = {
58
+ ok: false,
59
+ error: {
60
+ code: "invalid_request",
61
+ message: `Unknown or unsupported HeyClaude MCP tool: ${name}`,
62
+ },
63
+ policy: MCP_PUBLIC_POLICY,
64
+ };
48
65
  return {
49
66
  isError: true,
67
+ structuredContent,
50
68
  content: [
51
69
  {
52
70
  type: "text",
53
- text: JSON.stringify(
54
- {
55
- ok: false,
56
- error: {
57
- code: "invalid_request",
58
- message: `Unknown or unsupported HeyClaude MCP tool: ${name}`,
59
- },
60
- },
61
- null,
62
- 2,
63
- ),
71
+ text: JSON.stringify(structuredContent, null, 2),
64
72
  },
65
73
  ],
66
74
  };
67
75
  }
68
76
 
69
77
  function errorToolResult(error) {
78
+ const structuredContent = {
79
+ ok: false,
80
+ error: {
81
+ code: "remote_mcp_error",
82
+ message: safeErrorMessage(error),
83
+ },
84
+ policy: MCP_PUBLIC_POLICY,
85
+ };
70
86
  return {
71
87
  isError: true,
88
+ structuredContent,
72
89
  content: [
73
90
  {
74
91
  type: "text",
75
- text: JSON.stringify(
76
- {
77
- ok: false,
78
- error: {
79
- code: "remote_mcp_error",
80
- message: safeErrorMessage(error),
81
- },
82
- },
83
- null,
84
- 2,
85
- ),
92
+ text: JSON.stringify(structuredContent, null, 2),
86
93
  },
87
94
  ],
88
95
  };
89
96
  }
90
97
 
91
- export async function createRemoteMcpProxyServer(options = {}) {
98
+ function readOnlyToolDefinition(tool) {
99
+ if (!READ_ONLY_TOOL_NAMES.includes(tool?.name)) return null;
100
+ return {
101
+ ...tool,
102
+ annotations: {
103
+ ...(tool.annotations || {}),
104
+ readOnlyHint: true,
105
+ destructiveHint: false,
106
+ idempotentHint: true,
107
+ openWorldHint: false,
108
+ },
109
+ };
110
+ }
111
+
112
+ function parseTextToolPayload(result) {
113
+ const text = result?.content?.find((item) => item.type === "text")?.text;
114
+ if (!text) return null;
115
+ try {
116
+ const parsed = JSON.parse(text);
117
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
118
+ ? parsed
119
+ : null;
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
124
+
125
+ function withPolicy(payload) {
126
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
127
+ return payload;
128
+ }
129
+ if (payload.policy) return payload;
130
+ return { ...payload, policy: MCP_PUBLIC_POLICY };
131
+ }
132
+
133
+ function normalizeForwardedToolResult(result) {
134
+ if (!result || typeof result !== "object") return result;
135
+ if (result.structuredContent) {
136
+ return {
137
+ ...result,
138
+ structuredContent: withPolicy(result.structuredContent),
139
+ };
140
+ }
141
+
142
+ const parsed = parseTextToolPayload(result);
143
+ if (parsed) {
144
+ return {
145
+ ...result,
146
+ structuredContent: withPolicy(parsed),
147
+ };
148
+ }
149
+
150
+ return {
151
+ ...result,
152
+ structuredContent: {
153
+ ok: result.isError !== true,
154
+ policy: MCP_PUBLIC_POLICY,
155
+ },
156
+ };
157
+ }
158
+
159
+ export async function createRemoteMcpProxyServerFromClient(
160
+ client,
161
+ options = {},
162
+ ) {
92
163
  const endpointUrl = normalizeEndpointUrl(options.url);
93
164
  const timeoutMs = normalizeTimeoutMs(options.timeoutMs);
94
- const client = new Client({
95
- name: "heyclaude-mcp-stdio-proxy",
96
- version: packageVersion,
97
- });
98
- const remoteTransport = new StreamableHTTPClientTransport(endpointUrl, {
99
- fetch: createTimeoutFetch(timeoutMs),
100
- });
101
-
102
- await client.connect(remoteTransport, { timeout: timeoutMs });
165
+ const remoteCapabilities = client.getServerCapabilities() || {};
166
+ const remoteTools = await client.listTools(undefined, { timeout: timeoutMs });
167
+ const toolDefinitions = remoteTools.tools
168
+ .map(readOnlyToolDefinition)
169
+ .filter(Boolean);
170
+ const supportedToolNames = new Set(toolDefinitions.map((tool) => tool.name));
171
+ const capabilities = {
172
+ tools: {},
173
+ ...(remoteCapabilities.resources ? { resources: {} } : {}),
174
+ ...(remoteCapabilities.prompts ? { prompts: {} } : {}),
175
+ };
103
176
 
104
177
  const server = new Server(
105
178
  {
106
179
  name: "heyclaude-registry",
107
180
  version: packageVersion,
108
181
  },
109
- {
110
- capabilities: {
111
- tools: {},
112
- },
113
- },
182
+ { capabilities },
114
183
  );
115
184
 
116
185
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
117
- tools: TOOL_DEFINITIONS,
186
+ tools: toolDefinitions,
118
187
  }));
119
188
 
120
189
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
121
190
  const name = request.params.name;
122
- if (!READ_ONLY_TOOL_NAMES.includes(name)) {
191
+ if (!supportedToolNames.has(name)) {
123
192
  return invalidToolResult(name);
124
193
  }
125
194
 
126
195
  try {
127
- return await client.callTool(
196
+ const result = await client.callTool(
128
197
  {
129
198
  name,
130
199
  arguments: request.params.arguments || {},
@@ -132,11 +201,37 @@ export async function createRemoteMcpProxyServer(options = {}) {
132
201
  undefined,
133
202
  { timeout: timeoutMs },
134
203
  );
204
+ return normalizeForwardedToolResult(result);
135
205
  } catch (error) {
136
206
  return errorToolResult(error);
137
207
  }
138
208
  });
139
209
 
210
+ if (remoteCapabilities.resources) {
211
+ server.setRequestHandler(ListResourcesRequestSchema, async (request) =>
212
+ client.listResources(request.params || {}, { timeout: timeoutMs }),
213
+ );
214
+ server.setRequestHandler(
215
+ ListResourceTemplatesRequestSchema,
216
+ async (request) =>
217
+ client.listResourceTemplates(request.params || {}, {
218
+ timeout: timeoutMs,
219
+ }),
220
+ );
221
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) =>
222
+ client.readResource(request.params || {}, { timeout: timeoutMs }),
223
+ );
224
+ }
225
+
226
+ if (remoteCapabilities.prompts) {
227
+ server.setRequestHandler(ListPromptsRequestSchema, async (request) =>
228
+ client.listPrompts(request.params || {}, { timeout: timeoutMs }),
229
+ );
230
+ server.setRequestHandler(GetPromptRequestSchema, async (request) =>
231
+ client.getPrompt(request.params || {}, { timeout: timeoutMs }),
232
+ );
233
+ }
234
+
140
235
  server.onclose = () => {
141
236
  client.close().catch(() => {});
142
237
  };
@@ -144,6 +239,24 @@ export async function createRemoteMcpProxyServer(options = {}) {
144
239
  return { server, client, endpointUrl, timeoutMs };
145
240
  }
146
241
 
242
+ export async function createRemoteMcpProxyServer(options = {}) {
243
+ const endpointUrl = normalizeEndpointUrl(options.url);
244
+ const timeoutMs = normalizeTimeoutMs(options.timeoutMs);
245
+ const client = new Client({
246
+ name: "heyclaude-mcp-stdio-proxy",
247
+ version: packageVersion,
248
+ });
249
+ const remoteTransport = new StreamableHTTPClientTransport(endpointUrl, {
250
+ fetch: createTimeoutFetch(timeoutMs),
251
+ });
252
+
253
+ await client.connect(remoteTransport, { timeout: timeoutMs });
254
+ return createRemoteMcpProxyServerFromClient(client, {
255
+ url: endpointUrl,
256
+ timeoutMs,
257
+ });
258
+ }
259
+
147
260
  export async function runRemoteStdioProxy(options = {}) {
148
261
  const { server } = await createRemoteMcpProxyServer(options);
149
262
  const transport = new StdioServerTransport();
package/src/schemas.d.ts CHANGED
@@ -1,7 +1,15 @@
1
1
  import type { z } from "zod";
2
2
 
3
3
  export const SearchRegistryInputSchema: z.ZodType;
4
+ export const ServerInfoInputSchema: z.ZodType;
5
+ export const ListCategoryEntriesInputSchema: z.ZodType;
6
+ export const RecentUpdatesInputSchema: z.ZodType;
7
+ export const RelatedEntriesInputSchema: z.ZodType;
4
8
  export const EntryDetailInputSchema: z.ZodType;
9
+ export const CopyableAssetInputSchema: z.ZodType;
10
+ export const CompareEntriesInputSchema: z.ZodType;
11
+ export const RegistryStatsInputSchema: z.ZodType;
12
+ export const ClientSetupInputSchema: z.ZodType;
5
13
  export const CompatibilityInputSchema: z.ZodType;
6
14
  export const InstallGuidanceInputSchema: z.ZodType;
7
15
  export const PlatformAdapterInputSchema: z.ZodType;
@@ -12,9 +20,13 @@ export const ValidateSubmissionDraftInputSchema: z.ZodType;
12
20
  export const SearchDuplicateEntriesInputSchema: z.ZodType;
13
21
  export const BuildSubmissionUrlsInputSchema: z.ZodType;
14
22
  export const CategorySubmissionGuidanceInputSchema: z.ZodType;
23
+ export const PrepareSubmissionDraftInputSchema: z.ZodType;
24
+ export const GetSubmissionExamplesInputSchema: z.ZodType;
25
+ export const ReviewSubmissionDraftInputSchema: z.ZodType;
15
26
  export const TOOL_INPUT_SCHEMAS: Record<string, z.ZodType>;
16
27
 
17
28
  export function jsonSchemaForTool(name: string): Record<string, unknown>;
29
+ export function jsonSchemaForToolOutput(name: string): Record<string, unknown>;
18
30
  export function parseToolArguments(
19
31
  name: string,
20
32
  args?: Record<string, unknown>,
package/src/schemas.js CHANGED
@@ -8,6 +8,13 @@ const pathPart = z
8
8
  .regex(/^[a-z0-9-]+$/, "Use lowercase slug-safe path parts only.");
9
9
 
10
10
  const platform = z.string().trim().min(1).max(80);
11
+ const clientName = z.enum([
12
+ "codex",
13
+ "claude-desktop",
14
+ "cursor",
15
+ "windsurf",
16
+ "remote-http",
17
+ ]);
11
18
  const submissionCategory = z.enum([
12
19
  "agents",
13
20
  "rules",
@@ -77,6 +84,35 @@ export const SearchRegistryInputSchema = z
77
84
  })
78
85
  .strict();
79
86
 
87
+ export const ServerInfoInputSchema = z.object({}).strict();
88
+
89
+ export const ListCategoryEntriesInputSchema = z
90
+ .object({
91
+ category: pathPart.optional(),
92
+ platform: platform.optional(),
93
+ tag: z.string().trim().min(1).max(80).optional(),
94
+ query: z.string().trim().max(240).optional(),
95
+ offset: z.number().int().min(0).max(5000).optional(),
96
+ limit: z.number().int().min(1).max(25).optional(),
97
+ })
98
+ .strict();
99
+
100
+ export const RecentUpdatesInputSchema = z
101
+ .object({
102
+ category: pathPart.optional(),
103
+ since: z.string().trim().min(4).max(40).optional(),
104
+ limit: z.number().int().min(1).max(25).optional(),
105
+ })
106
+ .strict();
107
+
108
+ export const RelatedEntriesInputSchema = z
109
+ .object({
110
+ category: pathPart,
111
+ slug: pathPart,
112
+ limit: z.number().int().min(1).max(25).optional(),
113
+ })
114
+ .strict();
115
+
80
116
  export const EntryDetailInputSchema = z
81
117
  .object({
82
118
  category: pathPart,
@@ -84,6 +120,40 @@ export const EntryDetailInputSchema = z
84
120
  })
85
121
  .strict();
86
122
 
123
+ export const CopyableAssetInputSchema = z
124
+ .object({
125
+ category: pathPart,
126
+ slug: pathPart,
127
+ platform: platform.optional(),
128
+ })
129
+ .strict();
130
+
131
+ export const CompareEntriesInputSchema = z
132
+ .object({
133
+ entries: z
134
+ .array(
135
+ z
136
+ .object({
137
+ category: pathPart,
138
+ slug: pathPart,
139
+ })
140
+ .strict(),
141
+ )
142
+ .min(2)
143
+ .max(5),
144
+ platform: platform.optional(),
145
+ })
146
+ .strict();
147
+
148
+ export const RegistryStatsInputSchema = z.object({}).strict();
149
+
150
+ export const ClientSetupInputSchema = z
151
+ .object({
152
+ client: clientName.optional(),
153
+ endpointUrl: z.string().trim().url().max(500).optional(),
154
+ })
155
+ .strict();
156
+
87
157
  export const CompatibilityInputSchema = z
88
158
  .object({
89
159
  category: pathPart.optional(),
@@ -145,9 +215,36 @@ export const CategorySubmissionGuidanceInputSchema = z
145
215
  })
146
216
  .strict();
147
217
 
218
+ export const PrepareSubmissionDraftInputSchema = z
219
+ .object({
220
+ fields: SubmissionFieldsSchema,
221
+ })
222
+ .strict();
223
+
224
+ export const GetSubmissionExamplesInputSchema = z
225
+ .object({
226
+ category: submissionCategory.optional(),
227
+ })
228
+ .strict();
229
+
230
+ export const ReviewSubmissionDraftInputSchema = z
231
+ .object({
232
+ fields: SubmissionFieldsSchema,
233
+ duplicateLimit: z.number().int().min(1).max(10).optional(),
234
+ })
235
+ .strict();
236
+
148
237
  export const TOOL_INPUT_SCHEMAS = {
149
238
  search_registry: SearchRegistryInputSchema,
239
+ server_info: ServerInfoInputSchema,
240
+ list_category_entries: ListCategoryEntriesInputSchema,
241
+ get_recent_updates: RecentUpdatesInputSchema,
242
+ get_related_entries: RelatedEntriesInputSchema,
150
243
  get_entry_detail: EntryDetailInputSchema,
244
+ get_copyable_asset: CopyableAssetInputSchema,
245
+ compare_entries: CompareEntriesInputSchema,
246
+ get_registry_stats: RegistryStatsInputSchema,
247
+ get_client_setup: ClientSetupInputSchema,
151
248
  get_compatibility: CompatibilityInputSchema,
152
249
  get_install_guidance: InstallGuidanceInputSchema,
153
250
  get_platform_adapter: PlatformAdapterInputSchema,
@@ -157,6 +254,9 @@ export const TOOL_INPUT_SCHEMAS = {
157
254
  search_duplicate_entries: SearchDuplicateEntriesInputSchema,
158
255
  build_submission_urls: BuildSubmissionUrlsInputSchema,
159
256
  get_category_submission_guidance: CategorySubmissionGuidanceInputSchema,
257
+ prepare_submission_draft: PrepareSubmissionDraftInputSchema,
258
+ get_submission_examples: GetSubmissionExamplesInputSchema,
259
+ review_submission_draft: ReviewSubmissionDraftInputSchema,
160
260
  };
161
261
 
162
262
  function stripUnsupportedJsonSchemaFields(value) {
@@ -180,6 +280,25 @@ export function jsonSchemaForTool(name) {
180
280
  return stripUnsupportedJsonSchemaFields(z.toJSONSchema(schema));
181
281
  }
182
282
 
283
+ export function jsonSchemaForToolOutput(name) {
284
+ if (!TOOL_INPUT_SCHEMAS[name]) {
285
+ throw new Error(`Unknown HeyClaude MCP tool output schema: ${name}`);
286
+ }
287
+
288
+ return {
289
+ type: "object",
290
+ properties: {
291
+ ok: { type: "boolean" },
292
+ policy: {
293
+ type: "object",
294
+ additionalProperties: true,
295
+ },
296
+ },
297
+ required: ["ok"],
298
+ additionalProperties: true,
299
+ };
300
+ }
301
+
183
302
  export function parseToolArguments(name, args = {}) {
184
303
  const schema = TOOL_INPUT_SCHEMAS[name];
185
304
  if (!schema) {
package/src/server.js CHANGED
@@ -2,11 +2,24 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import {
4
4
  CallToolRequestSchema,
5
+ GetPromptRequestSchema,
6
+ ListPromptsRequestSchema,
7
+ ListResourcesRequestSchema,
8
+ ListResourceTemplatesRequestSchema,
5
9
  ListToolsRequestSchema,
10
+ ReadResourceRequestSchema,
6
11
  } from "@modelcontextprotocol/sdk/types.js";
7
12
 
8
13
  import { packageVersion } from "./package-metadata.js";
9
- import { callRegistryTool, TOOL_DEFINITIONS } from "./registry.js";
14
+ import {
15
+ callRegistryTool,
16
+ getRegistryPrompt,
17
+ listRegistryPrompts,
18
+ listRegistryResources,
19
+ listRegistryResourceTemplates,
20
+ readRegistryResource,
21
+ TOOL_DEFINITIONS,
22
+ } from "./registry.js";
10
23
 
11
24
  export function createHeyClaudeMcpServer(options = {}) {
12
25
  const server = new Server(
@@ -16,6 +29,8 @@ export function createHeyClaudeMcpServer(options = {}) {
16
29
  },
17
30
  {
18
31
  capabilities: {
32
+ prompts: {},
33
+ resources: {},
19
34
  tools: {},
20
35
  },
21
36
  },
@@ -33,6 +48,10 @@ export function createHeyClaudeMcpServer(options = {}) {
33
48
  );
34
49
  return {
35
50
  isError: result.ok === false,
51
+ structuredContent:
52
+ result && typeof result === "object" && !Array.isArray(result)
53
+ ? result
54
+ : { result },
36
55
  content: [
37
56
  {
38
57
  type: "text",
@@ -42,6 +61,26 @@ export function createHeyClaudeMcpServer(options = {}) {
42
61
  };
43
62
  });
44
63
 
64
+ server.setRequestHandler(ListResourcesRequestSchema, async (request) =>
65
+ listRegistryResources(request.params || {}, options),
66
+ );
67
+
68
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () =>
69
+ listRegistryResourceTemplates(),
70
+ );
71
+
72
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) =>
73
+ readRegistryResource(request.params || {}, options),
74
+ );
75
+
76
+ server.setRequestHandler(ListPromptsRequestSchema, async () =>
77
+ listRegistryPrompts(),
78
+ );
79
+
80
+ server.setRequestHandler(GetPromptRequestSchema, async (request) =>
81
+ getRegistryPrompt(request.params || {}),
82
+ );
83
+
45
84
  return server;
46
85
  }
47
86
 
@@ -21,6 +21,19 @@ export function validateSubmissionDraftFromSpec(
21
21
  spec: Record<string, unknown>,
22
22
  args?: Record<string, unknown>,
23
23
  ): Record<string, unknown>;
24
+ export function prepareSubmissionDraftFromSpec(
25
+ spec: Record<string, unknown>,
26
+ args?: Record<string, unknown>,
27
+ ): Record<string, unknown>;
28
+ export function getSubmissionExamplesFromSpec(
29
+ spec: Record<string, unknown>,
30
+ args?: Record<string, unknown>,
31
+ ): Record<string, unknown>;
32
+ export function reviewSubmissionDraftFromSpec(
33
+ spec: Record<string, unknown>,
34
+ args?: Record<string, unknown>,
35
+ entries?: Array<Record<string, unknown>>,
36
+ ): Record<string, unknown>;
24
37
  export function searchDuplicateEntries(
25
38
  entries?: Array<Record<string, unknown>>,
26
39
  args?: Record<string, unknown>,