@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.
- package/README.md +58 -22
- package/cli.js +152 -132
- package/package.json +4 -7
package/README.md
CHANGED
|
@@ -1,34 +1,70 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ecommaps/mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official Ecommaps MCP **stdio bridge**.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
13
|
+
npx @ecommaps/mcp <YOUR_STORE_MCP_KEY> --url https://api.ecommaps.com/api/v1/mcp
|
|
9
14
|
```
|
|
10
15
|
|
|
11
|
-
##
|
|
16
|
+
## CLI Options
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
## Transport Notes
|
|
20
61
|
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
const args =
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
//
|
|
63
|
-
(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"description": "Official Ecommaps MCP stdio bridge",
|
|
5
5
|
"bin": {
|
|
6
|
-
"mcp": "
|
|
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
|
+
}
|