@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,590 @@
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
+ const forestadmin_client_1 = __importDefault(require("@forestadmin/forestadmin-client"));
7
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
8
+ const forest_oauth_provider_1 = __importDefault(require("./forest-oauth-provider"));
9
+ const mock_server_1 = __importDefault(require("./test-utils/mock-server"));
10
+ jest.mock('jsonwebtoken');
11
+ jest.mock('@forestadmin/forestadmin-client');
12
+ const mockCreateForestAdminClient = forestadmin_client_1.default;
13
+ const mockJwtDecode = jsonwebtoken_1.default.decode;
14
+ const mockJwtSign = jsonwebtoken_1.default.sign;
15
+ const TEST_ENV_SECRET = 'test-env-secret';
16
+ const TEST_AUTH_SECRET = 'test-auth-secret';
17
+ const TEST_FOREST_APP_URL = 'https://app.forestadmin.com';
18
+ function createProvider(forestServerUrl = 'https://api.forestadmin.com') {
19
+ return new forest_oauth_provider_1.default({
20
+ forestServerUrl,
21
+ forestAppUrl: TEST_FOREST_APP_URL,
22
+ envSecret: TEST_ENV_SECRET,
23
+ authSecret: TEST_AUTH_SECRET,
24
+ logger: console.info,
25
+ });
26
+ }
27
+ describe('ForestOAuthProvider', () => {
28
+ let originalEnv;
29
+ let mockServer;
30
+ const originalFetch = global.fetch;
31
+ beforeAll(() => {
32
+ originalEnv = { ...process.env };
33
+ });
34
+ afterAll(() => {
35
+ process.env = originalEnv;
36
+ global.fetch = originalFetch;
37
+ });
38
+ beforeEach(() => {
39
+ process.env.FOREST_ENV_SECRET = TEST_ENV_SECRET;
40
+ process.env.FOREST_AUTH_SECRET = TEST_AUTH_SECRET;
41
+ mockServer = new mock_server_1.default();
42
+ });
43
+ afterEach(() => {
44
+ mockServer.reset();
45
+ });
46
+ describe('constructor', () => {
47
+ it('should create instance with forestServerUrl', () => {
48
+ const customProvider = createProvider('https://custom.forestadmin.com');
49
+ expect(customProvider).toBeDefined();
50
+ });
51
+ it('should create instance with custom forestAppUrl', () => {
52
+ const customProvider = new forest_oauth_provider_1.default({
53
+ forestServerUrl: 'https://api.forestadmin.com',
54
+ forestAppUrl: 'https://custom-app.forestadmin.com',
55
+ envSecret: TEST_ENV_SECRET,
56
+ authSecret: TEST_AUTH_SECRET,
57
+ logger: console.info,
58
+ });
59
+ expect(customProvider).toBeDefined();
60
+ });
61
+ });
62
+ describe('initialize', () => {
63
+ it('should not throw when envSecret is empty string', async () => {
64
+ const customProvider = new forest_oauth_provider_1.default({
65
+ forestServerUrl: 'https://api.forestadmin.com',
66
+ forestAppUrl: TEST_FOREST_APP_URL,
67
+ envSecret: '',
68
+ authSecret: TEST_AUTH_SECRET,
69
+ logger: console.info,
70
+ });
71
+ await expect(customProvider.initialize()).resolves.not.toThrow();
72
+ });
73
+ it('should fetch environmentId from Forest Admin API', async () => {
74
+ mockServer.get('/liana/environment', {
75
+ data: { id: '98765', attributes: { api_endpoint: 'https://api.example.com' } },
76
+ });
77
+ global.fetch = mockServer.fetch;
78
+ const testProvider = createProvider();
79
+ await testProvider.initialize();
80
+ // Verify fetch was called with correct URL and headers
81
+ expect(mockServer.fetch).toHaveBeenCalledWith('https://api.forestadmin.com/liana/environment', expect.objectContaining({
82
+ method: 'GET',
83
+ headers: expect.objectContaining({
84
+ 'forest-secret-key': 'test-env-secret',
85
+ 'Content-Type': 'application/json',
86
+ }),
87
+ }));
88
+ });
89
+ it('should set environmentId after successful initialization', async () => {
90
+ mockServer.get('/liana/environment', {
91
+ data: { id: '54321', attributes: { api_endpoint: 'https://api.example.com' } },
92
+ });
93
+ global.fetch = mockServer.fetch;
94
+ const testProvider = createProvider();
95
+ await testProvider.initialize();
96
+ // Verify environmentId is set by checking authorize redirect includes it
97
+ const mockResponse = { redirect: jest.fn() };
98
+ const mockClient = {
99
+ client_id: 'test-client',
100
+ redirect_uris: ['https://example.com/callback'],
101
+ };
102
+ await testProvider.authorize(mockClient, {
103
+ redirectUri: 'https://example.com/callback',
104
+ codeChallenge: 'challenge',
105
+ state: 'state',
106
+ scopes: ['mcp:read'],
107
+ resource: new URL('https://localhost:3931'),
108
+ }, mockResponse);
109
+ const redirectUrl = new URL(mockResponse.redirect.mock.calls[0][0]);
110
+ expect(redirectUrl.searchParams.get('environmentId')).toBe('54321');
111
+ });
112
+ it('should handle non-OK response from Forest Admin API', async () => {
113
+ mockServer.get('/liana/environment', { error: 'Unauthorized' }, 401);
114
+ global.fetch = mockServer.fetch;
115
+ const loggerSpy = jest.fn();
116
+ const testProvider = new forest_oauth_provider_1.default({
117
+ forestServerUrl: 'https://api.forestadmin.com',
118
+ forestAppUrl: TEST_FOREST_APP_URL,
119
+ envSecret: TEST_ENV_SECRET,
120
+ authSecret: TEST_AUTH_SECRET,
121
+ logger: loggerSpy,
122
+ });
123
+ await testProvider.initialize();
124
+ expect(loggerSpy).toHaveBeenCalledWith('Warn', expect.stringContaining('Failed to fetch environmentId from Forest Admin API'));
125
+ });
126
+ it('should handle fetch network errors gracefully', async () => {
127
+ global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));
128
+ const loggerSpy = jest.fn();
129
+ const testProvider = new forest_oauth_provider_1.default({
130
+ forestServerUrl: 'https://api.forestadmin.com',
131
+ forestAppUrl: TEST_FOREST_APP_URL,
132
+ envSecret: TEST_ENV_SECRET,
133
+ authSecret: TEST_AUTH_SECRET,
134
+ logger: loggerSpy,
135
+ });
136
+ await testProvider.initialize();
137
+ expect(loggerSpy).toHaveBeenCalledWith('Warn', expect.stringContaining('Failed to fetch environmentId from Forest Admin API'));
138
+ });
139
+ it('should use correct forest server URL for API call', async () => {
140
+ mockServer.get('/liana/environment', {
141
+ data: { id: '11111', attributes: { api_endpoint: 'https://api.example.com' } },
142
+ });
143
+ global.fetch = mockServer.fetch;
144
+ const testProvider = createProvider('https://custom.forestadmin.com');
145
+ await testProvider.initialize();
146
+ expect(mockServer.fetch).toHaveBeenCalledWith('https://custom.forestadmin.com/liana/environment', expect.any(Object));
147
+ });
148
+ });
149
+ describe('clientsStore.getClient', () => {
150
+ it('should fetch client from Forest Admin API', async () => {
151
+ const clientData = {
152
+ client_id: 'test-client-123',
153
+ redirect_uris: ['https://example.com/callback'],
154
+ client_name: 'Test Client',
155
+ };
156
+ mockServer.get('/oauth/register/test-client-123', clientData);
157
+ global.fetch = mockServer.fetch;
158
+ const provider = createProvider();
159
+ const client = await provider.clientsStore.getClient('test-client-123');
160
+ expect(client).toEqual(clientData);
161
+ expect(mockServer.fetch).toHaveBeenCalledWith('https://api.forestadmin.com/oauth/register/test-client-123', expect.objectContaining({
162
+ method: 'GET',
163
+ headers: expect.objectContaining({
164
+ 'Content-Type': 'application/json',
165
+ }),
166
+ }));
167
+ });
168
+ it('should return undefined when client is not found', async () => {
169
+ mockServer.get('/oauth/register/unknown-client', { error: 'Not found' }, 404);
170
+ global.fetch = mockServer.fetch;
171
+ const provider = createProvider();
172
+ const client = await provider.clientsStore.getClient('unknown-client');
173
+ expect(client).toBeUndefined();
174
+ });
175
+ it('should return undefined on server error', async () => {
176
+ mockServer.get('/oauth/register/error-client', { error: 'Internal error' }, 500);
177
+ global.fetch = mockServer.fetch;
178
+ const provider = createProvider();
179
+ const client = await provider.clientsStore.getClient('error-client');
180
+ expect(client).toBeUndefined();
181
+ });
182
+ });
183
+ describe('authorize', () => {
184
+ let mockResponse;
185
+ let mockClient;
186
+ let initializedProvider;
187
+ beforeEach(async () => {
188
+ mockResponse = {
189
+ redirect: jest.fn(),
190
+ };
191
+ mockClient = {
192
+ client_id: 'test-client-id',
193
+ redirect_uris: ['https://example.com/callback'],
194
+ };
195
+ // Create provider and mock the fetch to set environmentId
196
+ initializedProvider = createProvider();
197
+ // Mock fetch to return a valid response
198
+ const mockFetch = jest.fn().mockResolvedValue({
199
+ ok: true,
200
+ json: () => Promise.resolve({
201
+ data: { id: '12345', attributes: { api_endpoint: 'https://api.example.com' } },
202
+ }),
203
+ });
204
+ global.fetch = mockFetch;
205
+ await initializedProvider.initialize();
206
+ });
207
+ afterEach(() => {
208
+ jest.restoreAllMocks();
209
+ });
210
+ it('should redirect to Forest Admin authentication URL', async () => {
211
+ await initializedProvider.authorize(mockClient, {
212
+ redirectUri: 'https://example.com/callback',
213
+ codeChallenge: 'test-code-challenge',
214
+ state: 'test-state',
215
+ scopes: ['mcp:read', 'profile'],
216
+ resource: new URL('https://localhost:3931'),
217
+ }, mockResponse);
218
+ expect(mockResponse.redirect).toHaveBeenCalledWith(expect.stringContaining('https://app.forestadmin.com/oauth/authorize'));
219
+ });
220
+ it('should include all required query parameters in redirect URL', async () => {
221
+ await initializedProvider.authorize(mockClient, {
222
+ redirectUri: 'https://example.com/callback',
223
+ codeChallenge: 'test-code-challenge',
224
+ state: 'test-state',
225
+ scopes: ['mcp:read', 'profile'],
226
+ resource: new URL('https://localhost:3931'),
227
+ }, mockResponse);
228
+ const redirectCall = mockResponse.redirect.mock.calls[0][0];
229
+ const url = new URL(redirectCall);
230
+ expect(url.hostname).toBe('app.forestadmin.com');
231
+ expect(url.pathname).toBe('/oauth/authorize');
232
+ expect(url.searchParams.get('redirect_uri')).toBe('https://example.com/callback');
233
+ expect(url.searchParams.get('code_challenge')).toBe('test-code-challenge');
234
+ expect(url.searchParams.get('code_challenge_method')).toBe('S256');
235
+ expect(url.searchParams.get('response_type')).toBe('code');
236
+ expect(url.searchParams.get('client_id')).toBe('test-client-id');
237
+ expect(url.searchParams.get('state')).toBe('test-state');
238
+ expect(url.searchParams.get('scope')).toBe('mcp:read+profile');
239
+ expect(url.searchParams.get('environmentId')).toBe('12345');
240
+ });
241
+ it('should redirect to error URL when environmentId is not set', async () => {
242
+ // Create a provider without initializing (environmentId is undefined)
243
+ const uninitializedProvider = createProvider();
244
+ await uninitializedProvider.authorize(mockClient, {
245
+ redirectUri: 'https://example.com/callback',
246
+ codeChallenge: 'test-code-challenge',
247
+ state: 'test-state',
248
+ scopes: ['mcp:read'],
249
+ resource: new URL('https://localhost:3931'),
250
+ }, mockResponse);
251
+ const redirectCall = mockResponse.redirect.mock.calls[0][0];
252
+ expect(redirectCall).toContain('https://example.com/callback');
253
+ expect(redirectCall).toContain('error=server_error');
254
+ });
255
+ it('should use custom forestAppUrl for redirect', async () => {
256
+ const customAppUrl = 'https://custom-app.forestadmin.com';
257
+ const customProvider = new forest_oauth_provider_1.default({
258
+ forestServerUrl: 'https://api.forestadmin.com',
259
+ forestAppUrl: customAppUrl,
260
+ envSecret: TEST_ENV_SECRET,
261
+ authSecret: TEST_AUTH_SECRET,
262
+ logger: console.info,
263
+ });
264
+ // Mock fetch to return a valid response for initialize
265
+ global.fetch = jest.fn().mockResolvedValue({
266
+ ok: true,
267
+ json: () => Promise.resolve({
268
+ data: { id: '12345', attributes: { api_endpoint: 'https://api.example.com' } },
269
+ }),
270
+ });
271
+ await customProvider.initialize();
272
+ await customProvider.authorize(mockClient, {
273
+ redirectUri: 'https://example.com/callback',
274
+ codeChallenge: 'test-code-challenge',
275
+ state: 'test-state',
276
+ scopes: ['mcp:read'],
277
+ resource: new URL('https://localhost:3931'),
278
+ }, mockResponse);
279
+ const redirectCall = mockResponse.redirect.mock.calls[0][0];
280
+ const url = new URL(redirectCall);
281
+ expect(url.hostname).toBe('custom-app.forestadmin.com');
282
+ expect(url.pathname).toBe('/oauth/authorize');
283
+ });
284
+ });
285
+ describe('exchangeAuthorizationCode', () => {
286
+ let mockClient;
287
+ let mockGetUserInfo;
288
+ beforeEach(() => {
289
+ mockClient = {
290
+ client_id: 'test-client-id',
291
+ redirect_uris: ['https://example.com/callback'],
292
+ scope: 'mcp:read mcp:write',
293
+ };
294
+ // Setup mock for forestAdminClient
295
+ mockGetUserInfo = jest.fn().mockResolvedValue({
296
+ id: 123,
297
+ email: 'user@example.com',
298
+ firstName: 'Test',
299
+ lastName: 'User',
300
+ team: 'Operations',
301
+ role: 'Admin',
302
+ tags: {},
303
+ renderingId: 456,
304
+ permissionLevel: 'admin',
305
+ });
306
+ mockCreateForestAdminClient.mockReturnValue({
307
+ authService: {
308
+ getUserInfo: mockGetUserInfo,
309
+ },
310
+ });
311
+ // Setup mock for jsonwebtoken - decode is called twice:
312
+ // first for access token, then for refresh token
313
+ const now = Math.floor(Date.now() / 1000);
314
+ mockJwtDecode
315
+ .mockReturnValueOnce({
316
+ meta: { renderingId: 456 },
317
+ exp: now + 3600, // expires in 1 hour
318
+ iat: now,
319
+ scope: 'mcp:read',
320
+ })
321
+ .mockReturnValueOnce({
322
+ exp: now + 604800, // expires in 7 days
323
+ iat: now,
324
+ });
325
+ mockJwtSign.mockReturnValue('mocked-jwt-token');
326
+ });
327
+ afterEach(() => {
328
+ jest.clearAllMocks();
329
+ });
330
+ it('should exchange authorization code with Forest Admin server', async () => {
331
+ mockServer
332
+ .get('/liana/environment', {
333
+ data: { id: '12345', attributes: { api_endpoint: 'https://api.example.com' } },
334
+ })
335
+ .post('/oauth/token', {
336
+ access_token: 'forest-access-token',
337
+ refresh_token: 'forest-refresh-token',
338
+ expires_in: 3600,
339
+ token_type: 'Bearer',
340
+ scope: 'mcp:read',
341
+ });
342
+ global.fetch = mockServer.fetch;
343
+ const provider = createProvider();
344
+ const result = await provider.exchangeAuthorizationCode(mockClient, 'auth-code-123', 'code-verifier-456', 'https://example.com/callback');
345
+ expect(mockServer.fetch).toHaveBeenCalledWith('https://api.forestadmin.com/oauth/token', expect.objectContaining({
346
+ method: 'POST',
347
+ headers: expect.objectContaining({
348
+ 'Content-Type': 'application/json',
349
+ 'forest-secret-key': 'test-env-secret',
350
+ }),
351
+ body: JSON.stringify({
352
+ grant_type: 'authorization_code',
353
+ code: 'auth-code-123',
354
+ redirect_uri: 'https://example.com/callback',
355
+ client_id: 'test-client-id',
356
+ code_verifier: 'code-verifier-456',
357
+ }),
358
+ }));
359
+ expect(result.access_token).toBe('mocked-jwt-token');
360
+ expect(result.refresh_token).toBe('mocked-jwt-token');
361
+ expect(result.token_type).toBe('Bearer');
362
+ // expires_in is calculated as exp - now, so it should be approximately 3600
363
+ expect(result.expires_in).toBeGreaterThan(3590);
364
+ expect(result.expires_in).toBeLessThanOrEqual(3600);
365
+ expect(result.scope).toBe('mcp:read');
366
+ expect(mockJwtDecode).toHaveBeenCalledWith('forest-access-token');
367
+ expect(mockJwtDecode).toHaveBeenCalledWith('forest-refresh-token');
368
+ expect(mockGetUserInfo).toHaveBeenCalledWith(456, 'forest-access-token');
369
+ // First call: access token - expiresIn is calculated as exp - now, so it's approximately 3600
370
+ expect(mockJwtSign).toHaveBeenCalledWith(expect.objectContaining({
371
+ id: 123,
372
+ email: 'user@example.com',
373
+ serverToken: 'forest-access-token',
374
+ }), 'test-auth-secret', { expiresIn: expect.any(Number) });
375
+ // Second call: refresh token - expiresIn is calculated as exp - now, so it's approximately 604800
376
+ expect(mockJwtSign).toHaveBeenCalledWith(expect.objectContaining({
377
+ type: 'refresh',
378
+ clientId: 'test-client-id',
379
+ userId: 123,
380
+ renderingId: 456,
381
+ serverRefreshToken: 'forest-refresh-token',
382
+ }), 'test-auth-secret', { expiresIn: expect.any(Number) });
383
+ });
384
+ it('should throw error when token exchange fails', async () => {
385
+ mockServer
386
+ .get('/liana/environment', {
387
+ data: { id: '12345', attributes: { api_endpoint: 'https://api.example.com' } },
388
+ })
389
+ .post('/oauth/token', { error: 'invalid_grant' }, 400);
390
+ global.fetch = mockServer.fetch;
391
+ const provider = createProvider();
392
+ await expect(provider.exchangeAuthorizationCode(mockClient, 'invalid-code', 'code-verifier', 'https://example.com/callback')).rejects.toThrow('Failed to exchange authorization code');
393
+ });
394
+ });
395
+ describe('exchangeRefreshToken', () => {
396
+ let mockClient;
397
+ let mockGetUserInfo;
398
+ beforeEach(() => {
399
+ mockClient = {
400
+ client_id: 'test-client-id',
401
+ redirect_uris: ['https://example.com/callback'],
402
+ scope: 'mcp:read mcp:write',
403
+ };
404
+ mockGetUserInfo = jest.fn().mockResolvedValue({
405
+ id: 123,
406
+ email: 'user@example.com',
407
+ firstName: 'Test',
408
+ lastName: 'User',
409
+ team: 'Operations',
410
+ role: 'Admin',
411
+ tags: {},
412
+ renderingId: 456,
413
+ permissionLevel: 'admin',
414
+ });
415
+ mockCreateForestAdminClient.mockReturnValue({
416
+ authService: {
417
+ getUserInfo: mockGetUserInfo,
418
+ },
419
+ });
420
+ mockJwtSign.mockReturnValue('new-mocked-jwt-token');
421
+ });
422
+ afterEach(() => {
423
+ jest.clearAllMocks();
424
+ });
425
+ it('should exchange refresh token for new tokens', async () => {
426
+ // Mock jwt.verify to return decoded refresh token
427
+ jsonwebtoken_1.default.verify.mockReturnValue({
428
+ type: 'refresh',
429
+ clientId: 'test-client-id',
430
+ userId: 123,
431
+ renderingId: 456,
432
+ serverRefreshToken: 'forest-refresh-token',
433
+ });
434
+ // Mock jwt.decode - called twice: first for access token, then for refresh token
435
+ const now = Math.floor(Date.now() / 1000);
436
+ mockJwtDecode
437
+ .mockReturnValueOnce({
438
+ meta: { renderingId: 456 },
439
+ exp: now + 3600, // expires in 1 hour
440
+ iat: now,
441
+ scope: 'mcp:read',
442
+ })
443
+ .mockReturnValueOnce({
444
+ exp: now + 604800, // expires in 7 days
445
+ iat: now,
446
+ });
447
+ mockServer.post('/oauth/token', {
448
+ access_token: 'new-forest-access-token',
449
+ refresh_token: 'new-forest-refresh-token',
450
+ expires_in: 3600,
451
+ token_type: 'Bearer',
452
+ scope: 'mcp:read',
453
+ });
454
+ global.fetch = mockServer.fetch;
455
+ const provider = createProvider();
456
+ const result = await provider.exchangeRefreshToken(mockClient, 'valid-refresh-token');
457
+ expect(result.access_token).toBe('new-mocked-jwt-token');
458
+ expect(result.refresh_token).toBe('new-mocked-jwt-token');
459
+ expect(result.token_type).toBe('Bearer');
460
+ // expires_in is calculated as exp - now, so it should be approximately 3600
461
+ expect(result.expires_in).toBeGreaterThan(3590);
462
+ expect(result.expires_in).toBeLessThanOrEqual(3600);
463
+ expect(mockServer.fetch).toHaveBeenCalledWith('https://api.forestadmin.com/oauth/token', expect.objectContaining({
464
+ method: 'POST',
465
+ body: JSON.stringify({
466
+ grant_type: 'refresh_token',
467
+ refresh_token: 'forest-refresh-token',
468
+ client_id: 'test-client-id',
469
+ }),
470
+ }));
471
+ });
472
+ it('should throw error for invalid refresh token', async () => {
473
+ jsonwebtoken_1.default.verify.mockImplementation(() => {
474
+ throw new Error('invalid signature');
475
+ });
476
+ const provider = createProvider();
477
+ await expect(provider.exchangeRefreshToken(mockClient, 'invalid-refresh-token')).rejects.toThrow('Invalid or expired refresh token');
478
+ });
479
+ it('should throw error when token type is not refresh', async () => {
480
+ jsonwebtoken_1.default.verify.mockReturnValue({
481
+ type: 'access',
482
+ clientId: 'test-client-id',
483
+ });
484
+ const provider = createProvider();
485
+ await expect(provider.exchangeRefreshToken(mockClient, 'access-token')).rejects.toThrow('Invalid token type');
486
+ });
487
+ it('should throw error when client_id does not match', async () => {
488
+ jsonwebtoken_1.default.verify.mockReturnValue({
489
+ type: 'refresh',
490
+ clientId: 'different-client-id',
491
+ userId: 123,
492
+ renderingId: 456,
493
+ serverRefreshToken: 'forest-refresh-token',
494
+ });
495
+ const provider = createProvider();
496
+ await expect(provider.exchangeRefreshToken(mockClient, 'refresh-token-for-different-client')).rejects.toThrow('Token was not issued to this client');
497
+ });
498
+ it('should throw error when Forest Admin refresh fails', async () => {
499
+ jsonwebtoken_1.default.verify.mockReturnValue({
500
+ type: 'refresh',
501
+ clientId: 'test-client-id',
502
+ userId: 123,
503
+ renderingId: 456,
504
+ serverRefreshToken: 'expired-forest-refresh-token',
505
+ });
506
+ mockServer.post('/oauth/token', { error: 'invalid_grant' }, 400);
507
+ global.fetch = mockServer.fetch;
508
+ const provider = createProvider();
509
+ await expect(provider.exchangeRefreshToken(mockClient, 'valid-refresh-token')).rejects.toThrow('Failed to refresh token');
510
+ });
511
+ });
512
+ describe('verifyAccessToken', () => {
513
+ it('should verify and decode a valid access token', async () => {
514
+ const mockDecoded = {
515
+ id: 123,
516
+ email: 'user@example.com',
517
+ renderingId: 456,
518
+ serverToken: 'forest-server-token',
519
+ exp: Math.floor(Date.now() / 1000) + 3600,
520
+ iat: Math.floor(Date.now() / 1000),
521
+ };
522
+ jsonwebtoken_1.default.verify.mockReturnValue(mockDecoded);
523
+ const provider = createProvider();
524
+ const result = await provider.verifyAccessToken('valid-access-token');
525
+ expect(result.token).toBe('valid-access-token');
526
+ expect(result.clientId).toBe('123');
527
+ expect(result.expiresAt).toBe(mockDecoded.exp);
528
+ expect(result.scopes).toEqual(['mcp:read', 'mcp:write', 'mcp:action']);
529
+ expect(result.extra).toEqual({
530
+ userId: 123,
531
+ email: 'user@example.com',
532
+ renderingId: 456,
533
+ environmentApiEndpoint: undefined,
534
+ forestServerToken: 'forest-server-token',
535
+ });
536
+ });
537
+ it('should throw error for expired access token', async () => {
538
+ jsonwebtoken_1.default.verify.mockImplementation(() => {
539
+ throw new jsonwebtoken_1.default.TokenExpiredError('jwt expired', new Date());
540
+ });
541
+ const provider = createProvider();
542
+ await expect(provider.verifyAccessToken('expired-token')).rejects.toThrow('Access token has expired');
543
+ });
544
+ it('should throw error for invalid access token', async () => {
545
+ jsonwebtoken_1.default.verify.mockImplementation(() => {
546
+ throw new jsonwebtoken_1.default.JsonWebTokenError('invalid signature');
547
+ });
548
+ const provider = createProvider();
549
+ await expect(provider.verifyAccessToken('invalid-token')).rejects.toThrow('Invalid access token');
550
+ });
551
+ it('should throw error when using refresh token as access token', async () => {
552
+ jsonwebtoken_1.default.verify.mockReturnValue({
553
+ type: 'refresh',
554
+ clientId: 'test-client-id',
555
+ });
556
+ const provider = createProvider();
557
+ await expect(provider.verifyAccessToken('refresh-token')).rejects.toThrow('Cannot use refresh token as access token');
558
+ });
559
+ it('should include environmentApiEndpoint after initialize is called', async () => {
560
+ mockServer.get('/liana/environment', {
561
+ data: {
562
+ id: '12345',
563
+ attributes: { api_endpoint: 'https://api.example.com' },
564
+ },
565
+ });
566
+ global.fetch = mockServer.fetch;
567
+ const mockDecoded = {
568
+ id: 123,
569
+ email: 'user@example.com',
570
+ renderingId: 456,
571
+ serverToken: 'forest-server-token',
572
+ exp: Math.floor(Date.now() / 1000) + 3600,
573
+ iat: Math.floor(Date.now() / 1000),
574
+ };
575
+ jsonwebtoken_1.default.verify.mockReturnValue(mockDecoded);
576
+ const provider = createProvider();
577
+ // Call initialize to fetch environment data
578
+ await provider.initialize();
579
+ const result = await provider.verifyAccessToken('valid-access-token');
580
+ expect(result.extra).toEqual({
581
+ userId: 123,
582
+ email: 'user@example.com',
583
+ renderingId: 456,
584
+ environmentApiEndpoint: 'https://api.example.com',
585
+ forestServerToken: 'forest-server-token',
586
+ });
587
+ });
588
+ });
589
+ });
590
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yZXN0LW9hdXRoLXByb3ZpZGVyLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZm9yZXN0LW9hdXRoLXByb3ZpZGVyLnRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFHQSx5RkFBc0U7QUFDdEUsZ0VBQXdDO0FBRXhDLG9GQUEwRDtBQUMxRCwyRUFBa0Q7QUFFbEQsSUFBSSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQztBQUMxQixJQUFJLENBQUMsSUFBSSxDQUFDLGlDQUFpQyxDQUFDLENBQUM7QUFFN0MsTUFBTSwyQkFBMkIsR0FBRyw0QkFFbkMsQ0FBQztBQUNGLE1BQU0sYUFBYSxHQUFHLHNCQUFZLENBQUMsTUFBbUIsQ0FBQztBQUN2RCxNQUFNLFdBQVcsR0FBRyxzQkFBWSxDQUFDLElBQWlCLENBQUM7QUFFbkQsTUFBTSxlQUFlLEdBQUcsaUJBQWlCLENBQUM7QUFDMUMsTUFBTSxnQkFBZ0IsR0FBRyxrQkFBa0IsQ0FBQztBQUM1QyxNQUFNLG1CQUFtQixHQUFHLDZCQUE2QixDQUFDO0FBRTFELFNBQVMsY0FBYyxDQUFDLGVBQWUsR0FBRyw2QkFBNkI7SUFDckUsT0FBTyxJQUFJLCtCQUFtQixDQUFDO1FBQzdCLGVBQWU7UUFDZixZQUFZLEVBQUUsbUJBQW1CO1FBQ2pDLFNBQVMsRUFBRSxlQUFlO1FBQzFCLFVBQVUsRUFBRSxnQkFBZ0I7UUFDNUIsTUFBTSxFQUFFLE9BQU8sQ0FBQyxJQUFJO0tBQ3JCLENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCxRQUFRLENBQUMscUJBQXFCLEVBQUUsR0FBRyxFQUFFO0lBQ25DLElBQUksV0FBOEIsQ0FBQztJQUNuQyxJQUFJLFVBQXNCLENBQUM7SUFDM0IsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQztJQUVuQyxTQUFTLENBQUMsR0FBRyxFQUFFO1FBQ2IsV0FBVyxHQUFHLEVBQUUsR0FBRyxPQUFPLENBQUMsR0FBRyxFQUFFLENB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
@@ -0,0 +1,4 @@
1
+ export { default as ForestMCPServer } from './server';
2
+ export type { ForestMCPServerOptions, HttpCallback } from './server';
3
+ export { MCP_PATHS, isMcpRoute } from './mcp-paths';
4
+ //# sourceMappingURL=index.d.ts.map