@etavioxy/wocingflow-mcp-server 0.0.1
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 -0
- package/bin/wocingflow-mcp-server.js +191 -0
- package/package.json +19 -0
- package/vendor/mcp_server.exe +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @etavioxy/wocingflow-mcp-server
|
|
2
|
+
|
|
3
|
+
WocingFlow MCP launcher package compatible with `install-mcp`.
|
|
4
|
+
|
|
5
|
+
## What this package does
|
|
6
|
+
|
|
7
|
+
- Starts WocingFlow backend `mcp_server` over HTTP.
|
|
8
|
+
- Bridges HTTP MCP to stdio using `mcp-remote@latest`.
|
|
9
|
+
- Exposes a bin command: `wocingflow-mcp-server`.
|
|
10
|
+
|
|
11
|
+
## Configure Your AI Assistant
|
|
12
|
+
|
|
13
|
+
Use `install-mcp` to add the server to your AI assistant:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx -y install-mcp @etavioxy/wocingflow-mcp-server --client cursor --yes
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Supported clients: `claude-code`, `cursor`, `windsurf`, `vscode`, `cline`, `roo-cline`, `claude`, `zed`, `goose`, `warp`, `codex`
|
|
20
|
+
|
|
21
|
+
### Claude Code
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx -y install-mcp @etavioxy/wocingflow-mcp-server --client claude-code --yes
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Cursor
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx -y install-mcp @etavioxy/wocingflow-mcp-server --client cursor --yes
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### VS Code / Copilot
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx -y install-mcp @etavioxy/wocingflow-mcp-server --client vscode --yes
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Windsurf
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx -y install-mcp @etavioxy/wocingflow-mcp-server --client windsurf --yes
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Cline
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx -y install-mcp @etavioxy/wocingflow-mcp-server --client cline --yes
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Restart your AI assistant after adding the configuration.
|
|
52
|
+
|
|
53
|
+
## Environment variables
|
|
54
|
+
|
|
55
|
+
- `WF_MCP_SERVER_BIN`: absolute path to `mcp_server` binary (optional)
|
|
56
|
+
- `WF_SERVICE_DATA_DIR`: absolute path to `service/data` (optional)
|
|
57
|
+
- `WF_MCP_HTTP_HOST`: default `127.0.0.1`
|
|
58
|
+
- `WF_MCP_HTTP_PORT`: optional fixed port (if unset, launcher auto-selects a free port)
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
const net = require("node:net");
|
|
5
|
+
const { spawn } = require("node:child_process");
|
|
6
|
+
|
|
7
|
+
function sleep(ms) {
|
|
8
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isWindows() {
|
|
12
|
+
return process.platform === "win32";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fileExists(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
return fs.existsSync(filePath);
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function candidateRoots() {
|
|
24
|
+
const cwd = process.cwd();
|
|
25
|
+
return [cwd, path.resolve(cwd, ".."), path.resolve(cwd, "..", "..")];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveServiceDataDir() {
|
|
29
|
+
if (process.env.WF_SERVICE_DATA_DIR && fileExists(process.env.WF_SERVICE_DATA_DIR)) {
|
|
30
|
+
return process.env.WF_SERVICE_DATA_DIR;
|
|
31
|
+
}
|
|
32
|
+
for (const root of candidateRoots()) {
|
|
33
|
+
const candidate = path.join(root, "service", "data");
|
|
34
|
+
if (fileExists(candidate)) {
|
|
35
|
+
return candidate;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveServerLaunch() {
|
|
42
|
+
if (process.env.WF_MCP_SERVER_BIN && fileExists(process.env.WF_MCP_SERVER_BIN)) {
|
|
43
|
+
return { command: process.env.WF_MCP_SERVER_BIN, args: [] };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const exeName = isWindows() ? "mcp_server.exe" : "mcp_server";
|
|
47
|
+
const bundledPath = path.join(__dirname, "..", "vendor", exeName);
|
|
48
|
+
if (fileExists(bundledPath)) {
|
|
49
|
+
return { command: bundledPath, args: [] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const root of candidateRoots()) {
|
|
53
|
+
const debugPath = path.join(root, "service", "target", "debug", exeName);
|
|
54
|
+
const releasePath = path.join(root, "service", "target", "release", exeName);
|
|
55
|
+
if (fileExists(debugPath)) return { command: debugPath, args: [] };
|
|
56
|
+
if (fileExists(releasePath)) return { command: releasePath, args: [] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const root of candidateRoots()) {
|
|
60
|
+
const manifestPath = path.join(root, "service", "Cargo.toml");
|
|
61
|
+
if (fileExists(manifestPath)) {
|
|
62
|
+
return {
|
|
63
|
+
command: "cargo",
|
|
64
|
+
args: ["run", "--manifest-path", manifestPath, "--bin", "mcp_server", "--"],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw new Error(
|
|
70
|
+
"Cannot locate wocingflow mcp_server. Build service/bin/mcp_server or set WF_MCP_SERVER_BIN."
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function resolvePort() {
|
|
75
|
+
if (process.env.WF_MCP_HTTP_PORT) {
|
|
76
|
+
return String(process.env.WF_MCP_HTTP_PORT);
|
|
77
|
+
}
|
|
78
|
+
return String(
|
|
79
|
+
await new Promise((resolve, reject) => {
|
|
80
|
+
const server = net.createServer();
|
|
81
|
+
server.listen(0, "127.0.0.1", () => {
|
|
82
|
+
const address = server.address();
|
|
83
|
+
if (!address || typeof address === "string") {
|
|
84
|
+
server.close();
|
|
85
|
+
reject(new Error("Failed to allocate free TCP port."));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const freePort = address.port;
|
|
89
|
+
server.close(() => resolve(freePort));
|
|
90
|
+
});
|
|
91
|
+
server.on("error", reject);
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function waitHealth(endpoint, timeoutMs) {
|
|
97
|
+
const started = Date.now();
|
|
98
|
+
while (Date.now() - started < timeoutMs) {
|
|
99
|
+
try {
|
|
100
|
+
const response = await fetch(`${endpoint}/health`);
|
|
101
|
+
if (response.ok) return;
|
|
102
|
+
} catch {
|
|
103
|
+
// keep waiting
|
|
104
|
+
}
|
|
105
|
+
await sleep(300);
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`mcp_server health check timeout: ${endpoint}/health`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function spawnBackend(command, args, env) {
|
|
111
|
+
const child = spawn(command, args, {
|
|
112
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
113
|
+
env,
|
|
114
|
+
shell: false,
|
|
115
|
+
windowsHide: true,
|
|
116
|
+
});
|
|
117
|
+
child.stdout.on("data", (chunk) => {
|
|
118
|
+
process.stderr.write(String(chunk));
|
|
119
|
+
});
|
|
120
|
+
child.stderr.on("data", (chunk) => {
|
|
121
|
+
process.stderr.write(String(chunk));
|
|
122
|
+
});
|
|
123
|
+
return child;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function spawnBridge(command, args, env) {
|
|
127
|
+
const child = spawn(command, args, {
|
|
128
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
129
|
+
env,
|
|
130
|
+
shell: false,
|
|
131
|
+
windowsHide: true,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
process.stdin.pipe(child.stdin);
|
|
135
|
+
child.stdout.pipe(process.stdout);
|
|
136
|
+
child.stderr.pipe(process.stderr);
|
|
137
|
+
return child;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function main() {
|
|
141
|
+
const host = process.env.WF_MCP_HTTP_HOST || "127.0.0.1";
|
|
142
|
+
const port = await resolvePort();
|
|
143
|
+
const endpoint = `http://${host}:${port}`;
|
|
144
|
+
const serviceDataDir = resolveServiceDataDir();
|
|
145
|
+
const launch = resolveServerLaunch();
|
|
146
|
+
|
|
147
|
+
const backendEnv = { ...process.env };
|
|
148
|
+
if (serviceDataDir) {
|
|
149
|
+
backendEnv.WF_SERVICE_DATA_DIR = serviceDataDir;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const backend = spawnBackend(
|
|
153
|
+
launch.command,
|
|
154
|
+
[...launch.args, "--host", host, "--port", String(port)],
|
|
155
|
+
backendEnv
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
let shuttingDown = false;
|
|
159
|
+
const shutdown = () => {
|
|
160
|
+
if (shuttingDown) return;
|
|
161
|
+
shuttingDown = true;
|
|
162
|
+
if (!backend.killed) {
|
|
163
|
+
backend.kill();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
process.on("SIGINT", shutdown);
|
|
168
|
+
process.on("SIGTERM", shutdown);
|
|
169
|
+
process.on("exit", shutdown);
|
|
170
|
+
|
|
171
|
+
backend.on("exit", (code) => {
|
|
172
|
+
if (!shuttingDown && code !== 0) {
|
|
173
|
+
console.error(`[wocingflow-mcp] backend exited early with code ${code}`);
|
|
174
|
+
process.exit(code || 1);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await waitHealth(endpoint, 20000);
|
|
179
|
+
|
|
180
|
+
const npxCmd = isWindows() ? "npx.cmd" : "npx";
|
|
181
|
+
const bridge = spawnBridge(npxCmd, ["-y", "mcp-remote@latest", endpoint], process.env);
|
|
182
|
+
bridge.on("exit", (code) => {
|
|
183
|
+
shutdown();
|
|
184
|
+
process.exit(code || 0);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
main().catch((error) => {
|
|
189
|
+
console.error(`[wocingflow-mcp] ${error.message}`);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@etavioxy/wocingflow-mcp-server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "MCP stdio launcher for WocingFlow service mcp_server",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wocingflow-mcp-server": "bin/wocingflow-mcp-server.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"prepack": "node scripts/prepack.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin",
|
|
17
|
+
"vendor"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
Binary file
|