@natomalabs/natoma-mcp-gateway 1.0.2 ā 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -18
- package/build/base.js +11 -8
- package/build/gateway.js +17 -14
- package/package.json +2 -2
- package/build/cli.js +0 -64
- package/build/nms-gateway.js +0 -131
- package/build/setup.js +0 -186
package/README.md
CHANGED
|
@@ -4,12 +4,13 @@ A robust, production-ready gateway that bridges stdio-based MCP clients (like Cl
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
The Natoma MCP Gateway acts as a translation layer that enables seamless communication between different MCP (Model Context Protocol) implementations. It
|
|
7
|
+
The Natoma MCP Gateway acts as a translation layer that enables seamless communication between different MCP (Model Context Protocol) implementations. It operates exclusively in enterprise mode, providing production-grade features including automatic reconnection, health monitoring, and robust error handling.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- **
|
|
11
|
+
- **Enterprise Gateway**: Production-ready gateway with enhanced reliability and monitoring
|
|
12
12
|
- **Protocol Translation**: Converts stdio JSON-RPC messages to HTTP/SSE format and vice versa
|
|
13
|
+
- **Custom URL Support**: Configure custom endpoints for private deployments
|
|
13
14
|
|
|
14
15
|
## Installation
|
|
15
16
|
|
|
@@ -26,13 +27,28 @@ Required environment variables:
|
|
|
26
27
|
- `NATOMA_MCP_SERVER_INSTALLATION_ID`: Your MCP server installation ID/slug
|
|
27
28
|
- `NATOMA_MCP_API_KEY`: Your Natoma API key for authentication
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
Optional environment variables:
|
|
31
|
+
|
|
32
|
+
- `NATOMA_MCP_CUSTOM_URL`: Custom endpoint URL (overrides default api.natoma.app endpoint)
|
|
33
|
+
|
|
34
|
+
### Corporate Proxy/Firewall Support
|
|
35
|
+
|
|
36
|
+
If you're running in a corporate environment with tools like ZScaler that perform TLS interception, you may encounter certificate validation errors. You can resolve this using Node.js's built-in support for custom CA certificates:
|
|
30
37
|
|
|
31
38
|
```bash
|
|
32
|
-
#
|
|
39
|
+
# Export your corporate CA bundle
|
|
40
|
+
export NODE_EXTRA_CA_CERTS="/path/to/corporate-ca-bundle.pem"
|
|
41
|
+
|
|
42
|
+
# Run the gateway
|
|
33
43
|
npx @natomalabs/natoma-mcp-gateway
|
|
44
|
+
```
|
|
34
45
|
|
|
35
|
-
|
|
46
|
+
This approach is the standard Node.js solution for corporate environments.
|
|
47
|
+
|
|
48
|
+
### Command Line Usage
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Run the gateway (--enterprise flag is required)
|
|
36
52
|
npx @natomalabs/natoma-mcp-gateway --enterprise
|
|
37
53
|
```
|
|
38
54
|
|
|
@@ -46,7 +62,8 @@ Add to your Claude Desktop MCP configuration:
|
|
|
46
62
|
"your-server-name": {
|
|
47
63
|
"command": "npx",
|
|
48
64
|
"args": [
|
|
49
|
-
"@natomalabs/natoma-mcp-gateway"
|
|
65
|
+
"@natomalabs/natoma-mcp-gateway",
|
|
66
|
+
"--enterprise"
|
|
50
67
|
],
|
|
51
68
|
"env": {
|
|
52
69
|
"NATOMA_MCP_SERVER_INSTALLATION_ID": "your-installation-id",
|
|
@@ -57,26 +74,60 @@ Add to your Claude Desktop MCP configuration:
|
|
|
57
74
|
}
|
|
58
75
|
```
|
|
59
76
|
|
|
60
|
-
For
|
|
77
|
+
For custom endpoint URLs:
|
|
61
78
|
|
|
62
|
-
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"your-server-name": {
|
|
83
|
+
"command": "npx",
|
|
84
|
+
"args": [
|
|
85
|
+
"@natomalabs/natoma-mcp-gateway",
|
|
86
|
+
"--enterprise"
|
|
87
|
+
],
|
|
88
|
+
"env": {
|
|
89
|
+
"NATOMA_MCP_SERVER_INSTALLATION_ID": "your-installation-id",
|
|
90
|
+
"NATOMA_MCP_API_KEY": "your-api-key",
|
|
91
|
+
"NATOMA_MCP_CUSTOM_URL": "https://your-custom-endpoint.com/mcp"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
For corporate environments with custom certificates:
|
|
63
99
|
|
|
64
|
-
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"mcpServers": {
|
|
103
|
+
"your-server-name": {
|
|
104
|
+
"command": "npx",
|
|
105
|
+
"args": [
|
|
106
|
+
"@natomalabs/natoma-mcp-gateway",
|
|
107
|
+
"--enterprise"
|
|
108
|
+
],
|
|
109
|
+
"env": {
|
|
110
|
+
"NATOMA_MCP_SERVER_INSTALLATION_ID": "your-installation-id",
|
|
111
|
+
"NATOMA_MCP_API_KEY": "your-api-key",
|
|
112
|
+
"NODE_EXTRA_CA_CERTS": "/path/to/corporate-ca-bundle.pem"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
65
118
|
|
|
66
|
-
|
|
119
|
+
## Architecture
|
|
67
120
|
|
|
68
|
-
|
|
69
|
-
- Optimized for standard MCP deployments
|
|
70
|
-
- Faster reconnection times (1 second)
|
|
71
|
-
- HTTP-based message delivery
|
|
121
|
+
### Enterprise Gateway
|
|
72
122
|
|
|
73
|
-
|
|
123
|
+
The gateway operates in enterprise mode with the following features:
|
|
74
124
|
|
|
75
|
-
- Uses fetch API with enhanced
|
|
125
|
+
- Uses fetch API with enhanced reliability
|
|
76
126
|
- Support for both JSON and SSE responses
|
|
77
127
|
- Health check monitoring (every 5 minutes)
|
|
78
128
|
- Extended timeouts (60 seconds)
|
|
79
129
|
- Enhanced error recovery
|
|
130
|
+
- Custom endpoint URL support
|
|
80
131
|
|
|
81
132
|
### Protocol Flow
|
|
82
133
|
|
|
@@ -92,9 +143,11 @@ The gateway accepts several configuration options through the `MCPConfig` interf
|
|
|
92
143
|
|
|
93
144
|
- `slug`: Server installation ID (required)
|
|
94
145
|
- `apiKey`: Authentication API key (required)
|
|
146
|
+
- `isEnterprise`: Must be set to `true` (mandatory for enterprise mode)
|
|
147
|
+
- `customUrl`: Optional custom endpoint URL (overrides default api.natoma.app)
|
|
95
148
|
- `maxReconnectAttempts`: Maximum reconnection attempts (default: 5)
|
|
96
|
-
- `reconnectDelay`: Delay between reconnection attempts (default:
|
|
97
|
-
- `timeout`: Request timeout (default:
|
|
149
|
+
- `reconnectDelay`: Delay between reconnection attempts (default: 2000ms)
|
|
150
|
+
- `timeout`: Request timeout (default: 60000ms)
|
|
98
151
|
|
|
99
152
|
## What is MCP?
|
|
100
153
|
|
package/build/base.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// base.ts
|
|
2
|
-
export const
|
|
3
|
-
export const NATOMA_ENTERPRISE_SERVER_URL = "https://api.natoma.app/api/mcp";
|
|
2
|
+
export const NATOMA_ENTERPRISE_SERVER_URL = "https://api.natoma.app/mcp";
|
|
4
3
|
export const MCP_SESSION_ID_HEADER = "Mcp-Session-Id";
|
|
5
4
|
// Handle EPIPE errors gracefully
|
|
6
5
|
process.stdout.on("error", (err) => {
|
|
@@ -23,20 +22,24 @@ export class BaseMCPGateway {
|
|
|
23
22
|
reconnectDelay;
|
|
24
23
|
apiKey;
|
|
25
24
|
constructor(config) {
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
// Validate that config is provided with required isEnterprise flag
|
|
26
|
+
if (!config || config.isEnterprise !== true) {
|
|
27
|
+
throw new Error("Configuration with isEnterprise: true is required for MCP Gateway");
|
|
28
|
+
}
|
|
29
|
+
// Use custom URL if provided, otherwise use enterprise URL
|
|
30
|
+
this.baseUrl = config.customUrl || NATOMA_ENTERPRISE_SERVER_URL;
|
|
30
31
|
this.maxReconnectAttempts = 3;
|
|
31
32
|
this.reconnectDelay = 1000;
|
|
32
|
-
this.apiKey = config
|
|
33
|
+
this.apiKey = config.apiKey;
|
|
33
34
|
// Validate that API key is provided
|
|
34
35
|
if (!this.apiKey) {
|
|
35
36
|
throw new Error("API key is required for MCP Gateway");
|
|
36
37
|
}
|
|
37
38
|
// Debug logging
|
|
38
|
-
console.error(`[BaseMCPGateway] Gateway Type: ${config?.isEnterprise ? "Enterprise" : "NMS"}`);
|
|
39
39
|
console.error(`[BaseMCPGateway] Base URL set to: ${this.baseUrl}`);
|
|
40
|
+
if (config.customUrl) {
|
|
41
|
+
console.error(`[BaseMCPGateway] Using custom URL`);
|
|
42
|
+
}
|
|
40
43
|
console.error(`[BaseMCPGateway] API Key: ${this.apiKey ? "PROVIDED" : "NOT PROVIDED"}`);
|
|
41
44
|
console.error(`[BaseMCPGateway] Max reconnect attempts: ${this.maxReconnectAttempts}`);
|
|
42
45
|
}
|
package/build/gateway.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { NMSGateway } from "./nms-gateway.js";
|
|
3
2
|
import { EnterpriseGateway } from "./ent-gateway.js";
|
|
4
3
|
// Parse command line arguments
|
|
5
4
|
function parseArgs() {
|
|
6
5
|
const args = process.argv.slice(2);
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
if (!args.includes("--enterprise")) {
|
|
7
|
+
console.error("Error: --enterprise flag is required");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
10
|
}
|
|
11
11
|
// Main function
|
|
12
12
|
async function main() {
|
|
13
|
-
|
|
13
|
+
parseArgs();
|
|
14
14
|
const slug = process.env.NATOMA_MCP_SERVER_INSTALLATION_ID;
|
|
15
15
|
if (!slug) {
|
|
16
16
|
console.error("Please set NATOMA_MCP_SERVER_INSTALLATION_ID env var");
|
|
@@ -20,14 +20,17 @@ async function main() {
|
|
|
20
20
|
slug,
|
|
21
21
|
apiKey: process.env.NATOMA_MCP_API_KEY,
|
|
22
22
|
maxReconnectAttempts: 5,
|
|
23
|
-
reconnectDelay:
|
|
24
|
-
timeout:
|
|
23
|
+
reconnectDelay: 2000,
|
|
24
|
+
timeout: 60000,
|
|
25
|
+
isEnterprise: true, // Mandatory flag
|
|
26
|
+
customUrl: process.env.NATOMA_MCP_CUSTOM_URL, // Optional custom URL from env
|
|
25
27
|
};
|
|
26
|
-
//
|
|
27
|
-
const gateway =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
// Always create Enterprise gateway
|
|
29
|
+
const gateway = new EnterpriseGateway(config);
|
|
30
|
+
console.error(`--- Starting Enterprise Gateway ---`);
|
|
31
|
+
if (config.customUrl) {
|
|
32
|
+
console.error(`--- Using custom URL: ${config.customUrl} ---`);
|
|
33
|
+
}
|
|
31
34
|
try {
|
|
32
35
|
await gateway.connect();
|
|
33
36
|
process.stdin.on("data", (data) => {
|
|
@@ -46,8 +49,8 @@ async function main() {
|
|
|
46
49
|
console.error("Uncaught exception:", err);
|
|
47
50
|
shutdown();
|
|
48
51
|
});
|
|
49
|
-
//
|
|
50
|
-
if (
|
|
52
|
+
// Health check for enterprise gateway
|
|
53
|
+
if (gateway instanceof EnterpriseGateway) {
|
|
51
54
|
setInterval(async () => {
|
|
52
55
|
if (gateway.ready) {
|
|
53
56
|
const ok = await gateway.healthCheck();
|
package/package.json
CHANGED
package/build/cli.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import yargs from 'yargs';
|
|
2
|
-
import { hideBin } from 'yargs/helpers';
|
|
3
|
-
import { setupCommand } from './setup.js';
|
|
4
|
-
import { MCPGateway } from './gateway.js';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
const startCommand = {
|
|
7
|
-
command: 'start',
|
|
8
|
-
describe: 'Start the Natoma MCP Gateway',
|
|
9
|
-
builder: (yargs) => {
|
|
10
|
-
return yargs
|
|
11
|
-
.option('url', {
|
|
12
|
-
type: 'string',
|
|
13
|
-
describe: 'The Natoma MCP URL to connect to',
|
|
14
|
-
})
|
|
15
|
-
.option('apiKey', {
|
|
16
|
-
type: 'string',
|
|
17
|
-
describe: 'API key for authentication',
|
|
18
|
-
demandOption: true,
|
|
19
|
-
})
|
|
20
|
-
.option('slug', {
|
|
21
|
-
type: 'string',
|
|
22
|
-
describe: 'Installation ID/slug for the connection',
|
|
23
|
-
});
|
|
24
|
-
},
|
|
25
|
-
handler: async (argv) => {
|
|
26
|
-
try {
|
|
27
|
-
const { url, apiKey, slug } = argv;
|
|
28
|
-
console.log(chalk.cyan('š Starting Natoma MCP Gateway...'));
|
|
29
|
-
if (url) {
|
|
30
|
-
console.log(chalk.cyan(` URL: ${url}`));
|
|
31
|
-
}
|
|
32
|
-
if (slug) {
|
|
33
|
-
console.log(chalk.cyan(` Installation ID: ${slug}`));
|
|
34
|
-
}
|
|
35
|
-
const gateway = new MCPGateway({
|
|
36
|
-
apiKey: apiKey || process.env.NATOMA_MCP_API_KEY,
|
|
37
|
-
slug: slug || process.env.NATOMA_MCP_SERVER_INSTALLATION_ID,
|
|
38
|
-
});
|
|
39
|
-
await gateway.connect();
|
|
40
|
-
console.log(chalk.green('ā
Connected to Natoma MCP Server'));
|
|
41
|
-
process.stdin.on("data", (data) => gateway.processMessage(data));
|
|
42
|
-
// Handle cleanup
|
|
43
|
-
const cleanup = () => {
|
|
44
|
-
console.log(chalk.yellow("Shutting down..."));
|
|
45
|
-
gateway.cleanup();
|
|
46
|
-
process.exit(0);
|
|
47
|
-
};
|
|
48
|
-
process.on("SIGINT", cleanup);
|
|
49
|
-
process.on("SIGTERM", cleanup);
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
console.log(chalk.red('\nā Error occurred while starting Natoma MCP Gateway:'));
|
|
53
|
-
console.log(chalk.red(` ${error.message}`));
|
|
54
|
-
console.log(chalk.yellow('\nPlease try again or contact support if the issue persists.\n'));
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
yargs(hideBin(process.argv))
|
|
60
|
-
.command(setupCommand)
|
|
61
|
-
.command(startCommand)
|
|
62
|
-
.demandCommand(1, 'You need to specify a command.')
|
|
63
|
-
.help()
|
|
64
|
-
.argv;
|
package/build/nms-gateway.js
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
// nms-gateway.ts
|
|
2
|
-
import EventSource from "eventsource";
|
|
3
|
-
import { BaseMCPGateway } from "./base.js";
|
|
4
|
-
// NMS Gateway (Non-Enterprise) - EventSource based
|
|
5
|
-
export class NMSGateway extends BaseMCPGateway {
|
|
6
|
-
eventSource = null;
|
|
7
|
-
sessionId = null;
|
|
8
|
-
sseUrl;
|
|
9
|
-
messageUrl;
|
|
10
|
-
constructor(config) {
|
|
11
|
-
// Set isEnterprise to false for NMS Gateway
|
|
12
|
-
super({ ...config, isEnterprise: false });
|
|
13
|
-
const slug = config?.slug;
|
|
14
|
-
this.sseUrl = slug ? `${this.baseUrl}/${slug}` : `${this.baseUrl}`;
|
|
15
|
-
this.messageUrl = slug
|
|
16
|
-
? `${this.baseUrl}/${slug}/message`
|
|
17
|
-
: `${this.baseUrl}/message`;
|
|
18
|
-
// Debug the URLs
|
|
19
|
-
console.error(`[NMSGateway] Base URL: ${this.baseUrl}`);
|
|
20
|
-
console.error(`[NMSGateway] SSE URL: ${this.sseUrl}`);
|
|
21
|
-
console.error(`[NMSGateway] Message URL: ${this.messageUrl}`);
|
|
22
|
-
}
|
|
23
|
-
async connect() {
|
|
24
|
-
if (this.eventSource) {
|
|
25
|
-
console.error("Closing existing connection");
|
|
26
|
-
this.eventSource.close();
|
|
27
|
-
}
|
|
28
|
-
return new Promise((resolve, reject) => {
|
|
29
|
-
const headers = {
|
|
30
|
-
Accept: "text/event-stream",
|
|
31
|
-
};
|
|
32
|
-
if (this.apiKey) {
|
|
33
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
34
|
-
}
|
|
35
|
-
this.eventSource = new EventSource(this.sseUrl, { headers });
|
|
36
|
-
this.eventSource.onopen = () => {
|
|
37
|
-
console.error("--- SSE backend connected");
|
|
38
|
-
this.reconnectAttempts = 0;
|
|
39
|
-
resolve();
|
|
40
|
-
};
|
|
41
|
-
this.eventSource.onerror = (error) => {
|
|
42
|
-
console.error(`--- SSE backend error: ${error?.message}`);
|
|
43
|
-
this.handleConnectionError(error);
|
|
44
|
-
reject(error);
|
|
45
|
-
};
|
|
46
|
-
this.setupEventListeners();
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
setupEventListeners() {
|
|
50
|
-
if (!this.eventSource)
|
|
51
|
-
return;
|
|
52
|
-
this.eventSource.addEventListener("endpoint", (event) => {
|
|
53
|
-
const match = event.data.match(/sessionId=([^&]+)/);
|
|
54
|
-
if (match) {
|
|
55
|
-
this.sessionId = match[1];
|
|
56
|
-
this.isReady = true;
|
|
57
|
-
console.error(`Session established: ${this.sessionId}`);
|
|
58
|
-
this.processQueuedMessages();
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
this.eventSource.addEventListener("message", (event) => {
|
|
62
|
-
try {
|
|
63
|
-
console.error(`<-- ${event.data}`);
|
|
64
|
-
console.log(event.data);
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
console.error(`Error handling message: ${error}`);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
async handleConnectionError(error) {
|
|
72
|
-
console.error(`Connection error: ${error.message}`);
|
|
73
|
-
if (this.eventSource?.readyState === EventSource.CLOSED) {
|
|
74
|
-
console.error("EventSource connection closed");
|
|
75
|
-
await this.reconnect();
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
async reconnect() {
|
|
79
|
-
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
80
|
-
console.error(`Max reconnection attempts (${this.maxReconnectAttempts}) reached, exiting...`);
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
this.reconnectAttempts++;
|
|
84
|
-
this.isReady = false;
|
|
85
|
-
try {
|
|
86
|
-
await new Promise((resolve) => setTimeout(resolve, this.reconnectDelay));
|
|
87
|
-
await this.connect();
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
console.error(`Reconnection failed: ${error}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
async processMessage(input) {
|
|
94
|
-
if (!this.isReady || !this.sessionId) {
|
|
95
|
-
this.messageQueue.push(input);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
const message = input.toString().trim();
|
|
99
|
-
console.error(`--> ${message}`);
|
|
100
|
-
try {
|
|
101
|
-
const url = `${this.messageUrl}?sessionId=${this.sessionId}`;
|
|
102
|
-
const headers = {
|
|
103
|
-
"Content-Type": "application/json",
|
|
104
|
-
};
|
|
105
|
-
if (this.apiKey) {
|
|
106
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
107
|
-
}
|
|
108
|
-
const response = await fetch(url, {
|
|
109
|
-
method: "POST",
|
|
110
|
-
headers,
|
|
111
|
-
body: message,
|
|
112
|
-
});
|
|
113
|
-
if (!response.ok) {
|
|
114
|
-
if (response.status === 503) {
|
|
115
|
-
await this.reconnect();
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
console.error(`Error from server: ${response.status} ${response.statusText}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
catch (error) {
|
|
123
|
-
console.error(`Request error: ${error}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
cleanup() {
|
|
127
|
-
if (this.eventSource) {
|
|
128
|
-
this.eventSource.close();
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
package/build/setup.js
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
// setup.ts
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import os from "os";
|
|
5
|
-
import chalk from "chalk";
|
|
6
|
-
export const setupCommand = {
|
|
7
|
-
command: "setup <url>",
|
|
8
|
-
describe: "Setup command for Natoma MCP Gateway integration",
|
|
9
|
-
builder: (yargs) => {
|
|
10
|
-
return yargs
|
|
11
|
-
.positional("url", {
|
|
12
|
-
type: "string",
|
|
13
|
-
describe: "The Natoma MCP URL to use",
|
|
14
|
-
demandOption: true,
|
|
15
|
-
})
|
|
16
|
-
.option("client", {
|
|
17
|
-
type: "string",
|
|
18
|
-
describe: "Client to use (claude, windsurf, cursor)",
|
|
19
|
-
default: "claude",
|
|
20
|
-
choices: ["claude", "windsurf", "cursor"],
|
|
21
|
-
})
|
|
22
|
-
.option("apiKey", {
|
|
23
|
-
type: "string",
|
|
24
|
-
describe: "API key for authentication",
|
|
25
|
-
demandOption: true,
|
|
26
|
-
});
|
|
27
|
-
},
|
|
28
|
-
handler: async (argv) => {
|
|
29
|
-
const { url, client, apiKey } = argv;
|
|
30
|
-
try {
|
|
31
|
-
console.log(chalk.cyan("š Configuration Details:"));
|
|
32
|
-
console.log(` URL: ${chalk.green(url)}`);
|
|
33
|
-
console.log(` Client: ${chalk.green(client)}`);
|
|
34
|
-
if (apiKey) {
|
|
35
|
-
console.log(` API Key: ${chalk.green("****" + apiKey.slice(-4))}`);
|
|
36
|
-
}
|
|
37
|
-
console.log("");
|
|
38
|
-
console.log(chalk.cyan("š¾ Saving configurations..."));
|
|
39
|
-
saveMcpConfig(url, client, apiKey);
|
|
40
|
-
console.log(chalk.cyan(`\nš All done! Please restart ${client} for changes to take effect\n`));
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
console.log(chalk.red("\nā Error occurred while setting up Natoma MCP:"));
|
|
44
|
-
console.log(chalk.red(` ${error.message}`));
|
|
45
|
-
console.log(chalk.yellow("\nPlease try again or contact support if the issue persists.\n"));
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
function saveMcpConfig(url, clientType, apiKey) {
|
|
50
|
-
// Create config object for the gateway
|
|
51
|
-
const config = {
|
|
52
|
-
command: "npx",
|
|
53
|
-
args: ["@natomalabs/natoma-mcp-gateway@latest", "start", "--url", url],
|
|
54
|
-
env: {
|
|
55
|
-
npm_config_yes: "true",
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
// If API key is provided, add it to the environment variables
|
|
59
|
-
if (apiKey) {
|
|
60
|
-
config.env["NATOMA_MCP_API_KEY"] = apiKey;
|
|
61
|
-
// Also add it to the args so it's passed to the start command
|
|
62
|
-
config.args.push("--apiKey", apiKey);
|
|
63
|
-
}
|
|
64
|
-
// Extract installation ID from URL for Cursor
|
|
65
|
-
const urlParts = url.split("/");
|
|
66
|
-
const installationId = urlParts[urlParts.length - 1];
|
|
67
|
-
const sseConfig = {
|
|
68
|
-
url: url,
|
|
69
|
-
apiKey: apiKey,
|
|
70
|
-
slug: installationId,
|
|
71
|
-
};
|
|
72
|
-
const homeDir = os.homedir();
|
|
73
|
-
// Define platform-specific paths
|
|
74
|
-
const platformPaths = {
|
|
75
|
-
win32: {
|
|
76
|
-
baseDir: process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"),
|
|
77
|
-
vscodePath: path.join("Code", "User", "globalStorage"),
|
|
78
|
-
},
|
|
79
|
-
darwin: {
|
|
80
|
-
baseDir: path.join(homeDir, "Library", "Application Support"),
|
|
81
|
-
vscodePath: path.join("Code", "User", "globalStorage"),
|
|
82
|
-
},
|
|
83
|
-
linux: {
|
|
84
|
-
baseDir: process.env.XDG_CONFIG_HOME || path.join(homeDir, ".config"),
|
|
85
|
-
vscodePath: path.join("Code/User/globalStorage"),
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
const platform = process.platform;
|
|
89
|
-
// Check if platform is supported
|
|
90
|
-
if (!platformPaths[platform]) {
|
|
91
|
-
console.log(chalk.yellow(`\nā ļø Platform ${platform} is not supported.`));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const { baseDir } = platformPaths[platform];
|
|
95
|
-
// Define client-specific paths
|
|
96
|
-
const clientPaths = {
|
|
97
|
-
claude: {
|
|
98
|
-
configDir: path.join(baseDir, "Claude"),
|
|
99
|
-
configPath: path.join(baseDir, "Claude", "claude_desktop_config.json"),
|
|
100
|
-
},
|
|
101
|
-
windsurf: {
|
|
102
|
-
configDir: path.join(homeDir, ".codeium", "windsurf"),
|
|
103
|
-
configPath: path.join(homeDir, ".codeium", "windsurf", "mcp_config.json"),
|
|
104
|
-
},
|
|
105
|
-
cursor: {
|
|
106
|
-
configDir: path.join(homeDir, ".cursor"),
|
|
107
|
-
configPath: path.join(homeDir, ".cursor", "mcp.json"),
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
if (!clientPaths[clientType]) {
|
|
111
|
-
console.log(chalk.yellow(`\nā ļø Client ${clientType} is not supported.`));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const { configDir, configPath } = clientPaths[clientType];
|
|
115
|
-
// Create config directory if it doesn't exist
|
|
116
|
-
if (!fs.existsSync(configDir)) {
|
|
117
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
118
|
-
}
|
|
119
|
-
// Handle client-specific configuration format
|
|
120
|
-
if (clientType === "claude") {
|
|
121
|
-
let claudeConfig = { mcpServers: {} };
|
|
122
|
-
if (fs.existsSync(configPath)) {
|
|
123
|
-
try {
|
|
124
|
-
claudeConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
console.log(chalk.yellow("ā ļø Creating new config file"));
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
// Ensure mcpServers exists
|
|
131
|
-
if (!claudeConfig.mcpServers)
|
|
132
|
-
claudeConfig.mcpServers = {};
|
|
133
|
-
// Update only the mcpServers entry
|
|
134
|
-
claudeConfig.mcpServers[url] = config;
|
|
135
|
-
fs.writeFileSync(configPath, JSON.stringify(claudeConfig, null, 2));
|
|
136
|
-
console.log(chalk.green(`ā
Configuration saved to: ${configPath}`));
|
|
137
|
-
}
|
|
138
|
-
else if (clientType === "windsurf") {
|
|
139
|
-
let windsurfConfig = { mcpServers: {} };
|
|
140
|
-
if (fs.existsSync(configPath)) {
|
|
141
|
-
try {
|
|
142
|
-
windsurfConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
143
|
-
}
|
|
144
|
-
catch (error) {
|
|
145
|
-
console.log(chalk.yellow("ā ļø Creating new config file"));
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
// Ensure mcpServers exists
|
|
149
|
-
if (!windsurfConfig.mcpServers)
|
|
150
|
-
windsurfConfig.mcpServers = {};
|
|
151
|
-
// Now TypeScript knows mcpServers exists
|
|
152
|
-
windsurfConfig.mcpServers[url] = config;
|
|
153
|
-
fs.writeFileSync(configPath, JSON.stringify(windsurfConfig, null, 2));
|
|
154
|
-
console.log(chalk.green(`ā
Configuration saved to: ${configPath}`));
|
|
155
|
-
}
|
|
156
|
-
else if (clientType === "cursor") {
|
|
157
|
-
let cursorConfig = { mcpServers: {} };
|
|
158
|
-
if (fs.existsSync(configPath)) {
|
|
159
|
-
try {
|
|
160
|
-
cursorConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
161
|
-
}
|
|
162
|
-
catch (error) {
|
|
163
|
-
console.log(chalk.yellow("ā ļø Creating new config file"));
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
// Ensure mcpServers exists
|
|
167
|
-
if (!cursorConfig.mcpServers)
|
|
168
|
-
cursorConfig.mcpServers = {};
|
|
169
|
-
// Remove existing config if it exists
|
|
170
|
-
if (cursorConfig.mcpServers[url]) {
|
|
171
|
-
delete cursorConfig.mcpServers[url];
|
|
172
|
-
}
|
|
173
|
-
try {
|
|
174
|
-
// Create a unique key for Cursor's configuration
|
|
175
|
-
const newKey = `natoma_${installationId}`;
|
|
176
|
-
cursorConfig.mcpServers[newKey] = sseConfig;
|
|
177
|
-
fs.writeFileSync(configPath, JSON.stringify(cursorConfig, null, 2));
|
|
178
|
-
console.log(chalk.green(`ā
Configuration saved to: ${configPath}`));
|
|
179
|
-
}
|
|
180
|
-
catch (error) {
|
|
181
|
-
console.log(chalk.red("ā Error occurred while setting up MCP:"));
|
|
182
|
-
console.log(chalk.red(` ${error.message}`));
|
|
183
|
-
console.log(chalk.yellow("\nPlease try again or contact support if the issue persists.\n"));
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|