@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.
Files changed (54) hide show
  1. package/README.md +128 -0
  2. package/dist/__mocks__/version.d.ts +3 -0
  3. package/dist/__mocks__/version.js +7 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.js +14 -0
  6. package/dist/factory.d.ts +51 -0
  7. package/dist/factory.js +40 -0
  8. package/dist/forest-oauth-provider.d.ts +44 -0
  9. package/dist/forest-oauth-provider.js +253 -0
  10. package/dist/forest-oauth-provider.test.d.ts +2 -0
  11. package/dist/forest-oauth-provider.test.js +590 -0
  12. package/dist/index.d.ts +4 -0
  13. package/dist/index.js +13 -0
  14. package/dist/mcp-paths.d.ts +5 -0
  15. package/dist/mcp-paths.js +11 -0
  16. package/dist/polyfills.d.ts +12 -0
  17. package/dist/polyfills.js +27 -0
  18. package/dist/schemas/filter.d.ts +4 -0
  19. package/dist/schemas/filter.js +70 -0
  20. package/dist/schemas/filter.test.d.ts +2 -0
  21. package/dist/schemas/filter.test.js +234 -0
  22. package/dist/server.d.ts +87 -0
  23. package/dist/server.js +341 -0
  24. package/dist/server.test.d.ts +2 -0
  25. package/dist/server.test.js +901 -0
  26. package/dist/test-utils/mock-server.d.ts +62 -0
  27. package/dist/test-utils/mock-server.js +187 -0
  28. package/dist/tools/list.d.ts +4 -0
  29. package/dist/tools/list.js +98 -0
  30. package/dist/tools/list.test.d.ts +2 -0
  31. package/dist/tools/list.test.js +385 -0
  32. package/dist/utils/activity-logs-creator.d.ts +9 -0
  33. package/dist/utils/activity-logs-creator.js +65 -0
  34. package/dist/utils/activity-logs-creator.test.d.ts +2 -0
  35. package/dist/utils/activity-logs-creator.test.js +239 -0
  36. package/dist/utils/agent-caller.d.ts +13 -0
  37. package/dist/utils/agent-caller.js +24 -0
  38. package/dist/utils/agent-caller.test.d.ts +2 -0
  39. package/dist/utils/agent-caller.test.js +102 -0
  40. package/dist/utils/error-parser.d.ts +10 -0
  41. package/dist/utils/error-parser.js +56 -0
  42. package/dist/utils/error-parser.test.d.ts +2 -0
  43. package/dist/utils/error-parser.test.js +124 -0
  44. package/dist/utils/schema-fetcher.d.ts +53 -0
  45. package/dist/utils/schema-fetcher.js +85 -0
  46. package/dist/utils/schema-fetcher.test.d.ts +2 -0
  47. package/dist/utils/schema-fetcher.test.js +212 -0
  48. package/dist/utils/sse-error-logger.d.ts +14 -0
  49. package/dist/utils/sse-error-logger.js +112 -0
  50. package/dist/utils/tool-with-logging.d.ts +44 -0
  51. package/dist/utils/tool-with-logging.js +66 -0
  52. package/dist/version.d.ts +3 -0
  53. package/dist/version.js +43 -0
  54. package/package.json +49 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Schema Fetcher Utility
3
+ *
4
+ * Fetches the Forest Admin schema from the `/liana/forest-schema` endpoint
5
+ * and caches it for 24 hours.
6
+ */
7
+ export interface ForestField {
8
+ field: string;
9
+ type: string;
10
+ isFilterable?: boolean;
11
+ isSortable?: boolean;
12
+ enum: string[] | null;
13
+ inverseOf?: string | null;
14
+ reference: string | null;
15
+ isReadOnly: boolean;
16
+ isRequired: boolean;
17
+ integration?: string | null;
18
+ validations?: unknown[];
19
+ defaultValue?: unknown;
20
+ isPrimaryKey: boolean;
21
+ }
22
+ export interface ForestCollection {
23
+ name: string;
24
+ fields: ForestField[];
25
+ }
26
+ export interface ForestSchema {
27
+ collections: ForestCollection[];
28
+ }
29
+ /**
30
+ * Fetches the Forest Admin schema from the server.
31
+ * The schema is cached for 24 hours to reduce API calls.
32
+ *
33
+ * @param forestServerUrl - The Forest Admin server URL
34
+ * @returns The Forest Admin schema containing collections
35
+ */
36
+ export declare function fetchForestSchema(forestServerUrl: string): Promise<ForestSchema>;
37
+ /**
38
+ * Extracts collection names from the Forest Admin schema.
39
+ *
40
+ * @param schema - The Forest Admin schema
41
+ * @returns Array of collection names
42
+ */
43
+ export declare function getCollectionNames(schema: ForestSchema): string[];
44
+ export declare function getFieldsOfCollection(schema: ForestSchema, collectionName: string): ForestField[];
45
+ /**
46
+ * Clears the schema cache. Useful for testing.
47
+ */
48
+ export declare function clearSchemaCache(): void;
49
+ /**
50
+ * Sets the schema cache. Useful for testing.
51
+ */
52
+ export declare function setSchemaCache(schema: ForestSchema, fetchedAt?: number): void;
53
+ //# sourceMappingURL=schema-fetcher.d.ts.map
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fetchForestSchema = fetchForestSchema;
7
+ exports.getCollectionNames = getCollectionNames;
8
+ exports.getFieldsOfCollection = getFieldsOfCollection;
9
+ exports.clearSchemaCache = clearSchemaCache;
10
+ exports.setSchemaCache = setSchemaCache;
11
+ const jsonapi_serializer_1 = __importDefault(require("jsonapi-serializer"));
12
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
13
+ let schemaCache = null;
14
+ /**
15
+ * Fetches the Forest Admin schema from the server.
16
+ * The schema is cached for 24 hours to reduce API calls.
17
+ *
18
+ * @param forestServerUrl - The Forest Admin server URL
19
+ * @returns The Forest Admin schema containing collections
20
+ */
21
+ async function fetchForestSchema(forestServerUrl) {
22
+ const now = Date.now();
23
+ // Return cached schema if it's still valid (less than 24 hours old)
24
+ if (schemaCache && now - schemaCache.fetchedAt < ONE_DAY_MS) {
25
+ return schemaCache.schema;
26
+ }
27
+ const envSecret = process.env.FOREST_ENV_SECRET;
28
+ if (!envSecret) {
29
+ throw new Error('FOREST_ENV_SECRET environment variable is not set');
30
+ }
31
+ const response = await fetch(`${forestServerUrl}/liana/forest-schema`, {
32
+ method: 'GET',
33
+ headers: {
34
+ 'forest-secret-key': envSecret,
35
+ 'Content-Type': 'application/json',
36
+ },
37
+ });
38
+ if (!response.ok) {
39
+ const errorText = await response.text();
40
+ throw new Error(`Failed to fetch forest schema: ${errorText}`);
41
+ }
42
+ const schema = (await response.json());
43
+ const serializer = new jsonapi_serializer_1.default.Deserializer({
44
+ keyForAttribute: 'camelCase',
45
+ });
46
+ const collections = (await serializer.deserialize(schema));
47
+ // Update cache
48
+ schemaCache = {
49
+ schema: { collections },
50
+ fetchedAt: now,
51
+ };
52
+ return { collections };
53
+ }
54
+ /**
55
+ * Extracts collection names from the Forest Admin schema.
56
+ *
57
+ * @param schema - The Forest Admin schema
58
+ * @returns Array of collection names
59
+ */
60
+ function getCollectionNames(schema) {
61
+ return schema.collections.map(collection => collection.name);
62
+ }
63
+ function getFieldsOfCollection(schema, collectionName) {
64
+ const collection = schema.collections.find(col => col.name === collectionName);
65
+ if (!collection) {
66
+ throw new Error(`Collection "${collectionName}" not found in schema`);
67
+ }
68
+ return collection.fields;
69
+ }
70
+ /**
71
+ * Clears the schema cache. Useful for testing.
72
+ */
73
+ function clearSchemaCache() {
74
+ schemaCache = null;
75
+ }
76
+ /**
77
+ * Sets the schema cache. Useful for testing.
78
+ */
79
+ function setSchemaCache(schema, fetchedAt) {
80
+ schemaCache = {
81
+ schema,
82
+ fetchedAt: fetchedAt ?? Date.now(),
83
+ };
84
+ }
85
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2NoZW1hLWZldGNoZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvc2NoZW1hLWZldGNoZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUF5REEsOENBNENDO0FBUUQsZ0RBRUM7QUFFRCxzREFRQztBQUtELDRDQUVDO0FBS0Qsd0NBS0M7QUExSUQsNEVBQW1EO0FBOENuRCxNQUFNLFVBQVUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUM7QUFFdkMsSUFBSSxXQUFXLEdBQXVCLElBQUksQ0FBQztBQUUzQzs7Ozs7O0dBTUc7QUFDSSxLQUFLLFVBQVUsaUJBQWlCLENBQUMsZUFBdUI7SUFDN0QsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBRXZCLG9FQUFvRTtJQUNwRSxJQUFJLFdBQVcsSUFBSSxHQUFHLEdBQUcsV0FBVyxDQUFDLFNBQVMsR0FBRyxVQUFVLEVBQUUsQ0FBQztRQUM1RCxPQUFPLFdBQVcsQ0FBQyxNQUFNLENBQUM7SUFDNUIsQ0FBQztJQUVELE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLENBQUM7SUFFaEQsSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1FBQ2YsTUFBTSxJQUFJLEtBQUssQ0FBQyxtREFBbUQsQ0FBQyxDQUFDO0lBQ3ZFLENBQUM7SUFFRCxNQUFNLFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLGVBQWUsc0JBQXNCLEVBQUU7UUFDckUsTUFBTSxFQUFFLEtBQUs7UUFDYixPQUFPLEVBQUU7WUFDUCxtQkFBbUIsRUFBRSxTQUFTO1lBQzlCLGNBQWMsRUFBRSxrQkFBa0I7U0FDbkM7S0FDRixDQUFDLENBQUM7SUFFSCxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ2pCLE1BQU0sU0FBUyxHQUFHLE1BQU0sUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ3hDLE1BQU0sSUFBSSxLQUFLLENBQUMsa0NBQWtDLFNBQVMsRUFBRSxDQUFDLENBQUM7SUFDakUsQ0FBQztJQUVELE1BQU0sTUFBTSxHQUFHLENBQUMsTUFBTSxRQUFRLENBQUMsSUFBSSxFQUFFLENBSXBDLENBQUM7SUFDRixNQUFNLFVBQVUsR0FBRyxJQUFJLDRCQUFpQixDQUFDLFlBQVksQ0FBQztRQUNwRCxlQUFlLEVBQUUsV0FBVztLQUM3QixDQUFDLENBQUM7SUFDSCxNQUFNLFdBQVcsR0FBRyxDQUFDLE1BQU0sVUFBVSxDQUFDLFdBQVcsQ0FBQyxNQUFNLENBQUMsQ0FBdUIsQ0FBQztJQUVqRixlQUFlO0lBQ2YsV0FBVyxHQUFHO1FBQ1osTUFBTSxFQUFFLEVBQUUsV0FBVyxFQUFFO1FBQ3ZCLFNBQVMsRUFBRSxHQUFHO0tBQ2YsQ0FBQztJQUVGLE9BQU8sRUFBRSxXQUFXLEVBQUUsQ0FBQztBQUN6QixDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFnQixrQkFBa0IsQ0FBQyxNQUFvQjtJQUNyRCxPQUFPLE1BQU0sQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQy9ELENBQUM7QUFFRCxTQUFnQixxQkFBcUIsQ0FBQyxNQUFvQixFQUFFLGNBQXNCO0lBQ2hGLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLElBQUksS0FBSyxjQUFjLENBQUMsQ0FBQztJQUUvRSxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDaEIsTUFBTSxJQUFJLEtBQUssQ0FBQyxlQUFlLGNBQWMsdUJBQXVCLENBQUMsQ0FBQztJQUN4RSxDQUFDO0lBRUQsT0FBTyxVQUFVLENBQUMsTUFBTSxDQUFDO0FBQzNCLENBQUM7QUFFRDs7R0FFRztBQUNILFNBQWdCLGdCQUFnQjtJQUM5QixXQUFXLEdBQUcsSUFBSSxDQUFDO0FBQ3JCLENBQUM7QUFFRDs7R0FFRztBQUNILFNBQWdCLGNBQWMsQ0FBQyxNQUFvQixFQUFFLFNBQWtCO0lBQ3JFLFdBQVcsR0FBRztRQUNaLE1BQU07UUFDTixTQUFTLEVBQUUsU0FBUyxJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUU7S0FDbkMsQ0FBQztBQUNKLENBQUMifQ==
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=schema-fetcher.test.d.ts.map
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const schema_fetcher_1 = require("./schema-fetcher");
4
+ describe('schema-fetcher', () => {
5
+ const originalFetch = global.fetch;
6
+ const originalEnv = process.env.FOREST_ENV_SECRET;
7
+ let mockFetch;
8
+ beforeEach(() => {
9
+ mockFetch = jest.fn();
10
+ global.fetch = mockFetch;
11
+ process.env.FOREST_ENV_SECRET = 'test-env-secret';
12
+ (0, schema_fetcher_1.clearSchemaCache)();
13
+ });
14
+ afterAll(() => {
15
+ global.fetch = originalFetch;
16
+ process.env.FOREST_ENV_SECRET = originalEnv;
17
+ });
18
+ // Helper to create JSON:API formatted schema response
19
+ const createJsonApiSchema = (collections) => ({
20
+ data: collections.map((col, index) => ({
21
+ id: `collection-${index}`,
22
+ type: 'collections',
23
+ attributes: {
24
+ name: col.name,
25
+ fields: col.fields,
26
+ },
27
+ })),
28
+ meta: {
29
+ liana: 'forest-express-sequelize',
30
+ liana_version: '9.0.0',
31
+ liana_features: null,
32
+ },
33
+ });
34
+ describe('fetchForestSchema', () => {
35
+ const mockJsonApiResponse = createJsonApiSchema([
36
+ { name: 'users', fields: [{ field: 'id', type: 'Number' }] },
37
+ { name: 'products', fields: [{ field: 'name', type: 'String' }] },
38
+ ]);
39
+ it('should fetch schema from forest server and deserialize JSON:API response', async () => {
40
+ mockFetch.mockResolvedValue({
41
+ ok: true,
42
+ json: () => Promise.resolve(mockJsonApiResponse),
43
+ });
44
+ const result = await (0, schema_fetcher_1.fetchForestSchema)('https://api.forestadmin.com');
45
+ expect(mockFetch).toHaveBeenCalledWith('https://api.forestadmin.com/liana/forest-schema', {
46
+ method: 'GET',
47
+ headers: {
48
+ 'forest-secret-key': 'test-env-secret',
49
+ 'Content-Type': 'application/json',
50
+ },
51
+ });
52
+ expect(result.collections).toHaveLength(2);
53
+ expect(result.collections[0].name).toBe('users');
54
+ expect(result.collections[1].name).toBe('products');
55
+ });
56
+ it('should use cached schema on subsequent calls', async () => {
57
+ mockFetch.mockResolvedValue({
58
+ ok: true,
59
+ json: () => Promise.resolve(mockJsonApiResponse),
60
+ });
61
+ const result1 = await (0, schema_fetcher_1.fetchForestSchema)('https://api.forestadmin.com');
62
+ const result2 = await (0, schema_fetcher_1.fetchForestSchema)('https://api.forestadmin.com');
63
+ expect(mockFetch).toHaveBeenCalledTimes(1);
64
+ expect(result1).toEqual(result2);
65
+ });
66
+ it('should refetch schema after cache expires (24 hours)', async () => {
67
+ const oldSchema = {
68
+ collections: [{ name: 'old_collection', fields: [] }],
69
+ };
70
+ const newJsonApiResponse = createJsonApiSchema([{ name: 'new_collection', fields: [] }]);
71
+ // Set cache with an old timestamp (more than 24 hours ago)
72
+ const oneDayAgo = Date.now() - 25 * 60 * 60 * 1000;
73
+ (0, schema_fetcher_1.setSchemaCache)(oldSchema, oneDayAgo);
74
+ mockFetch.mockResolvedValue({
75
+ ok: true,
76
+ json: () => Promise.resolve(newJsonApiResponse),
77
+ });
78
+ const result = await (0, schema_fetcher_1.fetchForestSchema)('https://api.forestadmin.com');
79
+ expect(mockFetch).toHaveBeenCalledTimes(1);
80
+ expect(result.collections).toHaveLength(1);
81
+ expect(result.collections[0].name).toBe('new_collection');
82
+ });
83
+ it('should not refetch schema before cache expires', async () => {
84
+ const cachedSchema = {
85
+ collections: [{ name: 'cached_collection', fields: [] }],
86
+ };
87
+ // Set cache with a recent timestamp (less than 24 hours ago)
88
+ const recentTime = Date.now() - 1 * 60 * 60 * 1000; // 1 hour ago
89
+ (0, schema_fetcher_1.setSchemaCache)(cachedSchema, recentTime);
90
+ const result = await (0, schema_fetcher_1.fetchForestSchema)('https://api.forestadmin.com');
91
+ expect(mockFetch).not.toHaveBeenCalled();
92
+ expect(result).toEqual(cachedSchema);
93
+ });
94
+ it('should throw error when FOREST_ENV_SECRET is not set', async () => {
95
+ delete process.env.FOREST_ENV_SECRET;
96
+ await expect((0, schema_fetcher_1.fetchForestSchema)('https://api.forestadmin.com')).rejects.toThrow('FOREST_ENV_SECRET environment variable is not set');
97
+ });
98
+ it('should throw error when response is not ok', async () => {
99
+ mockFetch.mockResolvedValue({
100
+ ok: false,
101
+ text: () => Promise.resolve('Server error message'),
102
+ });
103
+ await expect((0, schema_fetcher_1.fetchForestSchema)('https://api.forestadmin.com')).rejects.toThrow('Failed to fetch forest schema: Server error message');
104
+ });
105
+ it('should use custom forest server URL', async () => {
106
+ mockFetch.mockResolvedValue({
107
+ ok: true,
108
+ json: () => Promise.resolve(mockJsonApiResponse),
109
+ });
110
+ await (0, schema_fetcher_1.fetchForestSchema)('https://custom.forestadmin.com');
111
+ expect(mockFetch).toHaveBeenCalledWith('https://custom.forestadmin.com/liana/forest-schema', expect.any(Object));
112
+ });
113
+ });
114
+ describe('getCollectionNames', () => {
115
+ it('should extract collection names from schema', () => {
116
+ const schema = {
117
+ collections: [
118
+ { name: 'users', fields: [] },
119
+ { name: 'products', fields: [] },
120
+ { name: 'orders', fields: [] },
121
+ ],
122
+ };
123
+ const result = (0, schema_fetcher_1.getCollectionNames)(schema);
124
+ expect(result).toEqual(['users', 'products', 'orders']);
125
+ });
126
+ it('should return empty array for empty collections', () => {
127
+ const schema = {
128
+ collections: [],
129
+ };
130
+ const result = (0, schema_fetcher_1.getCollectionNames)(schema);
131
+ expect(result).toEqual([]);
132
+ });
133
+ });
134
+ describe('clearSchemaCache', () => {
135
+ // Helper to create JSON:API formatted schema response
136
+ const createJsonApiSchema = (collections) => ({
137
+ data: collections.map((col, index) => ({
138
+ id: `collection-${index}`,
139
+ type: 'collections',
140
+ attributes: {
141
+ name: col.name,
142
+ fields: col.fields,
143
+ },
144
+ })),
145
+ meta: {
146
+ liana: 'forest-express-sequelize',
147
+ liana_version: '9.0.0',
148
+ liana_features: null,
149
+ },
150
+ });
151
+ it('should clear the cache so next fetch makes API call', async () => {
152
+ const jsonApiResponse = createJsonApiSchema([{ name: 'test', fields: [] }]);
153
+ mockFetch.mockResolvedValue({
154
+ ok: true,
155
+ json: () => Promise.resolve(jsonApiResponse),
156
+ });
157
+ // First fetch
158
+ await (0, schema_fetcher_1.fetchForestSchema)('https://api.forestadmin.com');
159
+ expect(mockFetch).toHaveBeenCalledTimes(1);
160
+ // Clear cache
161
+ (0, schema_fetcher_1.clearSchemaCache)();
162
+ // Second fetch should make API call
163
+ await (0, schema_fetcher_1.fetchForestSchema)('https://api.forestadmin.com');
164
+ expect(mockFetch).toHaveBeenCalledTimes(2);
165
+ });
166
+ });
167
+ describe('setSchemaCache', () => {
168
+ // Helper to create JSON:API formatted schema response
169
+ const createJsonApiSchema = (collections) => ({
170
+ data: collections.map((col, index) => ({
171
+ id: `collection-${index}`,
172
+ type: 'collections',
173
+ attributes: {
174
+ name: col.name,
175
+ fields: col.fields,
176
+ },
177
+ })),
178
+ meta: {
179
+ liana: 'forest-express-sequelize',
180
+ liana_version: '9.0.0',
181
+ liana_features: null,
182
+ },
183
+ });
184
+ it('should set cache with current timestamp by default', async () => {
185
+ const schema = {
186
+ collections: [{ name: 'cached', fields: [] }],
187
+ };
188
+ (0, schema_fetcher_1.setSchemaCache)(schema);
189
+ const result = await (0, schema_fetcher_1.fetchForestSchema)('https://api.forestadmin.com');
190
+ expect(mockFetch).not.toHaveBeenCalled();
191
+ expect(result).toEqual(schema);
192
+ });
193
+ it('should set cache with custom timestamp', async () => {
194
+ const schema = {
195
+ collections: [{ name: 'old_cached', fields: [] }],
196
+ };
197
+ const newJsonApiResponse = createJsonApiSchema([{ name: 'new', fields: [] }]);
198
+ // Set cache with old timestamp
199
+ const oldTime = Date.now() - 25 * 60 * 60 * 1000;
200
+ (0, schema_fetcher_1.setSchemaCache)(schema, oldTime);
201
+ mockFetch.mockResolvedValue({
202
+ ok: true,
203
+ json: () => Promise.resolve(newJsonApiResponse),
204
+ });
205
+ const result = await (0, schema_fetcher_1.fetchForestSchema)('https://api.forestadmin.com');
206
+ expect(mockFetch).toHaveBeenCalledTimes(1);
207
+ expect(result.collections).toHaveLength(1);
208
+ expect(result.collections[0].name).toBe('new');
209
+ });
210
+ });
211
+ });
212
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2NoZW1hLWZldGNoZXIudGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9zY2hlbWEtZmV0Y2hlci50ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEscURBTTBCO0FBRTFCLFFBQVEsQ0FBQyxnQkFBZ0IsRUFBRSxHQUFHLEVBQUU7SUFDOUIsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQztJQUNuQyxNQUFNLFdBQVcsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFDO0lBQ2xELElBQUksU0FBb0IsQ0FBQztJQUV6QixVQUFVLENBQUMsR0FBRyxFQUFFO1FBQ2QsU0FBUyxHQUFHLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUN0QixNQUFNLENBQUMsS0FBSyxHQUFHLFNBQVMsQ0FBQztRQUN6QixPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixHQUFHLGlCQUFpQixDQUFDO1FBQ2xELElBQUEsaUNBQWdCLEdBQUUsQ0FBQztJQUNyQixDQUFDLENBQUMsQ0FBQztJQUVILFFBQVEsQ0FBQyxHQUFHLEVBQUU7UUFDWixNQUFNLENBQUMsS0FBSyxHQUFHLGFBQWEsQ0FBQztRQUM3QixPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixHQUFHLFdBQVcsQ0FBQztJQUM5QyxDQUFDLENBQUMsQ0FBQztJQUVILHNEQUFzRDtJQUN0RCxNQUFNLG1CQUFtQixHQUFHLENBQzFCLFdBQW9GLEVBQ3BGLEVBQUUsQ0FBQyxDQUFDO1FBQ0osSUFBSSxFQUFFLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQ3JDLEVBQUUsRUFBRSxjQUFjLEtBQUssRUFBRTtZQUN6QixJQUFJLEVBQUUsYUFBYTtZQUNuQixVQUFVLEVBQUU7Z0JBQ1YsSUFBSSxFQUFFLEdBQUcsQ0FBQyxJQUFJO2dCQUNkLE1BQU0sRUFBRSxHQUFHLENBQUMsTUFBTTthQUNuQjtTQUNGLENBQUMsQ0FBQztRQUNILElBQUksRUFBRTtZQUNKLEtBQUssRUFBRSwwQkFBMEI7WUFDakMsYUFBYSxFQUFFLE9BQU87WUFDdEIsY0FBYyxFQUFFLElBQUk7U0FDckI7S0FDRixDQUFDLENBQUM7SUFFSCxRQUFRLENBQUMsbUJBQW1CLEVBQUUsR0FBRyxFQUFFO1FBQ2pDLE1BQU0sbUJBQW1CLEdBQUcsbUJBQW1CLENBQUM7WUFDOUMsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxDQUFDLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLENBQUMsRUFBRTtZQUM1RCxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLENBQUMsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsQ0FBQyxFQUFFO1NBQ2xFLENBQUMsQ0FBQztRQUVILEVBQUUsQ0FBQywwRUFBMEUsRUFBRSxLQUFLLElBQUksRUFBRTtZQUN4RixTQUFTLENBQUMsaUJBQWlCLENBQUM7Z0JBQzFCLEVBQUUsRUFBRSxJQUFJO2dCQUNSLElBQUksRUFBRSxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDO2FBQ2pELENBQUMsQ0FBQztZQUVILE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBQSxrQ0FBaUIsRUFBQyw2QkFBNkIsQ0FBQyxDQUFDO1lBRXRFLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxvQkFBb0IsQ0FDcEMsaURBQWlELEVBQ2pEO2dCQUNFLE1BQU0sRUFBRSxLQUFLO2dCQUNiLE9BQU8sRUFBRTtvQkFDUCxtQkFBbUIsRUFBRSxpQkFBaUI7b0JBQ3RDLGNBQWMsRUFBRSxrQkFBa0I7aUJBQ25DO2FBQ0YsQ0FDRixDQUFDO1lBQ0YsTUFBTSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDM0MsTUFBTSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ2pELE1BQU0sQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUN0RCxDQUFDLENBQUMsQ0FBQztRQUVILEVBQUUsQ0FBQyw4Q0FBOEMsRUFBRSxLQUFLLElBQUksRUFBRTtZQUM1RCxTQUFTLENBQUMsaUJBQWlCLENBQUM7Z0JBQzFCLEVBQUUsRUFBRSxJQUFJO2dCQUNSLElBQUksRUFBRSxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDO2FBQ2pELENBQUMsQ0FBQztZQUVILE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBQSxrQ0FBaUIsRUFBQyw2QkFBNkIsQ0FBQyxDQUFDO1lBQ3ZFLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBQSxrQ0FBaUIsRUFBQyw2QkFBNkIsQ0FBQyxDQUFDO1lBRXZFLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUMzQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ25DLENBQUMsQ0FBQyxDQUFDO1FBRUgsRUFBRSxDQUFDLHNEQUFzRCxFQUFFLEtBQUssSUFBSSxFQUFFO1lBQ3BFLE1BQU0sU0FBUyxHQUFpQjtnQkFDOUIsV0FBVyxFQUFFLENBQUMsRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxFQUFFLEVBQUUsRUFBRSxDQUFDO2FBQ3RELENBQUM7WUFDRixNQUFNLGtCQUFrQixHQUFHLG1CQUFtQixDQUFDLENBQUMsRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQztZQUV6RiwyREFBMkQ7WUFDM0QsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztZQUNuRCxJQUFBLCtCQUFjLEVBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQyxDQUFDO1lBRXJDLFNBQVMsQ0FBQyxpQkFBaUIsQ0FBQztnQkFDMUIsRUFBRSxFQUFFLElBQUk7Z0JBQ1IsSUFBSSxFQUFFLEdBQUcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUM7YUFDaEQsQ0FBQyxDQUFDO1lBRUgsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFBLGtDQUFpQixFQUFDLDZCQUE2QixDQUFDLENBQUM7WUFFdEUsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDLHFCQUFxQixDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQzNDLE1BQU0sQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQzNDLE1BQU0sQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBQzVELENBQUMsQ0FBQyxDQUFDO1FBRUgsRUFBRSxDQUFDLGdEQUFnRCxFQUFFLEtBQUssSUFBSSxFQUFFO1lBQzlELE1BQU0sWUFBWSxHQUFpQjtnQkFDakMsV0FBVyxFQUFFLENBQUMsRUFBRSxJQUFJLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxFQUFFLEVBQUUsRUFBRSxDQUFDO2FBQ3pELENBQUM7WUFFRiw2REFBNkQ7WUFDN0QsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDLGFBQWE7WUFDakUsSUFBQSwrQkFBYyxFQUFDLFlBQVksRUFBRSxVQUFVLENBQUMsQ0FBQztZQUV6QyxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUEsa0NBQWlCLEVBQUMsNkJBQTZCLENBQUMsQ0FBQztZQUV0RSxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsR0FBRyxDQUFDLGdCQUFnQixFQUFFLENBQUM7WUFDekMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUN2QyxDQUFDLENBQUMsQ0FBQztRQUVILEVBQUUsQ0FBQyxzREFBc0QsRUFBRSxLQUFLLElBQUksRUFBRTtZQUNwRSxPQUFPLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLENBQUM7WUFFckMsTUFBTSxNQUFNLENBQUMsSUFBQSxrQ0FBaUIsRUFBQyw2QkFBNkIsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FDNUUsbURBQW1ELENBQ3BELENBQUM7UUFDSixDQUFDLENBQUMsQ0FBQztRQUVILEVBQUUsQ0FBQyw0Q0FBNEMsRUFBRSxLQUFLLElBQUksRUFBRTtZQUMxRCxTQUFTLENBQUMsaUJBQWlCLENBQUM7Z0JBQzFCLEVBQUUsRUFBRSxLQUFLO2dCQUNULElBQUksRUFBRSxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLHNCQUFzQixDQUFDO2FBQ3BELENBQUMsQ0FBQztZQUVILE1BQU0sTUFBTSxDQUFDLElBQUEsa0NBQWlCLEVBQUMsNkJBQTZCLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQzVFLHFEQUFxRCxDQUN0RCxDQUFDO1FBQ0osQ0FBQyxDQUFDLENBQUM7UUFFSCxFQUFFLENBQUMscUNBQXFDLEVBQUUsS0FBSyxJQUFJLEVBQUU7WUFDbkQsU0FBUyxDQUFDLGlCQUFpQixDQUFDO2dCQUMxQixFQUFFLEVBQUUsSUFBSTtnQkFDUixJQUFJLEVBQUUsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQzthQUNqRCxDQUFDLENBQUM7WUFFSCxNQUFNLElBQUEsa0NBQWlCLEVBQUMsZ0NBQWdDLENBQUMsQ0FBQztZQUUxRCxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsb0JBQW9CLENBQ3BDLG9EQUFvRCxFQUNwRCxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUNuQixDQUFDO1FBQ0osQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDLENBQUMsQ0FBQztJQUVILFFBQVEsQ0FBQyxvQkFBb0IsRUFBRSxHQUFHLEVBQUU7UUFDbEMsRUFBRSxDQUFDLDZDQUE2QyxFQUFFLEdBQUcsRUFBRTtZQUNyRCxNQUFNLE1BQU0sR0FBaUI7Z0JBQzNCLFdBQVcsRUFBRTtvQkFDWCxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUUsRUFBRTtvQkFDN0IsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUU7b0JBQ2hDLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFO2lCQUMvQjthQUNGLENBQUM7WUFFRixNQUFNLE1BQU0sR0FBRyxJQUFBLG1DQUFrQixFQUFDLE1BQU0sQ0FBQyxDQUFDO1lBRTFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsVUFBVSxFQUFFLFFBQVEsQ0FBQyxDQUFDLENBQUM7UUFDMUQsQ0FBQyxDQUFDLENBQUM7UUFFSCxFQUFFLENBQUMsaURBQWlELEVBQUUsR0FBRyxFQUFFO1lBQ3pELE1BQU0sTUFBTSxHQUFpQjtnQkFDM0IsV0FBVyxFQUFFLEVBQUU7YUFDaEIsQ0FBQztZQUVGLE1BQU0sTUFBTSxHQUFHLElBQUEsbUNBQWtCLEVBQUMsTUFBTSxDQUFDLENBQUM7WUFFMUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUM3QixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUMsQ0FBQyxDQUFDO0lBRUgsUUFBUSxDQUFDLGtCQUFrQixFQUFFLEdBQUcsRUFBRTtRQUNoQyxzREFBc0Q7UUFDdEQsTUFBTSxtQkFBbUIsR0FBRyxDQUMxQixXQUFvRixFQUNwRixFQUFFLENBQUMsQ0FBQztZQUNKLElBQUksRUFBRSxXQUFXLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztnQkFDckMsRUFBRSxFQUFFLGNBQWMsS0FBSyxFQUFFO2dCQUN6QixJQUFJLEVBQUUsYUFBYTtnQkFDbkIsVUFBVSxFQUFFO29CQUNWLElBQUksRUFBRSxHQUFHLENBQUMsSUFBSTtvQkFDZCxNQUFNLEVBQUUsR0FBRyxDQUFDLE1BQU07aUJBQ25CO2FBQ0YsQ0FBQyxDQUFDO1lBQ0gsSUFBSSxFQUFFO2dCQUNKLEtBQUssRUFBRSwwQkFBMEI7Z0JBQ2pDLGFBQWEsRUFBRSxPQUFPO2dCQUN0QixjQUFjLEVBQUUsSUFBSTthQUNyQjtTQUNGLENBQUMsQ0FBQztRQUVILEVBQUUsQ0FBQyxxREFBcUQsRUFBRSxLQUFLLElBQUksRUFBRTtZQUNuRSxNQUFNLGVBQWUsR0FBRyxtQkFBbUIsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO1lBRTVFLFNBQVMsQ0FBQyxpQkFBaUIsQ0FBQztnQkFDMUIsRUFBRSxFQUFFLElBQUk7Z0JBQ1IsSUFBSSxFQUFFLEdBQUcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsZUFBZSxDQUFDO2FBQzdDLENBQUMsQ0FBQztZQUVILGNBQWM7WUFDZCxNQUFNLElBQUEsa0NBQWlCLEVBQUMsNkJBQTZCLENBQUMsQ0FBQztZQUN2RCxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMscUJBQXFCLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFFM0MsY0FBYztZQUNkLElBQUEsaUNBQWdCLEdBQUUsQ0FBQztZQUVuQixvQ0FBb0M7WUFDcEMsTUFBTSxJQUFBLGtDQUFpQixFQUFDLDZCQUE2QixDQUFDLENBQUM7WUFDdkQsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDLHFCQUFxQixDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQzdDLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQyxDQUFDLENBQUM7SUFFSCxRQUFRLENBQUMsZ0JBQWdCLEVBQUUsR0FBRyxFQUFFO1FBQzlCLHNEQUFzRDtRQUN0RCxNQUFNLG1CQUFtQixHQUFHLENBQzFCLFdBQW9GLEVBQ3BGLEVBQUUsQ0FBQyxDQUFDO1lBQ0osSUFBSSxFQUFFLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO2dCQUNyQyxFQUFFLEVBQUUsY0FBYyxLQUFLLEVBQUU7Z0JBQ3pCLElBQUksRUFBRSxhQUFhO2dCQUNuQixVQUFVLEVBQUU7b0JBQ1YsSUFBSSxFQUFFLEdBQUcsQ0FBQyxJQUFJO29CQUNkLE1BQU0sRUFBRSxHQUFHLENBQUMsTUFBTTtpQkFDbkI7YUFDRixDQUFDLENBQUM7WUFDSCxJQUFJLEVBQUU7Z0JBQ0osS0FBSyxFQUFFLDBCQUEwQjtnQkFDakMsYUFBYSxFQUFFLE9BQU87Z0JBQ3RCLGNBQWMsRUFBRSxJQUFJO2FBQ3JCO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsRUFBRSxDQUFDLG9EQUFvRCxFQUFFLEtBQUssSUFBSSxFQUFFO1lBQ2xFLE1BQU0sTUFBTSxHQUFpQjtnQkFDM0IsV0FBVyxFQUFFLENBQUMsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUUsQ0FBQzthQUM5QyxDQUFDO1lBRUYsSUFBQSwrQkFBYyxFQUFDLE1BQU0sQ0FBQyxDQUFDO1lBRXZCLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBQSxrQ0FBaUIsRUFBQyw2QkFBNkIsQ0FBQyxDQUFDO1lBRXRFLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztZQUN6QyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2pDLENBQUMsQ0FBQyxDQUFDO1FBRUgsRUFBRSxDQUFDLHdDQUF3QyxFQUFFLEtBQUssSUFBSSxFQUFFO1lBQ3RELE1BQU0sTUFBTSxHQUFpQjtnQkFDM0IsV0FBVyxFQUFFLENBQUMsRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUUsQ0FBQzthQUNsRCxDQUFDO1lBQ0YsTUFBTSxrQkFBa0IsR0FBRyxtQkFBbUIsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO1lBRTlFLCtCQUErQjtZQUMvQixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDO1lBQ2pELElBQUEsK0JBQWMsRUFBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUM7WUFFaEMsU0FBUyxDQUFDLGlCQUFpQixDQUFDO2dCQUMxQixFQUFFLEVBQUUsSUFBSTtnQkFDUixJQUFJLEVBQUUsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQzthQUNoRCxDQUFDLENBQUM7WUFFSCxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUEsa0NBQWlCLEVBQUMsNkJBQTZCLENBQUMsQ0FBQztZQUV0RSxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMscUJBQXFCLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDM0MsTUFBTSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDM0MsTUFBTSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ2pELENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyJ9
@@ -0,0 +1,14 @@
1
+ import type { Logger } from '../server.js';
2
+ import type { Response } from 'express';
3
+ /**
4
+ * Wraps an Express response to intercept and log MCP errors from SSE streams.
5
+ *
6
+ * Why we need this:
7
+ * The MCP SDK sends errors as SSE events, not HTTP errors. To log these errors
8
+ * server-side, we intercept the response stream and parse each chunk for errors.
9
+ *
10
+ * This function modifies res.write() and res.end() to add logging without
11
+ * affecting the actual response sent to the client.
12
+ */
13
+ export default function interceptResponseForErrorLogging(res: Response, logger: Logger): void;
14
+ //# sourceMappingURL=sse-error-logger.d.ts.map
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = interceptResponseForErrorLogging;
4
+ // -----------------------------------------------------------------------------
5
+ // Chunk Conversion
6
+ // -----------------------------------------------------------------------------
7
+ /**
8
+ * Converts various chunk types to a UTF-8 string.
9
+ * Handles Buffer, Uint8Array, and string inputs.
10
+ */
11
+ function chunkToString(chunk) {
12
+ if (typeof chunk === 'string') {
13
+ return chunk;
14
+ }
15
+ if (Buffer.isBuffer(chunk)) {
16
+ return chunk.toString('utf8');
17
+ }
18
+ return new TextDecoder('utf8').decode(chunk);
19
+ }
20
+ // -----------------------------------------------------------------------------
21
+ // SSE Parsing
22
+ // -----------------------------------------------------------------------------
23
+ /**
24
+ * Extracts the JSON payload from an SSE data line.
25
+ * SSE format: "event: message\ndata: {...}\n\n"
26
+ */
27
+ function extractJsonFromSseData(data) {
28
+ const match = data.match(/data:\s*({.+})/s);
29
+ return match ? match[1] : null;
30
+ }
31
+ /**
32
+ * Parses SSE data and logs any MCP errors found.
33
+ *
34
+ * MCP errors come in two forms:
35
+ * 1. JSON-RPC errors: { error: { message: "..." } }
36
+ * 2. Tool errors: { result: { isError: true, content: [{ text: "..." }] } }
37
+ */
38
+ function parseAndLogMcpErrors(data, logger) {
39
+ const jsonString = extractJsonFromSseData(data);
40
+ if (!jsonString) {
41
+ return;
42
+ }
43
+ let parsed;
44
+ try {
45
+ parsed = JSON.parse(jsonString);
46
+ }
47
+ catch (error) {
48
+ // SyntaxError is expected for non-JSON SSE events (like "ping")
49
+ if (!(error instanceof SyntaxError)) {
50
+ logger('Warn', `[MCP] Failed to parse SSE response: ${error}`);
51
+ }
52
+ return;
53
+ }
54
+ // Log JSON-RPC level errors
55
+ if (parsed.error?.message) {
56
+ logger('Error', `[MCP] ${parsed.error.message}`);
57
+ }
58
+ // Log tool execution errors (isError: true in result)
59
+ if (parsed.result?.isError) {
60
+ const errorText = parsed.result.content?.[0]?.text || 'Unknown error';
61
+ logger('Error', `[MCP] Tool error: ${errorText}`);
62
+ }
63
+ }
64
+ // -----------------------------------------------------------------------------
65
+ // Response Chunk Logging
66
+ // -----------------------------------------------------------------------------
67
+ /**
68
+ * Safely attempts to log errors from a response chunk.
69
+ * Never throws - logging failures should not affect the response.
70
+ */
71
+ function logChunkErrors(chunk, logger) {
72
+ if (chunk == null) {
73
+ return;
74
+ }
75
+ try {
76
+ const data = chunkToString(chunk);
77
+ parseAndLogMcpErrors(data, logger);
78
+ }
79
+ catch (error) {
80
+ logger('Warn', `[MCP] Failed to parse response for error logging: ${error}`);
81
+ }
82
+ }
83
+ // -----------------------------------------------------------------------------
84
+ // Response Interceptor
85
+ // -----------------------------------------------------------------------------
86
+ /**
87
+ * Wraps an Express response to intercept and log MCP errors from SSE streams.
88
+ *
89
+ * Why we need this:
90
+ * The MCP SDK sends errors as SSE events, not HTTP errors. To log these errors
91
+ * server-side, we intercept the response stream and parse each chunk for errors.
92
+ *
93
+ * This function modifies res.write() and res.end() to add logging without
94
+ * affecting the actual response sent to the client.
95
+ */
96
+ function interceptResponseForErrorLogging(res, logger) {
97
+ const originalWrite = res.write.bind(res);
98
+ const originalEnd = res.end.bind(res);
99
+ // Intercept streaming chunks (res.write)
100
+ res.write = function interceptedWrite(chunk, encodingOrCallback, callback) {
101
+ logChunkErrors(chunk, logger);
102
+ return originalWrite(chunk, encodingOrCallback, callback);
103
+ };
104
+ // Intercept final chunk (res.end)
105
+ res.end = function interceptedEnd(chunk, encodingOrCallback, callback) {
106
+ if (chunk && typeof chunk !== 'function') {
107
+ logChunkErrors(chunk, logger);
108
+ }
109
+ return originalEnd(chunk, encodingOrCallback, callback);
110
+ };
111
+ }
112
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3NlLWVycm9yLWxvZ2dlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9zc2UtZXJyb3ItbG9nZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBZ0lBLG1EQTJCQztBQTFJRCxnRkFBZ0Y7QUFDaEYsbUJBQW1CO0FBQ25CLGdGQUFnRjtBQUVoRjs7O0dBR0c7QUFDSCxTQUFTLGFBQWEsQ0FBQyxLQUFtQztJQUN4RCxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQzlCLE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztJQUVELElBQUksTUFBTSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQzNCLE9BQU8sS0FBSyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUNoQyxDQUFDO0lBRUQsT0FBTyxJQUFJLFdBQVcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDL0MsQ0FBQztBQUVELGdGQUFnRjtBQUNoRixjQUFjO0FBQ2QsZ0ZBQWdGO0FBRWhGOzs7R0FHRztBQUNILFNBQVMsc0JBQXNCLENBQUMsSUFBWTtJQUMxQyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLGlCQUFpQixDQUFDLENBQUM7SUFFNUMsT0FBTyxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO0FBQ2pDLENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxTQUFTLG9CQUFvQixDQUFDLElBQVksRUFBRSxNQUFjO0lBQ3hELE1BQU0sVUFBVSxHQUFHLHNCQUFzQixDQUFDLElBQUksQ0FBQyxDQUFDO0lBRWhELElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUNoQixPQUFPO0lBQ1QsQ0FBQztJQUVELElBQUksTUFBMEIsQ0FBQztJQUUvQixJQUFJLENBQUM7UUFDSCxNQUFNLEdB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
@@ -0,0 +1,44 @@
1
+ import type { Logger } from '../server.js';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { z } from 'zod';
4
+ type ZodRawShape = Record<string, z.ZodTypeAny>;
5
+ interface ToolConfig<TSchema extends ZodRawShape> {
6
+ title: string;
7
+ description: string;
8
+ inputSchema: TSchema;
9
+ }
10
+ type ToolResult = {
11
+ content: {
12
+ type: string;
13
+ text: string;
14
+ }[];
15
+ isError?: boolean;
16
+ };
17
+ /**
18
+ * Registers an MCP tool with automatic validation error logging.
19
+ *
20
+ * This wrapper logs validation errors with detailed field information,
21
+ * which helps debug tool calls when clients send invalid arguments.
22
+ *
23
+ * Note: Execution errors are logged by the SSE response interceptor in server.ts,
24
+ * so we don't duplicate that logging here.
25
+ *
26
+ * @example
27
+ * registerToolWithLogging(
28
+ * mcpServer,
29
+ * 'list',
30
+ * {
31
+ * title: 'List Records',
32
+ * description: 'Lists records from a collection',
33
+ * inputSchema: { collectionName: z.string() },
34
+ * },
35
+ * async (args) => {
36
+ * const records = await fetchRecords(args.collectionName);
37
+ * return { content: [{ type: 'text', text: JSON.stringify(records) }] };
38
+ * },
39
+ * logger,
40
+ * );
41
+ */
42
+ export default function registerToolWithLogging<TSchema extends ZodRawShape, TArgs = z.infer<z.ZodObject<TSchema>>>(mcpServer: McpServer, toolName: string, config: ToolConfig<TSchema>, handler: (args: TArgs, extra: any) => Promise<ToolResult>, logger: Logger): void;
43
+ export {};
44
+ //# sourceMappingURL=tool-with-logging.d.ts.map
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = registerToolWithLogging;
4
+ const zod_1 = require("zod");
5
+ // -----------------------------------------------------------------------------
6
+ // Validation Helpers
7
+ // -----------------------------------------------------------------------------
8
+ /**
9
+ * Formats a Zod error into a human-readable string.
10
+ * Example: "collectionName: Required, sort.field: Expected string"
11
+ */
12
+ function formatZodError(error) {
13
+ return error.issues.map(issue => `${issue.path.join('.')}: ${issue.message}`).join(', ');
14
+ }
15
+ /**
16
+ * Validates arguments against a schema and logs errors if validation fails.
17
+ * This is a pre-validation step for logging purposes only - the SDK handles actual validation.
18
+ */
19
+ function logValidationErrorsIfAny(args, schema, toolName, logger) {
20
+ const result = schema.safeParse(args);
21
+ if (!result.success) {
22
+ const errorMessage = formatZodError(result.error);
23
+ logger('Error', `[MCP] Tool "${toolName}" validation error: ${errorMessage}`);
24
+ }
25
+ }
26
+ // -----------------------------------------------------------------------------
27
+ // Tool Registration
28
+ // -----------------------------------------------------------------------------
29
+ /**
30
+ * Registers an MCP tool with automatic validation error logging.
31
+ *
32
+ * This wrapper logs validation errors with detailed field information,
33
+ * which helps debug tool calls when clients send invalid arguments.
34
+ *
35
+ * Note: Execution errors are logged by the SSE response interceptor in server.ts,
36
+ * so we don't duplicate that logging here.
37
+ *
38
+ * @example
39
+ * registerToolWithLogging(
40
+ * mcpServer,
41
+ * 'list',
42
+ * {
43
+ * title: 'List Records',
44
+ * description: 'Lists records from a collection',
45
+ * inputSchema: { collectionName: z.string() },
46
+ * },
47
+ * async (args) => {
48
+ * const records = await fetchRecords(args.collectionName);
49
+ * return { content: [{ type: 'text', text: JSON.stringify(records) }] };
50
+ * },
51
+ * logger,
52
+ * );
53
+ */
54
+ function registerToolWithLogging(mcpServer, toolName, config,
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ handler, logger) {
57
+ const schema = zod_1.z.object(config.inputSchema);
58
+ mcpServer.registerTool(toolName, config,
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ (async (args, extra) => {
61
+ logValidationErrorsIfAny(args, schema, toolName, logger);
62
+ return handler(args, extra);
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ }));
65
+ }
66
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9vbC13aXRoLWxvZ2dpbmcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvdG9vbC13aXRoLWxvZ2dpbmcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFpRkEsMENBd0JDO0FBdEdELDZCQUF3QjtBQW1CeEIsZ0ZBQWdGO0FBQ2hGLHFCQUFxQjtBQUNyQixnRkFBZ0Y7QUFFaEY7OztHQUdHO0FBQ0gsU0FBUyxjQUFjLENBQUMsS0FBaUI7SUFDdkMsT0FBTyxLQUFLLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEtBQUssS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQzNGLENBQUM7QUFFRDs7O0dBR0c7QUFDSCxTQUFTLHdCQUF3QixDQUMvQixJQUFhLEVBQ2IsTUFBbUIsRUFDbkIsUUFBZ0IsRUFDaEIsTUFBYztJQUVkLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7SUFFdEMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNwQixNQUFNLFlBQVksR0FBRyxjQUFjLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ2xELE1BQU0sQ0FBQyxPQUFPLEVBQUUsZUFBZSxRQUFRLHVCQUF1QixZQUFZLEVBQUUsQ0FBQyxDQUFDO0lBQ2hGLENBQUM7QUFDSCxDQUFDO0FBRUQsZ0ZBQWdGO0FBQ2hGLG9CQUFvQjtBQUNwQixnRkFBZ0Y7QUFFaEY7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXdCRztBQUNILFNBQXdCLHVCQUF1QixDQUk3QyxTQUFvQixFQUNwQixRQUFnQixFQUNoQixNQUEyQjtBQUMzQiw4REFBOEQ7QUFDOUQsT0FBeUQsRUFDekQsTUFBYztJQUVkLE1BQU0sTUFBTSxHQUFHLE9BQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBRTVDLFNBQVMsQ0FBQyxZQUFZLENBQ3BCLFFBQVEsRUFDUixNQUFNO0lBQ04sOERBQThEO0lBQzlELENBQUMsS0FBSyxFQUFFLElBQVMsRUFBRSxLQUFVLEVBQUUsRUFBRTtRQUMvQix3QkFBd0IsQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxNQUFNLENBQUMsQ0FBQztRQUV6RCxPQUFPLE9BQU8sQ0FBQyxJQUFhLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDckMsOERBQThEO0lBQ2hFLENBQUMsQ0FBUSxDQUNWLENBQUM7QUFDSixDQUFDIn0=
@@ -0,0 +1,3 @@
1
+ export declare const VERSION: string;
2
+ export declare const NAME: string;
3
+ //# sourceMappingURL=version.d.ts.map