Skip to content

Commit efbf645

Browse files
author
Daniele Briggi
committed
refact(mcp): portable sse transport and readme
1 parent 0da03b9 commit efbf645

File tree

8 files changed

+201
-24
lines changed

8 files changed

+201
-24
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
build/
3-
.vscode/
3+
.vscode/
4+
sqlitecloud-mcp-server-*.tgz

package-lock.json

Lines changed: 14 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
{
22
"name": "@sqlitecloud/mcp-server",
33
"version": "0.0.1",
4-
"description": "",
5-
"main": "index.ts",
4+
"description": "Model Context Protocol server for SQLite Cloud database",
5+
"author": "SQLite Cloud",
6+
"homepage": "https://sqlitecloud.ai",
7+
"repository": {
8+
"url": "https://github.com/sqlitecloud/sqlitecloud-mcp-server",
9+
"type": "git"
10+
},
11+
"bugs": "https://github.com/sqlitecloud/sqlitecloud-mcp-server/issues",
12+
"keywords": [],
13+
"main": "./build/index.js",
614
"scripts": {
715
"test": "echo \"Error: no test specified\" && exit 1",
8-
"build": "tsc && chmod 755 build/index.js"
16+
"build": "rm -rf build && tsc && chmod 755 build/index.js"
917
},
1018
"type": "module",
11-
"bin": {
12-
"weather": "./build/index.js"
13-
},
19+
"bin": "./build/index.js",
1420
"files": [
1521
"build"
1622
],
17-
"keywords": [],
18-
"author": "",
19-
"license": "ISC",
2023
"dependencies": {
2124
"@modelcontextprotocol/sdk": "^1.9.0",
2225
"@sqlitecloud/drivers": "^1.0.438",

readme.md

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ The MCP Server provides the following tools:
2727
To use the MCP Server, ensure you have a valid connection string for your SQLite Cloud database. The server can be started using the following command:
2828

2929
```bash
30-
node build/index.js --connectionString <your_connection_string>
30+
npx @sqlitecloud/mcp-server --connectionString <your_connection_string>
3131
```
3232

3333
Replace `<your_connection_string>` with the appropriate connection string for your SQLite Cloud project.
@@ -47,23 +47,82 @@ Replace `<your_connection_string>` with the appropriate connection string for yo
4747
"mcp": {
4848
"inputs": [],
4949
"servers": {
50+
// local (for development)
5051
"sqlitecloud-mcp-server": {
5152
"type": "stdio",
5253
"command": "node",
5354
"args": [
54-
"build/index.js",
55+
"./build/index.ts",
5556
"--connectionString",
56-
"<your_connection_string>"
57+
"<CONNECTION_STRING>"
5758
]
58-
}
59+
},
60+
// via package manager
61+
"sqlitecloud-mcp-server": {
62+
"type": "stdio",
63+
"command": "npx",
64+
"args": [
65+
"y"
66+
"@sqlitecloud/mcp-server",
67+
"--connectionString",
68+
"<CONNECTION_STRING>"
69+
]
70+
},
71+
// SSE
72+
"sqlitecloud-mcp-server-sse": {
73+
"type": "sse",
74+
"url": "<YOUR_NODE_ADDRESS>/v1/mcp/sse",
75+
"headers": {
76+
"Authorization": "Bearer <CONNECTION_STRING>"
77+
}
78+
},
5979
}
6080
}
6181
}
6282
```
6383

64-
# Build
84+
# Development
85+
86+
## Build
6587

6688

6789
```bash
6890
npm run build
69-
```
91+
```
92+
93+
## Run
94+
Build the package and then run it:
95+
96+
```bash
97+
node build/index.js --connectionString <CONNECTION_STRING>
98+
```
99+
100+
## Locally test the package
101+
102+
```bash
103+
npm pack
104+
```
105+
106+
Then:
107+
```bash
108+
npx <PACKAGE_FILENAME>.tgz --connectionString <CONNECTION_STRING>
109+
```
110+
111+
## Inspection
112+
Use the inspector to test both `stdio` and `sse` transports.
113+
First build the package then run:
114+
```bash
115+
npx npx @modelcontextprotocol/inspector@latest
116+
```
117+
and open it at the page: http://127.0.0.1:6274/
118+
119+
### Stdio Transport
120+
- **Transport type**: `stdio`
121+
- **Command**: `npx`
122+
- **Arguments**: `<PATH_TO_PACKAGE_FOLDER> --connectionString <CONNECTION_STRING>`
123+
124+
_Note: use the `PATH_TO_PACKAGE_FOLDER` from your home directory or you might get permission errors_
125+
126+
### SSE Transport
127+
To test `sse` transport to a remote or local server use
128+
**URL**: `http://localhost:8090/v1/mcp/sse`

src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
#!/usr/bin/env node
2+
13
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2-
import { parseArgs } from "node:util";
34
import { SQLiteCloudMcpServer } from "./server.js";
4-
import { SQLiteCloudMcpTransport } from "./transport.js";
5+
import { SQLiteCloudMcpTransport } from "./sqlitecloudTransport.js";
6+
import { parseArgs } from "util";
57

68
const server = new SQLiteCloudMcpServer();
79

810
async function main() {
11+
console.log("Starting SQLite Cloud MCP Server...");
912
const {
1013
values: { connectionString },
1114
} = parseArgs({

src/portableSseTransport.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { randomUUID } from 'node:crypto'
2+
import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'
3+
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
4+
import { JSONRPCMessage, JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js'
5+
6+
interface PortableWriter {
7+
write: (message: string) => Promise<void>
8+
close: () => void
9+
}
10+
11+
/**
12+
* Server transport for SSE to send messages over an SSE connection.
13+
*
14+
* This is a reimplementation of the `SSEServerTransport` class from `@modelcontextprotocol/sdk/server/see`
15+
* without the dependency with ExpressJS.
16+
*/
17+
export class PortableSSEServerTransport implements Transport {
18+
private _sseWriter?: PortableWriter
19+
private _sessionId: string
20+
21+
onclose?: () => void
22+
onerror?: (error: Error) => void
23+
onmessage?: (message: JSONRPCMessage, extra?: { authInfo?: AuthInfo }) => void
24+
25+
/**
26+
* Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`.
27+
*/
28+
constructor(
29+
private _endpoint: string,
30+
private writableStream: PortableWriter
31+
) {
32+
this._sessionId = randomUUID()
33+
}
34+
35+
/**
36+
* Handles the initial SSE connection request.
37+
*
38+
* This should be called when a GET request is made to establish the SSE stream.
39+
*/
40+
async start(): Promise<void> {
41+
if (this._sseWriter) {
42+
throw new Error('SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.')
43+
}
44+
45+
this._sseWriter = this.writableStream
46+
47+
// Send the endpoint event
48+
// Use a dummy base URL because this._endpoint is relative.
49+
// This allows using URL/URLSearchParams for robust parameter handling.
50+
const dummyBase = 'http://localhost' // Any valid base works
51+
const endpointUrl = new URL(this._endpoint, dummyBase)
52+
endpointUrl.searchParams.set('sessionId', this._sessionId)
53+
54+
// Reconstruct the relative URL string (pathname + search + hash)
55+
const relativeUrlWithSession = endpointUrl.pathname + endpointUrl.search + endpointUrl.hash
56+
57+
this._sseWriter?.write(`event: endpoint\ndata: ${relativeUrlWithSession}\n\n`)
58+
}
59+
60+
/**
61+
* Handle a client message, regardless of how it arrived. This can be used to inform the server of messages that arrive via a means different than HTTP POST.
62+
*/
63+
async handleMessage(message: unknown): Promise<void> {
64+
if (!this._sseWriter) {
65+
const message = 'SSE connection not established'
66+
throw new Error(message)
67+
}
68+
69+
let parsedMessage: JSONRPCMessage
70+
try {
71+
parsedMessage = JSONRPCMessageSchema.parse(message)
72+
this.onmessage?.(parsedMessage)
73+
} catch (error) {
74+
this.onerror?.(error as Error)
75+
throw error
76+
}
77+
}
78+
79+
async close(): Promise<void> {
80+
this._sseWriter?.close()
81+
this._sseWriter = undefined
82+
this.writableStream.close()
83+
this.onclose?.()
84+
}
85+
86+
async send(message: JSONRPCMessage): Promise<void> {
87+
if (!this._sseWriter) {
88+
throw new Error('Not connected')
89+
}
90+
91+
this._sseWriter.write(`event: message\ndata: ${JSON.stringify(message)}\n\n`)
92+
}
93+
94+
/**
95+
* Returns the session ID for this transport.
96+
*
97+
* This can be used to route incoming POST requests.
98+
*/
99+
get sessionId(): string {
100+
return this._sessionId
101+
}
102+
}

src/server.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { z } from "zod";
3-
import { SQLiteCloudMcpTransport } from "./transport.js";
3+
import { SQLiteCloudMcpTransport } from "./sqlitecloudTransport.js";
44

55
export class SQLiteCloudMcpServer {
66
private mcpServer: McpServer;
@@ -18,9 +18,6 @@ export class SQLiteCloudMcpServer {
1818
const mcpTransport = transport.mcpTransport;
1919
let sessionId = mcpTransport.sessionId;
2020
if (!sessionId) {
21-
console.warn(
22-
"Transport does not have a session ID. Using the anonymous session."
23-
);
2421
sessionId = "anonymous";
2522
mcpTransport.sessionId = sessionId;
2623
}
File renamed without changes.

0 commit comments

Comments
 (0)