@ecommaps/mcp 1.0.6 → 1.0.7

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 +63 -21
  2. package/cli.js +142 -134
  3. package/package.json +4 -7
package/README.md CHANGED
@@ -1,34 +1,76 @@
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 directly with your store key:
6
11
 
7
12
  ```bash
8
- npm install -g @ecommaps/mcp
13
+ npx @ecommaps/mcp <YOUR_STORE_MCP_KEY>
9
14
  ```
10
15
 
11
- ## Usage
16
+ Use a custom endpoint (production or local):
17
+
18
+ ```bash
19
+ npx @ecommaps/mcp <YOUR_STORE_MCP_KEY> --url https://api.ecommaps.com/api/v1/mcp
20
+ ```
12
21
 
13
22
  ```bash
14
- npx @ecommaps/mcp <YOUR_API_KEY>
23
+ npx @ecommaps/mcp <YOUR_STORE_MCP_KEY> --url http://127.0.0.1:8001/api/v1/mcp
24
+ ```
25
+
26
+ ## CLI Options
27
+
28
+ - `--url <MCP_URL>`: Override the MCP endpoint URL.
29
+ - `--api-key <KEY>`: Pass key explicitly instead of positional arg.
30
+ - `--help`: Show usage.
31
+
32
+ ## Environment Variables
33
+
34
+ - `MCP_BEARER_TOKEN`: Preferred token source.
35
+ - `MCP_API_KEY`: Backward-compatible token source.
36
+ - `ECOMMAPS_MCP_URL`: Default endpoint if `--url` is not provided.
37
+
38
+ ## Editor Configuration (Generic MCP stdio)
39
+
40
+ Use the following shape in any editor that supports stdio MCP servers:
41
+
42
+ ```json
43
+ {
44
+ "command": "npx",
45
+ "args": [
46
+ "@ecommaps/mcp",
47
+ "sk_eco_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
48
+ "--url",
49
+ "https://api.ecommaps.com/api/v1/mcp"
50
+ ]
51
+ }
15
52
  ```
16
53
 
17
- ## Development & Publishing
54
+ Or with env var:
18
55
 
19
- To publish this package to NPM:
56
+ ```json
57
+ {
58
+ "command": "npx",
59
+ "args": ["@ecommaps/mcp", "--url", "https://api.ecommaps.com/api/v1/mcp"],
60
+ "env": {
61
+ "MCP_BEARER_TOKEN": "sk_eco_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
62
+ }
63
+ }
64
+ ```
20
65
 
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
- ```
66
+ ## Transport Notes
33
67
 
34
- Once published, anyone can use `npx @ecommaps/mcp`!
68
+ - Recommended endpoint: `POST /api/v1/mcp`
69
+ - Legacy compatibility endpoints still exist during migration but are not recommended for new integrations.
70
+
71
+ ## Release
72
+
73
+ ```bash
74
+ cd packages/mcp-cli
75
+ npm publish --access public
76
+ ```
package/cli.js CHANGED
@@ -1,150 +1,158 @@
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 arg = args[i];
15
+ if (arg === '--help' || arg === '-h') {
16
+ showHelp = true;
17
+ continue;
18
+ }
19
+ if (arg === '--url' && args[i + 1]) {
20
+ mcpUrl = args[i + 1];
21
+ i += 1;
22
+ continue;
21
23
  }
24
+ if (arg === '--api-key' && args[i + 1]) {
25
+ apiKey = args[i + 1];
26
+ i += 1;
27
+ continue;
28
+ }
29
+ if (!arg.startsWith('--') && !apiKey) {
30
+ apiKey = arg;
31
+ }
32
+ }
33
+
34
+ return { apiKey, mcpUrl, showHelp };
22
35
  }
23
36
 
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);
37
+ function printUsageAndExit() {
38
+ console.error('Usage: npx @ecommaps/mcp <YOUR_API_KEY> [--url https://api.ecommaps.com/api/v1/mcp]');
39
+ console.error(' npx @ecommaps/mcp --api-key <YOUR_API_KEY> [--url <MCP_URL>]');
40
+ console.error('');
41
+ console.error('Environment variables:');
42
+ console.error(' MCP_BEARER_TOKEN Preferred auth token');
43
+ console.error(' MCP_API_KEY Backward-compatible auth token');
44
+ console.error(' ECOMMAPS_MCP_URL Default MCP endpoint URL');
45
+ process.exit(0);
28
46
  }
29
47
 
30
- const SSE_URL = DEFAULT_SSE_URL;
31
- const POST_URL = SSE_URL.replace('/sse', '/messages'); // Default, might be updated by endpoint event
48
+ function normalizeResponsePayload(rawBody, contentType) {
49
+ if (!rawBody) return null;
50
+
51
+ if (contentType && contentType.includes('text/event-stream')) {
52
+ // Streamable HTTP can return SSE; convert first data frame to JSON object if possible.
53
+ const lines = rawBody.split('\n');
54
+ for (const line of lines) {
55
+ if (!line.startsWith('data:')) continue;
56
+ const value = line.slice(5).trim();
57
+ if (!value || value === '[DONE]') continue;
58
+ try {
59
+ return JSON.parse(value);
60
+ } catch (_) {
61
+ return value;
62
+ }
63
+ }
64
+ return null;
65
+ }
66
+
67
+ try {
68
+ return JSON.parse(rawBody);
69
+ } catch (_) {
70
+ return rawBody;
71
+ }
72
+ }
32
73
 
33
- console.error(`🔌 EcoBaseAI MCP Client (Native) Connecting...`);
34
- console.error(` Target: ${SSE_URL}`);
74
+ async function postRpc(mcpUrl, apiKey, rpcPayload) {
75
+ const response = await fetch(mcpUrl, {
76
+ method: 'POST',
77
+ headers: {
78
+ Authorization: `Bearer ${apiKey}`,
79
+ 'Content-Type': 'application/json',
80
+ Accept: 'application/json, text/event-stream',
81
+ 'MCP-Protocol-Version': '2025-06-18',
82
+ },
83
+ body: JSON.stringify(rpcPayload),
84
+ });
85
+
86
+ const rawBody = await response.text();
87
+ const contentType = response.headers.get('content-type') || '';
88
+
89
+ if (!response.ok) {
90
+ throw new Error(`MCP HTTP ${response.status}: ${rawBody}`);
91
+ }
92
+
93
+ const payload = normalizeResponsePayload(rawBody, contentType);
94
+ if (payload === null) {
95
+ throw new Error('MCP response was empty.');
96
+ }
97
+
98
+ if (typeof payload === 'object' && payload && payload.error) {
99
+ throw new Error(`MCP JSON-RPC error: ${payload.error.message || 'Unknown error'}`);
100
+ }
101
+
102
+ return payload;
103
+ }
35
104
 
36
105
  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);
55
- }
56
-
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
- }
106
+ const { apiKey, mcpUrl, showHelp } = parseCliArgs(process.argv);
107
+ if (showHelp) {
108
+ printUsageAndExit();
109
+ }
110
+ if (!apiKey) {
111
+ console.error('Error: API key is required.');
112
+ process.exit(1);
113
+ }
114
+
115
+ console.error('Ecommaps MCP stdio bridge started');
116
+ console.error(`Target: ${mcpUrl}`);
117
+
118
+ const rl = readline.createInterface({
119
+ input: process.stdin,
120
+ output: process.stderr,
121
+ terminal: false,
122
+ });
123
+
124
+ let queue = Promise.resolve();
125
+
126
+ rl.on('line', (line) => {
127
+ const trimmed = line.trim();
128
+ if (!trimmed) return;
129
+
130
+ queue = queue.then(async () => {
131
+ let payload;
132
+ try {
133
+ payload = JSON.parse(trimmed);
134
+ } catch (error) {
135
+ console.error(`Invalid JSON input: ${error.message}`);
136
+ return;
137
+ }
138
+
139
+ try {
140
+ const result = await postRpc(mcpUrl, apiKey, payload);
141
+ process.stdout.write(`${JSON.stringify(result)}\n`);
142
+ } catch (error) {
143
+ const message = error && error.message ? error.message : String(error);
144
+ console.error(message);
145
+ // Best-effort JSON-RPC error response so MCP clients stay synchronized.
146
+ const id = payload && Object.prototype.hasOwnProperty.call(payload, 'id') ? payload.id : null;
147
+ process.stdout.write(
148
+ `${JSON.stringify({ jsonrpc: '2.0', id, error: { code: -32000, message } })}\n`
149
+ );
150
+ }
151
+ });
152
+ });
148
153
  }
149
154
 
150
- main();
155
+ main().catch((error) => {
156
+ console.error(error && error.message ? error.message : String(error));
157
+ process.exit(1);
158
+ });
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.7",
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
+ }