Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,12 @@ module.exports = {
],
},
})),
{
// ESM package requiring .js extensions for imports
files: ['packages/mcp-server/**/*'],
rules: {
'import/extensions': ['error', 'ignorePackages'],
},
},
],
};
118 changes: 118 additions & 0 deletions packages/mcp-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# @forestadmin/mcp-server

Model Context Protocol (MCP) server for Forest Admin with OAuth authentication support.

## Overview

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.

## Installation

```bash
npm install @forestadmin/mcp-server
```

## Usage

### Running the Server

You can start the server using the CLI:

```bash
npx forest-mcp-server
```

Or programmatically:

```bash
node dist/index.js
```

### Environment Variables

The following environment variables are required to run the server:

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `FOREST_ENV_SECRET` | **Yes** | - | Your Forest Admin environment secret |
| `FOREST_AUTH_SECRET` | **Yes** | - | Your Forest Admin authentication secret (must match your agent) |
| `FOREST_SERVER_URL` | No | `https://api.forestadmin.com` | Forest Admin server URL |
| `MCP_SERVER_PORT` | No | `3931` | Port for the HTTP server |

### Example Configuration

```bash
export FOREST_ENV_SECRET="your-env-secret"
export FOREST_AUTH_SECRET="your-auth-secret"
export MCP_SERVER_PORT=3931

npx forest-mcp-server
```

## API Endpoint

Once running, the MCP server exposes a single endpoint:

- **POST** `/mcp` - Main MCP protocol endpoint

The server expects MCP protocol messages in the request body and returns MCP-formatted responses.

## Features

- **HTTP Transport**: Uses streamable HTTP transport for MCP communication
- **OAuth Authentication**: Built-in support for Forest Admin OAuth
- **CORS Enabled**: Allows cross-origin requests
- **Express-based**: Built on top of Express.js for reliability and extensibility

## Development

### Building

```bash
npm run build
```

### Watch Mode

```bash
npm run build:watch
```

### Linting

```bash
npm run lint
```

### Testing

```bash
npm test
```

### Cleaning

```bash
npm run clean
```

## Architecture

The server consists of:

- **ForestAdminMCPServer**: Main server class managing the MCP server lifecycle
- **McpServer**: Core MCP protocol implementation
- **StreamableHTTPServerTransport**: HTTP transport layer for MCP
- **Express App**: HTTP server handling incoming requests

## License

GPL-3.0

## Repository

[https://github.com/ForestAdmin/agent-nodejs](https://github.com/ForestAdmin/agent-nodejs)

## Support

For issues and feature requests, please visit the [GitHub repository](https://github.com/ForestAdmin/agent-nodejs/tree/main/packages/mcp-server).
12 changes: 12 additions & 0 deletions packages/mcp-server/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* eslint-disable import/no-relative-packages */
import jestConfig from '../../jest.config';

export default {
...jestConfig,
collectCoverageFrom: ['<rootDir>/src/**/*.ts', '!<rootDir>/src/**/*.test.ts'],
testMatch: ['<rootDir>/src/**/*.test.ts'],
// Map .js imports to .ts files for ESM compatibility
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
};
44 changes: 44 additions & 0 deletions packages/mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@forestadmin/mcp-server",
"version": "0.1.0",
"description": "Model Context Protocol server for Forest Admin with OAuth authentication",
"type": "module",
"main": "dist/index.js",
"bin": {
"forest-mcp-server": "dist/index.js"
},
"license": "GPL-3.0",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ForestAdmin/agent-nodejs.git",
"directory": "packages/mcp-server"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"cors": "^2.8.5",
"express": "^4.18.2"
},
"files": [
"dist/**/*.js",
"dist/**/*.d.ts"
],
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"start": "node dist/index.js",
"clean": "rm -rf coverage dist",
"lint": "eslint src test",
"test": "jest"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^18.11.18",
"@types/supertest": "^6.0.2",
"supertest": "^7.1.3",
"typescript": "^5.0.0"
}
}
109 changes: 109 additions & 0 deletions packages/mcp-server/src/forest-oauth-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js';
import type {
AuthorizationParams,
OAuthServerProvider,
} from '@modelcontextprotocol/sdk/server/auth/provider.js';
import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
import type {
OAuthClientInformationFull,
OAuthTokenRevocationRequest,
OAuthTokens,
} from '@modelcontextprotocol/sdk/shared/auth.js';
import type { Response } from 'express';

/**
* OAuth Server Provider that integrates with Forest Admin authentication
*/
export default class ForestAdminOAuthProvider implements OAuthServerProvider {
private forestServerUrl: string;

constructor({ forestServerUrl }: { forestServerUrl: string }) {
this.forestServerUrl = forestServerUrl;
}

async initialize(): Promise<void> {
// FIXME: Fetch environmentId on startup if needed
}

get clientsStore(): OAuthRegisteredClientsStore {
return {
getClient: (clientId: string) => {
// FIXME: To implement
return clientId && null;
},
};
}

async authorize(
client: OAuthClientInformationFull,
params: AuthorizationParams,
res: Response,
): Promise<void> {
// FIXME: To implement
res.sendStatus(501);
}

async challengeForAuthorizationCode(
client: OAuthClientInformationFull,
authorizationCode: string,
): Promise<string> {
// This is never called but required by TS !
return authorizationCode;
}

async exchangeAuthorizationCode(
client: OAuthClientInformationFull,
authorizationCode: string,
codeVerifier?: string,
redirectUri?: string,
): Promise<OAuthTokens> {
// FIXME: To implement the exchange with Forest Admin server

return {
access_token: redirectUri && 'Fake token',
token_type: 'Bearer',
expires_in: 3600,
// refresh_token: refreshToken,
scope: 'mcp:read',
};
}

async exchangeRefreshToken(
client: OAuthClientInformationFull,
refreshToken: string,
scopes?: string[],
): Promise<OAuthTokens> {
// Generate new access token
// FIXME: To implement the exchange with Forest Admin server
return {
access_token: 'Fake token',
token_type: 'Bearer',
expires_in: 3600,
scope: scopes?.join(' ') || 'mcp:read',
};
}

async verifyAccessToken(token: string): Promise<AuthInfo> {
// FIXME: To implement the verification with Forest Admin server

return {
token,
clientId: 'fake client id',
expiresAt: 136472874,
scopes: ['mcp:read'],
};
}

async revokeToken(
client: OAuthClientInformationFull,
request: OAuthTokenRevocationRequest,
): Promise<void> {
// FIXME: To implement the revocation with Forest Admin server
if (request) {
// Remove this if
}
}

// Skip PKCE validation to match original implementation
skipLocalPkceValidation = true;
}
11 changes: 11 additions & 0 deletions packages/mcp-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env node

import ForestAdminMCPServer from './server.js';

// Start the server
const server = new ForestAdminMCPServer();

server.run().catch(error => {
console.error('[FATAL] Server crashed:', error);
process.exit(1);
});
Loading
Loading