diffler-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 +66 -0
- package/index.js +108 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# diffler-mcp
|
|
2
|
+
|
|
3
|
+
A tiny stdio↔HTTP bridge that lets Claude Code (or any stdio MCP client) talk to
|
|
4
|
+
the MCP server embedded in a running [diffler](https://github.com/matheusfillipe/diffler)
|
|
5
|
+
review session.
|
|
6
|
+
|
|
7
|
+
diffler's MCP server runs **inside the TUI** as a streamable-HTTP endpoint
|
|
8
|
+
(`http://127.0.0.1:8417/mcp` by default) because it serves the live review state
|
|
9
|
+
on the app's main loop. This proxy is spawned by Claude over stdio and forwards
|
|
10
|
+
every tool call to that endpoint — it owns no state itself.
|
|
11
|
+
|
|
12
|
+
## Use it with Claude Code
|
|
13
|
+
|
|
14
|
+
Run diffler in your repo (it prints the connect hint and writes
|
|
15
|
+
`.diffler/mcp.json` with the live port), then:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
claude mcp add diffler -- npx -y diffler-mcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or in a checked-in `.mcp.json`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"diffler": {
|
|
27
|
+
"command": "npx",
|
|
28
|
+
"args": ["-y", "diffler-mcp"]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Run Claude from the repo root and the proxy auto-discovers the port from
|
|
35
|
+
`.diffler/mcp.json`. No diffler running ⇒ the proxy exits with a clear error.
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
Resolution order (first match wins):
|
|
40
|
+
|
|
41
|
+
1. `--url <url>` / `DIFFLER_MCP_URL` — full endpoint, e.g. `http://127.0.0.1:8417/mcp`
|
|
42
|
+
2. `--port <n>` / `DIFFLER_MCP_PORT` and `--host <h>` / `DIFFLER_MCP_HOST`
|
|
43
|
+
3. the live port in `<repo>/.diffler/mcp.json` (`--repo <path>`, default: cwd)
|
|
44
|
+
4. `http://127.0.0.1:8417/mcp`
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"diffler": {
|
|
50
|
+
"command": "npx",
|
|
51
|
+
"args": ["-y", "diffler-mcp", "--port", "8417"],
|
|
52
|
+
"env": { "DIFFLER_MCP_HOST": "127.0.0.1" }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Prefer HTTP directly?
|
|
59
|
+
|
|
60
|
+
Claude Code speaks HTTP natively, so you can skip this proxy entirely:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
claude mcp add --transport http diffler http://127.0.0.1:8417/mcp
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The proxy exists for the `npx`, zero-config, auto-port-discovery ergonomics.
|
package/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// stdio↔HTTP MCP proxy: Claude Code spawns this over stdio, and it forwards
|
|
3
|
+
// every tool call to the streamable-HTTP MCP server embedded in a running
|
|
4
|
+
// diffler TUI. diffler's MCP serves the live review state, so the proxy never
|
|
5
|
+
// owns state — it only bridges transports.
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
11
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
12
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
13
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
|
+
import {
|
|
15
|
+
CallToolRequestSchema,
|
|
16
|
+
ListToolsRequestSchema,
|
|
17
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
18
|
+
|
|
19
|
+
const DEFAULT_HOST = "127.0.0.1";
|
|
20
|
+
const DEFAULT_PORT = 8417;
|
|
21
|
+
|
|
22
|
+
function parseArgs(argv) {
|
|
23
|
+
const opts = {};
|
|
24
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
25
|
+
const arg = argv[i];
|
|
26
|
+
const take = () => argv[(i += 1)];
|
|
27
|
+
switch (arg) {
|
|
28
|
+
case "--url":
|
|
29
|
+
opts.url = take();
|
|
30
|
+
break;
|
|
31
|
+
case "--host":
|
|
32
|
+
opts.host = take();
|
|
33
|
+
break;
|
|
34
|
+
case "--port":
|
|
35
|
+
opts.port = take();
|
|
36
|
+
break;
|
|
37
|
+
case "--repo":
|
|
38
|
+
opts.repo = take();
|
|
39
|
+
break;
|
|
40
|
+
default:
|
|
41
|
+
// unknown flags are ignored so future diffler args don't break older proxies
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return opts;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The port may differ from the configured one after an ephemeral fallback, so
|
|
49
|
+
// diffler publishes the live endpoint to .diffler/mcp.json in the repo.
|
|
50
|
+
function discoverPort(repo) {
|
|
51
|
+
try {
|
|
52
|
+
const raw = readFileSync(join(repo, ".diffler", "mcp.json"), "utf8");
|
|
53
|
+
const port = JSON.parse(raw).port;
|
|
54
|
+
return typeof port === "number" ? port : undefined;
|
|
55
|
+
} catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveUrl() {
|
|
61
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
62
|
+
const env = process.env;
|
|
63
|
+
const explicit = opts.url || env.DIFFLER_MCP_URL;
|
|
64
|
+
if (explicit) {
|
|
65
|
+
return explicit;
|
|
66
|
+
}
|
|
67
|
+
const host = opts.host || env.DIFFLER_MCP_HOST || DEFAULT_HOST;
|
|
68
|
+
const repo = opts.repo || process.cwd();
|
|
69
|
+
const port =
|
|
70
|
+
opts.port || env.DIFFLER_MCP_PORT || discoverPort(repo) || DEFAULT_PORT;
|
|
71
|
+
return `http://${host}:${port}/mcp`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function main() {
|
|
75
|
+
const url = resolveUrl();
|
|
76
|
+
|
|
77
|
+
const client = new Client({ name: "diffler-mcp-proxy", version: "0.1.0" });
|
|
78
|
+
try {
|
|
79
|
+
await client.connect(new StreamableHTTPClientTransport(new URL(url)));
|
|
80
|
+
} catch (err) {
|
|
81
|
+
process.stderr.write(
|
|
82
|
+
`diffler-mcp: cannot reach a diffler MCP server at ${url}\n` +
|
|
83
|
+
`Is diffler running in this repo? (${err.message ?? err})\n`,
|
|
84
|
+
);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const server = new Server(
|
|
89
|
+
{ name: "diffler", version: "0.1.0" },
|
|
90
|
+
{ capabilities: { tools: {} } },
|
|
91
|
+
);
|
|
92
|
+
server.setRequestHandler(ListToolsRequestSchema, () => client.listTools());
|
|
93
|
+
server.setRequestHandler(CallToolRequestSchema, (request) =>
|
|
94
|
+
client.callTool(request.params),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// when diffler exits the HTTP side drops; tear down so Claude sees the close
|
|
98
|
+
client.onclose = () => {
|
|
99
|
+
void server.close().finally(() => process.exit(0));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
await server.connect(new StdioServerTransport());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
main().catch((err) => {
|
|
106
|
+
process.stderr.write(`diffler-mcp: ${err.stack ?? err}\n`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "diffler-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "stdio↔HTTP MCP proxy bridging Claude Code (or any stdio MCP client) to a running diffler review session",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"diffler-mcp": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT OR Apache-2.0",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/matheusfillipe/diffler.git",
|
|
20
|
+
"directory": "npm/diffler-mcp"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"diffler",
|
|
26
|
+
"code-review",
|
|
27
|
+
"claude"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"prepublishOnly": "node --check index.js"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|