@flarewatch/mcp 0.1.0
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 +87 -0
- package/dist/bridge.js +145 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @flarewatch/mcp
|
|
2
|
+
|
|
3
|
+
stdio→HTTPS bridge for the FlareWatch MCP server.
|
|
4
|
+
|
|
5
|
+
Lets stdio-only MCP clients (Claude Desktop, Cursor, Continue) reach
|
|
6
|
+
the remote endpoint at `https://mcp.flarewatch.io/api/mcp` without
|
|
7
|
+
each client having to implement the Streamable HTTP transport.
|
|
8
|
+
|
|
9
|
+
## Install / use
|
|
10
|
+
|
|
11
|
+
No install needed — `npx` will fetch on demand:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx -y @flarewatch/mcp
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Claude Desktop
|
|
18
|
+
|
|
19
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"flarewatch": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["-y", "@flarewatch/mcp"]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Restart Claude Desktop. The 13 FlareWatch tools (validators, FTSO
|
|
33
|
+
providers, scoring methodology, agent transparency, etc.) become
|
|
34
|
+
available in every conversation.
|
|
35
|
+
|
|
36
|
+
### Cursor
|
|
37
|
+
|
|
38
|
+
Cursor supports Streamable HTTP natively — you don't need this bridge.
|
|
39
|
+
Configure directly:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"url": "https://mcp.flarewatch.io/api/mcp"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Continue.dev
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"experimental": {
|
|
52
|
+
"modelContextProtocolServers": [
|
|
53
|
+
{
|
|
54
|
+
"transport": { "type": "stdio", "command": "npx", "args": ["-y", "@flarewatch/mcp"] }
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Environment overrides
|
|
62
|
+
|
|
63
|
+
| Variable | Default | Purpose |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `FLAREWATCH_MCP_URL` | `https://mcp.flarewatch.io/api/mcp` | Endpoint to forward to. Useful for preview deploys or local dev. |
|
|
66
|
+
| `FLAREWATCH_MCP_TIMEOUT_MS` | `30000` | Per-request HTTP timeout in ms. |
|
|
67
|
+
|
|
68
|
+
## What this bridge does NOT do
|
|
69
|
+
|
|
70
|
+
It's intentionally dumb. ~150 LoC. The server is the single source of
|
|
71
|
+
truth for:
|
|
72
|
+
|
|
73
|
+
- Tool definitions
|
|
74
|
+
- Resource content
|
|
75
|
+
- Rate limiting
|
|
76
|
+
- Quarantine / honeypot / classifier walls
|
|
77
|
+
- Response signing
|
|
78
|
+
|
|
79
|
+
The bridge just forwards JSON-RPC frames between stdio and HTTPS. No
|
|
80
|
+
caching, no retries, no fallback. If the remote endpoint is down, the
|
|
81
|
+
bridge surfaces the upstream error rather than attempting recovery.
|
|
82
|
+
|
|
83
|
+
## Verification
|
|
84
|
+
|
|
85
|
+
Every tool response carries a `verification_signature` HMAC. To verify
|
|
86
|
+
a citation came from this server, call the `verify_response` tool with
|
|
87
|
+
the cited payload + signature.
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @flarewatch/mcp — stdio↔HTTPS bridge for the FlareWatch MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Speaks MCP-over-stdio on its standard input/output and forwards
|
|
6
|
+
* every JSON-RPC request to the remote endpoint at
|
|
7
|
+
* https://mcp.flarewatch.io/api/mcp. Lets stdio-only MCP clients
|
|
8
|
+
* (Claude Desktop, Cursor's filesystem-MCP integration, Continue's
|
|
9
|
+
* stdio transport, etc.) reach the remote server without each client
|
|
10
|
+
* having to implement Streamable HTTP.
|
|
11
|
+
*
|
|
12
|
+
* ~150 LoC of dumb forwarding. NOT a re-implementation of tools or
|
|
13
|
+
* resources — the server is the single source of truth.
|
|
14
|
+
*
|
|
15
|
+
* Environment overrides:
|
|
16
|
+
* FLAREWATCH_MCP_URL — point at a non-default endpoint (preview /
|
|
17
|
+
* local dev). Defaults to https://mcp.flarewatch.io/api/mcp.
|
|
18
|
+
* FLAREWATCH_MCP_TIMEOUT_MS — per-request HTTP timeout (default 30000).
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* npx @flarewatch/mcp
|
|
22
|
+
*
|
|
23
|
+
* Claude Desktop config:
|
|
24
|
+
* {
|
|
25
|
+
* "mcpServers": {
|
|
26
|
+
* "flarewatch": {
|
|
27
|
+
* "command": "npx",
|
|
28
|
+
* "args": ["-y", "@flarewatch/mcp"]
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
33
|
+
import { createInterface } from "node:readline";
|
|
34
|
+
import { stdin, stdout, stderr } from "node:process";
|
|
35
|
+
const DEFAULT_URL = "https://mcp.flarewatch.io/api/mcp";
|
|
36
|
+
const URL = process.env.FLAREWATCH_MCP_URL ?? DEFAULT_URL;
|
|
37
|
+
const TIMEOUT_MS = Number(process.env.FLAREWATCH_MCP_TIMEOUT_MS ?? "30000");
|
|
38
|
+
const VERSION = "0.1.0";
|
|
39
|
+
function write(obj) {
|
|
40
|
+
stdout.write(JSON.stringify(obj) + "\n");
|
|
41
|
+
}
|
|
42
|
+
function logStderr(msg, extra) {
|
|
43
|
+
// Stderr only — the MCP client reads JSON from stdout, so any
|
|
44
|
+
// diagnostic we emit must go to stderr to avoid corrupting the
|
|
45
|
+
// wire protocol.
|
|
46
|
+
const line = extra
|
|
47
|
+
? `[flarewatch-mcp] ${msg} ${JSON.stringify(extra)}`
|
|
48
|
+
: `[flarewatch-mcp] ${msg}`;
|
|
49
|
+
stderr.write(line + "\n");
|
|
50
|
+
}
|
|
51
|
+
async function forward(req) {
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(URL, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: {
|
|
58
|
+
"Content-Type": "application/json",
|
|
59
|
+
"User-Agent": `flarewatch-mcp-bridge/${VERSION}`,
|
|
60
|
+
"MCP-Protocol-Version": "2025-06-18",
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify(req),
|
|
63
|
+
signal: controller.signal,
|
|
64
|
+
});
|
|
65
|
+
if (res.status === 204) {
|
|
66
|
+
// Notification reply — no body.
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
if (!res.ok) {
|
|
70
|
+
return {
|
|
71
|
+
jsonrpc: "2.0",
|
|
72
|
+
id: req.id ?? null,
|
|
73
|
+
error: {
|
|
74
|
+
code: -32603,
|
|
75
|
+
message: `bridge: upstream HTTP ${res.status}`,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const parsed = (await res.json());
|
|
80
|
+
if (Array.isArray(parsed)) {
|
|
81
|
+
// Batch — return only the first response that matches the id.
|
|
82
|
+
// stdio doesn't support batches well; the bridge accepts only
|
|
83
|
+
// single requests at a time, so this branch shouldn't fire in
|
|
84
|
+
// practice. Defensive return so we don't crash.
|
|
85
|
+
const match = parsed.find((r) => r.id === req.id);
|
|
86
|
+
return match ?? null;
|
|
87
|
+
}
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
return {
|
|
92
|
+
jsonrpc: "2.0",
|
|
93
|
+
id: req.id ?? null,
|
|
94
|
+
error: {
|
|
95
|
+
code: -32603,
|
|
96
|
+
message: `bridge: forward failed — ${e instanceof Error ? e.message : String(e)}`,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function main() {
|
|
105
|
+
logStderr(`starting; forwarding to ${URL}`);
|
|
106
|
+
const rl = createInterface({
|
|
107
|
+
input: stdin,
|
|
108
|
+
crlfDelay: Infinity,
|
|
109
|
+
});
|
|
110
|
+
for await (const line of rl) {
|
|
111
|
+
if (!line.trim())
|
|
112
|
+
continue;
|
|
113
|
+
let req;
|
|
114
|
+
try {
|
|
115
|
+
req = JSON.parse(line);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
write({
|
|
119
|
+
jsonrpc: "2.0",
|
|
120
|
+
id: null,
|
|
121
|
+
error: {
|
|
122
|
+
code: -32700,
|
|
123
|
+
message: `bridge: parse error — ${e instanceof Error ? e.message : String(e)}`,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const isNotification = req.id === undefined || req.id === null;
|
|
129
|
+
const response = await forward(req);
|
|
130
|
+
if (response === null) {
|
|
131
|
+
// Notification or 204 — no stdio output.
|
|
132
|
+
if (!isNotification) {
|
|
133
|
+
// Server returned 204 to a request that had an id — synthesize
|
|
134
|
+
// an empty success so the client doesn't hang forever.
|
|
135
|
+
write({ jsonrpc: "2.0", id: req.id ?? null, result: {} });
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
write(response);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
main().catch((e) => {
|
|
143
|
+
logStderr("fatal", { error: e instanceof Error ? e.message : String(e) });
|
|
144
|
+
process.exit(1);
|
|
145
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flarewatch/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "stdio→HTTPS bridge for the FlareWatch MCP server. Lets stdio-only MCP clients (Claude Desktop, Cursor, etc.) attach via `npx @flarewatch/mcp`.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"flarewatch-mcp": "dist/bridge.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/bridge.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"start": "node dist/bridge.js",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"prepublishOnly": "tsc"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^20",
|
|
26
|
+
"typescript": "^5"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/nbridges333/flarewatch.git",
|
|
31
|
+
"directory": "packages/mcp-bridge"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"mcp",
|
|
35
|
+
"model-context-protocol",
|
|
36
|
+
"flare",
|
|
37
|
+
"flarewatch",
|
|
38
|
+
"ftso",
|
|
39
|
+
"validator"
|
|
40
|
+
]
|
|
41
|
+
}
|