@ecommaps/mcp 1.0.6 → 1.0.8

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 (3) hide show
  1. package/README.md +58 -22
  2. package/cli.js +152 -132
  3. package/package.json +4 -7
package/README.md CHANGED
@@ -1,34 +1,70 @@
1
- # EcoBaseAI MCP Client CLI
1
+ # @ecommaps/mcp
2
2
 
3
- This package provides the official CLI bridge for EcoBaseAI MCP servers.
3
+ Official Ecommaps MCP **stdio bridge**.
4
4
 
5
- ## Installation
5
+ This package lets MCP-capable editors connect to Ecommaps over the modern
6
+ Streamable HTTP endpoint (`/api/v1/mcp`) while speaking stdio locally.
7
+
8
+ ## Quick Start
9
+
10
+ Run with your store key and the official endpoint:
6
11
 
7
12
  ```bash
8
- npm install -g @ecommaps/mcp
13
+ npx @ecommaps/mcp <YOUR_STORE_MCP_KEY> --url https://api.ecommaps.com/api/v1/mcp
9
14
  ```
10
15
 
11
- ## Usage
16
+ ## CLI Options
12
17
 
13
- ```bash
14
- npx @ecommaps/mcp <YOUR_API_KEY>
18
+ - `--url <MCP_URL>`: Override the MCP endpoint URL.
19
+ - `--api-key <KEY>`: Pass key explicitly instead of positional arg.
20
+ - `--help`: Show usage.
21
+
22
+ ## Environment Variables
23
+
24
+ - `MCP_BEARER_TOKEN`: Preferred token source.
25
+ - `MCP_API_KEY`: Backward-compatible token source.
26
+ - `ECOMMAPS_MCP_URL`: Default endpoint if `--url` is not provided.
27
+
28
+ ## Editor Configuration (Generic MCP stdio)
29
+
30
+ Use the following shape in any editor that supports stdio MCP servers:
31
+
32
+ ```json
33
+ {
34
+ "command": "npx",
35
+ "args": [
36
+ "-y",
37
+ "@ecommaps/mcp",
38
+ "sk_eco_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
39
+ "--url",
40
+ "https://api.ecommaps.com/api/v1/mcp"
41
+ ]
42
+ }
43
+ ```
44
+
45
+ Or with env var:
46
+
47
+ ```json
48
+ {
49
+ "command": "npx",
50
+ "args": ["-y", "@ecommaps/mcp", "--url", "https://api.ecommaps.com/api/v1/mcp"],
51
+ "env": {
52
+ "MCP_BEARER_TOKEN": "sk_eco_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
53
+ }
54
+ }
15
55
  ```
16
56
 
17
- ## Development & Publishing
57
+ Important: keep `--url https://api.ecommaps.com/api/v1/mcp` in editor/server configs to avoid falling back to any outdated transport defaults.
58
+ Also make sure `--url` comes **after** `@ecommaps/mcp` in `args`; otherwise `npx` treats it as an npm flag and fails.
18
59
 
19
- To publish this package to NPM:
60
+ ## Transport Notes
20
61
 
21
- 1. Ensure you are logged in to NPM:
22
- ```bash
23
- npm login
24
- ```
25
- 2. Navigate to this directory:
26
- ```bash
27
- cd packages/mcp-cli
28
- ```
29
- 3. Publish with public access:
30
- ```bash
31
- npm publish --access public
32
- ```
62
+ - Recommended endpoint: `POST /api/v1/mcp`
63
+ - Legacy compatibility endpoints still exist during migration but are not recommended for new integrations.
33
64
 
34
- Once published, anyone can use `npx @ecommaps/mcp`!
65
+ ## Release
66
+
67
+ ```bash
68
+ cd packages/mcp-cli
69
+ npm publish --access public
70
+ ```
package/cli.js CHANGED
@@ -1,150 +1,170 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { parseArgs } = require('util');
4
3
  const readline = require('readline');
5
4
 
6
- // Configuration
7
- const DEFAULT_SSE_URL = "https://api.ecommaps.com/api/v1/mcp/sse";
5
+ const DEFAULT_MCP_URL = process.env.ECOMMAPS_MCP_URL || 'https://api.ecommaps.com/api/v1/mcp';
8
6
 
9
- // Parse Args manually or with util.parseArgs
10
- const args = process.argv.slice(2);
11
- let apiKey = null;
7
+ function parseCliArgs(argv) {
8
+ const args = argv.slice(2);
9
+ let apiKey = process.env.MCP_BEARER_TOKEN || process.env.MCP_API_KEY || null;
10
+ let mcpUrl = DEFAULT_MCP_URL;
11
+ let showHelp = false;
12
12
 
13
- // Simple arg parsing
14
- if (process.env.MCP_API_KEY) {
15
- apiKey = process.env.MCP_API_KEY;
16
- }
17
-
18
- for (let i = 0; i < args.length; i++) {
19
- if (args[i].startsWith('sk_eco_')) {
20
- apiKey = args[i];
13
+ for (let i = 0; i < args.length; i += 1) {
14
+ const rawArg = args[i];
15
+ const arg = typeof rawArg === 'string' ? rawArg.replace(/[–—−]/g, '-') : rawArg;
16
+ if (arg === '--help' || arg === '-h') {
17
+ showHelp = true;
18
+ continue;
19
+ }
20
+ if ((arg === '--url' || arg === '-url') && args[i + 1]) {
21
+ mcpUrl = args[i + 1];
22
+ i += 1;
23
+ continue;
21
24
  }
25
+ if ((arg === '--api-key' || arg === '-api-key') && args[i + 1]) {
26
+ apiKey = args[i + 1];
27
+ i += 1;
28
+ continue;
29
+ }
30
+ if (!arg.startsWith('--') && !apiKey) {
31
+ apiKey = arg;
32
+ }
33
+ }
34
+
35
+ return { apiKey, mcpUrl, showHelp };
22
36
  }
23
37
 
24
- if (!apiKey) {
25
- console.error("❌ Error: API Key is required.");
26
- console.error("Usage: npx @ecommaps/mcp <YOUR_API_KEY>");
27
- process.exit(1);
38
+ function printUsageAndExit() {
39
+ console.error('Usage: npx @ecommaps/mcp <YOUR_API_KEY> [--url https://api.ecommaps.com/api/v1/mcp]');
40
+ console.error(' npx @ecommaps/mcp --api-key <YOUR_API_KEY> [--url <MCP_URL>]');
41
+ console.error('');
42
+ console.error('Environment variables:');
43
+ console.error(' MCP_BEARER_TOKEN Preferred auth token');
44
+ console.error(' MCP_API_KEY Backward-compatible auth token');
45
+ console.error(' ECOMMAPS_MCP_URL Default MCP endpoint URL');
46
+ process.exit(0);
28
47
  }
29
48
 
30
- const SSE_URL = DEFAULT_SSE_URL;
31
- const POST_URL = SSE_URL.replace('/sse', '/messages'); // Default, might be updated by endpoint event
49
+ function normalizeResponsePayload(rawBody, contentType) {
50
+ if (!rawBody) return null;
51
+
52
+ if (contentType && contentType.includes('text/event-stream')) {
53
+ // Streamable HTTP can return SSE; convert first data frame to JSON object if possible.
54
+ const lines = rawBody.split('\n');
55
+ for (const line of lines) {
56
+ if (!line.startsWith('data:')) continue;
57
+ const value = line.slice(5).trim();
58
+ if (!value || value === '[DONE]') continue;
59
+ try {
60
+ return JSON.parse(value);
61
+ } catch (_) {
62
+ return value;
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+
68
+ try {
69
+ return JSON.parse(rawBody);
70
+ } catch (_) {
71
+ return rawBody;
72
+ }
73
+ }
32
74
 
33
- console.error(`🔌 EcoBaseAI MCP Client (Native) Connecting...`);
34
- console.error(` Target: ${SSE_URL}`);
75
+ async function postRpc(mcpUrl, apiKey, rpcPayload) {
76
+ const response = await fetch(mcpUrl, {
77
+ method: 'POST',
78
+ headers: {
79
+ Authorization: `Bearer ${apiKey}`,
80
+ 'Content-Type': 'application/json',
81
+ Accept: 'application/json, text/event-stream',
82
+ 'MCP-Protocol-Version': '2025-06-18',
83
+ },
84
+ body: JSON.stringify(rpcPayload),
85
+ });
86
+
87
+ const rawBody = await response.text();
88
+ const contentType = response.headers.get('content-type') || '';
89
+
90
+ if (!response.ok) {
91
+ throw new Error(`MCP HTTP ${response.status}: ${rawBody}`);
92
+ }
93
+
94
+ const payload = normalizeResponsePayload(rawBody, contentType);
95
+ if (payload === null) {
96
+ throw new Error('MCP response was empty.');
97
+ }
98
+
99
+ if (typeof payload === 'object' && payload && payload.error) {
100
+ throw new Error(`MCP JSON-RPC error: ${payload.error.message || 'Unknown error'}`);
101
+ }
102
+
103
+ return payload;
104
+ }
35
105
 
36
106
  async function main() {
37
- const headers = {
38
- 'Authorization': `Bearer ${apiKey}`,
39
- 'Accept': 'text/event-stream',
40
- 'Content-Type': 'application/json'
41
- };
42
-
43
- // 1. Start SSE Connection
44
- let controller = new AbortController();
45
-
46
- try {
47
- const response = await fetch(SSE_URL, {
48
- headers: headers,
49
- signal: controller.signal
50
- });
51
-
52
- if (!response.ok) {
53
- console.error(`❌ SSE Connection failed: ${response.status} ${response.statusText}`);
54
- process.exit(1);
107
+ const { apiKey, mcpUrl, showHelp } = parseCliArgs(process.argv);
108
+ if (showHelp) {
109
+ printUsageAndExit();
110
+ }
111
+ if (!apiKey) {
112
+ console.error('Error: API key is required.');
113
+ process.exit(1);
114
+ }
115
+
116
+ console.error('Ecommaps MCP stdio bridge started');
117
+ console.error(`Target: ${mcpUrl}`);
118
+
119
+ const rl = readline.createInterface({
120
+ input: process.stdin,
121
+ output: process.stderr,
122
+ terminal: false,
123
+ });
124
+
125
+ let queue = Promise.resolve();
126
+
127
+ rl.on('line', (line) => {
128
+ const trimmed = line.trim();
129
+ if (!trimmed) return;
130
+
131
+ queue = queue.then(async () => {
132
+ let payload;
133
+ try {
134
+ payload = JSON.parse(trimmed);
135
+ } catch (error) {
136
+ console.error(`Invalid JSON input: ${error.message}`);
137
+ return;
138
+ }
139
+
140
+ try {
141
+ // Some MCP clients may send params as [] or omit them for list methods.
142
+ // Ecommaps MCP expects params to be an object, so normalize empty variants.
143
+ if (payload && typeof payload === 'object') {
144
+ if (
145
+ payload.params == null ||
146
+ (Array.isArray(payload.params) && payload.params.length === 0)
147
+ ) {
148
+ payload.params = {};
149
+ }
55
150
  }
56
151
 
57
- // Handle SSE Stream
58
- const reader = response.body.getReader();
59
- const decoder = new TextDecoder();
60
- let buffer = '';
61
-
62
- // Process SSE in background
63
- (async () => {
64
- try {
65
- while (true) {
66
- const { done, value } = await reader.read();
67
- if (done) break;
68
-
69
- buffer += decoder.decode(value, { stream: true });
70
- const lines = buffer.split('\n');
71
- buffer = lines.pop(); // Keep incomplete line
72
-
73
- for (const line of lines) {
74
- if (line.startsWith('data: ')) {
75
- const data = line.slice(6).trim();
76
- if (data === '[DONE]') continue;
77
-
78
- try {
79
- // Try to parse as JSON-RPC
80
- const msg = JSON.parse(data);
81
-
82
- // FIX: Some clients (Antigravity/Cursor) fail if they receive 'notifications/initialized'
83
- // before they send their own 'initialize' handshake. The server sends this eagerly,
84
- // so we filter it out here to keep the client happy.
85
- if (msg.method === 'notifications/initialized') {
86
- return; // Skip this message
87
- }
88
-
89
- // Forward to StdOut
90
- console.log(JSON.stringify(msg));
91
- } catch (e) {
92
- // Not JSON (maybe endpoint url?)
93
- // console.error('Received non-JSON data:', data);
94
- }
95
- }
96
- if (line.startsWith('event: endpoint')) {
97
- // The next data line contains the endpoint.
98
- // Current simple parser might miss context, but our server sends data immediately after.
99
- }
100
- }
101
- }
102
- } catch (err) {
103
- console.error('❌ SSE Stream Error:', err);
104
- process.exit(1);
105
- }
106
- })();
107
-
108
- // 2. Setup Stdin Reader for POST requests
109
- const rl = readline.createInterface({
110
- input: process.stdin,
111
- output: process.stderr, // Important: Don't write prompts to stdout
112
- terminal: false
113
- });
114
-
115
- rl.on('line', async (line) => {
116
- if (!line.trim()) return;
117
-
118
- try {
119
- // Validate JSON
120
- const msg = JSON.parse(line);
121
-
122
- // Send POST
123
- const postResp = await fetch(POST_URL, {
124
- method: 'POST',
125
- headers: headers,
126
- body: JSON.stringify(msg)
127
- });
128
-
129
- if (!postResp.ok) {
130
- const text = await postResp.text();
131
- console.error(`❌ POST Error: ${postResp.status} - ${text}`);
132
- } else {
133
- // Some MCP servers return result in HTTP response
134
- const text = await postResp.text();
135
- if (text && postResp.headers.get('content-type')?.includes('application/json')) {
136
- console.log(text);
137
- }
138
- }
139
- } catch (e) {
140
- console.error('❌ Failed to process input line:', e);
141
- }
142
- });
143
-
144
- } catch (err) {
145
- console.error('❌ Connection Error:', err);
146
- process.exit(1);
147
- }
152
+ const result = await postRpc(mcpUrl, apiKey, payload);
153
+ process.stdout.write(`${JSON.stringify(result)}\n`);
154
+ } catch (error) {
155
+ const message = error && error.message ? error.message : String(error);
156
+ console.error(message);
157
+ // Best-effort JSON-RPC error response so MCP clients stay synchronized.
158
+ const id = payload && Object.prototype.hasOwnProperty.call(payload, 'id') ? payload.id : null;
159
+ process.stdout.write(
160
+ `${JSON.stringify({ jsonrpc: '2.0', id, error: { code: -32000, message } })}\n`
161
+ );
162
+ }
163
+ });
164
+ });
148
165
  }
149
166
 
150
- main();
167
+ main().catch((error) => {
168
+ console.error(error && error.message ? error.message : String(error));
169
+ process.exit(1);
170
+ });
package/package.json CHANGED
@@ -1,18 +1,15 @@
1
1
  {
2
2
  "name": "@ecommaps/mcp",
3
- "version": "1.0.6",
4
- "description": "EcoBaseAI Official MCP Client",
3
+ "version": "1.0.8",
4
+ "description": "Official Ecommaps MCP stdio bridge",
5
5
  "bin": {
6
- "mcp": "./cli.js"
6
+ "mcp": "cli.js"
7
7
  },
8
8
  "files": [
9
9
  "cli.js",
10
10
  "README.md"
11
11
  ],
12
12
  "main": "cli.js",
13
- "dependencies": {
14
- "minimist": "^1.2.8"
15
- },
16
13
  "keywords": [
17
14
  "mcp",
18
15
  "ai",
@@ -20,4 +17,4 @@
20
17
  ],
21
18
  "author": "EcoBaseAI",
22
19
  "license": "ISC"
23
- }
20
+ }