@forestadmin/mcp-server 0.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.
- package/README.md +128 -0
- package/dist/__mocks__/version.d.ts +3 -0
- package/dist/__mocks__/version.js +7 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +14 -0
- package/dist/factory.d.ts +51 -0
- package/dist/factory.js +40 -0
- package/dist/forest-oauth-provider.d.ts +44 -0
- package/dist/forest-oauth-provider.js +253 -0
- package/dist/forest-oauth-provider.test.d.ts +2 -0
- package/dist/forest-oauth-provider.test.js +590 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +13 -0
- package/dist/mcp-paths.d.ts +5 -0
- package/dist/mcp-paths.js +11 -0
- package/dist/polyfills.d.ts +12 -0
- package/dist/polyfills.js +27 -0
- package/dist/schemas/filter.d.ts +4 -0
- package/dist/schemas/filter.js +70 -0
- package/dist/schemas/filter.test.d.ts +2 -0
- package/dist/schemas/filter.test.js +234 -0
- package/dist/server.d.ts +87 -0
- package/dist/server.js +341 -0
- package/dist/server.test.d.ts +2 -0
- package/dist/server.test.js +901 -0
- package/dist/test-utils/mock-server.d.ts +62 -0
- package/dist/test-utils/mock-server.js +187 -0
- package/dist/tools/list.d.ts +4 -0
- package/dist/tools/list.js +98 -0
- package/dist/tools/list.test.d.ts +2 -0
- package/dist/tools/list.test.js +385 -0
- package/dist/utils/activity-logs-creator.d.ts +9 -0
- package/dist/utils/activity-logs-creator.js +65 -0
- package/dist/utils/activity-logs-creator.test.d.ts +2 -0
- package/dist/utils/activity-logs-creator.test.js +239 -0
- package/dist/utils/agent-caller.d.ts +13 -0
- package/dist/utils/agent-caller.js +24 -0
- package/dist/utils/agent-caller.test.d.ts +2 -0
- package/dist/utils/agent-caller.test.js +102 -0
- package/dist/utils/error-parser.d.ts +10 -0
- package/dist/utils/error-parser.js +56 -0
- package/dist/utils/error-parser.test.d.ts +2 -0
- package/dist/utils/error-parser.test.js +124 -0
- package/dist/utils/schema-fetcher.d.ts +53 -0
- package/dist/utils/schema-fetcher.js +85 -0
- package/dist/utils/schema-fetcher.test.d.ts +2 -0
- package/dist/utils/schema-fetcher.test.js +212 -0
- package/dist/utils/sse-error-logger.d.ts +14 -0
- package/dist/utils/sse-error-logger.js +112 -0
- package/dist/utils/tool-with-logging.d.ts +44 -0
- package/dist/utils/tool-with-logging.js +66 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +43 -0
- package/package.json +49 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
// Import polyfills FIRST - before any MCP SDK imports
|
|
40
|
+
// This ensures URL.canParse is available for MCP SDK's Zod validation
|
|
41
|
+
require("./polyfills");
|
|
42
|
+
const authorize_js_1 = require("@modelcontextprotocol/sdk/server/auth/handlers/authorize.js");
|
|
43
|
+
const token_js_1 = require("@modelcontextprotocol/sdk/server/auth/handlers/token.js");
|
|
44
|
+
const allowedMethods_js_1 = require("@modelcontextprotocol/sdk/server/auth/middleware/allowedMethods.js");
|
|
45
|
+
const bearerAuth_js_1 = require("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js");
|
|
46
|
+
const router_js_1 = require("@modelcontextprotocol/sdk/server/auth/router.js");
|
|
47
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
48
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
49
|
+
const cors_1 = __importDefault(require("cors"));
|
|
50
|
+
const express_1 = __importDefault(require("express"));
|
|
51
|
+
const http = __importStar(require("http"));
|
|
52
|
+
const forest_oauth_provider_1 = __importDefault(require("./forest-oauth-provider"));
|
|
53
|
+
const mcp_paths_1 = require("./mcp-paths");
|
|
54
|
+
const list_1 = __importDefault(require("./tools/list"));
|
|
55
|
+
const schema_fetcher_1 = require("./utils/schema-fetcher");
|
|
56
|
+
const sse_error_logger_1 = __importDefault(require("./utils/sse-error-logger"));
|
|
57
|
+
const version_1 = require("./version");
|
|
58
|
+
function getDefaultLogFn(level) {
|
|
59
|
+
if (level === 'Error')
|
|
60
|
+
return (msg) => console.error(`[MCP Server] ${msg}`);
|
|
61
|
+
if (level === 'Warn')
|
|
62
|
+
return (msg) => console.warn(`[MCP Server] ${msg}`);
|
|
63
|
+
return (msg) => console.info(`[MCP Server] ${msg}`);
|
|
64
|
+
}
|
|
65
|
+
const defaultLogger = (level, message) => {
|
|
66
|
+
getDefaultLogFn(level)(message);
|
|
67
|
+
};
|
|
68
|
+
/** Fields that are safe to log for each tool (non-sensitive data) */
|
|
69
|
+
const SAFE_ARGUMENTS_FOR_LOGGING = {
|
|
70
|
+
list: ['collectionName'],
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Forest Admin MCP Server
|
|
74
|
+
*
|
|
75
|
+
* This server provides HTTP REST API access to Forest Admin operations
|
|
76
|
+
* with OAuth authentication support.
|
|
77
|
+
*
|
|
78
|
+
* Environment Variables (used as fallback when options not provided):
|
|
79
|
+
* - FOREST_ENV_SECRET: Your Forest Admin environment secret (required)
|
|
80
|
+
* - FOREST_AUTH_SECRET: Your Forest Admin authentication secret, it must be the same one as the one on your agent (required)
|
|
81
|
+
* - FOREST_SERVER_URL: Forest Admin server URL (optional)
|
|
82
|
+
* - MCP_SERVER_PORT: Port for the HTTP server (default: 3931)
|
|
83
|
+
*/
|
|
84
|
+
class ForestMCPServer {
|
|
85
|
+
constructor(options) {
|
|
86
|
+
this.forestServerUrl =
|
|
87
|
+
options?.forestServerUrl ||
|
|
88
|
+
process.env.FOREST_SERVER_URL ||
|
|
89
|
+
process.env.FOREST_URL ||
|
|
90
|
+
'https://api.forestadmin.com';
|
|
91
|
+
this.forestAppUrl =
|
|
92
|
+
options?.forestAppUrl || process.env.FOREST_APP_URL || 'https://app.forestadmin.com';
|
|
93
|
+
this.envSecret = options?.envSecret || process.env.FOREST_ENV_SECRET;
|
|
94
|
+
this.authSecret = options?.authSecret || process.env.FOREST_AUTH_SECRET;
|
|
95
|
+
this.logger = options?.logger || defaultLogger;
|
|
96
|
+
this.mcpServer = new mcp_js_1.McpServer({
|
|
97
|
+
name: version_1.NAME,
|
|
98
|
+
version: version_1.VERSION,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async setupTools() {
|
|
102
|
+
let collectionNames = [];
|
|
103
|
+
try {
|
|
104
|
+
const schema = await (0, schema_fetcher_1.fetchForestSchema)(this.forestServerUrl);
|
|
105
|
+
collectionNames = (0, schema_fetcher_1.getCollectionNames)(schema);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
this.logger('Warn', `Failed to fetch forest schema, collection names will not be available: ${error}`);
|
|
109
|
+
}
|
|
110
|
+
(0, list_1.default)(this.mcpServer, this.forestServerUrl, this.logger, collectionNames);
|
|
111
|
+
}
|
|
112
|
+
ensureSecretsAreSet() {
|
|
113
|
+
if (!this.envSecret) {
|
|
114
|
+
throw new Error('FOREST_ENV_SECRET is not set. Provide it via options.envSecret or FOREST_ENV_SECRET environment variable.');
|
|
115
|
+
}
|
|
116
|
+
if (!this.authSecret) {
|
|
117
|
+
throw new Error('FOREST_AUTH_SECRET is not set. Provide it via options.authSecret or FOREST_AUTH_SECRET environment variable.');
|
|
118
|
+
}
|
|
119
|
+
return { envSecret: this.envSecret, authSecret: this.authSecret };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Filters tool arguments to only include non-sensitive fields for logging.
|
|
123
|
+
* Prevents accidentally logging sensitive data like search queries or filters.
|
|
124
|
+
*/
|
|
125
|
+
filterArgsForLogging(toolName, args) {
|
|
126
|
+
const allowedFields = SAFE_ARGUMENTS_FOR_LOGGING[toolName] || [];
|
|
127
|
+
return Object.fromEntries(Object.entries(args).filter(([key]) => allowedFields.includes(key)));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Logs tool call information if the request is a tools/call method.
|
|
131
|
+
*/
|
|
132
|
+
logToolCallIfPresent(req) {
|
|
133
|
+
const body = req.body;
|
|
134
|
+
if (body?.method !== 'tools/call' || !body.params?.name) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const toolName = body.params.name;
|
|
138
|
+
const args = body.params.arguments || {};
|
|
139
|
+
const safeArgs = this.filterArgsForLogging(toolName, args);
|
|
140
|
+
this.logger('Info', `[MCP] Tool call: ${toolName} - params: ${JSON.stringify(safeArgs)}`);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Handles an incoming MCP request.
|
|
144
|
+
* Logs the request, intercepts the response for error logging, and delegates to the transport.
|
|
145
|
+
*/
|
|
146
|
+
async handleMcpRequest(req, res) {
|
|
147
|
+
this.logger('Info', `[MCP] Incoming ${req.method} ${req.path}`);
|
|
148
|
+
if (!this.mcpTransport) {
|
|
149
|
+
throw new Error('MCP transport not initialized');
|
|
150
|
+
}
|
|
151
|
+
this.logToolCallIfPresent(req);
|
|
152
|
+
(0, sse_error_logger_1.default)(res, this.logger);
|
|
153
|
+
await this.mcpTransport.handleRequest(req, res, req.body);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Build and return the Express app without starting a standalone server.
|
|
157
|
+
* Useful for embedding the MCP server into another application.
|
|
158
|
+
*
|
|
159
|
+
* @param baseUrl - Optional base URL override. If not provided, will use the
|
|
160
|
+
* environmentApiEndpoint from Forest Admin API.
|
|
161
|
+
* @returns The configured Express application
|
|
162
|
+
*/
|
|
163
|
+
async buildExpressApp(baseUrl) {
|
|
164
|
+
const { envSecret, authSecret } = this.ensureSecretsAreSet();
|
|
165
|
+
// Fetch schema and setup tools before building the app
|
|
166
|
+
await this.setupTools();
|
|
167
|
+
this.mcpTransport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
168
|
+
sessionIdGenerator: undefined,
|
|
169
|
+
});
|
|
170
|
+
await this.mcpServer.connect(this.mcpTransport);
|
|
171
|
+
const app = (0, express_1.default)();
|
|
172
|
+
// Trust proxy headers when behind a reverse proxy (e.g., load balancer, nginx)
|
|
173
|
+
// This is required for express-rate-limit to correctly identify clients
|
|
174
|
+
app.set('trust proxy', 1);
|
|
175
|
+
app.use((0, cors_1.default)({
|
|
176
|
+
origin: '*',
|
|
177
|
+
}));
|
|
178
|
+
// Initialize OAuth provider
|
|
179
|
+
const oauthProvider = new forest_oauth_provider_1.default({
|
|
180
|
+
forestServerUrl: this.forestServerUrl,
|
|
181
|
+
forestAppUrl: this.forestAppUrl,
|
|
182
|
+
envSecret,
|
|
183
|
+
authSecret,
|
|
184
|
+
logger: this.logger,
|
|
185
|
+
});
|
|
186
|
+
await oauthProvider.initialize();
|
|
187
|
+
// Use provided baseUrl or get it from the OAuth provider (environmentApiEndpoint)
|
|
188
|
+
const effectiveBaseUrl = baseUrl || oauthProvider.getBaseUrl();
|
|
189
|
+
if (!effectiveBaseUrl) {
|
|
190
|
+
throw new Error('Could not determine base URL for MCP server. ' +
|
|
191
|
+
'Either provide a baseUrl parameter or ensure the Forest Admin environment has an api_endpoint configured.');
|
|
192
|
+
}
|
|
193
|
+
const scopesSupported = ['mcp:read', 'mcp:write', 'mcp:action', 'mcp:admin'];
|
|
194
|
+
// Create OAuth metadata with custom registration_endpoint pointing to Forest Admin
|
|
195
|
+
const oauthMetadata = (0, router_js_1.createOAuthMetadata)({
|
|
196
|
+
provider: oauthProvider,
|
|
197
|
+
issuerUrl: effectiveBaseUrl,
|
|
198
|
+
baseUrl: effectiveBaseUrl,
|
|
199
|
+
scopesSupported,
|
|
200
|
+
});
|
|
201
|
+
oauthMetadata.token_endpoint_auth_methods_supported = ['none'];
|
|
202
|
+
oauthMetadata.response_types_supported = ['code'];
|
|
203
|
+
oauthMetadata.code_challenge_methods_supported = ['S256'];
|
|
204
|
+
oauthMetadata.token_endpoint = `${effectiveBaseUrl.href}oauth/token`;
|
|
205
|
+
oauthMetadata.authorization_endpoint = `${effectiveBaseUrl.href}oauth/authorize`;
|
|
206
|
+
// Override registration_endpoint to point to Forest Admin server
|
|
207
|
+
oauthMetadata.registration_endpoint = `${this.forestServerUrl}/oauth/register`;
|
|
208
|
+
// Remove revocation_endpoint from metadata (not supported)
|
|
209
|
+
delete oauthMetadata.revocation_endpoint;
|
|
210
|
+
// Body parsers MUST come before OAuth handlers because the token handler
|
|
211
|
+
// expects req.body to be parsed. When proxied from Koa, the body is already
|
|
212
|
+
// available but Express needs to see it properly.
|
|
213
|
+
app.use(express_1.default.json());
|
|
214
|
+
app.use(express_1.default.urlencoded({ extended: true }));
|
|
215
|
+
// Request logging middleware - logs every request with response status
|
|
216
|
+
app.use((req, res, next) => {
|
|
217
|
+
const startTime = Date.now();
|
|
218
|
+
// Capture the original end method to log after response is sent
|
|
219
|
+
const originalEnd = res.end.bind(res);
|
|
220
|
+
res.end = ((chunk, encoding) => {
|
|
221
|
+
const duration = Date.now() - startTime;
|
|
222
|
+
this.logger('Info', `[${res.statusCode}] ${req.method} ${req.baseUrl || req.path} - ${duration}ms`);
|
|
223
|
+
return originalEnd(chunk, encoding);
|
|
224
|
+
});
|
|
225
|
+
next();
|
|
226
|
+
});
|
|
227
|
+
app.use('/oauth/authorize', (0, authorize_js_1.authorizationHandler)({
|
|
228
|
+
provider: oauthProvider,
|
|
229
|
+
}));
|
|
230
|
+
app.use('/oauth/token', (0, token_js_1.tokenHandler)({ provider: oauthProvider }));
|
|
231
|
+
// Mount metadata router with custom metadata
|
|
232
|
+
// The resourceServerUrl should include the /mcp path to match RFC 9728 requirements.
|
|
233
|
+
// This creates the .well-known/oauth-protected-resource/mcp endpoint.
|
|
234
|
+
const mcpResourceUrl = new URL('mcp', effectiveBaseUrl);
|
|
235
|
+
app.use((0, router_js_1.mcpAuthMetadataRouter)({
|
|
236
|
+
oauthMetadata,
|
|
237
|
+
resourceServerUrl: mcpResourceUrl,
|
|
238
|
+
scopesSupported,
|
|
239
|
+
}));
|
|
240
|
+
app.use((0, allowedMethods_js_1.allowedMethods)(['POST']));
|
|
241
|
+
app.post('/mcp', (0, bearerAuth_js_1.requireBearerAuth)({
|
|
242
|
+
verifier: oauthProvider,
|
|
243
|
+
requiredScopes: ['mcp:read'],
|
|
244
|
+
}), (req, res) => {
|
|
245
|
+
this.handleMcpRequest(req, res).catch(error => {
|
|
246
|
+
this.logger('Error', `MCP Error: ${error}`);
|
|
247
|
+
if (!res.headersSent) {
|
|
248
|
+
res.status(500).json({
|
|
249
|
+
jsonrpc: '2.0',
|
|
250
|
+
error: {
|
|
251
|
+
code: -32603,
|
|
252
|
+
message: error?.message || 'Internal server error',
|
|
253
|
+
},
|
|
254
|
+
id: null,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
// Global error handler to catch any unhandled errors
|
|
260
|
+
// Express requires all 4 parameters to recognize this as an error handler
|
|
261
|
+
// Capture logger for use in error handler (arrow function would lose context)
|
|
262
|
+
const { logger } = this;
|
|
263
|
+
app.use(
|
|
264
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
265
|
+
(err, _req, res, _next) => {
|
|
266
|
+
logger('Error', `Unhandled error: ${err.message}`);
|
|
267
|
+
if (!res.headersSent) {
|
|
268
|
+
res.status(500).json({
|
|
269
|
+
error: 'internal_server_error',
|
|
270
|
+
error_description: err.message,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
this.expressApp = app;
|
|
275
|
+
return app;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Build and return an HTTP callback that can be used as middleware.
|
|
279
|
+
* The callback will handle MCP-related routes (/.well-known/*, /oauth/*, /mcp)
|
|
280
|
+
* and call next() for other routes.
|
|
281
|
+
*
|
|
282
|
+
* @param baseUrl - Optional base URL override. If not provided, will use the
|
|
283
|
+
* environmentApiEndpoint from Forest Admin API.
|
|
284
|
+
* @returns An HTTP callback function
|
|
285
|
+
*/
|
|
286
|
+
async getHttpCallback(baseUrl) {
|
|
287
|
+
const app = await this.buildExpressApp(baseUrl);
|
|
288
|
+
return (req, res, next) => {
|
|
289
|
+
const url = req.url || '/';
|
|
290
|
+
if ((0, mcp_paths_1.isMcpRoute)(url)) {
|
|
291
|
+
// Fix for streams that have been consumed by another framework (like Koa)
|
|
292
|
+
// Express's finalhandler calls unpipe() which expects _readableState.pipes to exist
|
|
293
|
+
// Node.js unpipe() accesses _readableState.pipes.length, so pipes must be an array
|
|
294
|
+
/* eslint-disable @typescript-eslint/no-explicit-any, no-underscore-dangle */
|
|
295
|
+
const reqAny = req;
|
|
296
|
+
// Ensure _readableState exists with proper structure
|
|
297
|
+
if (!reqAny._readableState) {
|
|
298
|
+
reqAny._readableState = {
|
|
299
|
+
pipes: [],
|
|
300
|
+
pipesCount: 0,
|
|
301
|
+
flowing: null,
|
|
302
|
+
ended: true,
|
|
303
|
+
endEmitted: true,
|
|
304
|
+
reading: false,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
else if (!Array.isArray(reqAny._readableState.pipes)) {
|
|
308
|
+
// pipes must be an array for Node.js unpipe() to work
|
|
309
|
+
reqAny._readableState.pipes = [];
|
|
310
|
+
}
|
|
311
|
+
/* eslint-enable @typescript-eslint/no-explicit-any, no-underscore-dangle */
|
|
312
|
+
// Handle MCP route with Express app
|
|
313
|
+
app(req, res);
|
|
314
|
+
}
|
|
315
|
+
else if (next) {
|
|
316
|
+
// Not an MCP route, call next middleware
|
|
317
|
+
next();
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
// No next callback and not an MCP route - this shouldn't happen in normal usage
|
|
321
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
322
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Run the MCP server as a standalone HTTP server.
|
|
328
|
+
*/
|
|
329
|
+
async run() {
|
|
330
|
+
const port = Number(process.env.MCP_SERVER_PORT) || 3931;
|
|
331
|
+
const baseUrl = new URL(`http://localhost:${port}`);
|
|
332
|
+
const app = await this.buildExpressApp(baseUrl);
|
|
333
|
+
// Create HTTP server from Express app
|
|
334
|
+
this.httpServer = http.createServer(app);
|
|
335
|
+
this.httpServer.listen(port, () => {
|
|
336
|
+
this.logger('Info', `Forest Admin MCP Server running on http://localhost:${port}`);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
exports.default = ForestMCPServer;
|
|
341
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3NlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLHNEQUFzRDtBQUN0RCxzRUFBc0U7QUFDdEUsdUJBQXFCO0FBSXJCLDhGQUFtRztBQUNuRyxzRkFBdUY7QUFDdkYsMEdBQW9HO0FBQ3BHLGtHQUFtRztBQUNuRywrRUFHeUQ7QUFDekQsb0VBQW9FO0FBQ3BFLDBGQUFtRztBQUNuRyxnREFBd0I7QUFDeEIsc0RBQThCO0FBQzlCLDJDQUE2QjtBQUU3QixvRkFBMEQ7QUFDMUQsMkNBQXlDO0FBQ3pDLHdEQUEyQztBQUMzQywyREFBK0U7QUFDL0UsZ0ZBQXdFO0FBQ3hFLHVDQUEwQztBQVcxQyxTQUFTLGVBQWUsQ0FBQyxLQUFlO0lBQ3RDLElBQUksS0FBSyxLQUFLLE9BQU87UUFBRSxPQUFPLENBQUMsR0FBVyxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLGdCQUFnQixHQUFHLEVBQUUsQ0FBQyxDQUFDO0lBQ3BGLElBQUksS0FBSyxLQUFLLE1BQU07UUFBRSxPQUFPLENBQUMsR0FBVyxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLGdCQUFnQixHQUFHLEVBQUUsQ0FBQyxDQUFDO0lBRWxGLE9BQU8sQ0FBQyxHQUFXLEVBQUUsRUFBRSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsRUFBRSxDQUFDLENBQUM7QUFDOUQsQ0FBQztBQUVELE1BQU0sYUFBYSxHQUFXLENBQUMsS0FBSyxFQUFFLE9BQU8sRUFBRSxFQUFFO0lBQy9DLGVBQWUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUNsQyxDQUFDLENBQUM7QUFFRixxRUFBcUU7QUFDckUsTUFBTSwwQkFBMEIsR0FBNkI7SUFDM0QsSUFBSSxFQUFFLENBQUMsZ0JBQWdCLENB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
|