@forestnpm/n8n-nodes-forest 1.0.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 +73 -0
- package/dist/credentials/ForestApi.credentials.d.ts +9 -0
- package/dist/credentials/ForestApi.credentials.js +44 -0
- package/dist/credentials/ForestMcpApi.credentials.d.ts +7 -0
- package/dist/credentials/ForestMcpApi.credentials.js +66 -0
- package/dist/credentials/ForestMcpOAuth2Api.credentials.d.ts +8 -0
- package/dist/credentials/ForestMcpOAuth2Api.credentials.js +87 -0
- package/dist/nodes/Forest/Forest.node.d.ts +11 -0
- package/dist/nodes/Forest/Forest.node.js +26673 -0
- package/dist/nodes/Forest/forest.svg +23 -0
- package/dist/nodes/Forest/listSearch.d.ts +2 -0
- package/dist/nodes/Forest/listSearch.js +40 -0
- package/dist/nodes/Forest/resourceMapping.d.ts +2 -0
- package/dist/nodes/Forest/resourceMapping.js +100 -0
- package/dist/nodes/Forest/types.d.ts +7 -0
- package/dist/nodes/Forest/types.js +2 -0
- package/dist/nodes/Forest/utils.d.ts +38 -0
- package/dist/nodes/Forest/utils.js +185 -0
- package/package.json +68 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35.93 36">
|
|
3
|
+
<defs>
|
|
4
|
+
<style>
|
|
5
|
+
.cls-1 {
|
|
6
|
+
fill: #183225;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.cls-1, .cls-2 {
|
|
10
|
+
stroke-width: 0px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.cls-2 {
|
|
14
|
+
fill: #d5ee84;
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
17
|
+
</defs>
|
|
18
|
+
<g id="Isotype_only" data-name="Isotype only">
|
|
19
|
+
<rect class="cls-2" x="-.04" y="0" width="36" height="36" rx="6.75" ry="6.75"/>
|
|
20
|
+
<path class="cls-1" d="M17.96,9.86l7.82,7.16,2.31-2.12-10.13-9.28L7.84,14.91l2.31,2.11,7.81-7.16Z"/>
|
|
21
|
+
<path class="cls-1" d="M7.84,22.15l2.31,2.11,6.18-5.66v12.34h3.27v-12.32l6.18,5.65,2.31-2.11-10.13-9.28-10.12,9.28Z"/>
|
|
22
|
+
</g>
|
|
23
|
+
</svg>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTools = getTools;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
async function getTools(filter, paginationToken) {
|
|
6
|
+
const authentication = this.getNodeParameter('authentication');
|
|
7
|
+
const { headers, endpointUrl } = await (0, utils_1.getAuthHeadersAndEndpoint)(this, authentication);
|
|
8
|
+
// Return empty results if credentials are not configured yet
|
|
9
|
+
if (!endpointUrl) {
|
|
10
|
+
return { results: [] };
|
|
11
|
+
}
|
|
12
|
+
const node = this.getNode();
|
|
13
|
+
const client = await (0, utils_1.connectMcpClient)({
|
|
14
|
+
endpointUrl,
|
|
15
|
+
headers,
|
|
16
|
+
name: node.type,
|
|
17
|
+
version: node.typeVersion,
|
|
18
|
+
});
|
|
19
|
+
// Throw error if connection fails (e.g., auth error, network error)
|
|
20
|
+
if (!client.ok) {
|
|
21
|
+
throw (0, utils_1.mapToNodeOperationError)(node, client.error);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const result = await client.result.listTools({ cursor: paginationToken });
|
|
25
|
+
const tools = filter
|
|
26
|
+
? result.tools.filter((tool) => tool.name.toLowerCase().includes(filter.toLowerCase()))
|
|
27
|
+
: result.tools;
|
|
28
|
+
return {
|
|
29
|
+
results: tools.map((tool) => ({
|
|
30
|
+
name: tool.name,
|
|
31
|
+
value: tool.name,
|
|
32
|
+
description: tool.description,
|
|
33
|
+
})),
|
|
34
|
+
paginationToken: result.nextCursor,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
await client.result.close();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getToolParameters = getToolParameters;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
function jsonSchemaTypeToFieldType(type) {
|
|
6
|
+
const primaryType = Array.isArray(type) ? type[0] : type;
|
|
7
|
+
switch (primaryType) {
|
|
8
|
+
case 'string':
|
|
9
|
+
return 'string';
|
|
10
|
+
case 'number':
|
|
11
|
+
case 'integer':
|
|
12
|
+
return 'number';
|
|
13
|
+
case 'boolean':
|
|
14
|
+
return 'boolean';
|
|
15
|
+
case 'array':
|
|
16
|
+
return 'array';
|
|
17
|
+
case 'object':
|
|
18
|
+
return 'object';
|
|
19
|
+
default:
|
|
20
|
+
return 'string';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function convertJsonSchemaToResourceMapperFields(schema, requiredFields = []) {
|
|
24
|
+
const fields = [];
|
|
25
|
+
if (schema.type !== 'object' || !schema.properties) {
|
|
26
|
+
return fields;
|
|
27
|
+
}
|
|
28
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
29
|
+
if (typeof value === 'boolean')
|
|
30
|
+
continue;
|
|
31
|
+
const propertySchema = value;
|
|
32
|
+
const displayName = propertySchema.description ? `${key} - ${propertySchema.description}` : key;
|
|
33
|
+
const fieldType = jsonSchemaTypeToFieldType(propertySchema.type || 'string');
|
|
34
|
+
const field = {
|
|
35
|
+
id: key,
|
|
36
|
+
displayName,
|
|
37
|
+
type: fieldType,
|
|
38
|
+
required: requiredFields.includes(key),
|
|
39
|
+
defaultMatch: false,
|
|
40
|
+
canBeUsedToMatch: false,
|
|
41
|
+
display: true,
|
|
42
|
+
};
|
|
43
|
+
// Set default value for object and array types
|
|
44
|
+
if (fieldType === 'object') {
|
|
45
|
+
field.defaultValue = '{}';
|
|
46
|
+
}
|
|
47
|
+
else if (fieldType === 'array') {
|
|
48
|
+
field.defaultValue = '[]';
|
|
49
|
+
}
|
|
50
|
+
if (propertySchema.enum &&
|
|
51
|
+
Array.isArray(propertySchema.enum) &&
|
|
52
|
+
propertySchema.enum.length > 0) {
|
|
53
|
+
field.type = 'options';
|
|
54
|
+
field.options = propertySchema.enum.map((value) => ({
|
|
55
|
+
name: String(value),
|
|
56
|
+
value,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
fields.push(field);
|
|
60
|
+
}
|
|
61
|
+
return fields;
|
|
62
|
+
}
|
|
63
|
+
async function getToolParameters() {
|
|
64
|
+
const toolId = this.getNodeParameter('tool.value');
|
|
65
|
+
// Return empty fields if no tool is selected yet
|
|
66
|
+
if (!toolId) {
|
|
67
|
+
return { fields: [] };
|
|
68
|
+
}
|
|
69
|
+
const authentication = this.getNodeParameter('authentication');
|
|
70
|
+
const node = this.getNode();
|
|
71
|
+
const { headers, endpointUrl } = await (0, utils_1.getAuthHeadersAndEndpoint)(this, authentication);
|
|
72
|
+
if (!endpointUrl) {
|
|
73
|
+
return { fields: [] };
|
|
74
|
+
}
|
|
75
|
+
const client = await (0, utils_1.connectMcpClient)({
|
|
76
|
+
endpointUrl,
|
|
77
|
+
headers,
|
|
78
|
+
name: node.type,
|
|
79
|
+
version: node.typeVersion,
|
|
80
|
+
});
|
|
81
|
+
// Return empty fields if connection fails (e.g., auth error, network error)
|
|
82
|
+
if (!client.ok) {
|
|
83
|
+
return { fields: [] };
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const tools = await (0, utils_1.getAllTools)(client.result);
|
|
87
|
+
const tool = tools.find((t) => t.name === toolId);
|
|
88
|
+
// Return empty fields if tool not found
|
|
89
|
+
if (!tool) {
|
|
90
|
+
return { fields: [] };
|
|
91
|
+
}
|
|
92
|
+
const schema = tool.inputSchema;
|
|
93
|
+
const requiredFields = Array.isArray(schema.required) ? schema.required : [];
|
|
94
|
+
const fields = convertJsonSchemaToResourceMapperFields(schema, requiredFields);
|
|
95
|
+
return { fields };
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
await client.result.close();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import type { IExecuteFunctions, ILoadOptionsFunctions, INode } from 'n8n-workflow';
|
|
3
|
+
import { NodeOperationError } from 'n8n-workflow';
|
|
4
|
+
import type { McpAuthenticationOption, McpTool } from './types';
|
|
5
|
+
export type Result<T, E> = {
|
|
6
|
+
ok: true;
|
|
7
|
+
result: T;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
error: E;
|
|
11
|
+
};
|
|
12
|
+
export declare function cleanParameters<T>(obj: T): T;
|
|
13
|
+
export declare function getAllTools(client: Client, cursor?: string): Promise<McpTool[]>;
|
|
14
|
+
type ConnectMcpClientError = {
|
|
15
|
+
type: 'invalid_url';
|
|
16
|
+
error: Error;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'connection';
|
|
19
|
+
error: Error;
|
|
20
|
+
} | {
|
|
21
|
+
type: 'auth';
|
|
22
|
+
error: Error;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'not_found';
|
|
25
|
+
error: Error;
|
|
26
|
+
};
|
|
27
|
+
export declare function mapToNodeOperationError(node: INode, error: ConnectMcpClientError): NodeOperationError;
|
|
28
|
+
export declare function connectMcpClient({ headers, endpointUrl, name, version, }: {
|
|
29
|
+
endpointUrl: string;
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
name: string;
|
|
32
|
+
version: number;
|
|
33
|
+
}): Promise<Result<Client, ConnectMcpClientError>>;
|
|
34
|
+
export declare function getAuthHeadersAndEndpoint(ctx: Pick<IExecuteFunctions | ILoadOptionsFunctions, 'getCredentials'>, authentication: McpAuthenticationOption): Promise<{
|
|
35
|
+
headers?: Record<string, string>;
|
|
36
|
+
endpointUrl?: string;
|
|
37
|
+
}>;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cleanParameters = cleanParameters;
|
|
4
|
+
exports.getAllTools = getAllTools;
|
|
5
|
+
exports.mapToNodeOperationError = mapToNodeOperationError;
|
|
6
|
+
exports.connectMcpClient = connectMcpClient;
|
|
7
|
+
exports.getAuthHeadersAndEndpoint = getAuthHeadersAndEndpoint;
|
|
8
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
9
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
10
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
11
|
+
function cleanParameters(obj) {
|
|
12
|
+
if (obj === null || obj === undefined) {
|
|
13
|
+
return obj;
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(obj)) {
|
|
16
|
+
const cleanedArray = obj
|
|
17
|
+
.map((item) => cleanParameters(item))
|
|
18
|
+
.filter((item) => item !== undefined);
|
|
19
|
+
return cleanedArray;
|
|
20
|
+
}
|
|
21
|
+
if (typeof obj === 'object') {
|
|
22
|
+
const cleaned = {};
|
|
23
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
24
|
+
const cleanedValue = cleanParameters(value);
|
|
25
|
+
// Skip undefined values and empty objects
|
|
26
|
+
if (cleanedValue !== undefined) {
|
|
27
|
+
if (typeof cleanedValue === 'object' &&
|
|
28
|
+
cleanedValue !== null &&
|
|
29
|
+
!Array.isArray(cleanedValue) &&
|
|
30
|
+
Object.keys(cleanedValue).length === 0) {
|
|
31
|
+
// Skip empty objects
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
cleaned[key] = cleanedValue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return cleaned;
|
|
38
|
+
}
|
|
39
|
+
return obj;
|
|
40
|
+
}
|
|
41
|
+
function createResultOk(result) {
|
|
42
|
+
return { ok: true, result };
|
|
43
|
+
}
|
|
44
|
+
function createResultError(error) {
|
|
45
|
+
return { ok: false, error };
|
|
46
|
+
}
|
|
47
|
+
async function getAllTools(client, cursor) {
|
|
48
|
+
const { tools, nextCursor } = await client.listTools({ cursor });
|
|
49
|
+
if (nextCursor) {
|
|
50
|
+
return tools.concat(await getAllTools(client, nextCursor));
|
|
51
|
+
}
|
|
52
|
+
return tools;
|
|
53
|
+
}
|
|
54
|
+
function normalizeAndValidateUrl(input) {
|
|
55
|
+
const withProtocol = !/^https?:\/\//i.test(input) ? `https://${input}` : input;
|
|
56
|
+
try {
|
|
57
|
+
return createResultOk(new URL(withProtocol));
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return createResultError(error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function errorHasCode(error, code) {
|
|
64
|
+
return (!!error &&
|
|
65
|
+
typeof error === 'object' &&
|
|
66
|
+
(('code' in error && Number(error.code) === code) ||
|
|
67
|
+
('message' in error &&
|
|
68
|
+
typeof error.message === 'string' &&
|
|
69
|
+
error.message.includes(code.toString()))));
|
|
70
|
+
}
|
|
71
|
+
function isUnauthorizedError(error) {
|
|
72
|
+
return errorHasCode(error, 401);
|
|
73
|
+
}
|
|
74
|
+
function isForbiddenError(error) {
|
|
75
|
+
return errorHasCode(error, 403);
|
|
76
|
+
}
|
|
77
|
+
function isNotFoundError(error) {
|
|
78
|
+
return errorHasCode(error, 404);
|
|
79
|
+
}
|
|
80
|
+
function mapToNodeOperationError(node, error) {
|
|
81
|
+
switch (error.type) {
|
|
82
|
+
case 'invalid_url':
|
|
83
|
+
return new n8n_workflow_1.NodeOperationError(node, error.error, {
|
|
84
|
+
message: 'Could not connect to your Forest MCP server. The provided URL is invalid.',
|
|
85
|
+
});
|
|
86
|
+
case 'auth':
|
|
87
|
+
return new n8n_workflow_1.NodeOperationError(node, error.error, {
|
|
88
|
+
message: 'Could not connect to your Forest MCP server. Authentication failed.',
|
|
89
|
+
});
|
|
90
|
+
case 'not_found':
|
|
91
|
+
return new n8n_workflow_1.NodeOperationError(node, error.error, {
|
|
92
|
+
message: 'MCP server not found. The MCP server has not been setup on your Forest agent. See https://docs.forestadmin.com/developer-guide-agents-nodejs/agent-customization/ai/mcp-server',
|
|
93
|
+
});
|
|
94
|
+
case 'connection':
|
|
95
|
+
default:
|
|
96
|
+
return new n8n_workflow_1.NodeOperationError(node, error.error, {
|
|
97
|
+
message: 'Could not connect to your Forest MCP server',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function connectMcpClient({ headers, endpointUrl, name, version, }) {
|
|
102
|
+
const endpoint = normalizeAndValidateUrl(endpointUrl);
|
|
103
|
+
if (!endpoint.ok) {
|
|
104
|
+
return createResultError({ type: 'invalid_url', error: endpoint.error });
|
|
105
|
+
}
|
|
106
|
+
const client = new index_js_1.Client({ name, version: version.toString() }, { capabilities: {} });
|
|
107
|
+
try {
|
|
108
|
+
const transport = new streamableHttp_js_1.StreamableHTTPClientTransport(endpoint.result, {
|
|
109
|
+
requestInit: { headers },
|
|
110
|
+
});
|
|
111
|
+
await client.connect(transport);
|
|
112
|
+
return createResultOk(client);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (isUnauthorizedError(error) || isForbiddenError(error)) {
|
|
116
|
+
return createResultError({ type: 'auth', error: error });
|
|
117
|
+
}
|
|
118
|
+
else if (isNotFoundError(error)) {
|
|
119
|
+
return createResultError({ type: 'not_found', error: error });
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
return createResultError({ type: 'connection', error: error });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function getAuthHeadersAndEndpoint(ctx, authentication) {
|
|
127
|
+
var _a, _b, _c, _d, _e;
|
|
128
|
+
switch (authentication) {
|
|
129
|
+
case 'bearerAuth': {
|
|
130
|
+
let result = null;
|
|
131
|
+
try {
|
|
132
|
+
result = await ctx.getCredentials('forestMcpApi');
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Credentials not configured or not accessible
|
|
136
|
+
return {};
|
|
137
|
+
}
|
|
138
|
+
if (!result)
|
|
139
|
+
return {};
|
|
140
|
+
const serverUrl = ((_a = result.serverUrl) === null || _a === void 0 ? void 0 : _a.trim()) || '';
|
|
141
|
+
const endpointUrl = serverUrl.endsWith('/mcp') ? serverUrl : `${serverUrl}/mcp`;
|
|
142
|
+
const token = ((_b = result.token) === null || _b === void 0 ? void 0 : _b.trim()) || '';
|
|
143
|
+
if (!token) {
|
|
144
|
+
return {};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
148
|
+
endpointUrl,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
case 'mcpOAuth2Api':
|
|
152
|
+
default: {
|
|
153
|
+
let result = null;
|
|
154
|
+
try {
|
|
155
|
+
// n8n automatically refreshes the token if expired when getCredentials is called
|
|
156
|
+
result = await ctx.getCredentials('forestMcpOAuth2Api');
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
// This can happen if:
|
|
160
|
+
// - Credentials not configured
|
|
161
|
+
// - Token refresh failed (refresh_token expired or invalid)
|
|
162
|
+
// - OAuth server is unreachable
|
|
163
|
+
console.error('Failed to get OAuth2 credentials:', error);
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
if (!result)
|
|
167
|
+
return {};
|
|
168
|
+
const serverUrl = ((_c = result.serverUrl) === null || _c === void 0 ? void 0 : _c.trim()) || '';
|
|
169
|
+
const endpointUrl = serverUrl.endsWith('/mcp') ? serverUrl : `${serverUrl}/mcp`;
|
|
170
|
+
// Check if oauthTokenData exists and has an access_token
|
|
171
|
+
const accessToken = ((_e = (_d = result.oauthTokenData) === null || _d === void 0 ? void 0 : _d.access_token) === null || _e === void 0 ? void 0 : _e.trim()) || '';
|
|
172
|
+
if (!accessToken) {
|
|
173
|
+
// OAuth2 credentials exist but token data is not available yet
|
|
174
|
+
// This can happen if:
|
|
175
|
+
// - User hasn't completed the OAuth flow
|
|
176
|
+
// - Token refresh failed and n8n cleared the token data
|
|
177
|
+
return {};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
181
|
+
endpointUrl,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forestnpm/n8n-nodes-forest",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "n8n community node for Forest",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package"
|
|
7
|
+
],
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"homepage": "",
|
|
10
|
+
"author": {
|
|
11
|
+
"name": "Pierre Merlet",
|
|
12
|
+
"email": "pierre.merlet@forestadmin.com"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/ForestAdmin/n8n-nodes-forest.git"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18.10",
|
|
20
|
+
"pnpm": ">=9.1"
|
|
21
|
+
},
|
|
22
|
+
"main": "index.js",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"preinstall": "npx only-allow pnpm",
|
|
25
|
+
"build": "node esbuild.config.mjs && gulp build:icons",
|
|
26
|
+
"dev": "tsc --watch",
|
|
27
|
+
"format": "prettier nodes credentials --write",
|
|
28
|
+
"lint": "eslint nodes credentials package.json",
|
|
29
|
+
"lintfix": "eslint nodes credentials package.json --fix",
|
|
30
|
+
"prepublishOnly": "pnpm build && pnpm lint -c .eslintrc.prepublish.js nodes credentials package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"n8n": {
|
|
36
|
+
"n8nNodesApiVersion": 1,
|
|
37
|
+
"credentials": [
|
|
38
|
+
"dist/credentials/ForestMcpApi.credentials.js",
|
|
39
|
+
"dist/credentials/ForestMcpOAuth2Api.credentials.js"
|
|
40
|
+
],
|
|
41
|
+
"nodes": [
|
|
42
|
+
"dist/nodes/Forest/Forest.node.js"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
47
|
+
"@n8n/eslint-plugin-community-nodes": "^0.7.0",
|
|
48
|
+
"@types/json-schema": "^7.0.15",
|
|
49
|
+
"@types/node": "^22.0.0",
|
|
50
|
+
"@typescript-eslint/parser": "^7.15.0",
|
|
51
|
+
"esbuild": "^0.27.3",
|
|
52
|
+
"eslint": "^8.56.0",
|
|
53
|
+
"eslint-plugin-n8n-nodes-base": "^1.16.1",
|
|
54
|
+
"gulp": "^4.0.2",
|
|
55
|
+
"jsonc-eslint-parser": "^2.4.2",
|
|
56
|
+
"n8n-workflow": "*",
|
|
57
|
+
"prettier": "^3.3.2",
|
|
58
|
+
"typescript": "^5.5.3",
|
|
59
|
+
"zod": "^3.24.0"
|
|
60
|
+
},
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"n8n-workflow": "*"
|
|
66
|
+
},
|
|
67
|
+
"packageManager": "pnpm@9.15.9"
|
|
68
|
+
}
|