@get-technology-inc/jamf-docs-mcp-server 3.0.6 → 3.0.7

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.
Files changed (183) hide show
  1. package/dist/core/constants/doc-types.d.ts +48 -0
  2. package/dist/core/constants/doc-types.d.ts.map +1 -0
  3. package/dist/core/constants/doc-types.js +58 -0
  4. package/dist/core/constants/doc-types.js.map +1 -0
  5. package/dist/core/constants/index.d.ts +16 -0
  6. package/dist/core/constants/index.d.ts.map +1 -0
  7. package/dist/core/constants/index.js +12 -0
  8. package/dist/core/constants/index.js.map +1 -0
  9. package/dist/core/constants/limits.d.ts +39 -0
  10. package/dist/core/constants/limits.d.ts.map +1 -0
  11. package/dist/core/constants/limits.js +51 -0
  12. package/dist/core/constants/limits.js.map +1 -0
  13. package/dist/core/constants/locales.d.ts +34 -0
  14. package/dist/core/constants/locales.d.ts.map +1 -0
  15. package/dist/core/constants/locales.js +20 -0
  16. package/dist/core/constants/locales.js.map +1 -0
  17. package/dist/core/constants/products.d.ts +127 -0
  18. package/dist/core/constants/products.d.ts.map +1 -0
  19. package/dist/core/constants/products.js +135 -0
  20. package/dist/core/constants/products.js.map +1 -0
  21. package/dist/core/constants/topics.d.ts +168 -0
  22. package/dist/core/constants/topics.d.ts.map +1 -0
  23. package/dist/core/constants/topics.js +186 -0
  24. package/dist/core/constants/topics.js.map +1 -0
  25. package/dist/core/constants.d.ts +7 -404
  26. package/dist/core/constants.d.ts.map +1 -1
  27. package/dist/core/constants.js +16 -427
  28. package/dist/core/constants.js.map +1 -1
  29. package/dist/core/http-client.d.ts +14 -2
  30. package/dist/core/http-client.d.ts.map +1 -1
  31. package/dist/core/http-client.js +148 -17
  32. package/dist/core/http-client.js.map +1 -1
  33. package/dist/core/index.d.ts +12 -12
  34. package/dist/core/index.d.ts.map +1 -1
  35. package/dist/core/index.js +12 -13
  36. package/dist/core/index.js.map +1 -1
  37. package/dist/core/resources/index.js +1 -1
  38. package/dist/core/resources/index.js.map +1 -1
  39. package/dist/core/schemas/index.d.ts +3 -1
  40. package/dist/core/schemas/index.d.ts.map +1 -1
  41. package/dist/core/schemas/index.js +12 -1
  42. package/dist/core/schemas/index.js.map +1 -1
  43. package/dist/core/schemas/output.d.ts +7 -0
  44. package/dist/core/schemas/output.d.ts.map +1 -1
  45. package/dist/core/schemas/output.js +7 -0
  46. package/dist/core/schemas/output.js.map +1 -1
  47. package/dist/core/services/article-service.d.ts +24 -0
  48. package/dist/core/services/article-service.d.ts.map +1 -0
  49. package/dist/core/services/article-service.js +118 -0
  50. package/dist/core/services/article-service.js.map +1 -0
  51. package/dist/core/services/content-parser.d.ts +38 -0
  52. package/dist/core/services/content-parser.d.ts.map +1 -0
  53. package/dist/core/services/content-parser.js +146 -0
  54. package/dist/core/services/content-parser.js.map +1 -0
  55. package/dist/core/services/ft-client.d.ts +52 -0
  56. package/dist/core/services/ft-client.d.ts.map +1 -0
  57. package/dist/core/services/ft-client.js +87 -0
  58. package/dist/core/services/ft-client.js.map +1 -0
  59. package/dist/core/services/glossary.d.ts +9 -5
  60. package/dist/core/services/glossary.d.ts.map +1 -1
  61. package/dist/core/services/glossary.js +174 -107
  62. package/dist/core/services/glossary.js.map +1 -1
  63. package/dist/core/services/interfaces/cache.d.ts +6 -6
  64. package/dist/core/services/interfaces/cache.d.ts.map +1 -1
  65. package/dist/core/services/interfaces/index.d.ts +3 -3
  66. package/dist/core/services/interfaces/index.d.ts.map +1 -1
  67. package/dist/core/services/interfaces/logger.d.ts +17 -10
  68. package/dist/core/services/interfaces/logger.d.ts.map +1 -1
  69. package/dist/core/services/interfaces/metadata.d.ts +0 -10
  70. package/dist/core/services/interfaces/metadata.d.ts.map +1 -1
  71. package/dist/core/services/interfaces/providers.d.ts +23 -10
  72. package/dist/core/services/interfaces/providers.d.ts.map +1 -1
  73. package/dist/core/services/logging.d.ts +38 -18
  74. package/dist/core/services/logging.d.ts.map +1 -1
  75. package/dist/core/services/logging.js +78 -29
  76. package/dist/core/services/logging.js.map +1 -1
  77. package/dist/core/services/maps-registry.d.ts +82 -0
  78. package/dist/core/services/maps-registry.d.ts.map +1 -0
  79. package/dist/core/services/maps-registry.js +232 -0
  80. package/dist/core/services/maps-registry.js.map +1 -0
  81. package/dist/core/services/metadata.d.ts +18 -9
  82. package/dist/core/services/metadata.d.ts.map +1 -1
  83. package/dist/core/services/metadata.js +113 -254
  84. package/dist/core/services/metadata.js.map +1 -1
  85. package/dist/core/services/search-service.d.ts +31 -0
  86. package/dist/core/services/search-service.d.ts.map +1 -0
  87. package/dist/core/services/search-service.js +471 -0
  88. package/dist/core/services/search-service.js.map +1 -0
  89. package/dist/core/services/toc-service.d.ts +30 -0
  90. package/dist/core/services/toc-service.d.ts.map +1 -0
  91. package/dist/core/services/toc-service.js +101 -0
  92. package/dist/core/services/toc-service.js.map +1 -0
  93. package/dist/core/services/tokenizer.d.ts +1 -1
  94. package/dist/core/services/tokenizer.d.ts.map +1 -1
  95. package/dist/core/services/tokenizer.js +3 -3
  96. package/dist/core/services/tokenizer.js.map +1 -1
  97. package/dist/core/services/topic-resolver.d.ts +57 -0
  98. package/dist/core/services/topic-resolver.d.ts.map +1 -0
  99. package/dist/core/services/topic-resolver.js +185 -0
  100. package/dist/core/services/topic-resolver.js.map +1 -0
  101. package/dist/core/tools/batch-get-articles.d.ts +0 -2
  102. package/dist/core/tools/batch-get-articles.d.ts.map +1 -1
  103. package/dist/core/tools/batch-get-articles.js +12 -46
  104. package/dist/core/tools/batch-get-articles.js.map +1 -1
  105. package/dist/core/tools/get-article.d.ts.map +1 -1
  106. package/dist/core/tools/get-article.js +29 -112
  107. package/dist/core/tools/get-article.js.map +1 -1
  108. package/dist/core/tools/get-toc.js +1 -1
  109. package/dist/core/tools/get-toc.js.map +1 -1
  110. package/dist/core/tools/search.js +5 -5
  111. package/dist/core/tools/search.js.map +1 -1
  112. package/dist/core/types/context.d.ts +5 -2
  113. package/dist/core/types/context.d.ts.map +1 -1
  114. package/dist/core/types.d.ts +141 -31
  115. package/dist/core/types.d.ts.map +1 -1
  116. package/dist/core/types.js.map +1 -1
  117. package/dist/core/utils/bundle.d.ts +14 -1
  118. package/dist/core/utils/bundle.d.ts.map +1 -1
  119. package/dist/core/utils/bundle.js +19 -2
  120. package/dist/core/utils/bundle.js.map +1 -1
  121. package/dist/core/utils/format-article.d.ts +45 -0
  122. package/dist/core/utils/format-article.d.ts.map +1 -0
  123. package/dist/core/utils/format-article.js +138 -0
  124. package/dist/core/utils/format-article.js.map +1 -0
  125. package/dist/core/utils/ft-metadata.d.ts +22 -0
  126. package/dist/core/utils/ft-metadata.d.ts.map +1 -0
  127. package/dist/core/utils/ft-metadata.js +30 -0
  128. package/dist/core/utils/ft-metadata.js.map +1 -0
  129. package/dist/core/utils/sanitize.d.ts.map +1 -1
  130. package/dist/core/utils/sanitize.js +0 -2
  131. package/dist/core/utils/sanitize.js.map +1 -1
  132. package/dist/core/utils/url.d.ts.map +1 -1
  133. package/dist/core/utils/url.js +0 -1
  134. package/dist/core/utils/url.js.map +1 -1
  135. package/dist/index.js +10 -6
  136. package/dist/index.js.map +1 -1
  137. package/dist/platforms/node/http-server.d.ts +13 -0
  138. package/dist/platforms/node/http-server.d.ts.map +1 -0
  139. package/dist/platforms/node/http-server.js +231 -0
  140. package/dist/platforms/node/http-server.js.map +1 -0
  141. package/dist/platforms/node/index.d.ts +1 -1
  142. package/dist/platforms/node/index.d.ts.map +1 -1
  143. package/dist/platforms/node/index.js +1 -1
  144. package/dist/platforms/node/index.js.map +1 -1
  145. package/dist/platforms/node/logger.d.ts +13 -2
  146. package/dist/platforms/node/logger.d.ts.map +1 -1
  147. package/dist/platforms/node/logger.js +17 -4
  148. package/dist/platforms/node/logger.js.map +1 -1
  149. package/dist/transport/http-handler.d.ts +25 -0
  150. package/dist/transport/http-handler.d.ts.map +1 -0
  151. package/dist/transport/http-handler.js +193 -0
  152. package/dist/transport/http-handler.js.map +1 -0
  153. package/dist/transport/http-types.d.ts +22 -0
  154. package/dist/transport/http-types.d.ts.map +1 -0
  155. package/dist/transport/http-types.js +65 -0
  156. package/dist/transport/http-types.js.map +1 -0
  157. package/dist/transport/http.d.ts +4 -8
  158. package/dist/transport/http.d.ts.map +1 -1
  159. package/dist/transport/http.js +4 -376
  160. package/dist/transport/http.js.map +1 -1
  161. package/dist/transport/index.js +2 -2
  162. package/dist/transport/index.js.map +1 -1
  163. package/dist/transport/rate-limiter.d.ts +16 -0
  164. package/dist/transport/rate-limiter.d.ts.map +1 -0
  165. package/dist/transport/rate-limiter.js +55 -0
  166. package/dist/transport/rate-limiter.js.map +1 -0
  167. package/package.json +3 -3
  168. package/dist/core/services/interfaces.d.ts +0 -94
  169. package/dist/core/services/interfaces.d.ts.map +0 -1
  170. package/dist/core/services/interfaces.js +0 -8
  171. package/dist/core/services/interfaces.js.map +0 -1
  172. package/dist/core/services/scraper.d.ts +0 -90
  173. package/dist/core/services/scraper.d.ts.map +0 -1
  174. package/dist/core/services/scraper.js +0 -990
  175. package/dist/core/services/scraper.js.map +0 -1
  176. package/dist/core/utils/doc-type.d.ts +0 -12
  177. package/dist/core/utils/doc-type.d.ts.map +0 -1
  178. package/dist/core/utils/doc-type.js +0 -24
  179. package/dist/core/utils/doc-type.js.map +0 -1
  180. package/dist/platforms/node/metadata.d.ts +0 -18
  181. package/dist/platforms/node/metadata.d.ts.map +0 -1
  182. package/dist/platforms/node/metadata.js +0 -35
  183. package/dist/platforms/node/metadata.js.map +0 -1
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Node.js HTTP server adapter
3
+ *
4
+ * Bridges the platform-agnostic HTTP handler with Node.js `node:http`.
5
+ * All Node.js-specific code (IncomingMessage, ServerResponse, process,
6
+ * Buffer, env vars) is isolated here.
7
+ */
8
+ import { createServer } from 'node:http';
9
+ import { createHttpHandler } from '../../transport/http-handler.js';
10
+ import { createStderrLogger } from '../../core/services/logging.js';
11
+ import { createNodeConfig, getEnvNumber } from './config.js';
12
+ import { DEFAULT_HTTP_CONFIG } from '../../transport/http-types.js';
13
+ const log = createStderrLogger('http');
14
+ // ============================================================================
15
+ // Node.js request / response conversion
16
+ // ============================================================================
17
+ /**
18
+ * Convert Node.js IncomingMessage to a Web Standard Request.
19
+ *
20
+ * Injects the resolved client IP as an `X-Real-IP` header so the
21
+ * platform-agnostic handler can extract it without Node.js APIs.
22
+ */
23
+ function toWebRequest(req, body, url, clientIp) {
24
+ const headers = new Headers();
25
+ for (const [key, value] of Object.entries(req.headers)) {
26
+ if (value !== undefined) {
27
+ if (Array.isArray(value)) {
28
+ for (const v of value) {
29
+ headers.append(key, v);
30
+ }
31
+ }
32
+ else {
33
+ headers.set(key, value);
34
+ }
35
+ }
36
+ }
37
+ // Inject resolved client IP for the platform-agnostic handler
38
+ headers.set('x-real-ip', clientIp);
39
+ const method = req.method ?? 'GET';
40
+ const init = {
41
+ method,
42
+ headers,
43
+ };
44
+ // Only add body for methods that support it
45
+ if (method !== 'GET' && method !== 'HEAD' && method !== 'DELETE') {
46
+ init.body = body;
47
+ }
48
+ return new Request(url.toString(), init);
49
+ }
50
+ /**
51
+ * Write a Web Standard Response back to Node.js ServerResponse.
52
+ */
53
+ async function writeWebResponse(webRes, res) {
54
+ res.writeHead(webRes.status, Object.fromEntries(webRes.headers.entries()));
55
+ if (webRes.body === null) {
56
+ res.end();
57
+ return;
58
+ }
59
+ const reader = webRes.body.getReader();
60
+ try {
61
+ for (;;) {
62
+ const result = await reader.read();
63
+ if (result.done) {
64
+ break;
65
+ }
66
+ res.write(result.value);
67
+ }
68
+ }
69
+ finally {
70
+ reader.releaseLock();
71
+ res.end();
72
+ }
73
+ }
74
+ /**
75
+ * Collect request body from IncomingMessage into a Buffer.
76
+ * Enforces a maximum body size to prevent memory exhaustion.
77
+ */
78
+ async function collectBody(req, maxBodySize) {
79
+ const chunks = [];
80
+ let totalSize = 0;
81
+ for await (const chunk of req) {
82
+ const buf = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;
83
+ totalSize += buf.length;
84
+ if (totalSize > maxBodySize) {
85
+ throw new Error('Payload too large');
86
+ }
87
+ chunks.push(buf);
88
+ }
89
+ return Buffer.concat(chunks);
90
+ }
91
+ // ============================================================================
92
+ // Client IP extraction (Node.js-specific)
93
+ // ============================================================================
94
+ /**
95
+ * Get client IP from Node.js IncomingMessage.
96
+ * Only trusts X-Forwarded-For when TRUST_PROXY is enabled.
97
+ */
98
+ function getNodeClientIp(req, trustProxy) {
99
+ if (trustProxy) {
100
+ const forwarded = req.headers['x-forwarded-for'];
101
+ if (typeof forwarded === 'string') {
102
+ const ips = forwarded.split(',').map(s => s.trim()).filter(Boolean);
103
+ return ips[ips.length - 1] ?? 'unknown';
104
+ }
105
+ }
106
+ // socket may be undefined in test environments
107
+ const socket = req.socket;
108
+ return socket?.remoteAddress ?? 'unknown';
109
+ }
110
+ // ============================================================================
111
+ // Configuration from environment
112
+ // ============================================================================
113
+ function buildConfig() {
114
+ const nodeConfig = createNodeConfig();
115
+ const corsAllowedOrigins = (process.env.CORS_ALLOWED_ORIGINS ?? '')
116
+ .split(',')
117
+ .map(s => s.trim())
118
+ .filter(Boolean);
119
+ const trustProxy = process.env.TRUST_PROXY === 'true' || process.env.TRUST_PROXY === '1';
120
+ const rateLimitRpm = getEnvNumber('RATE_LIMIT_RPM', 60, 1, 10000);
121
+ return {
122
+ ...DEFAULT_HTTP_CONFIG,
123
+ serverVersion: nodeConfig.version,
124
+ corsAllowedOrigins,
125
+ trustProxy,
126
+ rateLimitRpm,
127
+ };
128
+ }
129
+ // ============================================================================
130
+ // Public API
131
+ // ============================================================================
132
+ /**
133
+ * Create and start an HTTP server for the MCP server (Node.js platform).
134
+ */
135
+ export async function startHttpServer(mcpServer, port, host) {
136
+ const config = buildConfig();
137
+ // Platform-specific IP extractor: reads from X-Real-IP header
138
+ // that we inject in toWebRequest
139
+ const getClientIp = (request) => {
140
+ return request.headers.get('x-real-ip') ?? 'unknown';
141
+ };
142
+ const { handler, cleanup } = createHttpHandler(mcpServer, config, getClientIp, log);
143
+ const httpServer = createServer((req, res) => {
144
+ void handleNodeRequest(req, res, handler, config);
145
+ });
146
+ // Graceful shutdown
147
+ let shuttingDown = false;
148
+ function shutdown(signal) {
149
+ if (shuttingDown) {
150
+ return;
151
+ }
152
+ shuttingDown = true;
153
+ log.info(`${signal} received, shutting down...`);
154
+ cleanup();
155
+ httpServer.close(() => {
156
+ log.info('HTTP server closed');
157
+ process.exit(0);
158
+ });
159
+ // Force exit after timeout
160
+ setTimeout(() => {
161
+ log.warning('Shutdown timeout, forcing exit');
162
+ process.exit(1);
163
+ }, config.shutdownTimeoutMs);
164
+ }
165
+ process.on('SIGINT', () => { shutdown('SIGINT'); });
166
+ process.on('SIGTERM', () => { shutdown('SIGTERM'); });
167
+ // Start listening
168
+ await new Promise((resolve, reject) => {
169
+ httpServer.on('error', (error) => {
170
+ if (error.code === 'EADDRINUSE') {
171
+ log.error(`Port ${port} is already in use`);
172
+ process.exit(1);
173
+ }
174
+ reject(error);
175
+ });
176
+ httpServer.listen(port, host, () => {
177
+ if (host !== '127.0.0.1' && host !== '::1') {
178
+ log.warning(`Server is binding to ${host}, which exposes it to the network. `
179
+ + 'Use 127.0.0.1 for local-only access.');
180
+ }
181
+ log.info(`Jamf Docs MCP Server running on http://${host}:${port}`);
182
+ log.info(`MCP endpoint: http://${host}:${port}/mcp`);
183
+ log.info(`Health check: http://${host}:${port}/health`);
184
+ log.info(`LLMs info: http://${host}:${port}/llms.txt`);
185
+ resolve();
186
+ });
187
+ });
188
+ }
189
+ // ============================================================================
190
+ // Node.js request handler bridge
191
+ // ============================================================================
192
+ /**
193
+ * Bridge a single Node.js HTTP request into the platform-agnostic handler.
194
+ */
195
+ async function handleNodeRequest(req, res, handler, config) {
196
+ const url = new URL(req.url ?? '/', 'http://localhost');
197
+ // Resolve client IP at the Node.js layer
198
+ const clientIp = getNodeClientIp(req, config.trustProxy);
199
+ // For methods that carry a body, collect it; otherwise use empty Buffer
200
+ const method = req.method ?? 'GET';
201
+ let body;
202
+ if (method !== 'GET' && method !== 'HEAD' && method !== 'DELETE' && method !== 'OPTIONS') {
203
+ try {
204
+ body = await collectBody(req, config.maxBodySize);
205
+ }
206
+ catch {
207
+ // Body too large — send 413, then destroy to stop reading remaining data
208
+ res.writeHead(413, { 'Content-Type': 'application/json' });
209
+ res.end(JSON.stringify({ error: 'Payload too large' }), () => {
210
+ req.destroy();
211
+ });
212
+ return;
213
+ }
214
+ }
215
+ else {
216
+ body = Buffer.alloc(0);
217
+ }
218
+ // Convert to Web Standard Request
219
+ const webReq = toWebRequest(req, body, url, clientIp);
220
+ try {
221
+ const webRes = await handler(webReq);
222
+ await writeWebResponse(webRes, res);
223
+ }
224
+ catch {
225
+ if (!res.headersSent) {
226
+ res.writeHead(500, { 'Content-Type': 'application/json' });
227
+ res.end(JSON.stringify({ error: 'Internal server error' }));
228
+ }
229
+ }
230
+ }
231
+ //# sourceMappingURL=http-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../../src/platforms/node/http-server.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export { FileCache } from './cache.js';
5
5
  export { NodeLoggerFactory } from './logger.js';
6
- export { NodeMetadataStore } from './metadata.js';
7
6
  export { createNodeConfig } from './config.js';
7
+ export { startHttpServer } from './http-server.js';
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/platforms/node/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/platforms/node/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export { FileCache } from './cache.js';
5
5
  export { NodeLoggerFactory } from './logger.js';
6
- export { NodeMetadataStore } from './metadata.js';
7
6
  export { createNodeConfig } from './config.js';
7
+ export { startHttpServer } from './http-server.js';
8
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/platforms/node/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/platforms/node/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
@@ -1,11 +1,22 @@
1
1
  /**
2
2
  * Node.js logger factory
3
3
  *
4
- * Wraps the existing logging implementation from core to implement LoggerFactory.
4
+ * Wraps a LoggingService instance to implement the LoggerFactory interface.
5
+ * Each NodeLoggerFactory holds its own LoggingService, avoiding
6
+ * module-level mutable state.
7
+ *
8
+ * Uses `console.error` (stderr) by default — the standard Node.js behaviour.
9
+ * A future WorkersLoggerFactory could pass `console.log` or a no-op instead.
5
10
  */
6
11
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
- import type { Logger, LoggerFactory } from '../../core/services/interfaces/index.js';
12
+ import type { Logger, LoggerFactory, WriteStderrFn } from '../../core/services/interfaces/index.js';
8
13
  export declare class NodeLoggerFactory implements LoggerFactory {
14
+ private readonly service;
15
+ /**
16
+ * @param writeStderr - Optional writer function. Defaults to `console.error`
17
+ * (Node.js stderr). Override for testing or alternative output targets.
18
+ */
19
+ constructor(writeStderr?: WriteStderrFn);
9
20
  createLogger(name: string): Logger;
10
21
  setServer(server: McpServer): void;
11
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/platforms/node/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAMrF,qBAAa,iBAAkB,YAAW,aAAa;IACrD,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIlC,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;CAGnC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/platforms/node/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAGpG,qBAAa,iBAAkB,YAAW,aAAa;IACrD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IAEzC;;;OAGG;gBACS,WAAW,CAAC,EAAE,aAAa;IAIvC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIlC,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;CAGnC"}
@@ -1,15 +1,28 @@
1
1
  /**
2
2
  * Node.js logger factory
3
3
  *
4
- * Wraps the existing logging implementation from core to implement LoggerFactory.
4
+ * Wraps a LoggingService instance to implement the LoggerFactory interface.
5
+ * Each NodeLoggerFactory holds its own LoggingService, avoiding
6
+ * module-level mutable state.
7
+ *
8
+ * Uses `console.error` (stderr) by default — the standard Node.js behaviour.
9
+ * A future WorkersLoggerFactory could pass `console.log` or a no-op instead.
5
10
  */
6
- import { createLogger as coreCreateLogger, setServer as coreSetServer, } from '../../core/services/logging.js';
11
+ import { LoggingService } from '../../core/services/logging.js';
7
12
  export class NodeLoggerFactory {
13
+ service;
14
+ /**
15
+ * @param writeStderr - Optional writer function. Defaults to `console.error`
16
+ * (Node.js stderr). Override for testing or alternative output targets.
17
+ */
18
+ constructor(writeStderr) {
19
+ this.service = new LoggingService(writeStderr);
20
+ }
8
21
  createLogger(name) {
9
- return coreCreateLogger(name);
22
+ return this.service.createLogger(name);
10
23
  }
11
24
  setServer(server) {
12
- coreSetServer(server);
25
+ this.service.setServer(server);
13
26
  }
14
27
  }
15
28
  //# sourceMappingURL=logger.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/platforms/node/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EACL,YAAY,IAAI,gBAAgB,EAChC,SAAS,IAAI,aAAa,GAC3B,MAAM,gCAAgC,CAAC;AAExC,MAAM,OAAO,iBAAiB;IAC5B,YAAY,CAAC,IAAY;QACvB,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,SAAS,CAAC,MAAiB;QACzB,aAAa,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;CACF"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/platforms/node/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAEhE,MAAM,OAAO,iBAAiB;IACX,OAAO,CAAiB;IAEzC;;;OAGG;IACH,YAAY,WAA2B;QACrC,IAAI,CAAC,OAAO,GAAG,IAAI,cAAc,CAAC,WAAW,CAAC,CAAC;IACjD,CAAC;IAED,YAAY,CAAC,IAAY;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,SAAS,CAAC,MAAiB;QACzB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;CACF"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Platform-agnostic HTTP handler for the MCP server.
3
+ *
4
+ * Platform adapters (Node.js, Cloudflare Workers) convert their
5
+ * native request/response types to/from Web Standard objects and
6
+ * call the `handler` function returned by `createHttpHandler`.
7
+ */
8
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { type HttpHandlerConfig, type ClientIpExtractor, type Logger } from './http-types.js';
10
+ /**
11
+ * Create a platform-agnostic HTTP handler for the MCP server.
12
+ *
13
+ * @param mcpServer - The MCP server instance to connect per-request transports to
14
+ * @param config - HTTP handler configuration
15
+ * @param getClientIp - Platform-specific function to extract client IP from a Request
16
+ * @param logger - Optional logger instance
17
+ * @returns An object with:
18
+ * - `handler`: async function that takes a Web Standard Request and returns a Response
19
+ * - `cleanup`: function to stop the rate limiter cleanup interval
20
+ */
21
+ export declare function createHttpHandler(mcpServer: McpServer, config: HttpHandlerConfig, getClientIp: ClientIpExtractor, logger?: Logger): {
22
+ handler: (request: Request) => Promise<Response>;
23
+ cleanup: () => void;
24
+ };
25
+ //# sourceMappingURL=http-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-handler.d.ts","sourceRoot":"","sources":["../../src/transport/http-handler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMzE,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,MAAM,EACZ,MAAM,iBAAiB,CAAC;AA6GzB;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,iBAAiB,EAC9B,MAAM,CAAC,EAAE,MAAM,GACd;IAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAuD3E"}
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Platform-agnostic HTTP handler for the MCP server.
3
+ *
4
+ * Platform adapters (Node.js, Cloudflare Workers) convert their
5
+ * native request/response types to/from Web Standard objects and
6
+ * call the `handler` function returned by `createHttpHandler`.
7
+ */
8
+ import { WebStandardStreamableHTTPServerTransport, } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
9
+ import { RateLimiter } from './rate-limiter.js';
10
+ import { LLMS_TXT, PayloadTooLargeError, } from './http-types.js';
11
+ // ============================================================================
12
+ // Security headers
13
+ // ============================================================================
14
+ /** Security headers applied to all responses (defense-in-depth) */
15
+ const SECURITY_HEADERS = {
16
+ 'X-Content-Type-Options': 'nosniff',
17
+ 'X-Frame-Options': 'DENY',
18
+ 'Cache-Control': 'no-store',
19
+ };
20
+ // ============================================================================
21
+ // CORS helper
22
+ // ============================================================================
23
+ function getCorsHeaders(origin, allowedOrigins) {
24
+ const headers = { ...SECURITY_HEADERS };
25
+ if (allowedOrigins.length === 0) {
26
+ // No origins configured -- deny cross-origin
27
+ return headers;
28
+ }
29
+ // If wildcard is in the list, allow all origins
30
+ if (allowedOrigins.includes('*')) {
31
+ headers['Access-Control-Allow-Origin'] = '*';
32
+ headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS';
33
+ headers['Access-Control-Allow-Headers'] = 'Content-Type';
34
+ return headers;
35
+ }
36
+ // Otherwise, check exact match against the allowed list
37
+ if (origin !== null && allowedOrigins.includes(origin)) {
38
+ headers['Access-Control-Allow-Origin'] = origin;
39
+ headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS';
40
+ headers['Access-Control-Allow-Headers'] = 'Content-Type';
41
+ headers['Access-Control-Max-Age'] = '86400';
42
+ headers.Vary = 'Origin';
43
+ }
44
+ return headers;
45
+ }
46
+ // ============================================================================
47
+ // Body reading helper (Web Standard)
48
+ // ============================================================================
49
+ async function readBodyText(request, maxBodySize) {
50
+ // Fast reject via Content-Length header when available
51
+ const contentLength = request.headers.get('content-length');
52
+ if (contentLength !== null) {
53
+ const length = parseInt(contentLength, 10);
54
+ if (!isNaN(length) && length > maxBodySize) {
55
+ throw new PayloadTooLargeError();
56
+ }
57
+ }
58
+ const text = await request.text();
59
+ if (text.length > maxBodySize) {
60
+ throw new PayloadTooLargeError();
61
+ }
62
+ return text;
63
+ }
64
+ // ============================================================================
65
+ // Response builder helpers
66
+ // ============================================================================
67
+ function jsonResponse(status, body, extraHeaders = {}) {
68
+ return new Response(JSON.stringify(body), {
69
+ status,
70
+ headers: {
71
+ 'Content-Type': 'application/json',
72
+ ...extraHeaders,
73
+ },
74
+ });
75
+ }
76
+ function textResponse(status, body, contentType, extraHeaders = {}) {
77
+ return new Response(body, {
78
+ status,
79
+ headers: {
80
+ 'Content-Type': contentType,
81
+ ...extraHeaders,
82
+ },
83
+ });
84
+ }
85
+ // ============================================================================
86
+ // Factory
87
+ // ============================================================================
88
+ /**
89
+ * Create a platform-agnostic HTTP handler for the MCP server.
90
+ *
91
+ * @param mcpServer - The MCP server instance to connect per-request transports to
92
+ * @param config - HTTP handler configuration
93
+ * @param getClientIp - Platform-specific function to extract client IP from a Request
94
+ * @param logger - Optional logger instance
95
+ * @returns An object with:
96
+ * - `handler`: async function that takes a Web Standard Request and returns a Response
97
+ * - `cleanup`: function to stop the rate limiter cleanup interval
98
+ */
99
+ export function createHttpHandler(mcpServer, config, getClientIp, logger) {
100
+ const rateLimiter = new RateLimiter(config.rateLimitRpm, 60_000);
101
+ const cleanupIntervalId = setInterval(() => { rateLimiter.cleanup(); }, 5 * 60_000);
102
+ // Prevent the interval from keeping the process alive (Node.js-specific,
103
+ // but calling .unref() is harmless on platforms that lack it).
104
+ if (typeof cleanupIntervalId === 'object' && 'unref' in cleanupIntervalId) {
105
+ cleanupIntervalId.unref();
106
+ }
107
+ function cleanup() {
108
+ clearInterval(cleanupIntervalId);
109
+ }
110
+ async function handler(request) {
111
+ // Use hardcoded base URL to prevent Host header injection
112
+ const url = new URL(request.url, 'http://localhost');
113
+ const { pathname } = url;
114
+ const origin = request.headers.get('origin');
115
+ const corsHeaders = getCorsHeaders(origin, config.corsAllowedOrigins);
116
+ // CORS preflight
117
+ if (request.method === 'OPTIONS') {
118
+ return new Response(null, {
119
+ status: 204,
120
+ headers: { ...corsHeaders, 'Content-Length': '0' },
121
+ });
122
+ }
123
+ // Rate limiting
124
+ const clientIp = getClientIp(request);
125
+ if (!rateLimiter.isAllowed(clientIp)) {
126
+ return jsonResponse(429, { error: 'Too many requests' }, corsHeaders);
127
+ }
128
+ // Health check
129
+ if (pathname === '/health' && request.method === 'GET') {
130
+ return jsonResponse(200, { status: 'ok', version: config.serverVersion }, corsHeaders);
131
+ }
132
+ // llms.txt
133
+ if (pathname === '/llms.txt' && request.method === 'GET') {
134
+ return textResponse(200, LLMS_TXT, 'text/plain; charset=utf-8', corsHeaders);
135
+ }
136
+ // MCP endpoint
137
+ if (pathname === '/mcp') {
138
+ return await handleMcp(request, mcpServer, config, corsHeaders, logger);
139
+ }
140
+ // 404 fallback
141
+ return jsonResponse(404, { error: 'Not found' }, corsHeaders);
142
+ }
143
+ return { handler, cleanup };
144
+ }
145
+ // ============================================================================
146
+ // MCP endpoint handler
147
+ // ============================================================================
148
+ async function handleMcp(request, mcpServer, config, corsHeaders, logger) {
149
+ // Read body (with size enforcement)
150
+ let bodyText;
151
+ try {
152
+ bodyText = await readBodyText(request, config.maxBodySize);
153
+ }
154
+ catch (err) {
155
+ if (err instanceof PayloadTooLargeError) {
156
+ return jsonResponse(413, { error: 'Payload too large' }, corsHeaders);
157
+ }
158
+ throw err;
159
+ }
160
+ // Create a per-request transport (stateless mode)
161
+ const transport = new WebStandardStreamableHTTPServerTransport({
162
+ enableJsonResponse: config.enableJsonResponse,
163
+ });
164
+ // Connect the MCP server to this transport
165
+ await mcpServer.connect(transport);
166
+ try {
167
+ // Parse body if present
168
+ let parsedBody;
169
+ if (bodyText.length > 0) {
170
+ try {
171
+ parsedBody = JSON.parse(bodyText);
172
+ }
173
+ catch {
174
+ return jsonResponse(400, { error: 'Invalid JSON in request body' }, corsHeaders);
175
+ }
176
+ }
177
+ // Handle the request via the MCP transport
178
+ const webRes = await transport.handleRequest(request, { parsedBody });
179
+ // Add CORS + security headers to the response
180
+ for (const [key, value] of Object.entries(corsHeaders)) {
181
+ webRes.headers.set(key, value);
182
+ }
183
+ return webRes;
184
+ }
185
+ catch (err) {
186
+ logger?.error(`MCP handler error: ${err instanceof Error ? err.message : String(err)}`);
187
+ return jsonResponse(500, { error: 'Internal server error' }, corsHeaders);
188
+ }
189
+ finally {
190
+ await transport.close();
191
+ }
192
+ }
193
+ //# sourceMappingURL=http-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-handler.js","sourceRoot":"","sources":["../../src/transport/http-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Shared types and constants for the HTTP transport layer.
3
+ */
4
+ import type { Logger } from '../core/services/interfaces/index.js';
5
+ export type { Logger };
6
+ export interface HttpHandlerConfig {
7
+ serverVersion: string;
8
+ corsAllowedOrigins: string[];
9
+ trustProxy: boolean;
10
+ rateLimitRpm: number;
11
+ maxBodySize: number;
12
+ enableJsonResponse: boolean;
13
+ shutdownTimeoutMs: number;
14
+ }
15
+ /** Platform-specific IP extraction -- Node reads socket, Workers reads CF header */
16
+ export type ClientIpExtractor = (request: Request) => string;
17
+ export declare const DEFAULT_HTTP_CONFIG: HttpHandlerConfig;
18
+ export declare const LLMS_TXT = "# Jamf Docs MCP Server\n\n> MCP server providing access to official Jamf documentation from learn.jamf.com\n\n## Products\n- Jamf Pro \u2014 Apple device management for enterprise\n- Jamf School \u2014 Apple device management for education\n- Jamf Connect \u2014 identity and access management\n- Jamf Protect \u2014 endpoint security for Apple\n- Jamf Now \u2014 simple device management for small businesses\n- Jamf Safe Internet \u2014 content filtering and web security\n- Jamf Insights \u2014 analytics and reporting for Apple fleet\n- RapidIdentity \u2014 identity and access management platform\n\n## Tools\n- jamf_docs_list_products: discover available products and versions\n- jamf_docs_search: search documentation by keyword with product/topic filters\n- jamf_docs_get_article: retrieve full article content in markdown\n- jamf_docs_get_toc: browse table of contents for a product\n- jamf_docs_glossary_lookup: look up glossary terms with fuzzy matching\n- jamf_docs_batch_get_articles: retrieve multiple articles in a single request\n\n## Resources\n- jamf://products \u2014 product list with metadata and versions\n- jamf://topics \u2014 topic categories per product\n- jamf://products/{productId}/toc \u2014 table of contents for a specific product\n- jamf://products/{productId}/versions \u2014 available documentation versions\n\n## Prompts\n- jamf_troubleshoot: guided troubleshooting workflow\n- jamf_setup_guide: step-by-step setup instructions\n- jamf_compare_versions: compare features across versions\n\n## Supported Locales\nen-US, ja-JP, zh-TW, de-DE, es-ES, fr-FR, nl-NL, th-TH\n\n## Limitations\n- Documentation content only \u2014 no Jamf REST API reference\n- Content is sourced from learn.jamf.com and cached; not real-time\n";
19
+ export declare class PayloadTooLargeError extends Error {
20
+ constructor();
21
+ }
22
+ //# sourceMappingURL=http-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-types.d.ts","sourceRoot":"","sources":["../../src/transport/http-types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sCAAsC,CAAC;AAEnE,YAAY,EAAE,MAAM,EAAE,CAAC;AAMvB,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,oFAAoF;AACpF,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;AAE7D,eAAO,MAAM,mBAAmB,EAAE,iBAQjC,CAAC;AAMF,eAAO,MAAM,QAAQ,+tDAuCpB,CAAC;AAMF,qBAAa,oBAAqB,SAAQ,KAAK;;CAK9C"}