Skip to content

Commit eed5c34

Browse files
authored
Cleanup and refactor (#4)
* do not push examples to module * install dev pkgs on CI * move examples * update files as examples are moved * Update README * Update README.md Add note about npx. * add dev deps * Exit bridge when Claude Desktop exits (stdin closed) * cosmetics * Add debug for the endpoint event from SSE server
1 parent 60ee035 commit eed5c34

File tree

10 files changed

+383
-133
lines changed

10 files changed

+383
-133
lines changed

.github/workflows/ci.yml

+9-9
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,25 @@ name: CI
22

33
on:
44
push:
5-
branches: [ main ]
5+
branches: [main]
66
pull_request:
7-
branches: [ main ]
7+
branches: [main]
88

99
jobs:
1010
build:
1111
runs-on: ubuntu-latest
12-
12+
1313
steps:
1414
- uses: actions/checkout@v4
15-
15+
1616
- name: Setup Node.js
1717
uses: actions/setup-node@v4
1818
with:
19-
node-version: '22.x'
20-
cache: 'yarn'
21-
19+
node-version: "22.x"
20+
cache: "yarn"
21+
2222
- name: Install dependencies
2323
run: yarn install
24-
24+
2525
- name: Build
26-
run: yarn build
26+
run: yarn build

.github/workflows/npm-publish.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ jobs:
1111
- uses: actions/checkout@v4
1212
- uses: actions/setup-node@v4
1313
with:
14-
node-version: '22.x'
15-
registry-url: 'https://registry.npmjs.org'
16-
- run: yarn install
14+
node-version: "22.x"
15+
registry-url: "https://registry.npmjs.org"
16+
- run: yarn install --production
1717
- run: yarn build
1818
- run: yarn publish
1919
env:
20-
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
20+
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
node_modules/
22
build/
3+
examples/*.js
34
*.log
45
.env*
56
test.duckdb

README.md

+11-7
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ You can install the package globally or use it directly with npx:
1111
npm install -g mcp-server-and-gw
1212

1313
# Using npx (no installation required)
14-
npx claude_gateway http://localhost:9999
14+
npx claude_gateway http://localhost:8808/
1515

1616
# If installed globally
17-
claude_gateway http://localhost:9999
17+
claude_gateway http://localhost:8808/
1818

1919
# Using environment variables
20-
MCP_HOST=localhost MCP_PORT=9999 npx claude_gateway
20+
MCP_HOST=localhost MCP_PORT=8808 npx claude_gateway
2121
```
2222

2323
## Add Configuration into Claude
@@ -38,26 +38,30 @@ echo '{
3838
"Claude Gateway Example": {
3939
"command": "/opt/homebrew/bin/npx",
4040
"args": [
41-
"claude_gateway", "http://localhost:9999/"
41+
"claude_gateway", "http://localhost:8808/"
4242
]
4343
}
4444
}
4545
}' > ~/Library/Application\ Support/Claude/claude_desktop_config.json
4646

4747
## 3. Start server so that claude can connect to it for discoverying its resources, tools, etc.
48-
node build/server.js
48+
PORT=8808 node examples/server.js
4949

5050
## 4. Start Claude Desktop
5151
```
5252

5353
## Example Server and Client
5454

55+
```shell
56+
yarn install @types/express @types/json-bigint @types/node duckdb express json-bigint
57+
```
58+
5559
You can also develop the SSE server independently from Claude Desktop so you get faster iterations. For example, run the `src/server.ts` and use the `src/client.ts` as the client.
5660

5761
Start server, once you start the client on another terminal, you see the server output.
5862

5963
```shell
60-
% node build/server.js
64+
% node examples/server.js
6165
Server is running on port 8808
6266

6367
--> Received connection: /sse
@@ -96,7 +100,7 @@ New SSE connection.
96100
Start the client
97101
98102
```shell
99-
% node build/client.js
103+
% node examples/client.js
100104
Connecting...
101105
Connected: { resources: {}, tools: {}, templates: {} }
102106
{ resources: [] }

src/client.ts examples/client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2-
import eventsource from "eventsource";
2+
import * as eventsource from "eventsource";
33
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
44
import {
55
CallToolResultSchema,

src/duckdb.ts examples/duckdb.ts

File renamed without changes.

src/server.ts examples/server.ts

File renamed without changes.

package.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,17 @@
1919
"release:major": "npm version major && git push && git push --tags"
2020
},
2121
"dependencies": {
22-
"@modelcontextprotocol/sdk": "0.6.0",
23-
"@types/node": "22",
24-
"duckdb": "^1.1.3",
25-
"eventsource": "^2.0.2",
26-
"express": "^4.21.1",
27-
"json-bigint": "^1.0.0"
22+
"@modelcontextprotocol/sdk": "1.8.0",
23+
"eventsource": "^3.0.6"
2824
},
2925
"devDependencies": {
3026
"@types/eventsource": "^1.1.15",
31-
"@types/express": "^5.0.0",
27+
"@types/express": "^5.0.1",
3228
"@types/json-bigint": "^1.0.4",
29+
"@types/node": "^22.13.14",
30+
"duckdb": "^1.2.1",
31+
"express": "^4.21.2",
32+
"json-bigint": "^1.0.0",
3333
"typescript": "^5.3.3"
3434
}
3535
}

src/claude_gateway.ts

+21-11
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
#!/usr/bin/env node
2-
import EventSource from "eventsource";
2+
import { EventSource } from "eventsource";
33

44
// Get the backend URL from command line arg or environment variables
55
let baseUrl: string;
66

77
if (process.argv.length > 2) {
88
baseUrl = process.argv[2];
9-
if (!baseUrl.startsWith('http://') && !baseUrl.startsWith('https://')) {
9+
if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) {
1010
baseUrl = `http://${baseUrl}`;
1111
}
1212
// Remove trailing slash if present
13-
if (baseUrl.endsWith('/')) {
13+
if (baseUrl.endsWith("/")) {
1414
baseUrl = baseUrl.slice(0, -1);
1515
}
1616
} else {
@@ -20,7 +20,7 @@ if (process.argv.length > 2) {
2020
}
2121

2222
const backendUrlSse = `${baseUrl}/sse`;
23-
const backendUrlMsg = `${baseUrl}/message`;
23+
let backendUrlMsg = `${baseUrl}/message`;
2424

2525
const debug = console.error; // With stdio transport stderr is the only channel for debugging
2626
const respond = console.log; // Message back to Claude Desktop App.
@@ -35,14 +35,19 @@ const respond = console.log; // Message back to Claude Desktop App.
3535

3636
// 1. Establish persistent MCP server SSE connection and forward received messages to stdin
3737
function connectSSEBackend() {
38-
return new Promise((resolve, reject) => {
38+
return new Promise<void>((resolve, reject) => {
39+
const timer = setTimeout(() => reject(new Error("SSE Backend Connection timeout")), 10_000);
3940
const source = new EventSource(backendUrlSse);
40-
source.onopen = (evt: MessageEvent) => resolve(evt);
41-
source.addEventListener("error", (__e) => reject(__e));
42-
source.addEventListener("open", (___e) => debug(`--- SSE backend connected`));
43-
source.addEventListener("error", (__e) => debug(`--- SSE backend disc./error: ${(<any>__e)?.message}`));
44-
source.addEventListener("message", (e) => debug(`<-- ${e.data}`));
41+
source.onopen = (evt: Event) => resolve(clearTimeout(timer));
42+
source.addEventListener("endpoint", (e) => {
43+
backendUrlMsg = `${baseUrl}${e.data}`;
44+
debug(`--- SSE backend sent "endpoint" event (${e.data}) ==> Setting message endpoint URL: "${backendUrlMsg}"`);
45+
});
46+
source.addEventListener("error", (e) => reject(e));
4547
source.addEventListener("message", (e) => respond(e.data)); // forward to Claude Desktop App via stdio transport
48+
source.addEventListener("message", (e) => debug(`<-- ${e.data}`));
49+
source.addEventListener("open", (e) => debug(`--- SSE backend connected`));
50+
source.addEventListener("error", (e) => debug(`--- SSE backend disc./error: ${(<any>e)?.message}`));
4651
});
4752
}
4853
@@ -51,13 +56,18 @@ async function processMessage(inp: Buffer) {
5156
const msg = inp.toString();
5257
debug("-->", msg.trim());
5358
const [method, body, headers] = ["POST", msg, { "Content-Type": "application/json" }];
54-
await fetch(new URL(backendUrlMsg), { method, body, headers }).catch((e) => debug("fetch error:", e));
59+
const resp = await fetch(new URL(backendUrlMsg), { method, body, headers }).catch((e) => debug("fetch error:", e));
60+
if (resp && !resp?.ok) debug(`HTTP error: ${resp.status} ${resp.statusText}`);
5561
}
5662

5763
async function runBridge() {
5864
debug(`-- Connecting to MCP server at ${baseUrl}`);
5965
await connectSSEBackend();
6066
process.stdin.on("data", processMessage);
67+
process.stdin.on("end", () => {
68+
debug("-- stdin disconnected, exiting");
69+
process.exit(0);
70+
});
6171
debug(`-- MCP stdio to SSE gateway running - connected to ${baseUrl}`);
6272
}
6373

0 commit comments

Comments
 (0)