@jaypie/mcp 0.1.6 → 0.1.8

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.
@@ -1,7 +1,11 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export interface CreateMcpServerOptions {
3
+ version?: string;
4
+ verbose?: boolean;
5
+ }
2
6
  /**
3
7
  * Creates and configures an MCP server instance with Jaypie tools
4
- * @param version - The version string for the server
8
+ * @param options - Configuration options (or legacy version string)
5
9
  * @returns Configured MCP server instance
6
10
  */
7
- export declare function createMcpServer(version?: string): McpServer;
11
+ export declare function createMcpServer(options?: CreateMcpServerOptions | string): McpServer;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  export { createMcpServer } from "./createMcpServer.js";
3
+ export type { CreateMcpServerOptions } from "./createMcpServer.js";
3
4
  export { mcpExpressHandler, type McpExpressHandlerOptions, } from "./mcpExpressHandler.js";
package/dist/index.js CHANGED
@@ -13,6 +13,19 @@ import { randomUUID } from 'node:crypto';
13
13
  const __filename = fileURLToPath(import.meta.url);
14
14
  const __dirname = path.dirname(__filename);
15
15
  const PROMPTS_PATH = path.join(__dirname, "..", "prompts");
16
+ // Logger utility
17
+ function createLogger(verbose) {
18
+ return {
19
+ info: (message, ...args) => {
20
+ if (verbose) {
21
+ console.error(`[jaypie-mcp] ${message}`, ...args);
22
+ }
23
+ },
24
+ error: (message, ...args) => {
25
+ console.error(`[jaypie-mcp ERROR] ${message}`, ...args);
26
+ },
27
+ };
28
+ }
16
29
  async function parseMarkdownFile(filePath) {
17
30
  try {
18
31
  const content = await fs.readFile(filePath, "utf-8");
@@ -49,22 +62,33 @@ function formatPromptListItem(prompt) {
49
62
  }
50
63
  /**
51
64
  * Creates and configures an MCP server instance with Jaypie tools
52
- * @param version - The version string for the server
65
+ * @param options - Configuration options (or legacy version string)
53
66
  * @returns Configured MCP server instance
54
67
  */
55
- function createMcpServer(version = "0.0.0") {
68
+ function createMcpServer(options = {}) {
69
+ // Support legacy signature: createMcpServer(version: string)
70
+ const config = typeof options === "string" ? { version: options } : options;
71
+ const { version = "0.0.0", verbose = false } = config;
72
+ const log = createLogger(verbose);
73
+ log.info("Creating MCP server instance");
74
+ log.info(`Prompts directory: ${PROMPTS_PATH}`);
56
75
  const server = new McpServer({
57
76
  name: "jaypie",
58
77
  version,
59
78
  }, {
60
79
  capabilities: {},
61
80
  });
81
+ log.info("Registering tools...");
62
82
  server.tool("list_prompts", "Returns a bulleted list of all .md files in the prompts directory with their descriptions and requirements", {}, async () => {
83
+ log.info("Tool called: list_prompts");
84
+ log.info(`Reading directory: ${PROMPTS_PATH}`);
63
85
  try {
64
86
  const files = await fs.readdir(PROMPTS_PATH);
65
87
  const mdFiles = files.filter((file) => file.endsWith(".md"));
88
+ log.info(`Found ${mdFiles.length} .md files`);
66
89
  const prompts = await Promise.all(mdFiles.map((file) => parseMarkdownFile(path.join(PROMPTS_PATH, file))));
67
90
  const formattedList = prompts.map(formatPromptListItem).join("\n");
91
+ log.info("Successfully listed prompts");
68
92
  return {
69
93
  content: [
70
94
  {
@@ -75,6 +99,7 @@ function createMcpServer(version = "0.0.0") {
75
99
  };
76
100
  }
77
101
  catch (error) {
102
+ log.error("Error listing prompts:", error);
78
103
  return {
79
104
  content: [
80
105
  {
@@ -85,14 +110,18 @@ function createMcpServer(version = "0.0.0") {
85
110
  };
86
111
  }
87
112
  });
113
+ log.info("Registered tool: list_prompts");
88
114
  server.tool("read_prompt", "Returns the contents of a specified prompt file", {
89
115
  filename: z
90
116
  .string()
91
117
  .describe("The name of the prompt file to read (e.g., example_prompt.md)"),
92
118
  }, async ({ filename }) => {
119
+ log.info(`Tool called: read_prompt (filename: ${filename})`);
93
120
  try {
94
121
  const filePath = path.join(PROMPTS_PATH, filename);
122
+ log.info(`Reading file: ${filePath}`);
95
123
  const content = await fs.readFile(filePath, "utf-8");
124
+ log.info(`Successfully read ${filename} (${content.length} bytes)`);
96
125
  return {
97
126
  content: [
98
127
  {
@@ -104,6 +133,7 @@ function createMcpServer(version = "0.0.0") {
104
133
  }
105
134
  catch (error) {
106
135
  if (error.code === "ENOENT") {
136
+ log.error(`File not found: ${filename}`);
107
137
  return {
108
138
  content: [
109
139
  {
@@ -113,6 +143,7 @@ function createMcpServer(version = "0.0.0") {
113
143
  ],
114
144
  };
115
145
  }
146
+ log.error("Error reading prompt file:", error);
116
147
  return {
117
148
  content: [
118
149
  {
@@ -123,6 +154,8 @@ function createMcpServer(version = "0.0.0") {
123
154
  };
124
155
  }
125
156
  });
157
+ log.info("Registered tool: read_prompt");
158
+ log.info("MCP server configuration complete");
126
159
  return server;
127
160
  }
128
161
 
@@ -178,6 +211,20 @@ async function mcpExpressHandler(options = {}) {
178
211
 
179
212
  // Version will be injected during build
180
213
  const version = "0.0.0";
214
+ // Parse command-line arguments
215
+ const args = process.argv.slice(2);
216
+ const verbose = args.includes("--verbose") || args.includes("-v");
217
+ // Logger for verbose mode (uses stderr to not interfere with JSON-RPC on stdout)
218
+ const log = {
219
+ info: (message, ...args) => {
220
+ if (verbose) {
221
+ console.error(`[jaypie-mcp] ${message}`, ...args);
222
+ }
223
+ },
224
+ error: (message, ...args) => {
225
+ console.error(`[jaypie-mcp ERROR] ${message}`, ...args);
226
+ },
227
+ };
181
228
  let server = null;
182
229
  let isShuttingDown = false;
183
230
  async function gracefulShutdown(exitCode = 0) {
@@ -185,33 +232,50 @@ async function gracefulShutdown(exitCode = 0) {
185
232
  return;
186
233
  }
187
234
  isShuttingDown = true;
235
+ log.info("Shutting down gracefully...");
188
236
  try {
189
237
  if (server) {
190
238
  await server.close();
239
+ log.info("Server closed successfully");
191
240
  }
192
241
  }
193
242
  catch (error) {
194
- // Ignore errors during shutdown
243
+ log.error("Error during shutdown:", error);
195
244
  }
196
245
  finally {
197
246
  process.exit(exitCode);
198
247
  }
199
248
  }
200
249
  async function main() {
201
- server = createMcpServer(version);
250
+ log.info("Starting Jaypie MCP server...");
251
+ log.info(`Version: ${version}`);
252
+ log.info(`Node version: ${process.version}`);
253
+ log.info(`Verbose mode: ${verbose ? "enabled" : "disabled"}`);
254
+ server = createMcpServer({ version, verbose });
255
+ log.info("MCP server created successfully");
202
256
  const transport = new StdioServerTransport();
203
257
  await server.connect(transport);
258
+ log.info("Connected to stdio transport");
259
+ log.info("Server is ready to accept requests");
204
260
  // Handle process termination signals
205
- process.on("SIGINT", () => gracefulShutdown(0));
206
- process.on("SIGTERM", () => gracefulShutdown(0));
261
+ process.on("SIGINT", () => {
262
+ log.info("Received SIGINT signal");
263
+ gracefulShutdown(0);
264
+ });
265
+ process.on("SIGTERM", () => {
266
+ log.info("Received SIGTERM signal");
267
+ gracefulShutdown(0);
268
+ });
207
269
  // Handle stdio stream errors (but let transport handle normal stdin end/close)
208
270
  process.stdin.on("error", (error) => {
209
271
  if (error.message?.includes("EPIPE") || error.message?.includes("EOF")) {
272
+ log.info("stdin closed");
210
273
  gracefulShutdown(0);
211
274
  }
212
275
  });
213
276
  process.stdout.on("error", (error) => {
214
277
  if (error.message?.includes("EPIPE")) {
278
+ log.info("stdout pipe broken");
215
279
  gracefulShutdown(0);
216
280
  }
217
281
  });
@@ -224,7 +288,7 @@ if (process.argv[1]) {
224
288
  const realPathAsUrl = pathToFileURL(realPath).href;
225
289
  if (import.meta.url === realPathAsUrl) {
226
290
  main().catch((error) => {
227
- console.error("Fatal error:", error);
291
+ log.error("Fatal error:", error);
228
292
  process.exit(1);
229
293
  });
230
294
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/createMcpServer.ts","../src/mcpExpressHandler.ts","../src/index.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport matter from \"gray-matter\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst PROMPTS_PATH = path.join(__dirname, \"..\", \"prompts\");\n\ninterface FrontMatter {\n description?: string;\n include?: string;\n globs?: string;\n}\n\nasync function parseMarkdownFile(filePath: string): Promise<{\n filename: string;\n description?: string;\n include?: string;\n}> {\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n const filename = path.basename(filePath);\n\n if (content.startsWith(\"---\")) {\n const parsed = matter(content);\n const frontMatter = parsed.data as FrontMatter;\n return {\n filename,\n description: frontMatter.description,\n include: frontMatter.include || frontMatter.globs,\n };\n }\n\n return { filename };\n } catch {\n return { filename: path.basename(filePath) };\n }\n}\n\nfunction formatPromptListItem(prompt: {\n filename: string;\n description?: string;\n include?: string;\n}): string {\n const { filename, description, include } = prompt;\n\n if (description && include) {\n return `* ${filename}: ${description} - Required for ${include}`;\n } else if (description) {\n return `* ${filename}: ${description}`;\n } else if (include) {\n return `* ${filename} - Required for ${include}`;\n } else {\n return `* ${filename}`;\n }\n}\n\n/**\n * Creates and configures an MCP server instance with Jaypie tools\n * @param version - The version string for the server\n * @returns Configured MCP server instance\n */\nexport function createMcpServer(version: string = \"0.0.0\"): McpServer {\n const server = new McpServer(\n {\n name: \"jaypie\",\n version,\n },\n {\n capabilities: {},\n },\n );\n\n server.tool(\n \"list_prompts\",\n \"Returns a bulleted list of all .md files in the prompts directory with their descriptions and requirements\",\n {},\n async () => {\n try {\n const files = await fs.readdir(PROMPTS_PATH);\n const mdFiles = files.filter((file) => file.endsWith(\".md\"));\n\n const prompts = await Promise.all(\n mdFiles.map((file) =>\n parseMarkdownFile(path.join(PROMPTS_PATH, file)),\n ),\n );\n\n const formattedList = prompts.map(formatPromptListItem).join(\"\\n\");\n\n return {\n content: [\n {\n type: \"text\" as const,\n text:\n formattedList || \"No .md files found in the prompts directory.\",\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error listing prompts: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n },\n ],\n };\n }\n },\n );\n\n server.tool(\n \"read_prompt\",\n \"Returns the contents of a specified prompt file\",\n {\n filename: z\n .string()\n .describe(\n \"The name of the prompt file to read (e.g., example_prompt.md)\",\n ),\n },\n async ({ filename }) => {\n try {\n const filePath = path.join(PROMPTS_PATH, filename);\n const content = await fs.readFile(filePath, \"utf-8\");\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: content,\n },\n ],\n };\n } catch (error) {\n if ((error as { code?: string }).code === \"ENOENT\") {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error: Prompt file \"${filename}\" not found in prompts directory`,\n },\n ],\n };\n }\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error reading prompt file: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n },\n ],\n };\n }\n },\n );\n\n return server;\n}\n","import { Request, Response } from \"express\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { randomUUID } from \"node:crypto\";\nimport { createMcpServer } from \"./createMcpServer.js\";\n\n// Version will be injected during build\nconst DEFAULT_VERSION = \"0.0.0\";\n\n/**\n * Options for configuring the MCP Express handler\n */\nexport interface McpExpressHandlerOptions {\n /**\n * Version string for the MCP server\n */\n version?: string;\n /**\n * Whether to enable session management (stateful mode)\n * Default: true\n */\n enableSessions?: boolean;\n /**\n * Custom session ID generator function\n */\n sessionIdGenerator?: () => string;\n /**\n * Enable JSON responses instead of SSE streaming\n * Default: false\n */\n enableJsonResponse?: boolean;\n}\n\n/**\n * Creates an Express handler for the Jaypie MCP server using HTTP transport\n *\n * @param options - Configuration options for the handler\n * @returns Promise that resolves to an Express middleware function\n *\n * @example\n * ```typescript\n * import express from 'express';\n * import { mcpExpressHandler } from '@jaypie/mcp';\n *\n * const app = express();\n * app.use(express.json());\n *\n * // Note: mcpExpressHandler is now async\n * app.use('/mcp', await mcpExpressHandler({\n * version: '1.0.0',\n * enableSessions: true\n * }));\n *\n * app.listen(3000, () => {\n * console.log('MCP server running on http://localhost:3000/mcp');\n * });\n * ```\n */\nexport async function mcpExpressHandler(\n options: McpExpressHandlerOptions = {},\n): Promise<(req: Request, res: Response) => Promise<void>> {\n const {\n version = DEFAULT_VERSION,\n enableSessions = true,\n sessionIdGenerator = () => randomUUID(),\n enableJsonResponse = false,\n } = options;\n\n const server = createMcpServer(version);\n\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: enableSessions ? sessionIdGenerator : undefined,\n enableJsonResponse,\n });\n\n await server.connect(transport);\n\n return async (req: Request, res: Response): Promise<void> => {\n try {\n await transport.handleRequest(req, res, req.body);\n } catch (error) {\n if (!res.headersSent) {\n res.status(500).json({\n error: \"Internal server error\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n }\n };\n}\n","#!/usr/bin/env node\nimport { realpathSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createMcpServer } from \"./createMcpServer.js\";\n\n// Version will be injected during build\nconst version = \"0.0.0\";\n\nlet server: McpServer | null = null;\nlet isShuttingDown = false;\n\nasync function gracefulShutdown(exitCode = 0) {\n if (isShuttingDown) {\n return;\n }\n isShuttingDown = true;\n\n try {\n if (server) {\n await server.close();\n }\n } catch (error) {\n // Ignore errors during shutdown\n } finally {\n process.exit(exitCode);\n }\n}\n\nasync function main() {\n server = createMcpServer(version);\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n // Handle process termination signals\n process.on(\"SIGINT\", () => gracefulShutdown(0));\n process.on(\"SIGTERM\", () => gracefulShutdown(0));\n\n // Handle stdio stream errors (but let transport handle normal stdin end/close)\n process.stdin.on(\"error\", (error) => {\n if (error.message?.includes(\"EPIPE\") || error.message?.includes(\"EOF\")) {\n gracefulShutdown(0);\n }\n });\n\n process.stdout.on(\"error\", (error) => {\n if (error.message?.includes(\"EPIPE\")) {\n gracefulShutdown(0);\n }\n });\n\n // Server is running on stdio\n}\n\n// Only run main() if this module is executed directly (not imported)\n// This handles npx and symlinks in node_modules/.bin correctly\nif (process.argv[1]) {\n const realPath = realpathSync(process.argv[1]);\n const realPathAsUrl = pathToFileURL(realPath).href;\n if (import.meta.url === realPathAsUrl) {\n main().catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n });\n }\n}\n\nexport { createMcpServer } from \"./createMcpServer.js\";\nexport {\n mcpExpressHandler,\n type McpExpressHandlerOptions,\n} from \"./mcpExpressHandler.js\";\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/createMcpServer.ts","../src/mcpExpressHandler.ts","../src/index.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport matter from \"gray-matter\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst PROMPTS_PATH = path.join(__dirname, \"..\", \"prompts\");\n\nexport interface CreateMcpServerOptions {\n version?: string;\n verbose?: boolean;\n}\n\n// Logger utility\nfunction createLogger(verbose: boolean) {\n return {\n info: (message: string, ...args: unknown[]) => {\n if (verbose) {\n console.error(`[jaypie-mcp] ${message}`, ...args);\n }\n },\n error: (message: string, ...args: unknown[]) => {\n console.error(`[jaypie-mcp ERROR] ${message}`, ...args);\n },\n };\n}\n\ninterface FrontMatter {\n description?: string;\n include?: string;\n globs?: string;\n}\n\nasync function parseMarkdownFile(filePath: string): Promise<{\n filename: string;\n description?: string;\n include?: string;\n}> {\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n const filename = path.basename(filePath);\n\n if (content.startsWith(\"---\")) {\n const parsed = matter(content);\n const frontMatter = parsed.data as FrontMatter;\n return {\n filename,\n description: frontMatter.description,\n include: frontMatter.include || frontMatter.globs,\n };\n }\n\n return { filename };\n } catch {\n return { filename: path.basename(filePath) };\n }\n}\n\nfunction formatPromptListItem(prompt: {\n filename: string;\n description?: string;\n include?: string;\n}): string {\n const { filename, description, include } = prompt;\n\n if (description && include) {\n return `* ${filename}: ${description} - Required for ${include}`;\n } else if (description) {\n return `* ${filename}: ${description}`;\n } else if (include) {\n return `* ${filename} - Required for ${include}`;\n } else {\n return `* ${filename}`;\n }\n}\n\n/**\n * Creates and configures an MCP server instance with Jaypie tools\n * @param options - Configuration options (or legacy version string)\n * @returns Configured MCP server instance\n */\nexport function createMcpServer(\n options: CreateMcpServerOptions | string = {},\n): McpServer {\n // Support legacy signature: createMcpServer(version: string)\n const config: CreateMcpServerOptions =\n typeof options === \"string\" ? { version: options } : options;\n\n const { version = \"0.0.0\", verbose = false } = config;\n\n const log = createLogger(verbose);\n\n log.info(\"Creating MCP server instance\");\n log.info(`Prompts directory: ${PROMPTS_PATH}`);\n\n const server = new McpServer(\n {\n name: \"jaypie\",\n version,\n },\n {\n capabilities: {},\n },\n );\n\n log.info(\"Registering tools...\");\n\n server.tool(\n \"list_prompts\",\n \"Returns a bulleted list of all .md files in the prompts directory with their descriptions and requirements\",\n {},\n async () => {\n log.info(\"Tool called: list_prompts\");\n log.info(`Reading directory: ${PROMPTS_PATH}`);\n\n try {\n const files = await fs.readdir(PROMPTS_PATH);\n const mdFiles = files.filter((file) => file.endsWith(\".md\"));\n\n log.info(`Found ${mdFiles.length} .md files`);\n\n const prompts = await Promise.all(\n mdFiles.map((file) =>\n parseMarkdownFile(path.join(PROMPTS_PATH, file)),\n ),\n );\n\n const formattedList = prompts.map(formatPromptListItem).join(\"\\n\");\n\n log.info(\"Successfully listed prompts\");\n\n return {\n content: [\n {\n type: \"text\" as const,\n text:\n formattedList || \"No .md files found in the prompts directory.\",\n },\n ],\n };\n } catch (error) {\n log.error(\"Error listing prompts:\", error);\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error listing prompts: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n },\n ],\n };\n }\n },\n );\n\n log.info(\"Registered tool: list_prompts\");\n\n server.tool(\n \"read_prompt\",\n \"Returns the contents of a specified prompt file\",\n {\n filename: z\n .string()\n .describe(\n \"The name of the prompt file to read (e.g., example_prompt.md)\",\n ),\n },\n async ({ filename }) => {\n log.info(`Tool called: read_prompt (filename: ${filename})`);\n\n try {\n const filePath = path.join(PROMPTS_PATH, filename);\n\n log.info(`Reading file: ${filePath}`);\n\n const content = await fs.readFile(filePath, \"utf-8\");\n\n log.info(`Successfully read ${filename} (${content.length} bytes)`);\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: content,\n },\n ],\n };\n } catch (error) {\n if ((error as { code?: string }).code === \"ENOENT\") {\n log.error(`File not found: ${filename}`);\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error: Prompt file \"${filename}\" not found in prompts directory`,\n },\n ],\n };\n }\n\n log.error(\"Error reading prompt file:\", error);\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error reading prompt file: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n },\n ],\n };\n }\n },\n );\n\n log.info(\"Registered tool: read_prompt\");\n log.info(\"MCP server configuration complete\");\n\n return server;\n}\n","import { Request, Response } from \"express\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { randomUUID } from \"node:crypto\";\nimport { createMcpServer } from \"./createMcpServer.js\";\n\n// Version will be injected during build\nconst DEFAULT_VERSION = \"0.0.0\";\n\n/**\n * Options for configuring the MCP Express handler\n */\nexport interface McpExpressHandlerOptions {\n /**\n * Version string for the MCP server\n */\n version?: string;\n /**\n * Whether to enable session management (stateful mode)\n * Default: true\n */\n enableSessions?: boolean;\n /**\n * Custom session ID generator function\n */\n sessionIdGenerator?: () => string;\n /**\n * Enable JSON responses instead of SSE streaming\n * Default: false\n */\n enableJsonResponse?: boolean;\n}\n\n/**\n * Creates an Express handler for the Jaypie MCP server using HTTP transport\n *\n * @param options - Configuration options for the handler\n * @returns Promise that resolves to an Express middleware function\n *\n * @example\n * ```typescript\n * import express from 'express';\n * import { mcpExpressHandler } from '@jaypie/mcp';\n *\n * const app = express();\n * app.use(express.json());\n *\n * // Note: mcpExpressHandler is now async\n * app.use('/mcp', await mcpExpressHandler({\n * version: '1.0.0',\n * enableSessions: true\n * }));\n *\n * app.listen(3000, () => {\n * console.log('MCP server running on http://localhost:3000/mcp');\n * });\n * ```\n */\nexport async function mcpExpressHandler(\n options: McpExpressHandlerOptions = {},\n): Promise<(req: Request, res: Response) => Promise<void>> {\n const {\n version = DEFAULT_VERSION,\n enableSessions = true,\n sessionIdGenerator = () => randomUUID(),\n enableJsonResponse = false,\n } = options;\n\n const server = createMcpServer(version);\n\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: enableSessions ? sessionIdGenerator : undefined,\n enableJsonResponse,\n });\n\n await server.connect(transport);\n\n return async (req: Request, res: Response): Promise<void> => {\n try {\n await transport.handleRequest(req, res, req.body);\n } catch (error) {\n if (!res.headersSent) {\n res.status(500).json({\n error: \"Internal server error\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n }\n };\n}\n","#!/usr/bin/env node\nimport { realpathSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createMcpServer } from \"./createMcpServer.js\";\n\n// Version will be injected during build\nconst version = \"0.0.0\";\n\n// Parse command-line arguments\nconst args = process.argv.slice(2);\nconst verbose = args.includes(\"--verbose\") || args.includes(\"-v\");\n\n// Logger for verbose mode (uses stderr to not interfere with JSON-RPC on stdout)\nconst log = {\n info: (message: string, ...args: unknown[]) => {\n if (verbose) {\n console.error(`[jaypie-mcp] ${message}`, ...args);\n }\n },\n error: (message: string, ...args: unknown[]) => {\n console.error(`[jaypie-mcp ERROR] ${message}`, ...args);\n },\n};\n\nlet server: McpServer | null = null;\nlet isShuttingDown = false;\n\nasync function gracefulShutdown(exitCode = 0) {\n if (isShuttingDown) {\n return;\n }\n isShuttingDown = true;\n\n log.info(\"Shutting down gracefully...\");\n\n try {\n if (server) {\n await server.close();\n log.info(\"Server closed successfully\");\n }\n } catch (error) {\n log.error(\"Error during shutdown:\", error);\n } finally {\n process.exit(exitCode);\n }\n}\n\nasync function main() {\n log.info(\"Starting Jaypie MCP server...\");\n log.info(`Version: ${version}`);\n log.info(`Node version: ${process.version}`);\n log.info(`Verbose mode: ${verbose ? \"enabled\" : \"disabled\"}`);\n\n server = createMcpServer({ version, verbose });\n\n log.info(\"MCP server created successfully\");\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n log.info(\"Connected to stdio transport\");\n log.info(\"Server is ready to accept requests\");\n\n // Handle process termination signals\n process.on(\"SIGINT\", () => {\n log.info(\"Received SIGINT signal\");\n gracefulShutdown(0);\n });\n process.on(\"SIGTERM\", () => {\n log.info(\"Received SIGTERM signal\");\n gracefulShutdown(0);\n });\n\n // Handle stdio stream errors (but let transport handle normal stdin end/close)\n process.stdin.on(\"error\", (error) => {\n if (error.message?.includes(\"EPIPE\") || error.message?.includes(\"EOF\")) {\n log.info(\"stdin closed\");\n gracefulShutdown(0);\n }\n });\n\n process.stdout.on(\"error\", (error) => {\n if (error.message?.includes(\"EPIPE\")) {\n log.info(\"stdout pipe broken\");\n gracefulShutdown(0);\n }\n });\n\n // Server is running on stdio\n}\n\n// Only run main() if this module is executed directly (not imported)\n// This handles npx and symlinks in node_modules/.bin correctly\nif (process.argv[1]) {\n const realPath = realpathSync(process.argv[1]);\n const realPathAsUrl = pathToFileURL(realPath).href;\n if (import.meta.url === realPathAsUrl) {\n main().catch((error) => {\n log.error(\"Fatal error:\", error);\n process.exit(1);\n });\n }\n}\n\nexport { createMcpServer } from \"./createMcpServer.js\";\nexport type { CreateMcpServerOptions } from \"./createMcpServer.js\";\nexport {\n mcpExpressHandler,\n type McpExpressHandlerOptions,\n} from \"./mcpExpressHandler.js\";\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jaypie/mcp",
3
- "version": "0.1.6",
4
- "description": "Jaypie MCP server",
3
+ "version": "0.1.8",
4
+ "description": "Jaypie MCP",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/finlaysonstudio/jaypie"
@@ -45,6 +45,5 @@
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public"
48
- },
49
- "gitHead": "4d7e735fac6495df0dbbc405f53e9d4113095cc6"
48
+ }
50
49
  }
@@ -0,0 +1,361 @@
1
+ ---
2
+ description: coding style resource, helpful when stuck on lint errors
3
+ globs: packages/fabricator/**
4
+ version: 0.3.0
5
+ ---
6
+ # Brief Guide to @jaypie/fabricator
7
+
8
+ ## Overview
9
+
10
+ `@jaypie/fabricator` is a test data generation library built on top of `@faker-js/faker` that provides deterministic, seeded data generation with additional utilities for randomness and complex data patterns.
11
+
12
+ ## Core Concepts
13
+
14
+ ### 1. The Fabricator Class
15
+
16
+ The `Fabricator` class wraps faker.js and provides:
17
+ - **Deterministic seeding**: Pass a string, number, or UUID to get reproducible test data
18
+ - **Direct faker access**: All faker modules are proxied for convenience
19
+ - **Enhanced random number generation**: Built-in `random()` function with advanced options
20
+ - **Custom generators**: Specialized methods like `words()` and `generate.person()`
21
+ - **Unique identifiers**: Each fabricator has an `id` (UUID) and `name` property
22
+ - **Nested fabricators**: Support for hierarchical data generation with child fabricators
23
+
24
+ ```typescript
25
+ import { fabricator } from "@jaypie/fabricator";
26
+
27
+ // Create unseeded (random) fabricator
28
+ const fab = fabricator();
29
+
30
+ // Create seeded (deterministic) fabricator
31
+ const seededFab = fabricator("my-seed");
32
+ const uuidFab = fabricator("550e8400-e29b-41d4-a716-446655440000");
33
+
34
+ // Access built-in properties
35
+ console.log(fab.id); // UUID
36
+ console.log(fab.name); // Generated name like "Happy Mountain"
37
+ ```
38
+
39
+ **Options**:
40
+ ```typescript
41
+ const fab = fabricator("my-seed", {
42
+ name: "Custom Name", // String or function
43
+ generator: {
44
+ name: ({ fabricator }) => fabricator.faker.person.firstName()
45
+ }
46
+ });
47
+ ```
48
+
49
+ ### 2. Deterministic Seeding
50
+
51
+ Any string or number can be used as a seed. The same seed always produces the same output:
52
+
53
+ ```typescript
54
+ const fab1 = fabricator("test-seed");
55
+ const fab2 = fabricator("test-seed");
56
+
57
+ fab1.faker.person.firstName(); // "Jordan"
58
+ fab2.faker.person.firstName(); // "Jordan" (same!)
59
+ ```
60
+
61
+ **UUID Seeding**: UUIDs are converted to numeric arrays using `uuidToBytes` for efficient seeding.
62
+
63
+ **Environment Variable**: Set `PROJECT_SEED` environment variable to provide a default seed for all unseeded fabricators:
64
+ ```typescript
65
+ process.env.PROJECT_SEED = "my-project-seed";
66
+ const fab = fabricator(); // Uses PROJECT_SEED
67
+ ```
68
+
69
+ ### 3. Random Number Generation
70
+
71
+ The `random()` function provides advanced randomness with multiple distribution types:
72
+
73
+ **Basic Usage:**
74
+ ```typescript
75
+ fab.random(); // 0-1 float
76
+ fab.random({ min: 1, max: 10 }); // 1-10 float
77
+ fab.random({ min: 1, max: 10, integer: true }); // 1-10 integer
78
+ ```
79
+
80
+ **All Options:**
81
+ ```typescript
82
+ fab.random({
83
+ min: 1, // Minimum value (default: 0)
84
+ max: 10, // Maximum value (default: 1)
85
+ integer: true, // Return integer instead of float (default: false)
86
+ mean: 5, // For normal distribution
87
+ stddev: 2, // For normal distribution
88
+ precision: 2, // Decimal places
89
+ currency: true, // Shorthand for precision: 2
90
+ start: 100, // Add offset to result
91
+ seed: "override" // Override fabricator's seed for this call only
92
+ });
93
+ ```
94
+
95
+ **Smart Defaults:**
96
+ - If only `min` is set, `max` defaults to `min * 2`
97
+ - Normal distribution with `mean/stddev` can optionally be clamped by `min/max`
98
+
99
+ **Common Patterns:**
100
+ ```typescript
101
+ // Currency values
102
+ fab.random({ min: 10, max: 1000, currency: true }); // e.g., 542.73
103
+
104
+ // Percentages
105
+ fab.random({ min: 0, max: 100, integer: true }); // e.g., 42
106
+
107
+ // Weighted random with normal distribution
108
+ fab.random({ mean: 50, stddev: 15, min: 0, max: 100 }); // Clusters around 50
109
+ ```
110
+
111
+ ### 4. Faker Integration
112
+
113
+ All faker modules are directly accessible through getters:
114
+
115
+ ```typescript
116
+ fab.person.firstName();
117
+ fab.internet.email();
118
+ fab.location.city();
119
+ fab.company.name();
120
+ // ... all faker modules available
121
+ ```
122
+
123
+ ### 5. Custom Methods
124
+
125
+ #### `generate.words()`: Two-Word Combinations
126
+ Generates one of three patterns randomly:
127
+ - adjective + noun
128
+ - adjective + verb
129
+ - noun + verb
130
+
131
+ ```typescript
132
+ fab.generate.words(); // "happy dog" or "quick run" or "mountain climb"
133
+ ```
134
+
135
+ #### `generate.person(id?)`: Complex Person Generator
136
+ Creates realistic person data with probabilistic variations:
137
+
138
+ ```typescript
139
+ const person = fab.generate.person();
140
+ // {
141
+ // id: "uuid",
142
+ // firstName: "Jordan",
143
+ // middleName: "Alex" | undefined,
144
+ // lastName: "Smith" | "Smith-Jones",
145
+ // fullName: "Jordan Smith" | "Jordan Alex Smith"
146
+ // }
147
+ ```
148
+
149
+ **Probabilistic Rules** (using `CHANCE` constants):
150
+ - **firstName**: 2.1% chance (RARE) to be a lastName instead
151
+ - **middleName**:
152
+ - 14.6% chance (UNCOMMON) to be missing
153
+ - 2.1% chance (RARE) to be a lastName
154
+ - 0.307% chance (EPIC) to have two middle names
155
+ - **lastName**: 2.1% chance (RARE) to be hyphenated
156
+ - **fullName**: 14.6% chance (UNCOMMON) to include middle name
157
+
158
+ **Independent Seeding**: Pass an optional UUID to get deterministic person data that's independent of the parent fabricator's seed:
159
+
160
+ ```typescript
161
+ const person1 = fab1.generate.person("same-uuid");
162
+ const person2 = fab2.generate.person("same-uuid");
163
+ // person1 === person2, regardless of fab1/fab2 seeds
164
+ ```
165
+
166
+ ### 6. Probability Constants
167
+
168
+ ```typescript
169
+ export const CHANCE = {
170
+ UNCOMMON: 0.146, // ~14.6%
171
+ RARE: 0.021, // ~2.1%
172
+ EPIC: 0.00307, // ~0.3%
173
+ LEGENDARY: 0.000441 // ~0.04%
174
+ };
175
+ ```
176
+
177
+ Use these for consistent probability calculations across your test data.
178
+
179
+ ### 7. Nested Fabricators
180
+
181
+ Create hierarchical data structures with `Fabricator.new()` and nested configurations:
182
+
183
+ ```typescript
184
+ import { Fabricator } from "@jaypie/fabricator";
185
+
186
+ // Define a nested structure
187
+ const world = Fabricator.new({
188
+ seed: "my-world",
189
+ name: ({ fabricator }) => fabricator.location.city(),
190
+ fabricators: {
191
+ cities: {
192
+ name: ({ fabricator }) => fabricator.location.city(),
193
+ fabricators: {
194
+ streets: {
195
+ name: ({ fabricator }) => fabricator.location.street()
196
+ }
197
+ }
198
+ },
199
+ exports: {
200
+ name: ({ fabricator }) => fabricator.commerce.product()
201
+ }
202
+ }
203
+ });
204
+
205
+ // Access nested fabricators
206
+ const cities = world.cities(5); // Array of 5 city fabricators
207
+ const cityGen = world.cities(); // Generator for infinite cities
208
+
209
+ // Access nested child fabricators
210
+ const city = cities[0];
211
+ const streets = city.streets(10); // 10 streets for this city
212
+ ```
213
+
214
+ **Deterministic Child Seeding**: Child fabricators are seeded deterministically based on their parent's ID and their index, ensuring reproducible hierarchies.
215
+
216
+ **Generator vs Array**:
217
+ - `world.cities()` - Returns a generator for unlimited fabricators
218
+ - `world.cities(n)` - Returns an array of exactly n fabricators
219
+
220
+ ## Architecture
221
+
222
+ ### Internal Utilities
223
+
224
+ - **`numericSeed()`**: Converts strings to numeric seeds using cyrb53 hash
225
+ - **`uuidToBytes()`**: Converts UUIDs to byte arrays for seeding
226
+ - **`uuidToNumber()`**: Converts UUIDs to single numbers (fallback)
227
+ - **`numericSeedArray()`**: Smart converter that detects UUIDs and uses appropriate conversion
228
+ - **`uuidFrom()`**: Generates deterministic UUID v5 from string or number using `JAYPIE_FABRICATOR_UUID` as namespace
229
+ - **`isUuid()`**: Validates if a string is a properly formatted UUID
230
+
231
+ ## Best Practices & Patterns
232
+
233
+ ### Seeding Strategy
234
+
235
+ Always tie seeds to fabricator instance ID for determinism with variety:
236
+
237
+ ```typescript
238
+ // In parent fabricator method
239
+ for (let i = 0; i < count; i++) {
240
+ const seed = `${this.id}-tenant-${i}`;
241
+ tenants.push(new TenantFabricator({ seed, name }));
242
+ }
243
+
244
+ // In child fabricator method
245
+ for (let i = 0; i < count; i++) {
246
+ const seed = `${this.id}-merchant-${i}`;
247
+ merchants.push(new MerchantFabricator({ seed, name }));
248
+ }
249
+ ```
250
+
251
+ **Result:**
252
+ - Same fabricator instance → same data (deterministic)
253
+ - Different fabricator instance → different data (variety)
254
+ - Each entity gets unique seed: `parentId-entityType-index`
255
+
256
+ ### Name Handling Pattern
257
+
258
+ When passing name functions to child fabricators, pass the function itself (don't call it):
259
+
260
+ ```typescript
261
+ // ✅ Correct - parent will invoke function
262
+ const name = config?.name ? () => config.name : undefined;
263
+ new TenantFabricator({ name, seed });
264
+
265
+ // ❌ Wrong - calling function too early
266
+ const name = config?.name ? config.name : generateName({ fabricator: this });
267
+ new TenantFabricator({ name: () => name, seed });
268
+ ```
269
+
270
+ The parent Fabricator class invokes name functions lazily when `get name()` is accessed and caches the result.
271
+
272
+ ### Generator Functions
273
+
274
+ Export generator functions separately for reusability and testability:
275
+
276
+ ```typescript
277
+ // Export the generator function
278
+ export const generateTenantName = ({ fabricator }: FabricatorNameParams) => {
279
+ const city = fabricator.faker.location.city();
280
+ const suffixes = ["Financial", "Payments", "Commerce"];
281
+ const suffixIndex = Math.floor(fabricator.random() * suffixes.length);
282
+ return `${city} ${suffixes[suffixIndex]}`;
283
+ };
284
+
285
+ // Use in fabricator via generator option
286
+ const fab = new Fabricator({
287
+ seed: "my-seed",
288
+ generator: {
289
+ name: generateTenantName
290
+ }
291
+ });
292
+ ```
293
+
294
+ ### Extending Fabricator
295
+
296
+ Create domain-specific fabricators by extending the base class:
297
+
298
+ ```typescript
299
+ import { Fabricator as JaypieFabricator } from "@jaypie/fabricator";
300
+
301
+ export class TenantFabricator extends JaypieFabricator {
302
+ constructor({
303
+ name,
304
+ seed,
305
+ }: {
306
+ name?: string | ((params: FabricatorNameParams) => string);
307
+ seed?: string | number;
308
+ } = {}) {
309
+ super({
310
+ name,
311
+ seed,
312
+ generator: {
313
+ name: generateTenantName,
314
+ },
315
+ });
316
+ }
317
+
318
+ // Add domain-specific methods
319
+ merchants(count: number): MerchantFabricator[] {
320
+ const merchants: MerchantFabricator[] = [];
321
+ for (let i = 0; i < count; i++) {
322
+ merchants.push(new MerchantFabricator({
323
+ seed: `${this.id}-merchant-${i}`
324
+ }));
325
+ }
326
+ return merchants;
327
+ }
328
+ }
329
+ ```
330
+
331
+ ### Common Anti-Patterns
332
+
333
+ **❌ Don't manage name storage yourself**
334
+ ```typescript
335
+ // Wrong - parent already handles this
336
+ private readonly _name: string;
337
+ get name(): string {
338
+ return this._name;
339
+ }
340
+ ```
341
+ **✅ Right:** Use parent's `get name()` accessor via `generator` option.
342
+
343
+ **❌ Don't pass index parameters**
344
+ ```typescript
345
+ // Wrong - couples fabricator to sequence position
346
+ class TenantFabricator {
347
+ constructor({ index, seed }) {
348
+ this._name = this.generateName(index);
349
+ }
350
+ }
351
+ ```
352
+ **✅ Right:** Use constructor config or generator functions.
353
+
354
+ ## Key Design Patterns
355
+
356
+ 1. **Subfaker Pattern**: Complex generators create independent seeded faker instances to ensure deterministic output regardless of parent state
357
+ 2. **Probabilistic Variations**: Use float rolls with CHANCE constants for realistic data variety
358
+ 3. **Proxy Access**: All faker modules exposed through getters for ergonomic API
359
+ 4. **Seed Flexibility**: Accepts strings, numbers, or UUIDs as seeds
360
+ 5. **Hierarchical Generation**: Nested fabricators enable deterministic tree structures for complex data models
361
+ 6. **Identity & Naming**: Every fabricator has a unique ID and name for tracking and debugging
@@ -35,7 +35,7 @@ npm install --save-dev @jaypie/eslint @jaypie/testkit eslint rimraf sort-package
35
35
  ## Context
36
36
 
37
37
  prompts/Jaypie_Ideal_Project_Structure.md
38
- prompts/templates/project-monorepo/.gitignore
38
+ prompts/templates/project-monorepo/gitignore (rename to .gitignore when copying)
39
39
  prompts/templates/project-monorepo/.vscode/settings.json
40
40
  prompts/templates/project-monorepo/eslint.config.mjs
41
41
  prompts/templates/project-monorepo/package.json
@@ -0,0 +1,11 @@
1
+ .DS_Store
2
+ .env
3
+ .jaypie
4
+ cdk.out
5
+ context.out
6
+ dist
7
+ local
8
+ Local
9
+ LOCAL
10
+ node_modules
11
+ var
package/LICENSE.txt DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Finlayson Studio, LLC
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.