@fitzzero/quickdraw-core 2.0.0 → 2.1.1

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.
@@ -2,6 +2,9 @@ import express from 'express';
2
2
  import { createServer } from 'http';
3
3
  import { Server } from 'socket.io';
4
4
  import * as jose from 'jose';
5
+ import * as readline from 'readline';
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
5
8
 
6
9
  // src/shared/types.ts
7
10
  var consoleLogger = {
@@ -325,6 +328,19 @@ var BaseService = class {
325
328
  `Emitted ${eventName} to room ${roomName} (via socket fallback)`
326
329
  );
327
330
  }
331
+ /**
332
+ * Emit a custom event to a specific user's room.
333
+ * Users join `user:{userId}` on connection (set up in auth middleware).
334
+ * Useful for targeted notifications like permission changes or direct messages.
335
+ *
336
+ * @example
337
+ * ```typescript
338
+ * this.emitToUserRoom(userId, "budget:shared", { budgetId, sharedBy });
339
+ * ```
340
+ */
341
+ emitToUserRoom(userId, eventName, data) {
342
+ this.emitToRoom(`user:${userId}`, eventName, data);
343
+ }
328
344
  /**
329
345
  * Get any active socket for room-based operations.
330
346
  */
@@ -1669,6 +1685,677 @@ function requireEnv(name, defaultValue) {
1669
1685
  return value;
1670
1686
  }
1671
1687
 
1672
- export { BaseService, ServiceRegistry, applyRateLimitMiddleware, checkEnv, createJWT, createOAuthURL, createQuickdrawServer, createRateLimiter, createTieredRateLimiter, discordProvider, exchangeOAuthCode, getDefaultEntityFields, googleProvider, isRedisAdapterAvailable, mergeWithDefaultFields, requireEnv, setupRedisAdapter, validateEnv, verifyJWT, zodToAdminFields };
1688
+ // src/server/mcp/McpRegistry.ts
1689
+ var McpRegistry = class {
1690
+ services = /* @__PURE__ */ new Map();
1691
+ methodCache = /* @__PURE__ */ new Map();
1692
+ hydrateUserContext;
1693
+ logger;
1694
+ customToolSpecs = null;
1695
+ constructor(options) {
1696
+ this.hydrateUserContext = options.hydrateUserContext;
1697
+ this.logger = options.logger?.child({ service: "McpRegistry" }) ?? consoleLogger.child({ service: "McpRegistry" });
1698
+ }
1699
+ /**
1700
+ * Optionally provide pre-generated tool specs for richer descriptions.
1701
+ * If not set, tool definitions are built dynamically from registered services.
1702
+ */
1703
+ setToolSpecs(specs) {
1704
+ this.customToolSpecs = specs;
1705
+ }
1706
+ registerService(serviceName, serviceInstance) {
1707
+ this.services.set(serviceName, serviceInstance);
1708
+ const methods = serviceInstance.getPublicMethods?.() ?? [];
1709
+ const methodMap = /* @__PURE__ */ new Map();
1710
+ for (const method of methods) {
1711
+ methodMap.set(method.name, method);
1712
+ }
1713
+ this.methodCache.set(serviceName, methodMap);
1714
+ this.logger.info(`Registered MCP service: ${serviceName}`, {
1715
+ category: "startup",
1716
+ methodCount: methods.length,
1717
+ methods: methods.map((m) => m.name)
1718
+ });
1719
+ }
1720
+ listTools(options) {
1721
+ const hasDefaultUser = !!options?.defaultUserId;
1722
+ if (this.customToolSpecs) {
1723
+ return this.customToolSpecs.filter((s) => this.services.has(s.name)).map((svc) => this.buildToolDefinition(svc.name, svc.description, svc.methodNames, hasDefaultUser));
1724
+ }
1725
+ const tools = [];
1726
+ for (const [serviceName] of this.services) {
1727
+ const methodMap = this.methodCache.get(serviceName);
1728
+ if (!methodMap) continue;
1729
+ const methodNames = Array.from(methodMap.keys());
1730
+ const description = this.buildDescription(serviceName, methodNames);
1731
+ tools.push(this.buildToolDefinition(serviceName, description, methodNames, hasDefaultUser));
1732
+ }
1733
+ return tools;
1734
+ }
1735
+ async invoke(serviceName, methodName, payload, userId) {
1736
+ const { instance, method } = this.resolveServiceMethod(serviceName, methodName);
1737
+ const { serviceAccess } = await this.hydrateUserContext(userId);
1738
+ const ctx = {
1739
+ id: `mcp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
1740
+ userId,
1741
+ serviceAccess,
1742
+ connected: true,
1743
+ disconnected: false
1744
+ };
1745
+ const entryId = this.resolveEntryId(method, payload);
1746
+ const logTag = `${serviceName}.${methodName}`;
1747
+ this.logger.info(`MCP invoke start: ${logTag}`, {
1748
+ category: "request_processing",
1749
+ source: "mcp",
1750
+ serviceName,
1751
+ methodName,
1752
+ userId,
1753
+ entryId
1754
+ });
1755
+ const startTime = Date.now();
1756
+ try {
1757
+ const validatedPayload = this.validatePayload(method, payload);
1758
+ if (instance.ensureAccessForMethod) {
1759
+ await instance.ensureAccessForMethod(
1760
+ method.access,
1761
+ ctx,
1762
+ entryId
1763
+ );
1764
+ }
1765
+ const result = await method.handler(validatedPayload, {
1766
+ userId,
1767
+ socketId: ctx.id,
1768
+ serviceAccess
1769
+ });
1770
+ this.logger.info(`MCP invoke success: ${logTag}`, {
1771
+ category: "request_processing",
1772
+ outcome: "success",
1773
+ source: "mcp",
1774
+ serviceName,
1775
+ methodName,
1776
+ userId,
1777
+ durationMs: Date.now() - startTime
1778
+ });
1779
+ return result;
1780
+ } catch (error) {
1781
+ this.logger.error(`MCP invoke error: ${logTag}`, {
1782
+ category: "error",
1783
+ source: "mcp",
1784
+ serviceName,
1785
+ methodName,
1786
+ userId,
1787
+ error: error instanceof Error ? error.message : String(error),
1788
+ durationMs: Date.now() - startTime
1789
+ });
1790
+ throw error;
1791
+ }
1792
+ }
1793
+ getServices() {
1794
+ return Array.from(this.services.keys());
1795
+ }
1796
+ resolveServiceMethod(serviceName, methodName) {
1797
+ const instance = this.services.get(serviceName);
1798
+ if (!instance) {
1799
+ throw new Error(
1800
+ `Unknown service: ${serviceName}. Available: ${Array.from(this.services.keys()).join(", ")}`
1801
+ );
1802
+ }
1803
+ const methodMap = this.methodCache.get(serviceName);
1804
+ const method = methodMap?.get(methodName);
1805
+ if (!method) {
1806
+ throw new Error(
1807
+ `Unknown method "${methodName}" on ${serviceName}. Available: ${Array.from(methodMap?.keys() ?? []).join(", ")}`
1808
+ );
1809
+ }
1810
+ return { instance, method };
1811
+ }
1812
+ validatePayload(method, payload) {
1813
+ if (method.schema) {
1814
+ const result = method.schema.safeParse(payload);
1815
+ if (!result.success) {
1816
+ throw new Error(
1817
+ `Validation error: ${result.error?.message ?? "invalid payload"}`
1818
+ );
1819
+ }
1820
+ return result.data;
1821
+ }
1822
+ return payload;
1823
+ }
1824
+ resolveEntryId(method, payload) {
1825
+ if (method.resolveEntryId) {
1826
+ return method.resolveEntryId(payload) ?? void 0;
1827
+ }
1828
+ if (payload && typeof payload === "object" && "id" in payload) {
1829
+ const v = payload.id;
1830
+ return typeof v === "string" ? v : void 0;
1831
+ }
1832
+ return void 0;
1833
+ }
1834
+ buildToolDefinition(name, description, methodNames, hasDefaultUser) {
1835
+ return {
1836
+ name,
1837
+ description,
1838
+ inputSchema: {
1839
+ type: "object",
1840
+ properties: {
1841
+ method: {
1842
+ type: "string",
1843
+ enum: methodNames,
1844
+ description: "The service method to call"
1845
+ },
1846
+ payload: {
1847
+ type: "object",
1848
+ description: "Method-specific payload (see method listing in description for fields)"
1849
+ },
1850
+ userId: {
1851
+ type: "string",
1852
+ description: hasDefaultUser ? "User ID (optional - defaults to MCP_USER_ID env var)" : "The authenticated user ID (required for ACL checks)"
1853
+ }
1854
+ },
1855
+ required: hasDefaultUser ? ["method"] : ["method", "userId"]
1856
+ }
1857
+ };
1858
+ }
1859
+ buildDescription(serviceName, methodNames) {
1860
+ const displayName = serviceName.replace(/Service$/i, "").replace(/([a-z])([A-Z])/g, "$1 $2") + "Service";
1861
+ const methodList = methodNames.map((m) => `- ${m}`).join("\n");
1862
+ return `${displayName} service.
1863
+
1864
+ Methods:
1865
+ ${methodList}`;
1866
+ }
1867
+ };
1868
+ function createMcpStdioServer(options) {
1869
+ const { name, version, registry, defaultUserId } = options;
1870
+ const serverInfo = { name, version };
1871
+ const capabilities = { tools: {} };
1872
+ function getToolDefinitions() {
1873
+ return registry.listTools({
1874
+ defaultUserId: defaultUserId ?? process.env.MCP_USER_ID
1875
+ });
1876
+ }
1877
+ async function handleRequest(request) {
1878
+ const { id, method, params } = request;
1879
+ try {
1880
+ switch (method) {
1881
+ case "initialize":
1882
+ return {
1883
+ jsonrpc: "2.0",
1884
+ id,
1885
+ result: {
1886
+ protocolVersion: "2024-11-05",
1887
+ serverInfo,
1888
+ capabilities
1889
+ }
1890
+ };
1891
+ case "initialized":
1892
+ case "notifications/initialized":
1893
+ return null;
1894
+ case "tools/list":
1895
+ return {
1896
+ jsonrpc: "2.0",
1897
+ id,
1898
+ result: { tools: getToolDefinitions() }
1899
+ };
1900
+ case "tools/call": {
1901
+ const { name: serviceName, arguments: args } = params;
1902
+ const methodName = args?.method;
1903
+ if (!methodName) {
1904
+ return {
1905
+ jsonrpc: "2.0",
1906
+ id,
1907
+ error: {
1908
+ code: -32602,
1909
+ message: `Missing required argument "method" for service ${serviceName}`
1910
+ }
1911
+ };
1912
+ }
1913
+ const userId = args?.userId ?? defaultUserId ?? process.env.MCP_USER_ID ?? "mcp-anonymous";
1914
+ const payload = args?.payload;
1915
+ const result = await registry.invoke(
1916
+ serviceName,
1917
+ methodName,
1918
+ payload,
1919
+ userId
1920
+ );
1921
+ return {
1922
+ jsonrpc: "2.0",
1923
+ id,
1924
+ result: {
1925
+ content: [
1926
+ {
1927
+ type: "text",
1928
+ text: JSON.stringify(result, null, 2)
1929
+ }
1930
+ ]
1931
+ }
1932
+ };
1933
+ }
1934
+ case "ping":
1935
+ return { jsonrpc: "2.0", id, result: {} };
1936
+ default:
1937
+ return {
1938
+ jsonrpc: "2.0",
1939
+ id,
1940
+ error: { code: -32601, message: `Method not found: ${method}` }
1941
+ };
1942
+ }
1943
+ } catch (error) {
1944
+ return {
1945
+ jsonrpc: "2.0",
1946
+ id,
1947
+ error: {
1948
+ code: -32e3,
1949
+ message: error instanceof Error ? error.message : "Unknown error"
1950
+ }
1951
+ };
1952
+ }
1953
+ }
1954
+ async function main() {
1955
+ const rl = readline.createInterface({
1956
+ input: process.stdin,
1957
+ output: process.stdout,
1958
+ terminal: false
1959
+ });
1960
+ function send(response) {
1961
+ process.stdout.write(JSON.stringify(response) + "\n");
1962
+ }
1963
+ for await (const line of rl) {
1964
+ if (!line.trim()) continue;
1965
+ try {
1966
+ const request = JSON.parse(line);
1967
+ const response = await handleRequest(request);
1968
+ if (response !== null) {
1969
+ send(response);
1970
+ }
1971
+ } catch {
1972
+ send({
1973
+ jsonrpc: "2.0",
1974
+ id: null,
1975
+ error: { code: -32700, message: "Parse error" }
1976
+ });
1977
+ }
1978
+ }
1979
+ }
1980
+ main().catch((error) => {
1981
+ process.stderr.write(`Fatal error: ${error}
1982
+ `);
1983
+ process.exit(1);
1984
+ });
1985
+ }
1986
+
1987
+ // src/server/mcp/McpBootstrap.ts
1988
+ function bootstrapMcpServer(serverModulePath) {
1989
+ process.env.MCP_MODE = "true";
1990
+ process.env.LOG_LEVEL = process.env.LOG_LEVEL ?? "warn";
1991
+ const stderrWrite = (data) => {
1992
+ process.stderr.write(data + "\n");
1993
+ };
1994
+ console.log = (...args) => {
1995
+ stderrWrite(args.map(String).join(" "));
1996
+ };
1997
+ console.info = (...args) => {
1998
+ stderrWrite(args.map(String).join(" "));
1999
+ };
2000
+ console.warn = (...args) => {
2001
+ stderrWrite(args.map(String).join(" "));
2002
+ };
2003
+ console.debug = (...args) => {
2004
+ stderrWrite(args.map(String).join(" "));
2005
+ };
2006
+ import(serverModulePath).catch((error) => {
2007
+ process.stderr.write(`Bootstrap error: ${error}
2008
+ `);
2009
+ process.exit(1);
2010
+ });
2011
+ }
2012
+
2013
+ // src/server/mcp/McpHttpRoutes.ts
2014
+ function createMcpRoutes(router, options) {
2015
+ const { registry, verifyToken } = options;
2016
+ const logger = options.logger?.child({ service: "McpHttpRoutes" }) ?? consoleLogger.child({ service: "McpHttpRoutes" });
2017
+ router.get("/mcp/tools", (_req, res) => {
2018
+ const tools = registry.listTools();
2019
+ res.json({ tools });
2020
+ });
2021
+ router.post("/mcp/invoke", async (req, res) => {
2022
+ try {
2023
+ const authHeader = req.headers.authorization;
2024
+ if (!authHeader?.startsWith("Bearer ")) {
2025
+ res.status(401).json({ error: "Missing or invalid Authorization header" });
2026
+ return;
2027
+ }
2028
+ const token = authHeader.slice(7);
2029
+ const tokenPayload = await verifyToken(token);
2030
+ if (!tokenPayload?.userId) {
2031
+ res.status(401).json({ error: "Invalid token" });
2032
+ return;
2033
+ }
2034
+ const {
2035
+ service,
2036
+ method,
2037
+ payload: toolPayload
2038
+ } = req.body;
2039
+ if (!service || !method) {
2040
+ res.status(400).json({ error: "Missing service or method" });
2041
+ return;
2042
+ }
2043
+ const result = await registry.invoke(
2044
+ service,
2045
+ method,
2046
+ toolPayload,
2047
+ tokenPayload.userId
2048
+ );
2049
+ res.json({ success: true, data: result });
2050
+ } catch (error) {
2051
+ const message = error instanceof Error ? error.message : "Internal error";
2052
+ logger.error("MCP HTTP invoke error", { error: message });
2053
+ res.status(500).json({ error: message });
2054
+ }
2055
+ });
2056
+ return router;
2057
+ }
2058
+ function extractFieldNames(schemaContent) {
2059
+ const fields = [];
2060
+ const lines = schemaContent.split("\n");
2061
+ for (const line of lines) {
2062
+ const fieldMatch = line.match(/^\s*(\w+)\s*:/);
2063
+ if (fieldMatch?.[1]) {
2064
+ const name = fieldMatch[1];
2065
+ const isOptional2 = line.includes(".optional()") || line.includes(".nullable()");
2066
+ fields.push(isOptional2 ? `${name}?` : name);
2067
+ }
2068
+ }
2069
+ return fields;
2070
+ }
2071
+ function buildPayloadHint(fields) {
2072
+ if (!fields || fields.length === 0) return "";
2073
+ return `(${fields.join(", ")})`;
2074
+ }
2075
+ function parseServiceFile(filePath) {
2076
+ const content = fs.readFileSync(filePath, "utf-8");
2077
+ const classMatch = content.match(/export class (\w+Service)/);
2078
+ if (!classMatch?.[1]) return null;
2079
+ const className = classMatch[1];
2080
+ const serviceName = className.charAt(0).toLowerCase() + className.slice(1);
2081
+ const aclMatch = content.match(/hasEntryACL:\s*(true|false)/);
2082
+ const hasEntryACL = aclMatch ? aclMatch[1] === "true" : false;
2083
+ let aclPattern = "No ACL";
2084
+ if (hasEntryACL) {
2085
+ if (content.includes("override async checkEntryACL")) {
2086
+ aclPattern = "Membership table";
2087
+ } else {
2088
+ aclPattern = "JSON ACL (default)";
2089
+ }
2090
+ }
2091
+ if (content.includes("override checkAccess")) {
2092
+ aclPattern = "Custom (self-access pattern)";
2093
+ }
2094
+ const schemas = /* @__PURE__ */ new Map();
2095
+ const schemaRegex = /const (\w+Schema) = z\.object\(\{([\s\S]*?)\}\);/g;
2096
+ let schemaMatch;
2097
+ while ((schemaMatch = schemaRegex.exec(content)) !== null) {
2098
+ if (!schemaMatch[1] || !schemaMatch[2]) continue;
2099
+ const schemaContent = schemaMatch[2].split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n ");
2100
+ schemas.set(schemaMatch[1], schemaContent);
2101
+ }
2102
+ const methods = [];
2103
+ const methodRegex = /this\.defineMethod\(\s*["'](\w+)["'],\s*["'](\w+)["']/g;
2104
+ let methodMatch;
2105
+ while ((methodMatch = methodRegex.exec(content)) !== null) {
2106
+ const methodName = methodMatch[1] ?? "";
2107
+ const accessLevel = methodMatch[2] ?? "Read";
2108
+ const methodStart = methodMatch.index;
2109
+ let parenDepth = 0;
2110
+ let methodEnd = methodStart;
2111
+ let foundStart = false;
2112
+ for (let i = methodStart; i < content.length; i++) {
2113
+ if (content[i] === "(") {
2114
+ parenDepth++;
2115
+ foundStart = true;
2116
+ } else if (content[i] === ")") {
2117
+ parenDepth--;
2118
+ if (foundStart && parenDepth === 0) {
2119
+ methodEnd = i;
2120
+ break;
2121
+ }
2122
+ }
2123
+ }
2124
+ const methodBlock = content.substring(methodStart, methodEnd + 1);
2125
+ const hasSchema = methodBlock.includes("schema:");
2126
+ const schemaRef = methodBlock.match(/schema:\s*(\w+Schema)/);
2127
+ const hasResolver = methodBlock.includes("resolveEntryId:");
2128
+ let payloadFields;
2129
+ if (schemaRef?.[1]) {
2130
+ const schemaContent = schemas.get(schemaRef[1]);
2131
+ if (schemaContent) {
2132
+ payloadFields = extractFieldNames(schemaContent);
2133
+ }
2134
+ }
2135
+ methods.push({
2136
+ name: methodName,
2137
+ accessLevel,
2138
+ hasSchema,
2139
+ schemaName: schemaRef?.[1],
2140
+ hasResolver,
2141
+ payloadFields
2142
+ });
2143
+ }
2144
+ return {
2145
+ name: serviceName,
2146
+ className,
2147
+ hasEntryACL,
2148
+ aclPattern,
2149
+ methods,
2150
+ schemas
2151
+ };
2152
+ }
2153
+ function findServiceDirs(dir, basePath = "") {
2154
+ const results = [];
2155
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
2156
+ for (const entry of entries) {
2157
+ if (entry.isDirectory()) {
2158
+ const fullPath = path.join(dir, entry.name);
2159
+ const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
2160
+ if (fs.existsSync(path.join(fullPath, "index.ts"))) {
2161
+ results.push(relativePath);
2162
+ }
2163
+ const nested = findServiceDirs(fullPath, relativePath);
2164
+ results.push(...nested);
2165
+ }
2166
+ }
2167
+ return results;
2168
+ }
2169
+ function generateToolsFileContent(services) {
2170
+ let content = `/**
2171
+ * MCP Tool Metadata
2172
+ *
2173
+ * Auto-generated from service definitions.
2174
+ * DO NOT EDIT - regenerate with: pnpm docs:generate
2175
+ */
2176
+
2177
+ export type MethodSpec = {
2178
+ name: string;
2179
+ access: "Public" | "Read" | "Moderate" | "Admin";
2180
+ entryScoped: boolean;
2181
+ payloadHint: string;
2182
+ };
2183
+
2184
+ export type ServiceToolSpec = {
2185
+ name: string;
2186
+ description: string;
2187
+ methods: MethodSpec[];
2188
+ methodNames: string[];
2189
+ };
2190
+
2191
+ `;
2192
+ const serviceSpecs = [];
2193
+ for (const service of services) {
2194
+ const methodSpecs = service.methods.map((method) => {
2195
+ const hint = buildPayloadHint(method.payloadFields);
2196
+ return ` {
2197
+ name: "${method.name}",
2198
+ access: "${method.accessLevel}" as const,
2199
+ entryScoped: ${method.hasResolver},
2200
+ payloadHint: "${hint}",
2201
+ }`;
2202
+ });
2203
+ const methodDescriptions = service.methods.map((method) => {
2204
+ const hint = buildPayloadHint(method.payloadFields);
2205
+ return `- ${method.name}${hint}`;
2206
+ }).join("\\n");
2207
+ const description = `${service.className} service.\\n\\nMethods:\\n${methodDescriptions}`;
2208
+ serviceSpecs.push(` {
2209
+ name: "${service.name}",
2210
+ description: "${description}",
2211
+ methods: [
2212
+ ${methodSpecs.join(",\n")}
2213
+ ],
2214
+ methodNames: [${service.methods.map((m) => `"${m.name}"`).join(", ")}],
2215
+ }`);
2216
+ }
2217
+ content += `export const SERVICE_TOOLS: ServiceToolSpec[] = [
2218
+ ${serviceSpecs.join(",\n")}
2219
+ ];
2220
+
2221
+ `;
2222
+ content += `export const SERVICE_TOOLS_BY_NAME: Record<string, ServiceToolSpec> = Object.fromEntries(
2223
+ SERVICE_TOOLS.map(s => [s.name, s])
2224
+ );
2225
+
2226
+ `;
2227
+ content += `export function getServiceNames(): string[] {
2228
+ return SERVICE_TOOLS.map(s => s.name);
2229
+ }
2230
+
2231
+ export function getServiceTool(serviceName: string): ServiceToolSpec | undefined {
2232
+ return SERVICE_TOOLS_BY_NAME[serviceName];
2233
+ }
2234
+
2235
+ export function methodExists(serviceName: string, methodName: string): boolean {
2236
+ const service = SERVICE_TOOLS_BY_NAME[serviceName];
2237
+ return !!service?.methodNames.includes(methodName);
2238
+ }
2239
+
2240
+ export function getMethodSpec(serviceName: string, methodName: string): MethodSpec | undefined {
2241
+ const service = SERVICE_TOOLS_BY_NAME[serviceName];
2242
+ return service?.methods.find(m => m.name === methodName);
2243
+ }
2244
+ `;
2245
+ return content;
2246
+ }
2247
+ function generateServiceDoc(service) {
2248
+ let doc = `# ${service.className}
2249
+
2250
+ `;
2251
+ doc += `**Service Name:** \`${service.name}\`
2252
+
2253
+ `;
2254
+ doc += `**Access Control:** ${service.aclPattern}
2255
+
2256
+ `;
2257
+ if (service.hasEntryACL) {
2258
+ doc += `**Entry-level ACL:** Enabled
2259
+
2260
+ `;
2261
+ }
2262
+ doc += `---
2263
+
2264
+ ## Methods
2265
+
2266
+ `;
2267
+ for (const method of service.methods) {
2268
+ doc += `### ${method.name}
2269
+
2270
+ `;
2271
+ doc += `- **Access Level:** \`${method.accessLevel}\`
2272
+ `;
2273
+ doc += `- **Socket Event:** \`${service.name}:${method.name}\`
2274
+ `;
2275
+ if (method.hasResolver) {
2276
+ doc += `- **Entry-scoped:** Yes
2277
+ `;
2278
+ }
2279
+ if (method.hasSchema && method.schemaName) {
2280
+ doc += `- **Validation:** Zod schema (\`${method.schemaName}\`)
2281
+ `;
2282
+ const schemaContent = service.schemas.get(method.schemaName);
2283
+ if (schemaContent) {
2284
+ doc += `
2285
+ **Payload Schema:**
2286
+
2287
+ \`\`\`typescript
2288
+ {
2289
+ ${schemaContent}
2290
+ }
2291
+ \`\`\`
2292
+ `;
2293
+ }
2294
+ } else {
2295
+ doc += `- **Validation:** None
2296
+ `;
2297
+ }
2298
+ doc += `
2299
+ `;
2300
+ }
2301
+ doc += `---
2302
+
2303
+ *Generated by quickdraw-core*
2304
+ `;
2305
+ return doc;
2306
+ }
2307
+ function generateToolMetadata(options) {
2308
+ const { servicesDir, toolsOutputPath, docsOutputDir } = options;
2309
+ const serviceDirs = findServiceDirs(servicesDir);
2310
+ if (docsOutputDir && !fs.existsSync(docsOutputDir)) {
2311
+ fs.mkdirSync(docsOutputDir, { recursive: true });
2312
+ }
2313
+ const toolsDir = path.dirname(toolsOutputPath);
2314
+ if (!fs.existsSync(toolsDir)) {
2315
+ fs.mkdirSync(toolsDir, { recursive: true });
2316
+ }
2317
+ const allServices = [];
2318
+ for (const serviceDir of serviceDirs) {
2319
+ const indexPath = path.join(servicesDir, serviceDir, "index.ts");
2320
+ if (!fs.existsSync(indexPath)) continue;
2321
+ const serviceInfo = parseServiceFile(indexPath);
2322
+ if (!serviceInfo) continue;
2323
+ allServices.push(serviceInfo);
2324
+ if (docsOutputDir) {
2325
+ const doc = generateServiceDoc(serviceInfo);
2326
+ const outputPath = path.join(
2327
+ docsOutputDir,
2328
+ `${serviceInfo.className}.md`
2329
+ );
2330
+ fs.writeFileSync(outputPath, doc);
2331
+ }
2332
+ }
2333
+ if (docsOutputDir) {
2334
+ const indexContent = `# API Documentation
2335
+
2336
+ Auto-generated documentation for all services.
2337
+
2338
+ ## Services
2339
+
2340
+ ` + allServices.map((s) => `- [${s.className}](./${s.className}.md)`).join("\n") + `
2341
+
2342
+ ---
2343
+
2344
+ *Generated by quickdraw-core*
2345
+ `;
2346
+ fs.writeFileSync(path.join(docsOutputDir, "README.md"), indexContent);
2347
+ }
2348
+ const toolsContent = generateToolsFileContent(allServices);
2349
+ fs.writeFileSync(toolsOutputPath, toolsContent);
2350
+ const totalMethods = allServices.reduce(
2351
+ (sum, s) => sum + s.methods.length,
2352
+ 0
2353
+ );
2354
+ console.log(
2355
+ `Generated ${allServices.length} service tools (${totalMethods} methods) -> ${toolsOutputPath}`
2356
+ );
2357
+ }
2358
+
2359
+ export { BaseService, McpRegistry, ServiceRegistry, applyRateLimitMiddleware, bootstrapMcpServer, checkEnv, createJWT, createMcpRoutes, createMcpStdioServer, createOAuthURL, createQuickdrawServer, createRateLimiter, createTieredRateLimiter, discordProvider, exchangeOAuthCode, generateToolMetadata, getDefaultEntityFields, googleProvider, isRedisAdapterAvailable, mergeWithDefaultFields, requireEnv, setupRedisAdapter, validateEnv, verifyJWT, zodToAdminFields };
1673
2360
  //# sourceMappingURL=index.js.map
1674
2361
  //# sourceMappingURL=index.js.map