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