@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
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # @forestadmin/mcp-server
2
+
3
+ Model Context Protocol (MCP) server for Forest Admin with OAuth authentication support.
4
+
5
+ ## Overview
6
+
7
+ This MCP server provides HTTP REST API access to Forest Admin operations, enabling AI assistants and other MCP clients to interact with your Forest Admin data through a standardized protocol.
8
+
9
+ ## Usage
10
+
11
+ ### With Forest Admin Agent
12
+
13
+ The MCP server is included with the Forest Admin agent. Simply call `mountAiMcpServer()`:
14
+
15
+ ```typescript
16
+ import { createAgent } from '@forestadmin/agent';
17
+
18
+ const agent = createAgent(options)
19
+ .addDataSource(myDataSource)
20
+ .mountAiMcpServer();
21
+
22
+ agent.mountOnExpress(app);
23
+ agent.start();
24
+ ```
25
+
26
+ The MCP server will be automatically initialized and mounted on your application.
27
+
28
+ ### Standalone Server
29
+
30
+ You can also run the MCP server standalone using the CLI:
31
+
32
+ ```bash
33
+ npx forest-mcp-server
34
+ ```
35
+
36
+ Or programmatically:
37
+
38
+ ```bash
39
+ node dist/index.js
40
+ ```
41
+
42
+ ### Environment Variables
43
+
44
+ The following environment variables are required to run the server:
45
+
46
+ | Variable | Required | Default | Description |
47
+ |----------|----------|---------|-------------|
48
+ | `FOREST_ENV_SECRET` | **Yes** | - | Your Forest Admin environment secret |
49
+ | `FOREST_AUTH_SECRET` | **Yes** | - | Your Forest Admin authentication secret (must match your agent) |
50
+ | `MCP_SERVER_PORT` | No | `3931` | Port for the HTTP server |
51
+
52
+ ### Example Configuration
53
+
54
+ ```bash
55
+ export FOREST_ENV_SECRET="your-env-secret"
56
+ export FOREST_AUTH_SECRET="your-auth-secret"
57
+ export MCP_SERVER_PORT=3931
58
+
59
+ npx forest-mcp-server
60
+ ```
61
+
62
+ ## API Endpoint
63
+
64
+ Once running, the MCP server exposes a single endpoint:
65
+
66
+ - **POST** `/mcp` - Main MCP protocol endpoint
67
+
68
+ The server expects MCP protocol messages in the request body and returns MCP-formatted responses.
69
+
70
+ ## Features
71
+
72
+ - **HTTP Transport**: Uses streamable HTTP transport for MCP communication
73
+ - **OAuth Authentication**: Built-in support for Forest Admin OAuth
74
+ - **CORS Enabled**: Allows cross-origin requests
75
+ - **Express-based**: Built on top of Express.js for reliability and extensibility
76
+
77
+ ## Development
78
+
79
+ ### Building
80
+
81
+ ```bash
82
+ npm run build
83
+ ```
84
+
85
+ ### Watch Mode
86
+
87
+ ```bash
88
+ npm run build:watch
89
+ ```
90
+
91
+ ### Linting
92
+
93
+ ```bash
94
+ npm run lint
95
+ ```
96
+
97
+ ### Testing
98
+
99
+ ```bash
100
+ npm test
101
+ ```
102
+
103
+ ### Cleaning
104
+
105
+ ```bash
106
+ npm run clean
107
+ ```
108
+
109
+ ## Architecture
110
+
111
+ The server consists of:
112
+
113
+ - **ForestMCPServer**: Main server class managing the MCP server lifecycle
114
+ - **McpServer**: Core MCP protocol implementation
115
+ - **StreamableHTTPServerTransport**: HTTP transport layer for MCP
116
+ - **Express App**: HTTP server handling incoming requests
117
+
118
+ ## License
119
+
120
+ GPL-3.0
121
+
122
+ ## Repository
123
+
124
+ [https://github.com/ForestAdmin/agent-nodejs](https://github.com/ForestAdmin/agent-nodejs)
125
+
126
+ ## Support
127
+
128
+ For issues and feature requests, please visit the [GitHub repository](https://github.com/ForestAdmin/agent-nodejs/tree/main/packages/mcp-server).
@@ -0,0 +1,3 @@
1
+ export declare const VERSION = "0.1.0";
2
+ export declare const NAME = "@forestadmin/mcp-server";
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NAME = exports.VERSION = void 0;
4
+ // Mock version module for Jest (avoids import.meta.url issues in CommonJS)
5
+ exports.VERSION = '0.1.0';
6
+ exports.NAME = '@forestadmin/mcp-server';
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyc2lvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9fX21vY2tzX18vdmVyc2lvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwyRUFBMkU7QUFDOUQsUUFBQSxPQUFPLEdBQUcsT0FBTyxDQUFDO0FBQ2xCLFFBQUEsSUFBSSxHQUFHLHlCQUF5QixDQUFDIn0=
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
package/dist/cli.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const server_1 = __importDefault(require("./server"));
8
+ // Start the server when run directly as CLI
9
+ const server = new server_1.default();
10
+ server.run().catch(error => {
11
+ console.error('[FATAL] Server crashed:', error);
12
+ process.exit(1);
13
+ });
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2NsaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFFQSxzREFBdUM7QUFFdkMsNENBQTRDO0FBQzVDLE1BQU0sTUFBTSxHQUFHLElBQUksZ0JBQWUsRUFBRSxDQUFDO0FBRXJDLE1BQU0sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLEVBQUU7SUFDekIsT0FBTyxDQUFDLEtBQUssQ0FBQyx5QkFBeUIsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUNoRCxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2xCLENBQUMsQ0FBQyxDQUFDIn0=
@@ -0,0 +1,51 @@
1
+ import type { HttpCallback } from './server';
2
+ import { Logger } from './server';
3
+ /**
4
+ * Context passed from the Forest Admin agent to the MCP factory.
5
+ */
6
+ export interface McpFactoryContext {
7
+ /** Forest Admin server URL */
8
+ forestServerUrl: string;
9
+ /** Environment secret */
10
+ envSecret: string;
11
+ /** Authentication secret */
12
+ authSecret: string;
13
+ /** Logger function */
14
+ logger: Logger;
15
+ }
16
+ /**
17
+ * Options for the MCP factory function.
18
+ */
19
+ export interface McpFactoryOptions {
20
+ /**
21
+ * Optional override for the base URL where the agent is publicly accessible.
22
+ * If not provided, it will be automatically fetched from Forest Admin API
23
+ * (the environment's api_endpoint configuration).
24
+ * Example: 'https://my-app.example.com' or 'http://localhost:3000'
25
+ */
26
+ baseUrl?: string;
27
+ }
28
+ /**
29
+ * Factory function to create an MCP HTTP callback for use with the Forest Admin agent.
30
+ *
31
+ * This function is designed to be used with the `agent.useMcp()` method:
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import { createAgent } from '@forestadmin/agent';
36
+ * import { createMcpServer } from '@forestadmin/mcp-server';
37
+ *
38
+ * const agent = createAgent(options)
39
+ * .addDataSource(myDataSource)
40
+ * .useMcp(createMcpServer, { baseUrl: 'https://my-app.example.com' });
41
+ *
42
+ * agent.mountOnExpress(app);
43
+ * agent.start();
44
+ * ```
45
+ *
46
+ * @param context - Context containing Forest Admin configuration (provided by the agent)
47
+ * @param options - Optional configuration for the MCP server
48
+ * @returns An HTTP callback that handles MCP routes
49
+ */
50
+ export declare function createMcpServer(context: McpFactoryContext, options?: McpFactoryOptions): Promise<HttpCallback>;
51
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1,40 @@
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.createMcpServer = createMcpServer;
7
+ const server_1 = __importDefault(require("./server"));
8
+ /**
9
+ * Factory function to create an MCP HTTP callback for use with the Forest Admin agent.
10
+ *
11
+ * This function is designed to be used with the `agent.useMcp()` method:
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { createAgent } from '@forestadmin/agent';
16
+ * import { createMcpServer } from '@forestadmin/mcp-server';
17
+ *
18
+ * const agent = createAgent(options)
19
+ * .addDataSource(myDataSource)
20
+ * .useMcp(createMcpServer, { baseUrl: 'https://my-app.example.com' });
21
+ *
22
+ * agent.mountOnExpress(app);
23
+ * agent.start();
24
+ * ```
25
+ *
26
+ * @param context - Context containing Forest Admin configuration (provided by the agent)
27
+ * @param options - Optional configuration for the MCP server
28
+ * @returns An HTTP callback that handles MCP routes
29
+ */
30
+ async function createMcpServer(context, options) {
31
+ const mcpServer = new server_1.default({
32
+ forestServerUrl: context.forestServerUrl,
33
+ envSecret: context.envSecret,
34
+ authSecret: context.authSecret,
35
+ logger: context.logger,
36
+ });
37
+ const baseUrl = options?.baseUrl ? new URL('/', options.baseUrl) : undefined;
38
+ return mcpServer.getHttpCallback(baseUrl);
39
+ }
40
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9mYWN0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBcURBLDBDQWNDO0FBakVELHNEQUFtRDtBQTZCbkQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXFCRztBQUNJLEtBQUssVUFBVSxlQUFlLENBQ25DLE9BQTBCLEVBQzFCLE9BQTJCO0lBRTNCLE1BQU0sU0FBUyxHQUFHLElBQUksZ0JBQWUsQ0FBQztRQUNwQyxlQUFlLEVBQUUsT0FBTyxDQUFDLGVBQWU7UUFDeEMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxTQUFTO1FBQzVCLFVBQVUsRUFBRSxPQUFPLENBQUMsVUFBVTtRQUM5QixNQUFNLEVBQUUsT0FBTyxDQUFDLE1BQU07S0FDdkIsQ0FBQyxDQUFDO0lBRUgsTUFBTSxPQUFPLEdBQUcsT0FBTyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsSUFBSSxHQUFHLENBQUMsR0FBRyxFQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0lBRTdFLE9BQU8sU0FBUyxDQUFDLGVBQWUsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUM1QyxDQUFDIn0=
@@ -0,0 +1,44 @@
1
+ import type { Logger } from './server';
2
+ import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js';
3
+ import type { AuthorizationParams, OAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/provider.js';
4
+ import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
5
+ import type { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
6
+ import type { Response } from 'express';
7
+ export interface ForestOAuthProviderOptions {
8
+ forestServerUrl: string;
9
+ forestAppUrl: string;
10
+ envSecret: string;
11
+ authSecret: string;
12
+ logger: Logger;
13
+ }
14
+ /**
15
+ * OAuth Server Provider that integrates with Forest Admin authentication
16
+ */
17
+ export default class ForestOAuthProvider implements OAuthServerProvider {
18
+ private forestServerUrl;
19
+ private forestAppUrl;
20
+ private envSecret;
21
+ private authSecret;
22
+ private forestClient;
23
+ private environmentId?;
24
+ private environmentApiEndpoint?;
25
+ private logger;
26
+ constructor({ forestServerUrl, forestAppUrl, envSecret, authSecret, logger, }: ForestOAuthProviderOptions);
27
+ initialize(): Promise<void>;
28
+ private fetchEnvironmentId;
29
+ /**
30
+ * Get the base URL for the MCP server from the environment's api_endpoint.
31
+ * Returns undefined if the environment info hasn't been fetched yet.
32
+ */
33
+ getBaseUrl(): URL | undefined;
34
+ get clientsStore(): OAuthRegisteredClientsStore;
35
+ authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void>;
36
+ challengeForAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string): Promise<string>;
37
+ exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string, codeVerifier?: string, redirectUri?: string): Promise<OAuthTokens>;
38
+ exchangeRefreshToken(client: OAuthClientInformationFull, refreshToken: string, scopes?: string[]): Promise<OAuthTokens>;
39
+ private generateAccessToken;
40
+ verifyAccessToken(token: string): Promise<AuthInfo>;
41
+ revokeToken(_client: OAuthClientInformationFull, _request: OAuthTokenRevocationRequest): Promise<void>;
42
+ skipLocalPkceValidation: boolean;
43
+ }
44
+ //# sourceMappingURL=forest-oauth-provider.d.ts.map
@@ -0,0 +1,253 @@
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 errors_js_1 = require("@modelcontextprotocol/sdk/server/auth/errors.js");
8
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
9
+ /**
10
+ * OAuth Server Provider that integrates with Forest Admin authentication
11
+ */
12
+ class ForestOAuthProvider {
13
+ constructor({ forestServerUrl, forestAppUrl, envSecret, authSecret, logger, }) {
14
+ // Skip PKCE validation to match original implementation
15
+ this.skipLocalPkceValidation = true;
16
+ this.forestServerUrl = forestServerUrl;
17
+ this.forestAppUrl = forestAppUrl;
18
+ this.envSecret = envSecret;
19
+ this.authSecret = authSecret;
20
+ this.logger = logger;
21
+ this.forestClient = (0, forestadmin_client_1.default)({
22
+ forestServerUrl: this.forestServerUrl,
23
+ envSecret: this.envSecret,
24
+ });
25
+ }
26
+ async initialize() {
27
+ try {
28
+ await this.fetchEnvironmentId();
29
+ }
30
+ catch (error) {
31
+ // Log warning but don't throw - the MCP server can still partially function
32
+ // The authorize method will return an appropriate error when environmentId is missing
33
+ this.logger('Warn', `Failed to fetch environmentId from Forest Admin API: ${error}`);
34
+ }
35
+ }
36
+ async fetchEnvironmentId() {
37
+ if (!this.envSecret) {
38
+ throw new Error('FOREST_ENV_SECRET is required to fetch environment ID');
39
+ }
40
+ // Call Forest Admin API to get environment information
41
+ const response = await fetch(`${this.forestServerUrl}/liana/environment`, {
42
+ method: 'GET',
43
+ headers: {
44
+ 'forest-secret-key': this.envSecret,
45
+ 'Content-Type': 'application/json',
46
+ },
47
+ });
48
+ if (!response.ok) {
49
+ const errorText = await response.text();
50
+ throw new Error(`Failed to fetch environment from Forest Admin API: ${response.status} ${response.statusText}. ${errorText}`);
51
+ }
52
+ const data = (await response.json());
53
+ this.environmentId = parseInt(data.data.id, 10);
54
+ this.environmentApiEndpoint = data.data.attributes.api_endpoint;
55
+ }
56
+ /**
57
+ * Get the base URL for the MCP server from the environment's api_endpoint.
58
+ * Returns undefined if the environment info hasn't been fetched yet.
59
+ */
60
+ getBaseUrl() {
61
+ if (!this.environmentApiEndpoint) {
62
+ return undefined;
63
+ }
64
+ return new URL(this.environmentApiEndpoint);
65
+ }
66
+ get clientsStore() {
67
+ return {
68
+ getClient: async (clientId) => {
69
+ // Call Forest Admin API to get client information
70
+ const response = await fetch(`${this.forestServerUrl}/oauth/register/${clientId}`, {
71
+ method: 'GET',
72
+ headers: {
73
+ 'Content-Type': 'application/json',
74
+ },
75
+ });
76
+ // Log and return undefined for other errors (don't expose internal errors)
77
+ if (!response.ok) {
78
+ console.error(`[ForestOAuthProvider] Failed to fetch client ${clientId}: ${response.status} ${response.statusText}`);
79
+ return undefined;
80
+ }
81
+ // Return registered client if exists
82
+ return response.json();
83
+ },
84
+ };
85
+ }
86
+ async authorize(client, params, res) {
87
+ try {
88
+ // Ensure environmentId is available
89
+ if (!this.environmentId) {
90
+ throw new Error('Environment ID not available. Make sure initialize() was called and the Forest Admin API is reachable.');
91
+ }
92
+ // Redirect to Forest Admin agent for actual authentication
93
+ const agentAuthUrl = new URL('/oauth/authorize', this.forestAppUrl);
94
+ agentAuthUrl.searchParams.set('redirect_uri', params.redirectUri);
95
+ agentAuthUrl.searchParams.set('code_challenge', params.codeChallenge);
96
+ agentAuthUrl.searchParams.set('code_challenge_method', 'S256');
97
+ agentAuthUrl.searchParams.set('response_type', 'code');
98
+ agentAuthUrl.searchParams.set('client_id', client.client_id);
99
+ agentAuthUrl.searchParams.set('state', params.state);
100
+ agentAuthUrl.searchParams.set('scope', params.scopes.join('+'));
101
+ if (params.resource?.href) {
102
+ agentAuthUrl.searchParams.set('resource', params.resource.href);
103
+ }
104
+ agentAuthUrl.searchParams.set('environmentId', this.environmentId.toString());
105
+ res.redirect(agentAuthUrl.toString());
106
+ }
107
+ catch (error) {
108
+ const errorMessage = error instanceof Error ? error.message : String(error);
109
+ this.logger('Error', `[ForestOAuthProvider] Authorization error:: ${errorMessage}`);
110
+ // Don't expose internal error details to the client - use a generic message
111
+ // The actual error is logged above for debugging
112
+ res.redirect(`${params.redirectUri}?error=server_error&error_description=${encodeURIComponent('Authorization failed. Please try again or contact support.')}`);
113
+ }
114
+ }
115
+ async challengeForAuthorizationCode(client, authorizationCode) {
116
+ // This is never called but required by TS !
117
+ return authorizationCode;
118
+ }
119
+ async exchangeAuthorizationCode(client, authorizationCode, codeVerifier, redirectUri) {
120
+ try {
121
+ return await this.generateAccessToken(client, {
122
+ grant_type: 'authorization_code',
123
+ code: authorizationCode,
124
+ redirect_uri: redirectUri,
125
+ client_id: client.client_id,
126
+ code_verifier: codeVerifier,
127
+ });
128
+ }
129
+ catch (error) {
130
+ const message = error instanceof Error ? error.message : String(error);
131
+ throw new errors_js_1.InvalidRequestError(`Failed to exchange authorization code: ${message}`);
132
+ }
133
+ }
134
+ async exchangeRefreshToken(client, refreshToken, scopes) {
135
+ // Verify and decode the refresh token
136
+ let decoded;
137
+ try {
138
+ decoded = jsonwebtoken_1.default.verify(refreshToken, this.authSecret);
139
+ }
140
+ catch (error) {
141
+ throw new errors_js_1.InvalidTokenError('Invalid or expired refresh token');
142
+ }
143
+ // Validate token type
144
+ if (decoded.type !== 'refresh') {
145
+ throw new errors_js_1.UnsupportedTokenTypeError('Invalid token type');
146
+ }
147
+ // Validate client_id matches
148
+ if (decoded.clientId !== client.client_id) {
149
+ throw new errors_js_1.InvalidClientError('Token was not issued to this client');
150
+ }
151
+ // Exchange the Forest refresh token for new tokens
152
+ try {
153
+ return await this.generateAccessToken(client, {
154
+ grant_type: 'refresh_token',
155
+ refresh_token: decoded.serverRefreshToken,
156
+ client_id: client.client_id,
157
+ scopes,
158
+ });
159
+ }
160
+ catch (error) {
161
+ const message = error instanceof Error ? error.message : String(error);
162
+ throw new errors_js_1.InvalidRequestError(`Failed to refresh token: ${message}`);
163
+ }
164
+ }
165
+ async generateAccessToken(client, tokenPayload) {
166
+ const response = await fetch(`${this.forestServerUrl}/oauth/token`, {
167
+ method: 'POST',
168
+ headers: {
169
+ 'forest-secret-key': this.envSecret,
170
+ 'Content-Type': 'application/json',
171
+ },
172
+ body: JSON.stringify(tokenPayload),
173
+ });
174
+ if (!response.ok) {
175
+ const errorBody = await response.json();
176
+ throw new errors_js_1.CustomOAuthError(errorBody.error || 'server_error', errorBody.error_description || 'Failed to exchange authorization code');
177
+ }
178
+ const { access_token: forestServerAccessToken, refresh_token: forestServerRefreshToken } = (await response.json());
179
+ // Get updated user info
180
+ const decodedAccessToken = jsonwebtoken_1.default.decode(forestServerAccessToken);
181
+ if (!decodedAccessToken) {
182
+ throw new Error('Failed to decode access token from Forest Admin server');
183
+ }
184
+ const { meta: { renderingId }, exp: expirationDate, scope, } = decodedAccessToken;
185
+ const decodedRefreshToken = jsonwebtoken_1.default.decode(forestServerRefreshToken);
186
+ if (!decodedRefreshToken) {
187
+ throw new Error('Failed to decode refresh token from Forest Admin server');
188
+ }
189
+ const { exp: refreshTokenExpirationDate } = decodedRefreshToken;
190
+ const user = await this.forestClient.authService.getUserInfo(renderingId, forestServerAccessToken);
191
+ // Create new access token
192
+ const expiresIn = expirationDate - Math.floor(Date.now() / 1000);
193
+ const tokenScopes = scope ? scope.split(' ') : ['mcp:read', 'mcp:write', 'mcp:action'];
194
+ const accessToken = jsonwebtoken_1.default.sign({ ...user, serverToken: forestServerAccessToken, scopes: tokenScopes }, this.authSecret, { expiresIn });
195
+ // Create new refresh token (token rotation for security)
196
+ const refreshToken = jsonwebtoken_1.default.sign({
197
+ type: 'refresh',
198
+ clientId: client.client_id,
199
+ userId: user.id,
200
+ renderingId,
201
+ serverRefreshToken: forestServerRefreshToken,
202
+ }, this.authSecret, { expiresIn: refreshTokenExpirationDate - Math.floor(Date.now() / 1000) });
203
+ return {
204
+ access_token: accessToken,
205
+ token_type: 'Bearer',
206
+ expires_in: expiresIn > 0 ? expiresIn : 3600,
207
+ refresh_token: refreshToken,
208
+ scope: scope || client.scope,
209
+ };
210
+ }
211
+ async verifyAccessToken(token) {
212
+ try {
213
+ const decoded = jsonwebtoken_1.default.verify(token, this.authSecret);
214
+ // Ensure this is an access token (not a refresh token)
215
+ if ('type' in decoded && decoded.type === 'refresh') {
216
+ throw new errors_js_1.UnsupportedTokenTypeError('Cannot use refresh token as access token');
217
+ }
218
+ // Use scopes from token if available, otherwise fall back to defaults
219
+ const scopes = decoded.scopes || ['mcp:read', 'mcp:write', 'mcp:action'];
220
+ return {
221
+ token,
222
+ clientId: decoded.id.toString(),
223
+ expiresAt: decoded.exp,
224
+ scopes,
225
+ extra: {
226
+ userId: decoded.id,
227
+ email: decoded.email,
228
+ renderingId: decoded.renderingId,
229
+ environmentApiEndpoint: this.environmentApiEndpoint,
230
+ forestServerToken: decoded.serverToken,
231
+ },
232
+ };
233
+ }
234
+ catch (error) {
235
+ this.logger('Error', `Error verifying token: ${error}`);
236
+ if (error instanceof jsonwebtoken_1.default.TokenExpiredError) {
237
+ throw new errors_js_1.InvalidTokenError('Access token has expired');
238
+ }
239
+ if (error instanceof jsonwebtoken_1.default.JsonWebTokenError) {
240
+ throw new errors_js_1.InvalidTokenError('Invalid access token');
241
+ }
242
+ throw error;
243
+ }
244
+ }
245
+ async revokeToken(_client, _request) {
246
+ // Token revocation is not currently implemented.
247
+ // Per RFC 7009, the revocation endpoint should return success even if the token
248
+ // is already invalid or unknown, so we silently succeed here.
249
+ // TODO: Implement actual token revocation with Forest Admin server when supported.
250
+ }
251
+ }
252
+ exports.default = ForestOAuthProvider;
253
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yZXN0LW9hdXRoLXByb3ZpZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ZvcmVzdC1vYXV0aC1wcm92aWRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQWVBLHlGQUFzRTtBQUN0RSwrRUFNeUQ7QUFDekQsZ0VBQXdDO0FBVXhDOztHQUVHO0FBQ0gsTUFBcUIsbUJBQW1CO0lBVXRDLFlBQVksRUFDVixlQUFlLEVBQ2YsWUFBWSxFQUNaLFNBQVMsRUFDVCxVQUFVLEVBQ1YsTUFBTSxHQUNxQjtRQXNXN0Isd0RBQXdEO1FBQ3hELDRCQUF1QixHQUFHLElBQUksQ0FBQztRQXRXN0IsSUFBSSxDQUFDLGVBQWUsR0FBRyxlQUFlLENBQUM7UUFDdkMsSUFBSSxDQUFDLFlBQVksR0FBRyxZQUFZLENBQUM7UUFDakMsSUFBSSxDQUFDLFNBQVMsR0FBRyxTQUFTLENBQUM7UUFDM0IsSUFBSSxDQUFDLFVBQVUsR0FBRyxVQUFVLENBQUM7UUFDN0IsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLFlBQVksR0FBRyxJQUFBLDRCQUF1QixFQUFDO1lBQzFDLGVBQWUsRUFBRSxJQUFJLENBQUMsZUFBZTtZQUNyQyxTQUFTLEVBQUUsSUFBSSxDQUFDLFNBQVM7U0FDMUIsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELEtBQUssQ0FBQyxVQUFVO1FBQ2QsSUFBSSxDQUFDO1lBQ0gsTUFBTSxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUNsQyxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLDRFQUE0RTtZQUM1RSxzRkFBc0Y7WUFDdEYsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsd0RBQXdELEtBQUssRUFBRSxDQUFDLENBQUM7UUFDdkYsQ0FBQztJQUNILENBQUM7SUFFTyxLQUFLLENBQUMsa0JBQWtCO1FBQzlCLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFF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
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=forest-oauth-provider.test.d.ts.map