sombra-mcp 1.0.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 +80 -0
- package/dist/index.js +188 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# sombra-mcp
|
|
2
|
+
|
|
3
|
+
Stdio-to-HTTP bridge for [Sombra](https://sombra.so). Persistent reader mode for AI.
|
|
4
|
+
|
|
5
|
+
MCP clients that only support stdio-based servers (OpenClaw, Cursor, Windsurf, and others) can't connect directly to remote HTTP MCP servers. This package bridges that gap: it speaks stdio locally and proxies requests to `https://sombra.so/mcp` over Streamable HTTP.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx sombra-mcp --token sombra_pat_YOUR_TOKEN
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or set the token as an environment variable:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
SOMBRA_TOKEN=sombra_pat_YOUR_TOKEN npx sombra-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Getting a token
|
|
20
|
+
|
|
21
|
+
1. Sign up or log in at [sombra.so](https://sombra.so/login)
|
|
22
|
+
2. Go to **Settings > Access Tokens**
|
|
23
|
+
3. Click **Create Token**
|
|
24
|
+
4. Copy the token (starts with `sombra_pat_`)
|
|
25
|
+
|
|
26
|
+
Tokens authenticate your agent against your personal Sombra library. Each token has full read/write access to your account.
|
|
27
|
+
|
|
28
|
+
## Configuring MCP clients
|
|
29
|
+
|
|
30
|
+
### OpenClaw, Claude Desktop, Cursor, Windsurf
|
|
31
|
+
|
|
32
|
+
Add to your MCP config file (`openclaw.json`, `claude_desktop_config.json`, etc.):
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"sombra": {
|
|
38
|
+
"command": "npx",
|
|
39
|
+
"args": ["-y", "sombra-mcp", "--token", "sombra_pat_YOUR_TOKEN"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Claude.ai
|
|
46
|
+
|
|
47
|
+
Add Sombra as a connector in **Settings > Connectors**. No bridge needed - and it will automatically pop up in Claude Code.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
### Claude Code
|
|
51
|
+
|
|
52
|
+
Claude Code supports remote HTTP MCP servers directly, so you don't need this bridge, or if you don't want to set it up in Claude.ai or Claude Desktop.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
claude mcp add --transport http sombra https://sombra.so/mcp
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## What is Sombra?
|
|
59
|
+
|
|
60
|
+
Sombra is a research library that your AI agent can read from and write to through MCP. Save web pages as clean markdown, organise them into collections, distil the important parts into dense context, and search across everything.
|
|
61
|
+
|
|
62
|
+
19 tools for saving, searching, organising, and distilling web content and notes. Two prompts for context distillation. Collections exposed as MCP resources.
|
|
63
|
+
|
|
64
|
+
Your research persists between sessions. Tomorrow's conversation picks up where today's left off.
|
|
65
|
+
|
|
66
|
+
[sombra.so](https://sombra.so) | [MCP setup docs](https://sombra.so/mcp) | [What is Sombra?](https://sombra.so/blog/what-is-sombra)
|
|
67
|
+
|
|
68
|
+
## How it works
|
|
69
|
+
|
|
70
|
+
The bridge reads JSON-RPC messages from stdin, forwards them to `https://sombra.so/mcp` as HTTP POST requests with your Bearer token, and writes the responses back to stdout. It handles session management, protocol version negotiation, and streaming (SSE) responses.
|
|
71
|
+
|
|
72
|
+
No config beyond the token. No local state.
|
|
73
|
+
|
|
74
|
+
## Requirements
|
|
75
|
+
|
|
76
|
+
Node.js 18 or later.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
const VERSION = "1.0.0";
|
|
4
|
+
const SOMBRA_URL = "https://sombra.so/mcp";
|
|
5
|
+
function log(msg) {
|
|
6
|
+
process.stderr.write(`[sombra-mcp] ${msg}\n`);
|
|
7
|
+
}
|
|
8
|
+
function printHelp() {
|
|
9
|
+
process.stdout.write(`sombra-mcp v${VERSION} — Sombra MCP stdio-to-HTTP bridge
|
|
10
|
+
|
|
11
|
+
Reader mode for AI. Give your agent a persistent research library.
|
|
12
|
+
|
|
13
|
+
USAGE
|
|
14
|
+
|
|
15
|
+
npx sombra-mcp --token <token>
|
|
16
|
+
SOMBRA_TOKEN=<token> npx sombra-mcp
|
|
17
|
+
|
|
18
|
+
OPTIONS
|
|
19
|
+
|
|
20
|
+
--token <token> Personal Access Token (or set SOMBRA_TOKEN env var)
|
|
21
|
+
--help, -h Show this help message
|
|
22
|
+
--version, -v Show version number
|
|
23
|
+
|
|
24
|
+
GETTING A TOKEN
|
|
25
|
+
|
|
26
|
+
1. Sign up or log in at https://sombra.so
|
|
27
|
+
2. Go to Settings → Access Tokens
|
|
28
|
+
3. Click "Create Token" — tokens start with sombra_pat_
|
|
29
|
+
4. Copy the token immediately (it won't be shown again)
|
|
30
|
+
|
|
31
|
+
CONFIGURING MCP CLIENTS
|
|
32
|
+
|
|
33
|
+
Claude Code:
|
|
34
|
+
|
|
35
|
+
claude mcp add --transport http sombra https://sombra.so/mcp
|
|
36
|
+
|
|
37
|
+
OpenClaw, Claude Desktop, Cursor, Windsurf (stdio clients):
|
|
38
|
+
|
|
39
|
+
Add to your MCP config file:
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"sombra": {
|
|
44
|
+
"command": "npx",
|
|
45
|
+
"args": ["-y", "sombra-mcp", "--token", "sombra_pat_YOUR_TOKEN"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
ABOUT SOMBRA
|
|
51
|
+
|
|
52
|
+
Sombra gives your AI agent a persistent research library accessible
|
|
53
|
+
via the Model Context Protocol (MCP). Save web pages as clean markdown,
|
|
54
|
+
organise them into collections, distil the key insights, and search
|
|
55
|
+
across everything — all through 19 MCP tools.
|
|
56
|
+
|
|
57
|
+
https://sombra.so Sign up & manage your library
|
|
58
|
+
https://sombra.so/mcp MCP setup for all clients
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
function getToken() {
|
|
62
|
+
const args = process.argv.slice(2);
|
|
63
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
64
|
+
printHelp();
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
68
|
+
process.stdout.write(`${VERSION}\n`);
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
const tokenIdx = args.indexOf("--token");
|
|
72
|
+
const token = (tokenIdx !== -1 ? args[tokenIdx + 1] : undefined) ??
|
|
73
|
+
process.env.SOMBRA_TOKEN;
|
|
74
|
+
if (!token) {
|
|
75
|
+
process.stderr.write([
|
|
76
|
+
"Error: token required.",
|
|
77
|
+
"",
|
|
78
|
+
" Use --token <token> or set SOMBRA_TOKEN env var.",
|
|
79
|
+
" Get a token at https://sombra.so → Settings → Access Tokens.",
|
|
80
|
+
"",
|
|
81
|
+
' Run "npx sombra-mcp --help" for full setup instructions.',
|
|
82
|
+
"",
|
|
83
|
+
].join("\n"));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
return token;
|
|
87
|
+
}
|
|
88
|
+
async function readSSE(response, stdio) {
|
|
89
|
+
const body = response.body;
|
|
90
|
+
if (!body)
|
|
91
|
+
return;
|
|
92
|
+
const reader = body.getReader();
|
|
93
|
+
const decoder = new TextDecoder();
|
|
94
|
+
let buffer = "";
|
|
95
|
+
for (;;) {
|
|
96
|
+
const { done, value } = await reader.read();
|
|
97
|
+
if (done)
|
|
98
|
+
break;
|
|
99
|
+
buffer += decoder.decode(value, { stream: true });
|
|
100
|
+
const parts = buffer.split("\n\n");
|
|
101
|
+
buffer = parts.pop() || "";
|
|
102
|
+
for (const part of parts) {
|
|
103
|
+
let data = "";
|
|
104
|
+
for (const line of part.split("\n")) {
|
|
105
|
+
if (line.startsWith("data: "))
|
|
106
|
+
data += line.slice(6);
|
|
107
|
+
else if (line.startsWith("data:"))
|
|
108
|
+
data += line.slice(5);
|
|
109
|
+
}
|
|
110
|
+
if (data) {
|
|
111
|
+
try {
|
|
112
|
+
await stdio.send(JSON.parse(data));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
/* skip malformed events */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function main() {
|
|
122
|
+
const token = getToken();
|
|
123
|
+
const stdio = new StdioServerTransport();
|
|
124
|
+
let sessionId;
|
|
125
|
+
let protocolVersion;
|
|
126
|
+
stdio.onmessage = async (message) => {
|
|
127
|
+
try {
|
|
128
|
+
const headers = {
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
Accept: "application/json, text/event-stream",
|
|
131
|
+
Authorization: `Bearer ${token}`,
|
|
132
|
+
};
|
|
133
|
+
if (sessionId)
|
|
134
|
+
headers["mcp-session-id"] = sessionId;
|
|
135
|
+
if (protocolVersion)
|
|
136
|
+
headers["mcp-protocol-version"] = protocolVersion;
|
|
137
|
+
const response = await fetch(SOMBRA_URL, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers,
|
|
140
|
+
body: JSON.stringify(message),
|
|
141
|
+
});
|
|
142
|
+
const sid = response.headers.get("mcp-session-id");
|
|
143
|
+
if (sid)
|
|
144
|
+
sessionId = sid;
|
|
145
|
+
// Capture negotiated protocol version from initialize response
|
|
146
|
+
if ("method" in message && message.method === "initialize") {
|
|
147
|
+
const clone = response.clone();
|
|
148
|
+
try {
|
|
149
|
+
const body = await clone.json();
|
|
150
|
+
if (body?.result?.protocolVersion) {
|
|
151
|
+
protocolVersion = body.result.protocolVersion;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch { /* handled below */ }
|
|
155
|
+
}
|
|
156
|
+
if (response.status === 202)
|
|
157
|
+
return; // notification accepted
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
const text = await response.text();
|
|
160
|
+
log(`server ${response.status}: ${text}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const ct = response.headers.get("content-type") || "";
|
|
164
|
+
if (ct.includes("text/event-stream")) {
|
|
165
|
+
await readSSE(response, stdio);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
await stdio.send((await response.json()));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
log(`send error: ${err.message}`);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
stdio.onclose = () => process.exit(0);
|
|
176
|
+
const shutdown = () => {
|
|
177
|
+
stdio.close().catch(() => { });
|
|
178
|
+
process.exit(0);
|
|
179
|
+
};
|
|
180
|
+
process.on("SIGINT", shutdown);
|
|
181
|
+
process.on("SIGTERM", shutdown);
|
|
182
|
+
await stdio.start();
|
|
183
|
+
log("ready");
|
|
184
|
+
}
|
|
185
|
+
main().catch((err) => {
|
|
186
|
+
log(`fatal: ${err.message}`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sombra-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Sombra MCP stdio-to-HTTP bridge. Persistent reader mode for AI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sombra-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "1.27.1"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^22.0.0",
|
|
21
|
+
"typescript": "^5.7.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.0.0"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"mcp",
|
|
28
|
+
"sombra",
|
|
29
|
+
"ai",
|
|
30
|
+
"model-context-protocol",
|
|
31
|
+
"research",
|
|
32
|
+
"context-engineering"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/dazld/sombra-mcp"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://sombra.so"
|
|
40
|
+
}
|