@forestadmin/mcp-server 1.21.1 → 1.22.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.
- package/README.md +255 -0
- package/dist/cli.js +36 -17
- package/dist/file-uploads/ephemeral-storage.d.ts +46 -0
- package/dist/file-uploads/ephemeral-storage.js +216 -0
- package/dist/file-uploads/file-reference.d.ts +13 -0
- package/dist/file-uploads/file-reference.js +21 -0
- package/dist/file-uploads/handles.d.ts +11 -0
- package/dist/file-uploads/handles.js +41 -0
- package/dist/file-uploads/resolve.d.ts +8 -0
- package/dist/file-uploads/resolve.js +145 -0
- package/dist/file-uploads/semaphore.d.ts +3 -0
- package/dist/file-uploads/semaphore.js +37 -0
- package/dist/file-uploads/types.d.ts +74 -0
- package/dist/file-uploads/types.js +58 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/server.d.ts +15 -1
- package/dist/server.js +107 -5
- package/dist/tool-context.d.ts +2 -0
- package/dist/tools/execute-action.js +12 -4
- package/dist/tools/get-action-form.js +37 -6
- package/dist/tools/request-action-file-upload.d.ts +4 -0
- package/dist/tools/request-action-file-upload.js +162 -0
- package/dist/utils/load-file-uploads.d.ts +14 -0
- package/dist/utils/load-file-uploads.js +105 -0
- package/package.json +3 -3
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
exports.default = declareRequestActionFileUploadTool;
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
const zod_1 = require("zod");
|
|
42
|
+
const file_reference_1 = require("../file-uploads/file-reference");
|
|
43
|
+
const handles_1 = require("../file-uploads/handles");
|
|
44
|
+
const tool_with_logging_1 = __importDefault(require("../utils/tool-with-logging"));
|
|
45
|
+
const MIME_TYPE_PATTERN = /^[\w.+-]+\/[\w.+-]+$/;
|
|
46
|
+
const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i;
|
|
47
|
+
const SHA256_BASE64_PATTERN = /^[A-Za-z0-9+/]{43}=$/;
|
|
48
|
+
const MAX_FILENAME_LENGTH = 128;
|
|
49
|
+
const REQUIRED_SCOPE = 'mcp:action';
|
|
50
|
+
// Names the requirement rather than a menu path: the setting that satisfies it is not in the same
|
|
51
|
+
// place in every client, and on a managed workspace the end user may have no access to it at all.
|
|
52
|
+
const UPLOAD_PREREQUISITE = 'Uploading requires an outbound HTTP request from your environment. A hosted code execution ' +
|
|
53
|
+
'sandbox runs on its own network and reaches only allowed hosts, so the host of uploadUrl must ' +
|
|
54
|
+
'be publicly reachable — a localhost or private address never is from there — and allowed for ' +
|
|
55
|
+
'outbound traffic in that environment. Where that is configured depends on the client, and on a ' +
|
|
56
|
+
'managed workspace it may be an administrator setting rather than a user one. A blocked or ' +
|
|
57
|
+
'unreachable request is a client configuration issue, not an expired handle: report it as such ' +
|
|
58
|
+
'instead of requesting another destination.';
|
|
59
|
+
// The storage key delimits segments with '/', so keep a conservative charset. Truncation keeps
|
|
60
|
+
// the tail so the extension survives. A name made only of dots would let a backend that joins
|
|
61
|
+
// paths climb out of the per-upload directory.
|
|
62
|
+
function sanitizeFilename(filename) {
|
|
63
|
+
const safe = filename
|
|
64
|
+
.trim()
|
|
65
|
+
.slice(-MAX_FILENAME_LENGTH)
|
|
66
|
+
// Unicode-aware: ASCII \w turns `facture-été.pdf` into `facture-_t_.pdf`, so the accented
|
|
67
|
+
// letters vanish while the spaces and parentheses survive. Same key safety, no `/`.
|
|
68
|
+
.replace(/[^\p{L}\p{N}._\- ()]/gu, '_');
|
|
69
|
+
return !safe || /^\.+$/.test(safe) ? 'file' : safe;
|
|
70
|
+
}
|
|
71
|
+
function normalizeSha256(sha256) {
|
|
72
|
+
if (!sha256)
|
|
73
|
+
return undefined;
|
|
74
|
+
if (SHA256_BASE64_PATTERN.test(sha256))
|
|
75
|
+
return sha256;
|
|
76
|
+
if (SHA256_HEX_PATTERN.test(sha256))
|
|
77
|
+
return Buffer.from(sha256, 'hex').toString('base64');
|
|
78
|
+
throw new Error('sha256 must be the file digest as hex or base64.');
|
|
79
|
+
}
|
|
80
|
+
function declareRequestActionFileUploadTool(mcpServer, ctx) {
|
|
81
|
+
const { logger, fileUploads } = ctx;
|
|
82
|
+
return (0, tool_with_logging_1.default)(mcpServer, 'requestActionFileUpload', {
|
|
83
|
+
title: 'Request an upload destination for an action file field',
|
|
84
|
+
description: `Get a destination to upload a file to, then reference it in an action form field.
|
|
85
|
+
|
|
86
|
+
Call this whenever getActionForm shows a field of type "File" or "FileList". Never inline base64 file content as a field value: it would be far larger than any payload limit.
|
|
87
|
+
|
|
88
|
+
Workflow:
|
|
89
|
+
1. Compute the file's sha256 and call this tool with the filename, mimeType and sha256. The digest pins the destination to that exact content — without it, on some backends anyone who reads the upload url in a log can replace the bytes before the action runs. Skip it only when you cannot compute a digest.
|
|
90
|
+
2. Upload the raw bytes to the returned uploadUrl, with the returned method and every returned header. Some backends sign a pinned sha256 into a checksum header and reject the upload without it, so apply the headers as returned rather than assuming which ones matter. A pinned digest is re-verified when the action runs either way. The bytes must not pass through this tool or through your own output.
|
|
91
|
+
3. Pass the returned fileHandle string as the value of the file field in executeAction.
|
|
92
|
+
|
|
93
|
+
${UPLOAD_PREREQUISITE}
|
|
94
|
+
|
|
95
|
+
The url accepts one upload: to send different bytes, call this tool again for a fresh destination. The handle expires too, so run the upload and the action without a long pause in between.`,
|
|
96
|
+
inputSchema: {
|
|
97
|
+
filename: zod_1.z
|
|
98
|
+
.string()
|
|
99
|
+
.trim()
|
|
100
|
+
.min(1)
|
|
101
|
+
.describe('Original file name, e.g. "invoice-2026-01.pdf".'),
|
|
102
|
+
mimeType: zod_1.z.string().describe('Media type of the file, e.g. "application/pdf".'),
|
|
103
|
+
sha256: zod_1.z
|
|
104
|
+
.string()
|
|
105
|
+
.optional()
|
|
106
|
+
.describe('Sha256 digest of the file, hex or base64. Pins the destination to that exact ' +
|
|
107
|
+
'content; compute it and pass it whenever you can.'),
|
|
108
|
+
},
|
|
109
|
+
}, async (options, extra) => {
|
|
110
|
+
if (!fileUploads) {
|
|
111
|
+
throw new Error('File uploads are not configured on this server. ' +
|
|
112
|
+
'Ask the administrator to set the fileUploads option to enable action file fields.');
|
|
113
|
+
}
|
|
114
|
+
const userId = extra.authInfo?.extra?.userId;
|
|
115
|
+
if (userId === undefined || userId === null) {
|
|
116
|
+
throw new Error('Cannot request a file upload without an authenticated user');
|
|
117
|
+
}
|
|
118
|
+
// /mcp only requires mcp:read, but this mints a pre-authorized write into the host's
|
|
119
|
+
// storage. The route this tool replaced enforced mcp:action, so it is enforced here.
|
|
120
|
+
if (!extra.authInfo?.scopes?.includes(REQUIRED_SCOPE)) {
|
|
121
|
+
throw new Error(`Requesting a file upload requires the "${REQUIRED_SCOPE}" scope.`);
|
|
122
|
+
}
|
|
123
|
+
if (!MIME_TYPE_PATTERN.test(options.mimeType)) {
|
|
124
|
+
throw new Error(`"${options.mimeType}" is not a media type. Expected a type/subtype pair, e.g. application/pdf.`);
|
|
125
|
+
}
|
|
126
|
+
const sha256Base64 = normalizeSha256(options.sha256);
|
|
127
|
+
const safeName = sanitizeFilename(options.filename);
|
|
128
|
+
const key = `${fileUploads.keyPrefix}${crypto.randomUUID()}/${safeName}`;
|
|
129
|
+
const destination = await fileUploads.storage.createUploadUrl({
|
|
130
|
+
key,
|
|
131
|
+
mimeType: options.mimeType,
|
|
132
|
+
...(sha256Base64 && { sha256: sha256Base64 }),
|
|
133
|
+
expiresInSeconds: fileUploads.uploadUrlTtlSeconds,
|
|
134
|
+
});
|
|
135
|
+
const handle = (0, handles_1.signUploadHandle)({
|
|
136
|
+
key,
|
|
137
|
+
name: safeName,
|
|
138
|
+
mimeType: options.mimeType,
|
|
139
|
+
userId,
|
|
140
|
+
...(sha256Base64 && { sha256Base64 }),
|
|
141
|
+
}, fileUploads.authSecret, fileUploads.handleTtlSeconds);
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: 'text',
|
|
146
|
+
text: JSON.stringify({
|
|
147
|
+
uploadUrl: destination.url,
|
|
148
|
+
method: destination.method ?? 'PUT',
|
|
149
|
+
headers: destination.headers ?? { 'Content-Type': options.mimeType },
|
|
150
|
+
expiresInSeconds: fileUploads.uploadUrlTtlSeconds,
|
|
151
|
+
maxBytes: fileUploads.maxBytes,
|
|
152
|
+
fileHandle: `${file_reference_1.UPLOADED_FILE_PREFIX}${handle}`,
|
|
153
|
+
// Repeated in the response so the model has the diagnosis in context at the moment
|
|
154
|
+
// its upload fails, not only when it first read the tool description.
|
|
155
|
+
prerequisite: UPLOAD_PREREQUISITE,
|
|
156
|
+
}),
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}, logger);
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVxdWVzdC1hY3Rpb24tZmlsZS11cGxvYWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdG9vbHMvcmVxdWVzdC1hY3Rpb24tZmlsZS11cGxvYWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUF1REEscURBNkdDO0FBaktELCtDQUFpQztBQUNqQyw2QkFBd0I7QUFFeEIsbUVBQXNFO0FBQ3RFLHFEQUEyRDtBQUMzRCxtRkFBaUU7QUFFakUsTUFBTSxpQkFBaUIsR0FBRyxzQkFBc0IsQ0FBQztBQUNqRCxNQUFNLGtCQUFrQixHQUFHLGlCQUFpQixDQUFDO0FBQzdDLE1BQU0scUJBQXFCLEdBQUcsc0JBQXNCLENBQUM7QUFDckQsTUFBTSxtQkFBbUIsR0FBRyxHQUFHLENBQUM7QUFDaEMsTUFBTSxjQUFjLEdBQUcsWUFBWSxDQUFDO0FBRXBDLGtHQUFrRztBQUNsRyxrR0FBa0c7QUFDbEcsTUFBTSxtQkFBbUIsR0FDdkIsNkZBQTZGO0lBQzdGLGdHQUFnRztJQUNoRywrRkFBK0Y7SUFDL0YsaUdBQWlHO0lBQ2pHLDRGQUE0RjtJQUM1RixnR0FBZ0c7SUFDaEcsNENBQTRDLENBQUM7QUFRL0MsK0ZBQStGO0FBQy9GLDhGQUE4RjtBQUM5RiwrQ0FBK0M7QUFDL0MsU0FBUyxnQkFBZ0IsQ0FBQyxRQUFnQjtJQUN4QyxNQUFNLElBQUksR0FBRyxRQUFRO1NBQ2xCLElBQUksRUFBRTtTQUNOLEtBQUssQ0FBQyxDQUFDLG1CQUFtQixDQUFDO1FBQzVCLDBGQUEwRjtRQUMxRixvRkFBb0Y7U0FDbkYsT0FBTyxDQUFDLHdCQUF3QixFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBRTFDLE9BQU8sQ0FBQyxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7QUFDckQsQ0FBQztB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
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FileUploadsOptions } from '../file-uploads/types';
|
|
2
|
+
/**
|
|
3
|
+
* Loads the `fileUploads` options of the standalone server from a module on disk.
|
|
4
|
+
*
|
|
5
|
+
* A storage backend is an object with methods, so it cannot travel through an environment
|
|
6
|
+
* variable like every other standalone option. Without this, file uploads would be reachable
|
|
7
|
+
* only by embedding the server in an agent or by writing a custom entry point.
|
|
8
|
+
*
|
|
9
|
+
* The module default-exports the options, or a function returning them:
|
|
10
|
+
*
|
|
11
|
+
* module.exports = { storage: myS3Storage, maxBytes: 50 * 1024 * 1024 };
|
|
12
|
+
*/
|
|
13
|
+
export default function loadFileUploads(modulePath?: string): Promise<FileUploadsOptions | undefined>;
|
|
14
|
+
//# sourceMappingURL=load-file-uploads.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = loadFileUploads;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
// A module is free to throw a non-Error, and reading .message off it yields undefined.
|
|
39
|
+
const describe = (error) => (error instanceof Error ? error.message : String(error));
|
|
40
|
+
// Attached rather than passed to the constructor: the error cause option needs a newer lib than
|
|
41
|
+
// this package targets, while Node itself carries the property fine.
|
|
42
|
+
const withCause = (message, cause) => Object.assign(new Error(message), { cause });
|
|
43
|
+
/**
|
|
44
|
+
* Loads the `fileUploads` options of the standalone server from a module on disk.
|
|
45
|
+
*
|
|
46
|
+
* A storage backend is an object with methods, so it cannot travel through an environment
|
|
47
|
+
* variable like every other standalone option. Without this, file uploads would be reachable
|
|
48
|
+
* only by embedding the server in an agent or by writing a custom entry point.
|
|
49
|
+
*
|
|
50
|
+
* The module default-exports the options, or a function returning them:
|
|
51
|
+
*
|
|
52
|
+
* module.exports = { storage: myS3Storage, maxBytes: 50 * 1024 * 1024 };
|
|
53
|
+
*/
|
|
54
|
+
async function loadFileUploads(modulePath) {
|
|
55
|
+
if (!modulePath)
|
|
56
|
+
return undefined;
|
|
57
|
+
const resolved = path.resolve(process.cwd(), modulePath);
|
|
58
|
+
let loaded;
|
|
59
|
+
try {
|
|
60
|
+
// require rather than import(): this package is CommonJS, and Node handles an ESM module here
|
|
61
|
+
// too, whereas a file:// specifier breaks under the test runner.
|
|
62
|
+
// eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requires
|
|
63
|
+
loaded = require(resolved);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
// "cannot find it" and "it ran and failed" send the operator to different places, and only the
|
|
67
|
+
// first is about the path they configured.
|
|
68
|
+
const absent = error?.code === 'MODULE_NOT_FOUND' &&
|
|
69
|
+
String(error?.message).includes(resolved);
|
|
70
|
+
throw withCause(absent
|
|
71
|
+
? `FOREST_MCP_UPLOAD_STORAGE_MODULE "${modulePath}" was not found (resolved to ${resolved}).`
|
|
72
|
+
: `FOREST_MCP_UPLOAD_STORAGE_MODULE "${modulePath}" failed while loading: ${describe(error)}`, error);
|
|
73
|
+
}
|
|
74
|
+
const exported = loaded?.default ?? loaded;
|
|
75
|
+
let options;
|
|
76
|
+
try {
|
|
77
|
+
options = (typeof exported === 'function' ? await exported() : exported);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
// The sync path above is wrapped; a rejecting factory would otherwise surface with no mention
|
|
81
|
+
// of the module it came from.
|
|
82
|
+
throw withCause(`FOREST_MCP_UPLOAD_STORAGE_MODULE "${modulePath}" failed while building the options: ${describe(error)}`, error);
|
|
83
|
+
}
|
|
84
|
+
// An object without a storage key is legitimate — it selects the in-memory store. Nothing at all
|
|
85
|
+
// is a mistake, most likely a module that forgot to export.
|
|
86
|
+
if (!options || typeof options !== 'object') {
|
|
87
|
+
throw new Error(`FOREST_MCP_UPLOAD_STORAGE_MODULE "${modulePath}" must export the fileUploads options, or a ` +
|
|
88
|
+
'function returning them. See the fileUploads section of the mcp-server README.');
|
|
89
|
+
}
|
|
90
|
+
// Whoever sets this variable did it *for* the storage: a bad import or a factory that returned
|
|
91
|
+
// early leaves `{ storage: undefined }`, which the server would silently replace with the
|
|
92
|
+
// in-memory store — putting a deliberately replicated deployment on the one backend that cannot
|
|
93
|
+
// serve it. A missing method fails later, on the first user's upload, so it is checked here too.
|
|
94
|
+
if ('storage' in options) {
|
|
95
|
+
const storage = options.storage;
|
|
96
|
+
const missing = ['createUploadUrl', 'download', 'getSize'].filter(method => typeof storage?.[method] !== 'function');
|
|
97
|
+
if (missing.length) {
|
|
98
|
+
throw new Error(`FOREST_MCP_UPLOAD_STORAGE_MODULE "${modulePath}" exports a storage missing ` +
|
|
99
|
+
`${missing.join(', ')}. An UploadStorage implements createUploadUrl, download and ` +
|
|
100
|
+
'getSize. Omit the storage entirely to use the in-memory store on purpose.');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return options;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9hZC1maWxlLXVwbG9hZHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvbG9hZC1maWxlLXVwbG9hZHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFzQkEsa0NBMkVDO0FBL0ZELDJDQUE2QjtBQUU3Qix1RkFBdUY7QUFDdkYsTUFBTSxRQUFRLEdBQUcsQ0FBQyxLQUFjLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7QUFFOUYsZ0dBQWdHO0FBQ2hHLHFFQUFxRTtBQUNyRSxNQUFNLFNBQVMsR0FBRyxDQUFDLE9BQWUsRUFBRSxLQUFjLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLEVBQUUsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO0FBRXBHOzs7Ozs7Ozs7O0dBVUc7QUFDWSxLQUFLLFVBQVUsZUFBZSxDQUMzQyxVQUFtQjtJQUVuQixJQUFJLENBQUMsVUFBVTtRQUFFLE9BQU8sU0FBUyxDQUFDO0lBRWxDLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQ3pELElBQUksTUFBdUQsQ0FBQztJQUU1RCxJQUFJLENBQUM7UUFDSCw4RkFBOEY7UUFDOUYsaUVBQWlFO1FBQ2pFLHlHQUF5RztRQUN6RyxNQUFNLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQzdCLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2YsK0ZBQStGO1FBQy9GLDJDQUEyQztRQUMzQyxNQUFNLE1BQU0sR0FDVCxLQUErQixFQUFFLElBQUksS0FBSyxrQkFBa0I7WUFDN0QsTUFBTSxDQUFFLEtBQWUsRUFBRSxPQUFPLENBQUMsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUM7UUFFdkQsTUFBTSxTQUFTLENBQ2IsTUFBTTtZQUNKLENBQUMsQ0FBQyxxQ0FBcUMsVUFBVSxnQ0FBZ0MsUUFBUSxJQUFJO1lBQzdGLENBQUMsQ0FBQyxxQ0FBcUMsVUFBVSwyQkFBMkIsUUFBUSxDQUNoRixLQUFLLENBQ04sRUFBRSxFQUNQLEtBQUssQ0FDTixDQUFDO0lBQ0osQ0FBQztJQUVELE1BQU0sUUFBUSxHQUFHLE1BQU0sRUFBRSxPQUFPLElBQUksTUFBTSxDQUFDO0lBQzNDLElBQUksT0FBdUMsQ0FBQztJQUU1QyxJQUFJLENBQUM7UUFDSCxPQUFPLEdBQUcsQ0FBQyxPQUFPLFFBQVEsS0FBSyxVQUFVLENBQUMsQ0FBQyxDQUFDLE1BQU0sUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBdUIsQ0FBQztJQUNqRyxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLDhGQUE4RjtRQUM5Riw4QkFBOEI7UUFDOUIsTUFBTSxTQUFTLENBQ2IscUNBQXFDLFVBQVUsd0NBQXdDLFFBQVEsQ0FDN0YsS0FBSyxDQUNOLEVBQUUsRUFDSCxLQUFLLENBQ04sQ0FBQztJQUNKLENBQUM7SUFFRCxpR0FBaUc7SUFDakcsNERBQTREO0lBQzVELElBQUksQ0FBQyxPQUFPLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDNUMsTUFBTSxJQUFJLEtBQUssQ0FDYixxQ0FBcUMsVUFBVSw4Q0FBOEM7WUFDM0YsZ0ZBQWdGLENBQ25GLENBQUM7SUFDSixDQUFDO0lBRUQsK0ZBQStGO0lBQy9GLDBGQUEwRjtJQUMxRixnR0FBZ0c7SUFDaEcsaUdBQWlHO0lBQ2pHLElBQUksU0FBUyxJQUFJLE9BQU8sRUFBRSxDQUFDO1FBQ3pCLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxPQUF5RCxDQUFDO1FBQ2xGLE1BQU0sT0FBTyxHQUFHLENBQUMsaUJBQWlCLEVBQUUsVUFBVSxFQUFFLFNBQVMsQ0FBQyxDQUFDLE1BQU0sQ0FDL0QsTUFBTSxDQUFDLEVBQUUsQ0FBQyxPQUFPLE9BQU8sRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLLFVBQVUsQ0FDbEQsQ0FBQztRQUVGLElBQUksT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ25CLE1BQU0sSUFBSSxLQUFLLENBQ2IscUNBQXFDLFVBQVUsOEJBQThCO2dCQUMzRSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLDhEQUE4RDtnQkFDbkYsMkVBQTJFLENBQzlFLENBQUM7UUFDSixDQUFDO0lBQ0gsQ0FBQztJQUVELE9BQU8sT0FBTyxDQUFDO0FBQ2pCLENBQUMifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forestadmin/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.1",
|
|
4
4
|
"description": "Model Context Protocol server for Forest Admin with OAuth authentication",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"directory": "packages/mcp-server"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@forestadmin/agent-client": "1.
|
|
20
|
-
"@forestadmin/forestadmin-client": "1.42.
|
|
19
|
+
"@forestadmin/agent-client": "1.14.0",
|
|
20
|
+
"@forestadmin/forestadmin-client": "1.42.1",
|
|
21
21
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
22
22
|
"cors": "^2.8.5",
|
|
23
23
|
"express": "^5.2.1",
|