@squirrelnotes.app/mcp 2.0.1 → 2.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 +15 -109
- package/dist/stdio-handler.js +76 -672
- package/package.json +2 -6
package/README.md
CHANGED
|
@@ -6,11 +6,9 @@ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that
|
|
|
6
6
|
|
|
7
7
|
## ⚠️ Security Caveat — Read This First
|
|
8
8
|
|
|
9
|
-
The MCP server necessarily decrypts your notes — that is its job.
|
|
9
|
+
The MCP server necessarily decrypts your notes — that is its job. It runs only as a local process on your machine (the stdio transport); there is no hosted MCP server.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
The passphrase lives in `claude_desktop_config.json` on your machine and is passed as an env var to the local process. It never leaves your machine, and decryption happens locally — similar to the standard Squirrel Notes app, where the passphrase is entered locally in the browser and not sent to the server. Compared with the Remote HTTP/Lambda transport below, stdio avoids sending your passphrase over the network, at the cost of storing it in a local config file.
|
|
11
|
+
The passphrase lives in `claude_desktop_config.json` on your machine and is passed as an env var to the local process. It never leaves your machine, and decryption happens locally — similar to the standard Squirrel Notes app, where the passphrase is entered locally in the browser and not sent to the server. The cost is that the passphrase is stored in a local config file.
|
|
14
12
|
|
|
15
13
|
| Risk | Mitigation |
|
|
16
14
|
|------|------------|
|
|
@@ -18,21 +16,6 @@ The passphrase lives in `claude_desktop_config.json` on your machine and is pass
|
|
|
18
16
|
| Passphrase in process memory | Env var; cleared when process exits |
|
|
19
17
|
| Decrypted content visible to AI | This is the intended behaviour — AI agents need to read your notes |
|
|
20
18
|
|
|
21
|
-
### Remote HTTP (Lambda)
|
|
22
|
-
|
|
23
|
-
Your passphrase is sent over HTTPS to a Lambda function on every call.
|
|
24
|
-
|
|
25
|
-
| Risk | Mitigation |
|
|
26
|
-
|------|------------|
|
|
27
|
-
| Passphrase sent over the network | HTTPS-only (API Gateway always TLS) |
|
|
28
|
-
| Passphrase in server RAM during invocation | Stack-scoped variable; never assigned to module state; GC'd after invocation |
|
|
29
|
-
| Passphrase in CloudWatch logs | Lambda code never logs headers |
|
|
30
|
-
| Lambda container reuse retaining secrets | Passphrase is NOT module-scope — reused containers hold no residual secret |
|
|
31
|
-
|
|
32
|
-
**You must trust:** AWS Lambda, the self-hosted MCP server code, and anyone with access to your CloudWatch logs.
|
|
33
|
-
|
|
34
|
-
If this trade-off is not acceptable, use stdio transport instead.
|
|
35
|
-
|
|
36
19
|
---
|
|
37
20
|
|
|
38
21
|
## Tools
|
|
@@ -65,12 +48,12 @@ If this trade-off is not acceptable, use stdio transport instead.
|
|
|
65
48
|
|
|
66
49
|
### Attachments
|
|
67
50
|
|
|
68
|
-
| Tool |
|
|
69
|
-
|
|
70
|
-
| `list_attachments` |
|
|
71
|
-
| `get_attachment_download_url` |
|
|
72
|
-
| `delete_attachment` |
|
|
73
|
-
| `upload_attachment_from_path` |
|
|
51
|
+
| Tool | Description |
|
|
52
|
+
|------|-------------|
|
|
53
|
+
| `list_attachments` | Lists all attachments for a note with decrypted filenames and metadata. |
|
|
54
|
+
| `get_attachment_download_url` | Returns a presigned S3 download URL (expires in 5 minutes). |
|
|
55
|
+
| `delete_attachment` | Permanently deletes an attachment from S3 and the database. |
|
|
56
|
+
| `upload_attachment_from_path` | Uploads a file from a local path — **no file bytes in context**. Use after `write_file` (Filesystem MCP) to attach Claude-generated files. See [Filesystem MCP companion](#filesystem-mcp-companion-stdio-only) below. |
|
|
74
57
|
|
|
75
58
|
---
|
|
76
59
|
|
|
@@ -142,28 +125,15 @@ Add both servers to `claude_desktop_config.json`:
|
|
|
142
125
|
1. Claude calls `get_note(id)` (Squirrel MCP) to read the note
|
|
143
126
|
2. Claude calls `write_file("/tmp/roadmap.md", content)` (Filesystem MCP)
|
|
144
127
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
```
|
|
148
|
-
Authorization: Bearer sqn_<api_key>
|
|
149
|
-
X-Passphrase: <your_passphrase>
|
|
150
|
-
```
|
|
128
|
+
The server needs an API key and your passphrase, both set as env vars in your client's config (see Setup). The API key is sent to the Squirrel Notes API in the `Authorization` header; the passphrase is never sent anywhere.
|
|
151
129
|
|
|
152
130
|
Generate an API key in Squirrel Notes → Settings → API Key (requires Pro tier).
|
|
153
131
|
|
|
154
|
-
Neither header is ever stored, cached, or logged by the server.
|
|
155
|
-
|
|
156
132
|
---
|
|
157
133
|
|
|
158
134
|
## Setup
|
|
159
135
|
|
|
160
|
-
|
|
161
|
-
> - **Claude Desktop** → use **stdio (recommended)** — no OAuth, passphrase stays on your machine
|
|
162
|
-
> - **API automation / Cursor / other agents** → use **Remote HTTP (Lambda)**
|
|
163
|
-
|
|
164
|
-
---
|
|
165
|
-
|
|
166
|
-
### Setup — Claude Desktop (stdio) ✅ Recommended
|
|
136
|
+
### Setup — Claude Desktop, Cursor and other MCP clients (stdio)
|
|
167
137
|
|
|
168
138
|
The stdio transport runs the MCP server as a local process on your machine. No OAuth, no network headers, no AWS account needed. Your passphrase is set as an env var in the config file and **never leaves your machine**.
|
|
169
139
|
|
|
@@ -232,47 +202,6 @@ Then reference the built file directly:
|
|
|
232
202
|
|
|
233
203
|
---
|
|
234
204
|
|
|
235
|
-
### Setup — Remote HTTP (Lambda)
|
|
236
|
-
|
|
237
|
-
Use this if you want to connect agents or tools that support remote HTTP MCP endpoints (Cursor, custom automations, etc.). This requires deploying to AWS and sending the passphrase over HTTPS on every call.
|
|
238
|
-
|
|
239
|
-
#### 1. Deploy with CDK
|
|
240
|
-
|
|
241
|
-
```bash
|
|
242
|
-
cd infra
|
|
243
|
-
npx cdk deploy SquirrelMcp
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
This creates:
|
|
247
|
-
- A Lambda function running the MCP server Docker image
|
|
248
|
-
- An HTTP API Gateway at `https://<id>.execute-api.<region>.amazonaws.com/mcp`
|
|
249
|
-
|
|
250
|
-
The deployed URL is printed as a CDK output (`McpApiUrl`).
|
|
251
|
-
|
|
252
|
-
#### 2. Configure your client
|
|
253
|
-
|
|
254
|
-
```json
|
|
255
|
-
{
|
|
256
|
-
"mcpServers": {
|
|
257
|
-
"squirrel-notes": {
|
|
258
|
-
"url": "https://<your-mcp-api-url>/mcp",
|
|
259
|
-
"headers": {
|
|
260
|
-
"Authorization": "Bearer sqn_<your_api_key>",
|
|
261
|
-
"X-Passphrase": "<your_passphrase>"
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
> **Note:** Newer versions of Claude Desktop enforce OAuth 2.0 for remote HTTP servers and will reject this config. Use the stdio setup above for Claude Desktop.
|
|
269
|
-
|
|
270
|
-
#### 3. Verify
|
|
271
|
-
|
|
272
|
-
Ask Claude: *"List my Squirrel Notes"* — you should see decrypted note titles.
|
|
273
|
-
|
|
274
|
-
---
|
|
275
|
-
|
|
276
205
|
## Common Workflows
|
|
277
206
|
|
|
278
207
|
Once configured, you can speak to Claude naturally. Some examples:
|
|
@@ -296,28 +225,15 @@ Once configured, you can speak to Claude naturally. Some examples:
|
|
|
296
225
|
## Local Development
|
|
297
226
|
|
|
298
227
|
```bash
|
|
299
|
-
|
|
300
|
-
npm
|
|
301
|
-
npm
|
|
228
|
+
npm --prefix mcp install
|
|
229
|
+
npm --prefix mcp run build # builds dist/stdio-handler.js
|
|
230
|
+
npm --prefix mcp test
|
|
302
231
|
```
|
|
303
232
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
Smoke test with curl:
|
|
233
|
+
Point a client at `mcp/dist/stdio-handler.js` with `SQUIRREL_API_BASE_URL=http://localhost:3001` (the local backend), as in Option B above. Or open the MCP Inspector on it — set the three env vars in the Inspector's UI:
|
|
307
234
|
|
|
308
235
|
```bash
|
|
309
|
-
|
|
310
|
-
-H "Authorization: Bearer sqn_<your_api_key>" \
|
|
311
|
-
-H "X-Passphrase: <your_passphrase>" \
|
|
312
|
-
-H "Content-Type: application/json" \
|
|
313
|
-
-H "Accept: application/json, text/event-stream" \
|
|
314
|
-
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | jq .
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
Or use the MCP Inspector for a browser UI:
|
|
318
|
-
|
|
319
|
-
```bash
|
|
320
|
-
npx @modelcontextprotocol/inspector http://localhost:3002
|
|
236
|
+
make mcp-inspect
|
|
321
237
|
```
|
|
322
238
|
|
|
323
239
|
---
|
|
@@ -384,19 +300,9 @@ tail -f /tmp/squirrel-mcp.log
|
|
|
384
300
|
|
|
385
301
|
## Environment Variables
|
|
386
302
|
|
|
387
|
-
### stdio transport
|
|
388
|
-
|
|
389
303
|
| Variable | Required | Description |
|
|
390
304
|
|----------|----------|-------------|
|
|
391
305
|
| `SQUIRREL_API_BASE_URL` | ✅ | Base URL of the Squirrel Notes backend API (e.g. `https://api.squirrelnotes.app`) |
|
|
392
306
|
| `SQUIRREL_API_KEY` | ✅ | Your API key from Settings → API Key (requires Pro tier) |
|
|
393
307
|
| `SQUIRREL_PASSPHRASE` | ✅ | Your encryption passphrase — never leaves your machine |
|
|
394
308
|
| `SQUIRREL_LOG_FILE` | — | Absolute path to a log file. When set, all stderr output is also appended to this file. |
|
|
395
|
-
|
|
396
|
-
### Lambda (HTTP) transport
|
|
397
|
-
|
|
398
|
-
| Variable | Description |
|
|
399
|
-
|----------|-------------|
|
|
400
|
-
| `SQUIRREL_API_BASE_URL` | Base URL of the Squirrel Notes backend API |
|
|
401
|
-
|
|
402
|
-
For Lambda, the API key and passphrase are passed per-request via `Authorization` and `X-Passphrase` headers — **not** environment variables.
|
package/dist/stdio-handler.js
CHANGED
|
@@ -4755,12 +4755,11 @@ function assertCompleteRequestResourceTemplate(request) {
|
|
|
4755
4755
|
}
|
|
4756
4756
|
void request;
|
|
4757
4757
|
}
|
|
4758
|
-
var LATEST_PROTOCOL_VERSION,
|
|
4758
|
+
var LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, RELATED_TASK_META_KEY, JSONRPC_VERSION, AssertObjectSchema, ProgressTokenSchema, CursorSchema, TaskCreationParamsSchema, TaskMetadataSchema, RelatedTaskMetadataSchema, RequestMetaSchema, BaseRequestParamsSchema, TaskAugmentedRequestParamsSchema, isTaskAugmentedRequestParams, RequestSchema, NotificationsParamsSchema, NotificationSchema, ResultSchema, RequestIdSchema, JSONRPCRequestSchema, isJSONRPCRequest, JSONRPCNotificationSchema, isJSONRPCNotification, JSONRPCResultResponseSchema, isJSONRPCResultResponse, ErrorCode, JSONRPCErrorResponseSchema, isJSONRPCErrorResponse, JSONRPCMessageSchema, JSONRPCResponseSchema, EmptyResultSchema, CancelledNotificationParamsSchema, CancelledNotificationSchema, IconSchema, IconsSchema, BaseMetadataSchema, ImplementationSchema, FormElicitationCapabilitySchema, ElicitationCapabilitySchema, ClientTasksCapabilitySchema, ServerTasksCapabilitySchema, ClientCapabilitiesSchema, InitializeRequestParamsSchema, InitializeRequestSchema, ServerCapabilitiesSchema, InitializeResultSchema, InitializedNotificationSchema, PingRequestSchema, ProgressSchema, ProgressNotificationParamsSchema, ProgressNotificationSchema, PaginatedRequestParamsSchema, PaginatedRequestSchema, PaginatedResultSchema, TaskStatusSchema, TaskSchema, CreateTaskResultSchema, TaskStatusNotificationParamsSchema, TaskStatusNotificationSchema, GetTaskRequestSchema, GetTaskResultSchema, GetTaskPayloadRequestSchema, GetTaskPayloadResultSchema, ListTasksRequestSchema, ListTasksResultSchema, CancelTaskRequestSchema, CancelTaskResultSchema, ResourceContentsSchema, TextResourceContentsSchema, Base64Schema, BlobResourceContentsSchema, RoleSchema, AnnotationsSchema, ResourceSchema, ResourceTemplateSchema, ListResourcesRequestSchema, ListResourcesResultSchema, ListResourceTemplatesRequestSchema, ListResourceTemplatesResultSchema, ResourceRequestParamsSchema, ReadResourceRequestParamsSchema, ReadResourceRequestSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, SubscribeRequestParamsSchema, SubscribeRequestSchema, UnsubscribeRequestParamsSchema, UnsubscribeRequestSchema, ResourceUpdatedNotificationParamsSchema, ResourceUpdatedNotificationSchema, PromptArgumentSchema, PromptSchema, ListPromptsRequestSchema, ListPromptsResultSchema, GetPromptRequestParamsSchema, GetPromptRequestSchema, TextContentSchema, ImageContentSchema, AudioContentSchema, ToolUseContentSchema, EmbeddedResourceSchema, ResourceLinkSchema, ContentBlockSchema, PromptMessageSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ToolAnnotationsSchema, ToolExecutionSchema, ToolSchema, ListToolsRequestSchema, ListToolsResultSchema, CallToolResultSchema, CompatibilityCallToolResultSchema, CallToolRequestParamsSchema, CallToolRequestSchema, ToolListChangedNotificationSchema, ListChangedOptionsBaseSchema, LoggingLevelSchema, SetLevelRequestParamsSchema, SetLevelRequestSchema, LoggingMessageNotificationParamsSchema, LoggingMessageNotificationSchema, ModelHintSchema, ModelPreferencesSchema, ToolChoiceSchema, ToolResultContentSchema, SamplingContentSchema, SamplingMessageContentBlockSchema, SamplingMessageSchema, CreateMessageRequestParamsSchema, CreateMessageRequestSchema, CreateMessageResultSchema, CreateMessageResultWithToolsSchema, BooleanSchemaSchema, StringSchemaSchema, NumberSchemaSchema, UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema, LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, UntitledMultiSelectEnumSchemaSchema, TitledMultiSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema, EnumSchemaSchema, PrimitiveSchemaDefinitionSchema, ElicitRequestFormParamsSchema, ElicitRequestURLParamsSchema, ElicitRequestParamsSchema, ElicitRequestSchema, ElicitationCompleteNotificationParamsSchema, ElicitationCompleteNotificationSchema, ElicitResultSchema, ResourceTemplateReferenceSchema, PromptReferenceSchema, CompleteRequestParamsSchema, CompleteRequestSchema, CompleteResultSchema, RootSchema, ListRootsRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, ClientRequestSchema, ClientNotificationSchema, ClientResultSchema, ServerRequestSchema, ServerNotificationSchema, ServerResultSchema, McpError, UrlElicitationRequiredError;
|
|
4759
4759
|
var init_types = __esm({
|
|
4760
4760
|
"node_modules/@modelcontextprotocol/sdk/dist/esm/types.js"() {
|
|
4761
4761
|
init_v4();
|
|
4762
4762
|
LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
4763
|
-
DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26";
|
|
4764
4763
|
SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
|
|
4765
4764
|
RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
|
|
4766
4765
|
JSONRPC_VERSION = "2.0";
|
|
@@ -4769,10 +4768,9 @@ var init_types = __esm({
|
|
|
4769
4768
|
CursorSchema = string2();
|
|
4770
4769
|
TaskCreationParamsSchema = looseObject({
|
|
4771
4770
|
/**
|
|
4772
|
-
*
|
|
4773
|
-
* If null, the task has unlimited lifetime until manually cleaned up.
|
|
4771
|
+
* Requested duration in milliseconds to retain task from creation.
|
|
4774
4772
|
*/
|
|
4775
|
-
ttl:
|
|
4773
|
+
ttl: number2().optional(),
|
|
4776
4774
|
/**
|
|
4777
4775
|
* Time in milliseconds to wait between task status requests.
|
|
4778
4776
|
*/
|
|
@@ -5071,7 +5069,11 @@ var init_types = __esm({
|
|
|
5071
5069
|
/**
|
|
5072
5070
|
* Present if the client supports task creation.
|
|
5073
5071
|
*/
|
|
5074
|
-
tasks: ClientTasksCapabilitySchema.optional()
|
|
5072
|
+
tasks: ClientTasksCapabilitySchema.optional(),
|
|
5073
|
+
/**
|
|
5074
|
+
* Extensions that the client supports. Keys are extension identifiers (vendor-prefix/extension-name).
|
|
5075
|
+
*/
|
|
5076
|
+
extensions: record(string2(), AssertObjectSchema).optional()
|
|
5075
5077
|
});
|
|
5076
5078
|
InitializeRequestParamsSchema = BaseRequestParamsSchema.extend({
|
|
5077
5079
|
/**
|
|
@@ -5085,7 +5087,6 @@ var init_types = __esm({
|
|
|
5085
5087
|
method: literal("initialize"),
|
|
5086
5088
|
params: InitializeRequestParamsSchema
|
|
5087
5089
|
});
|
|
5088
|
-
isInitializeRequest = (value) => InitializeRequestSchema.safeParse(value).success;
|
|
5089
5090
|
ServerCapabilitiesSchema = object({
|
|
5090
5091
|
/**
|
|
5091
5092
|
* Experimental, non-standard capabilities that the server supports.
|
|
@@ -5133,7 +5134,11 @@ var init_types = __esm({
|
|
|
5133
5134
|
/**
|
|
5134
5135
|
* Present if the server supports task creation.
|
|
5135
5136
|
*/
|
|
5136
|
-
tasks: ServerTasksCapabilitySchema.optional()
|
|
5137
|
+
tasks: ServerTasksCapabilitySchema.optional(),
|
|
5138
|
+
/**
|
|
5139
|
+
* Extensions that the server supports. Keys are extension identifiers (vendor-prefix/extension-name).
|
|
5140
|
+
*/
|
|
5141
|
+
extensions: record(string2(), AssertObjectSchema).optional()
|
|
5137
5142
|
});
|
|
5138
5143
|
InitializeResultSchema = ResultSchema.extend({
|
|
5139
5144
|
/**
|
|
@@ -5325,6 +5330,12 @@ var init_types = __esm({
|
|
|
5325
5330
|
* The MIME type of this resource, if known.
|
|
5326
5331
|
*/
|
|
5327
5332
|
mimeType: optional(string2()),
|
|
5333
|
+
/**
|
|
5334
|
+
* The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
|
|
5335
|
+
*
|
|
5336
|
+
* This can be used by Hosts to display file sizes and estimate context window usage.
|
|
5337
|
+
*/
|
|
5338
|
+
size: optional(number2()),
|
|
5328
5339
|
/**
|
|
5329
5340
|
* Optional annotations for the client.
|
|
5330
5341
|
*/
|
|
@@ -6274,12 +6285,21 @@ function deserializeMessage(line) {
|
|
|
6274
6285
|
function serializeMessage(message) {
|
|
6275
6286
|
return JSON.stringify(message) + "\n";
|
|
6276
6287
|
}
|
|
6277
|
-
var ReadBuffer;
|
|
6288
|
+
var STDIO_DEFAULT_MAX_BUFFER_SIZE, ReadBuffer;
|
|
6278
6289
|
var init_stdio = __esm({
|
|
6279
6290
|
"node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js"() {
|
|
6280
6291
|
init_types();
|
|
6292
|
+
STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
|
|
6281
6293
|
ReadBuffer = class {
|
|
6294
|
+
constructor(options) {
|
|
6295
|
+
this._maxBufferSize = options?.maxBufferSize ?? STDIO_DEFAULT_MAX_BUFFER_SIZE;
|
|
6296
|
+
}
|
|
6282
6297
|
append(chunk) {
|
|
6298
|
+
const newSize = (this._buffer?.length ?? 0) + chunk.length;
|
|
6299
|
+
if (newSize > this._maxBufferSize) {
|
|
6300
|
+
this.clear();
|
|
6301
|
+
throw new Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);
|
|
6302
|
+
}
|
|
6283
6303
|
this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
|
|
6284
6304
|
}
|
|
6285
6305
|
readMessage() {
|
|
@@ -6312,18 +6332,24 @@ var init_stdio2 = __esm({
|
|
|
6312
6332
|
"node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js"() {
|
|
6313
6333
|
init_stdio();
|
|
6314
6334
|
StdioServerTransport = class {
|
|
6315
|
-
constructor(_stdin = process2.stdin, _stdout = process2.stdout) {
|
|
6335
|
+
constructor(_stdin = process2.stdin, _stdout = process2.stdout, options) {
|
|
6316
6336
|
this._stdin = _stdin;
|
|
6317
6337
|
this._stdout = _stdout;
|
|
6318
|
-
this._readBuffer = new ReadBuffer();
|
|
6319
6338
|
this._started = false;
|
|
6320
6339
|
this._ondata = (chunk) => {
|
|
6321
|
-
|
|
6322
|
-
|
|
6340
|
+
try {
|
|
6341
|
+
this._readBuffer.append(chunk);
|
|
6342
|
+
this.processReadBuffer();
|
|
6343
|
+
} catch (error2) {
|
|
6344
|
+
this.onerror?.(error2);
|
|
6345
|
+
this.close().catch(() => {
|
|
6346
|
+
});
|
|
6347
|
+
}
|
|
6323
6348
|
};
|
|
6324
6349
|
this._onerror = (error2) => {
|
|
6325
6350
|
this.onerror?.(error2);
|
|
6326
6351
|
};
|
|
6352
|
+
this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
|
|
6327
6353
|
}
|
|
6328
6354
|
/**
|
|
6329
6355
|
* Starts listening for messages on stdin.
|
|
@@ -10669,17 +10695,33 @@ function normalizeObjectSchema(schema) {
|
|
|
10669
10695
|
}
|
|
10670
10696
|
return void 0;
|
|
10671
10697
|
}
|
|
10698
|
+
function getDotPath(path) {
|
|
10699
|
+
if (path.length === 0) {
|
|
10700
|
+
return "object root";
|
|
10701
|
+
}
|
|
10702
|
+
return path.reduce((acc, seg, index) => {
|
|
10703
|
+
if (index === 0) {
|
|
10704
|
+
return String(seg);
|
|
10705
|
+
}
|
|
10706
|
+
if (typeof seg === "number") {
|
|
10707
|
+
return `${acc}[${seg}]`;
|
|
10708
|
+
}
|
|
10709
|
+
return `${acc}.${seg}`;
|
|
10710
|
+
}, "");
|
|
10711
|
+
}
|
|
10672
10712
|
function getParseErrorMessage(error2) {
|
|
10673
10713
|
if (error2 && typeof error2 === "object") {
|
|
10714
|
+
if ("issues" in error2 && Array.isArray(error2.issues) && error2.issues.length > 0) {
|
|
10715
|
+
return error2.issues.map((i) => {
|
|
10716
|
+
if (!i.path?.length) {
|
|
10717
|
+
return i.message;
|
|
10718
|
+
}
|
|
10719
|
+
return `${i.message} at ${getDotPath(i.path)}`;
|
|
10720
|
+
}).join("\n");
|
|
10721
|
+
}
|
|
10674
10722
|
if ("message" in error2 && typeof error2.message === "string") {
|
|
10675
10723
|
return error2.message;
|
|
10676
10724
|
}
|
|
10677
|
-
if ("issues" in error2 && Array.isArray(error2.issues) && error2.issues.length > 0) {
|
|
10678
|
-
const firstIssue = error2.issues[0];
|
|
10679
|
-
if (firstIssue && typeof firstIssue === "object" && "message" in firstIssue) {
|
|
10680
|
-
return String(firstIssue.message);
|
|
10681
|
-
}
|
|
10682
|
-
}
|
|
10683
10725
|
try {
|
|
10684
10726
|
return JSON.stringify(error2);
|
|
10685
10727
|
} catch {
|
|
@@ -12604,6 +12646,10 @@ var init_protocol = __esm({
|
|
|
12604
12646
|
this._progressHandlers.clear();
|
|
12605
12647
|
this._taskProgressTokens.clear();
|
|
12606
12648
|
this._pendingDebouncedNotifications.clear();
|
|
12649
|
+
for (const info of this._timeoutInfo.values()) {
|
|
12650
|
+
clearTimeout(info.timeoutId);
|
|
12651
|
+
}
|
|
12652
|
+
this._timeoutInfo.clear();
|
|
12607
12653
|
for (const controller of this._requestHandlerAbortControllers.values()) {
|
|
12608
12654
|
controller.abort();
|
|
12609
12655
|
}
|
|
@@ -12734,7 +12780,9 @@ var init_protocol = __esm({
|
|
|
12734
12780
|
await capturedTransport?.send(errorResponse);
|
|
12735
12781
|
}
|
|
12736
12782
|
}).catch((error2) => this._onerror(new Error(`Failed to send response: ${error2}`))).finally(() => {
|
|
12737
|
-
this._requestHandlerAbortControllers.
|
|
12783
|
+
if (this._requestHandlerAbortControllers.get(request.id) === abortController) {
|
|
12784
|
+
this._requestHandlerAbortControllers.delete(request.id);
|
|
12785
|
+
}
|
|
12738
12786
|
});
|
|
12739
12787
|
}
|
|
12740
12788
|
_onprogress(notification) {
|
|
@@ -20895,16 +20943,7 @@ var init_server2 = __esm({
|
|
|
20895
20943
|
if (!methodSchema) {
|
|
20896
20944
|
throw new Error("Schema is missing a method literal");
|
|
20897
20945
|
}
|
|
20898
|
-
|
|
20899
|
-
if (isZ4Schema(methodSchema)) {
|
|
20900
|
-
const v4Schema = methodSchema;
|
|
20901
|
-
const v4Def = v4Schema._zod?.def;
|
|
20902
|
-
methodValue = v4Def?.value ?? v4Schema.value;
|
|
20903
|
-
} else {
|
|
20904
|
-
const v3Schema = methodSchema;
|
|
20905
|
-
const legacyDef = v3Schema._def;
|
|
20906
|
-
methodValue = legacyDef?.value ?? v3Schema.value;
|
|
20907
|
-
}
|
|
20946
|
+
const methodValue = getLiteralValue(methodSchema);
|
|
20908
20947
|
if (typeof methodValue !== "string") {
|
|
20909
20948
|
throw new Error("Schema method literal must be a string");
|
|
20910
20949
|
}
|
|
@@ -21352,6 +21391,9 @@ function getZodSchemaObject(schema) {
|
|
|
21352
21391
|
if (isZodRawShapeCompat(schema)) {
|
|
21353
21392
|
return objectFromShape(schema);
|
|
21354
21393
|
}
|
|
21394
|
+
if (!isZodSchemaInstance(schema)) {
|
|
21395
|
+
throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");
|
|
21396
|
+
}
|
|
21355
21397
|
return schema;
|
|
21356
21398
|
}
|
|
21357
21399
|
function promptArgumentsFromSchema(schema) {
|
|
@@ -22024,6 +22066,9 @@ var init_mcp = __esm({
|
|
|
22024
22066
|
annotations = rest.shift();
|
|
22025
22067
|
}
|
|
22026
22068
|
} else if (typeof firstArg === "object" && firstArg !== null) {
|
|
22069
|
+
if (Object.values(firstArg).some((v) => typeof v === "object" && v !== null)) {
|
|
22070
|
+
throw new Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);
|
|
22071
|
+
}
|
|
22027
22072
|
annotations = rest.shift();
|
|
22028
22073
|
}
|
|
22029
22074
|
}
|
|
@@ -22126,614 +22171,6 @@ var init_mcp = __esm({
|
|
|
22126
22171
|
}
|
|
22127
22172
|
});
|
|
22128
22173
|
|
|
22129
|
-
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
|
|
22130
|
-
var WebStandardStreamableHTTPServerTransport;
|
|
22131
|
-
var init_webStandardStreamableHttp = __esm({
|
|
22132
|
-
"node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js"() {
|
|
22133
|
-
init_types();
|
|
22134
|
-
WebStandardStreamableHTTPServerTransport = class {
|
|
22135
|
-
constructor(options = {}) {
|
|
22136
|
-
this._started = false;
|
|
22137
|
-
this._hasHandledRequest = false;
|
|
22138
|
-
this._streamMapping = /* @__PURE__ */ new Map();
|
|
22139
|
-
this._requestToStreamMapping = /* @__PURE__ */ new Map();
|
|
22140
|
-
this._requestResponseMap = /* @__PURE__ */ new Map();
|
|
22141
|
-
this._initialized = false;
|
|
22142
|
-
this._enableJsonResponse = false;
|
|
22143
|
-
this._standaloneSseStreamId = "_GET_stream";
|
|
22144
|
-
this.sessionIdGenerator = options.sessionIdGenerator;
|
|
22145
|
-
this._enableJsonResponse = options.enableJsonResponse ?? false;
|
|
22146
|
-
this._eventStore = options.eventStore;
|
|
22147
|
-
this._onsessioninitialized = options.onsessioninitialized;
|
|
22148
|
-
this._onsessionclosed = options.onsessionclosed;
|
|
22149
|
-
this._allowedHosts = options.allowedHosts;
|
|
22150
|
-
this._allowedOrigins = options.allowedOrigins;
|
|
22151
|
-
this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
|
|
22152
|
-
this._retryInterval = options.retryInterval;
|
|
22153
|
-
}
|
|
22154
|
-
/**
|
|
22155
|
-
* Starts the transport. This is required by the Transport interface but is a no-op
|
|
22156
|
-
* for the Streamable HTTP transport as connections are managed per-request.
|
|
22157
|
-
*/
|
|
22158
|
-
async start() {
|
|
22159
|
-
if (this._started) {
|
|
22160
|
-
throw new Error("Transport already started");
|
|
22161
|
-
}
|
|
22162
|
-
this._started = true;
|
|
22163
|
-
}
|
|
22164
|
-
/**
|
|
22165
|
-
* Helper to create a JSON error response
|
|
22166
|
-
*/
|
|
22167
|
-
createJsonErrorResponse(status, code, message, options) {
|
|
22168
|
-
const error2 = { code, message };
|
|
22169
|
-
if (options?.data !== void 0) {
|
|
22170
|
-
error2.data = options.data;
|
|
22171
|
-
}
|
|
22172
|
-
return new Response(JSON.stringify({
|
|
22173
|
-
jsonrpc: "2.0",
|
|
22174
|
-
error: error2,
|
|
22175
|
-
id: null
|
|
22176
|
-
}), {
|
|
22177
|
-
status,
|
|
22178
|
-
headers: {
|
|
22179
|
-
"Content-Type": "application/json",
|
|
22180
|
-
...options?.headers
|
|
22181
|
-
}
|
|
22182
|
-
});
|
|
22183
|
-
}
|
|
22184
|
-
/**
|
|
22185
|
-
* Validates request headers for DNS rebinding protection.
|
|
22186
|
-
* @returns Error response if validation fails, undefined if validation passes.
|
|
22187
|
-
*/
|
|
22188
|
-
validateRequestHeaders(req) {
|
|
22189
|
-
if (!this._enableDnsRebindingProtection) {
|
|
22190
|
-
return void 0;
|
|
22191
|
-
}
|
|
22192
|
-
if (this._allowedHosts && this._allowedHosts.length > 0) {
|
|
22193
|
-
const hostHeader = req.headers.get("host");
|
|
22194
|
-
if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
|
|
22195
|
-
const error2 = `Invalid Host header: ${hostHeader}`;
|
|
22196
|
-
this.onerror?.(new Error(error2));
|
|
22197
|
-
return this.createJsonErrorResponse(403, -32e3, error2);
|
|
22198
|
-
}
|
|
22199
|
-
}
|
|
22200
|
-
if (this._allowedOrigins && this._allowedOrigins.length > 0) {
|
|
22201
|
-
const originHeader = req.headers.get("origin");
|
|
22202
|
-
if (originHeader && !this._allowedOrigins.includes(originHeader)) {
|
|
22203
|
-
const error2 = `Invalid Origin header: ${originHeader}`;
|
|
22204
|
-
this.onerror?.(new Error(error2));
|
|
22205
|
-
return this.createJsonErrorResponse(403, -32e3, error2);
|
|
22206
|
-
}
|
|
22207
|
-
}
|
|
22208
|
-
return void 0;
|
|
22209
|
-
}
|
|
22210
|
-
/**
|
|
22211
|
-
* Handles an incoming HTTP request, whether GET, POST, or DELETE
|
|
22212
|
-
* Returns a Response object (Web Standard)
|
|
22213
|
-
*/
|
|
22214
|
-
async handleRequest(req, options) {
|
|
22215
|
-
if (!this.sessionIdGenerator && this._hasHandledRequest) {
|
|
22216
|
-
throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");
|
|
22217
|
-
}
|
|
22218
|
-
this._hasHandledRequest = true;
|
|
22219
|
-
const validationError = this.validateRequestHeaders(req);
|
|
22220
|
-
if (validationError) {
|
|
22221
|
-
return validationError;
|
|
22222
|
-
}
|
|
22223
|
-
switch (req.method) {
|
|
22224
|
-
case "POST":
|
|
22225
|
-
return this.handlePostRequest(req, options);
|
|
22226
|
-
case "GET":
|
|
22227
|
-
return this.handleGetRequest(req);
|
|
22228
|
-
case "DELETE":
|
|
22229
|
-
return this.handleDeleteRequest(req);
|
|
22230
|
-
default:
|
|
22231
|
-
return this.handleUnsupportedRequest();
|
|
22232
|
-
}
|
|
22233
|
-
}
|
|
22234
|
-
/**
|
|
22235
|
-
* Writes a priming event to establish resumption capability.
|
|
22236
|
-
* Only sends if eventStore is configured (opt-in for resumability) and
|
|
22237
|
-
* the client's protocol version supports empty SSE data (>= 2025-11-25).
|
|
22238
|
-
*/
|
|
22239
|
-
async writePrimingEvent(controller, encoder, streamId, protocolVersion) {
|
|
22240
|
-
if (!this._eventStore) {
|
|
22241
|
-
return;
|
|
22242
|
-
}
|
|
22243
|
-
if (protocolVersion < "2025-11-25") {
|
|
22244
|
-
return;
|
|
22245
|
-
}
|
|
22246
|
-
const primingEventId = await this._eventStore.storeEvent(streamId, {});
|
|
22247
|
-
let primingEvent = `id: ${primingEventId}
|
|
22248
|
-
data:
|
|
22249
|
-
|
|
22250
|
-
`;
|
|
22251
|
-
if (this._retryInterval !== void 0) {
|
|
22252
|
-
primingEvent = `id: ${primingEventId}
|
|
22253
|
-
retry: ${this._retryInterval}
|
|
22254
|
-
data:
|
|
22255
|
-
|
|
22256
|
-
`;
|
|
22257
|
-
}
|
|
22258
|
-
controller.enqueue(encoder.encode(primingEvent));
|
|
22259
|
-
}
|
|
22260
|
-
/**
|
|
22261
|
-
* Handles GET requests for SSE stream
|
|
22262
|
-
*/
|
|
22263
|
-
async handleGetRequest(req) {
|
|
22264
|
-
const acceptHeader = req.headers.get("accept");
|
|
22265
|
-
if (!acceptHeader?.includes("text/event-stream")) {
|
|
22266
|
-
this.onerror?.(new Error("Not Acceptable: Client must accept text/event-stream"));
|
|
22267
|
-
return this.createJsonErrorResponse(406, -32e3, "Not Acceptable: Client must accept text/event-stream");
|
|
22268
|
-
}
|
|
22269
|
-
const sessionError = this.validateSession(req);
|
|
22270
|
-
if (sessionError) {
|
|
22271
|
-
return sessionError;
|
|
22272
|
-
}
|
|
22273
|
-
const protocolError = this.validateProtocolVersion(req);
|
|
22274
|
-
if (protocolError) {
|
|
22275
|
-
return protocolError;
|
|
22276
|
-
}
|
|
22277
|
-
if (this._eventStore) {
|
|
22278
|
-
const lastEventId = req.headers.get("last-event-id");
|
|
22279
|
-
if (lastEventId) {
|
|
22280
|
-
return this.replayEvents(lastEventId);
|
|
22281
|
-
}
|
|
22282
|
-
}
|
|
22283
|
-
if (this._streamMapping.get(this._standaloneSseStreamId) !== void 0) {
|
|
22284
|
-
this.onerror?.(new Error("Conflict: Only one SSE stream is allowed per session"));
|
|
22285
|
-
return this.createJsonErrorResponse(409, -32e3, "Conflict: Only one SSE stream is allowed per session");
|
|
22286
|
-
}
|
|
22287
|
-
const encoder = new TextEncoder();
|
|
22288
|
-
let streamController;
|
|
22289
|
-
const readable = new ReadableStream({
|
|
22290
|
-
start: (controller) => {
|
|
22291
|
-
streamController = controller;
|
|
22292
|
-
},
|
|
22293
|
-
cancel: () => {
|
|
22294
|
-
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
22295
|
-
}
|
|
22296
|
-
});
|
|
22297
|
-
const headers = {
|
|
22298
|
-
"Content-Type": "text/event-stream",
|
|
22299
|
-
"Cache-Control": "no-cache, no-transform",
|
|
22300
|
-
Connection: "keep-alive"
|
|
22301
|
-
};
|
|
22302
|
-
if (this.sessionId !== void 0) {
|
|
22303
|
-
headers["mcp-session-id"] = this.sessionId;
|
|
22304
|
-
}
|
|
22305
|
-
this._streamMapping.set(this._standaloneSseStreamId, {
|
|
22306
|
-
controller: streamController,
|
|
22307
|
-
encoder,
|
|
22308
|
-
cleanup: () => {
|
|
22309
|
-
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
22310
|
-
try {
|
|
22311
|
-
streamController.close();
|
|
22312
|
-
} catch {
|
|
22313
|
-
}
|
|
22314
|
-
}
|
|
22315
|
-
});
|
|
22316
|
-
return new Response(readable, { headers });
|
|
22317
|
-
}
|
|
22318
|
-
/**
|
|
22319
|
-
* Replays events that would have been sent after the specified event ID
|
|
22320
|
-
* Only used when resumability is enabled
|
|
22321
|
-
*/
|
|
22322
|
-
async replayEvents(lastEventId) {
|
|
22323
|
-
if (!this._eventStore) {
|
|
22324
|
-
this.onerror?.(new Error("Event store not configured"));
|
|
22325
|
-
return this.createJsonErrorResponse(400, -32e3, "Event store not configured");
|
|
22326
|
-
}
|
|
22327
|
-
try {
|
|
22328
|
-
let streamId;
|
|
22329
|
-
if (this._eventStore.getStreamIdForEventId) {
|
|
22330
|
-
streamId = await this._eventStore.getStreamIdForEventId(lastEventId);
|
|
22331
|
-
if (!streamId) {
|
|
22332
|
-
this.onerror?.(new Error("Invalid event ID format"));
|
|
22333
|
-
return this.createJsonErrorResponse(400, -32e3, "Invalid event ID format");
|
|
22334
|
-
}
|
|
22335
|
-
if (this._streamMapping.get(streamId) !== void 0) {
|
|
22336
|
-
this.onerror?.(new Error("Conflict: Stream already has an active connection"));
|
|
22337
|
-
return this.createJsonErrorResponse(409, -32e3, "Conflict: Stream already has an active connection");
|
|
22338
|
-
}
|
|
22339
|
-
}
|
|
22340
|
-
const headers = {
|
|
22341
|
-
"Content-Type": "text/event-stream",
|
|
22342
|
-
"Cache-Control": "no-cache, no-transform",
|
|
22343
|
-
Connection: "keep-alive"
|
|
22344
|
-
};
|
|
22345
|
-
if (this.sessionId !== void 0) {
|
|
22346
|
-
headers["mcp-session-id"] = this.sessionId;
|
|
22347
|
-
}
|
|
22348
|
-
const encoder = new TextEncoder();
|
|
22349
|
-
let streamController;
|
|
22350
|
-
const readable = new ReadableStream({
|
|
22351
|
-
start: (controller) => {
|
|
22352
|
-
streamController = controller;
|
|
22353
|
-
},
|
|
22354
|
-
cancel: () => {
|
|
22355
|
-
}
|
|
22356
|
-
});
|
|
22357
|
-
const replayedStreamId = await this._eventStore.replayEventsAfter(lastEventId, {
|
|
22358
|
-
send: async (eventId, message) => {
|
|
22359
|
-
const success = this.writeSSEEvent(streamController, encoder, message, eventId);
|
|
22360
|
-
if (!success) {
|
|
22361
|
-
this.onerror?.(new Error("Failed replay events"));
|
|
22362
|
-
try {
|
|
22363
|
-
streamController.close();
|
|
22364
|
-
} catch {
|
|
22365
|
-
}
|
|
22366
|
-
}
|
|
22367
|
-
}
|
|
22368
|
-
});
|
|
22369
|
-
this._streamMapping.set(replayedStreamId, {
|
|
22370
|
-
controller: streamController,
|
|
22371
|
-
encoder,
|
|
22372
|
-
cleanup: () => {
|
|
22373
|
-
this._streamMapping.delete(replayedStreamId);
|
|
22374
|
-
try {
|
|
22375
|
-
streamController.close();
|
|
22376
|
-
} catch {
|
|
22377
|
-
}
|
|
22378
|
-
}
|
|
22379
|
-
});
|
|
22380
|
-
return new Response(readable, { headers });
|
|
22381
|
-
} catch (error2) {
|
|
22382
|
-
this.onerror?.(error2);
|
|
22383
|
-
return this.createJsonErrorResponse(500, -32e3, "Error replaying events");
|
|
22384
|
-
}
|
|
22385
|
-
}
|
|
22386
|
-
/**
|
|
22387
|
-
* Writes an event to an SSE stream via controller with proper formatting
|
|
22388
|
-
*/
|
|
22389
|
-
writeSSEEvent(controller, encoder, message, eventId) {
|
|
22390
|
-
try {
|
|
22391
|
-
let eventData = `event: message
|
|
22392
|
-
`;
|
|
22393
|
-
if (eventId) {
|
|
22394
|
-
eventData += `id: ${eventId}
|
|
22395
|
-
`;
|
|
22396
|
-
}
|
|
22397
|
-
eventData += `data: ${JSON.stringify(message)}
|
|
22398
|
-
|
|
22399
|
-
`;
|
|
22400
|
-
controller.enqueue(encoder.encode(eventData));
|
|
22401
|
-
return true;
|
|
22402
|
-
} catch (error2) {
|
|
22403
|
-
this.onerror?.(error2);
|
|
22404
|
-
return false;
|
|
22405
|
-
}
|
|
22406
|
-
}
|
|
22407
|
-
/**
|
|
22408
|
-
* Handles unsupported requests (PUT, PATCH, etc.)
|
|
22409
|
-
*/
|
|
22410
|
-
handleUnsupportedRequest() {
|
|
22411
|
-
this.onerror?.(new Error("Method not allowed."));
|
|
22412
|
-
return new Response(JSON.stringify({
|
|
22413
|
-
jsonrpc: "2.0",
|
|
22414
|
-
error: {
|
|
22415
|
-
code: -32e3,
|
|
22416
|
-
message: "Method not allowed."
|
|
22417
|
-
},
|
|
22418
|
-
id: null
|
|
22419
|
-
}), {
|
|
22420
|
-
status: 405,
|
|
22421
|
-
headers: {
|
|
22422
|
-
Allow: "GET, POST, DELETE",
|
|
22423
|
-
"Content-Type": "application/json"
|
|
22424
|
-
}
|
|
22425
|
-
});
|
|
22426
|
-
}
|
|
22427
|
-
/**
|
|
22428
|
-
* Handles POST requests containing JSON-RPC messages
|
|
22429
|
-
*/
|
|
22430
|
-
async handlePostRequest(req, options) {
|
|
22431
|
-
try {
|
|
22432
|
-
const acceptHeader = req.headers.get("accept");
|
|
22433
|
-
if (!acceptHeader?.includes("application/json") || !acceptHeader.includes("text/event-stream")) {
|
|
22434
|
-
this.onerror?.(new Error("Not Acceptable: Client must accept both application/json and text/event-stream"));
|
|
22435
|
-
return this.createJsonErrorResponse(406, -32e3, "Not Acceptable: Client must accept both application/json and text/event-stream");
|
|
22436
|
-
}
|
|
22437
|
-
const ct = req.headers.get("content-type");
|
|
22438
|
-
if (!ct || !ct.includes("application/json")) {
|
|
22439
|
-
this.onerror?.(new Error("Unsupported Media Type: Content-Type must be application/json"));
|
|
22440
|
-
return this.createJsonErrorResponse(415, -32e3, "Unsupported Media Type: Content-Type must be application/json");
|
|
22441
|
-
}
|
|
22442
|
-
const requestInfo = {
|
|
22443
|
-
headers: Object.fromEntries(req.headers.entries()),
|
|
22444
|
-
url: new URL(req.url)
|
|
22445
|
-
};
|
|
22446
|
-
let rawMessage;
|
|
22447
|
-
if (options?.parsedBody !== void 0) {
|
|
22448
|
-
rawMessage = options.parsedBody;
|
|
22449
|
-
} else {
|
|
22450
|
-
try {
|
|
22451
|
-
rawMessage = await req.json();
|
|
22452
|
-
} catch {
|
|
22453
|
-
this.onerror?.(new Error("Parse error: Invalid JSON"));
|
|
22454
|
-
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON");
|
|
22455
|
-
}
|
|
22456
|
-
}
|
|
22457
|
-
let messages;
|
|
22458
|
-
try {
|
|
22459
|
-
if (Array.isArray(rawMessage)) {
|
|
22460
|
-
messages = rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg));
|
|
22461
|
-
} else {
|
|
22462
|
-
messages = [JSONRPCMessageSchema.parse(rawMessage)];
|
|
22463
|
-
}
|
|
22464
|
-
} catch {
|
|
22465
|
-
this.onerror?.(new Error("Parse error: Invalid JSON-RPC message"));
|
|
22466
|
-
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON-RPC message");
|
|
22467
|
-
}
|
|
22468
|
-
const isInitializationRequest = messages.some(isInitializeRequest);
|
|
22469
|
-
if (isInitializationRequest) {
|
|
22470
|
-
if (this._initialized && this.sessionId !== void 0) {
|
|
22471
|
-
this.onerror?.(new Error("Invalid Request: Server already initialized"));
|
|
22472
|
-
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Server already initialized");
|
|
22473
|
-
}
|
|
22474
|
-
if (messages.length > 1) {
|
|
22475
|
-
this.onerror?.(new Error("Invalid Request: Only one initialization request is allowed"));
|
|
22476
|
-
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Only one initialization request is allowed");
|
|
22477
|
-
}
|
|
22478
|
-
this.sessionId = this.sessionIdGenerator?.();
|
|
22479
|
-
this._initialized = true;
|
|
22480
|
-
if (this.sessionId && this._onsessioninitialized) {
|
|
22481
|
-
await Promise.resolve(this._onsessioninitialized(this.sessionId));
|
|
22482
|
-
}
|
|
22483
|
-
}
|
|
22484
|
-
if (!isInitializationRequest) {
|
|
22485
|
-
const sessionError = this.validateSession(req);
|
|
22486
|
-
if (sessionError) {
|
|
22487
|
-
return sessionError;
|
|
22488
|
-
}
|
|
22489
|
-
const protocolError = this.validateProtocolVersion(req);
|
|
22490
|
-
if (protocolError) {
|
|
22491
|
-
return protocolError;
|
|
22492
|
-
}
|
|
22493
|
-
}
|
|
22494
|
-
const hasRequests = messages.some(isJSONRPCRequest);
|
|
22495
|
-
if (!hasRequests) {
|
|
22496
|
-
for (const message of messages) {
|
|
22497
|
-
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
|
|
22498
|
-
}
|
|
22499
|
-
return new Response(null, { status: 202 });
|
|
22500
|
-
}
|
|
22501
|
-
const streamId = crypto.randomUUID();
|
|
22502
|
-
const initRequest = messages.find((m) => isInitializeRequest(m));
|
|
22503
|
-
const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
|
|
22504
|
-
if (this._enableJsonResponse) {
|
|
22505
|
-
return new Promise((resolve2) => {
|
|
22506
|
-
this._streamMapping.set(streamId, {
|
|
22507
|
-
resolveJson: resolve2,
|
|
22508
|
-
cleanup: () => {
|
|
22509
|
-
this._streamMapping.delete(streamId);
|
|
22510
|
-
}
|
|
22511
|
-
});
|
|
22512
|
-
for (const message of messages) {
|
|
22513
|
-
if (isJSONRPCRequest(message)) {
|
|
22514
|
-
this._requestToStreamMapping.set(message.id, streamId);
|
|
22515
|
-
}
|
|
22516
|
-
}
|
|
22517
|
-
for (const message of messages) {
|
|
22518
|
-
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
|
|
22519
|
-
}
|
|
22520
|
-
});
|
|
22521
|
-
}
|
|
22522
|
-
const encoder = new TextEncoder();
|
|
22523
|
-
let streamController;
|
|
22524
|
-
const readable = new ReadableStream({
|
|
22525
|
-
start: (controller) => {
|
|
22526
|
-
streamController = controller;
|
|
22527
|
-
},
|
|
22528
|
-
cancel: () => {
|
|
22529
|
-
this._streamMapping.delete(streamId);
|
|
22530
|
-
}
|
|
22531
|
-
});
|
|
22532
|
-
const headers = {
|
|
22533
|
-
"Content-Type": "text/event-stream",
|
|
22534
|
-
"Cache-Control": "no-cache",
|
|
22535
|
-
Connection: "keep-alive"
|
|
22536
|
-
};
|
|
22537
|
-
if (this.sessionId !== void 0) {
|
|
22538
|
-
headers["mcp-session-id"] = this.sessionId;
|
|
22539
|
-
}
|
|
22540
|
-
for (const message of messages) {
|
|
22541
|
-
if (isJSONRPCRequest(message)) {
|
|
22542
|
-
this._streamMapping.set(streamId, {
|
|
22543
|
-
controller: streamController,
|
|
22544
|
-
encoder,
|
|
22545
|
-
cleanup: () => {
|
|
22546
|
-
this._streamMapping.delete(streamId);
|
|
22547
|
-
try {
|
|
22548
|
-
streamController.close();
|
|
22549
|
-
} catch {
|
|
22550
|
-
}
|
|
22551
|
-
}
|
|
22552
|
-
});
|
|
22553
|
-
this._requestToStreamMapping.set(message.id, streamId);
|
|
22554
|
-
}
|
|
22555
|
-
}
|
|
22556
|
-
await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
|
|
22557
|
-
for (const message of messages) {
|
|
22558
|
-
let closeSSEStream;
|
|
22559
|
-
let closeStandaloneSSEStream;
|
|
22560
|
-
if (isJSONRPCRequest(message) && this._eventStore && clientProtocolVersion >= "2025-11-25") {
|
|
22561
|
-
closeSSEStream = () => {
|
|
22562
|
-
this.closeSSEStream(message.id);
|
|
22563
|
-
};
|
|
22564
|
-
closeStandaloneSSEStream = () => {
|
|
22565
|
-
this.closeStandaloneSSEStream();
|
|
22566
|
-
};
|
|
22567
|
-
}
|
|
22568
|
-
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo, closeSSEStream, closeStandaloneSSEStream });
|
|
22569
|
-
}
|
|
22570
|
-
return new Response(readable, { status: 200, headers });
|
|
22571
|
-
} catch (error2) {
|
|
22572
|
-
this.onerror?.(error2);
|
|
22573
|
-
return this.createJsonErrorResponse(400, -32700, "Parse error", { data: String(error2) });
|
|
22574
|
-
}
|
|
22575
|
-
}
|
|
22576
|
-
/**
|
|
22577
|
-
* Handles DELETE requests to terminate sessions
|
|
22578
|
-
*/
|
|
22579
|
-
async handleDeleteRequest(req) {
|
|
22580
|
-
const sessionError = this.validateSession(req);
|
|
22581
|
-
if (sessionError) {
|
|
22582
|
-
return sessionError;
|
|
22583
|
-
}
|
|
22584
|
-
const protocolError = this.validateProtocolVersion(req);
|
|
22585
|
-
if (protocolError) {
|
|
22586
|
-
return protocolError;
|
|
22587
|
-
}
|
|
22588
|
-
await Promise.resolve(this._onsessionclosed?.(this.sessionId));
|
|
22589
|
-
await this.close();
|
|
22590
|
-
return new Response(null, { status: 200 });
|
|
22591
|
-
}
|
|
22592
|
-
/**
|
|
22593
|
-
* Validates session ID for non-initialization requests.
|
|
22594
|
-
* Returns Response error if invalid, undefined otherwise
|
|
22595
|
-
*/
|
|
22596
|
-
validateSession(req) {
|
|
22597
|
-
if (this.sessionIdGenerator === void 0) {
|
|
22598
|
-
return void 0;
|
|
22599
|
-
}
|
|
22600
|
-
if (!this._initialized) {
|
|
22601
|
-
this.onerror?.(new Error("Bad Request: Server not initialized"));
|
|
22602
|
-
return this.createJsonErrorResponse(400, -32e3, "Bad Request: Server not initialized");
|
|
22603
|
-
}
|
|
22604
|
-
const sessionId = req.headers.get("mcp-session-id");
|
|
22605
|
-
if (!sessionId) {
|
|
22606
|
-
this.onerror?.(new Error("Bad Request: Mcp-Session-Id header is required"));
|
|
22607
|
-
return this.createJsonErrorResponse(400, -32e3, "Bad Request: Mcp-Session-Id header is required");
|
|
22608
|
-
}
|
|
22609
|
-
if (sessionId !== this.sessionId) {
|
|
22610
|
-
this.onerror?.(new Error("Session not found"));
|
|
22611
|
-
return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
22612
|
-
}
|
|
22613
|
-
return void 0;
|
|
22614
|
-
}
|
|
22615
|
-
/**
|
|
22616
|
-
* Validates the MCP-Protocol-Version header on incoming requests.
|
|
22617
|
-
*
|
|
22618
|
-
* For initialization: Version negotiation handles unknown versions gracefully
|
|
22619
|
-
* (server responds with its supported version).
|
|
22620
|
-
*
|
|
22621
|
-
* For subsequent requests with MCP-Protocol-Version header:
|
|
22622
|
-
* - Accept if in supported list
|
|
22623
|
-
* - 400 if unsupported
|
|
22624
|
-
*
|
|
22625
|
-
* For HTTP requests without the MCP-Protocol-Version header:
|
|
22626
|
-
* - Accept and default to the version negotiated at initialization
|
|
22627
|
-
*/
|
|
22628
|
-
validateProtocolVersion(req) {
|
|
22629
|
-
const protocolVersion = req.headers.get("mcp-protocol-version");
|
|
22630
|
-
if (protocolVersion !== null && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
|
|
22631
|
-
this.onerror?.(new Error(`Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`));
|
|
22632
|
-
return this.createJsonErrorResponse(400, -32e3, `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`);
|
|
22633
|
-
}
|
|
22634
|
-
return void 0;
|
|
22635
|
-
}
|
|
22636
|
-
async close() {
|
|
22637
|
-
this._streamMapping.forEach(({ cleanup }) => {
|
|
22638
|
-
cleanup();
|
|
22639
|
-
});
|
|
22640
|
-
this._streamMapping.clear();
|
|
22641
|
-
this._requestResponseMap.clear();
|
|
22642
|
-
this.onclose?.();
|
|
22643
|
-
}
|
|
22644
|
-
/**
|
|
22645
|
-
* Close an SSE stream for a specific request, triggering client reconnection.
|
|
22646
|
-
* Use this to implement polling behavior during long-running operations -
|
|
22647
|
-
* client will reconnect after the retry interval specified in the priming event.
|
|
22648
|
-
*/
|
|
22649
|
-
closeSSEStream(requestId) {
|
|
22650
|
-
const streamId = this._requestToStreamMapping.get(requestId);
|
|
22651
|
-
if (!streamId)
|
|
22652
|
-
return;
|
|
22653
|
-
const stream = this._streamMapping.get(streamId);
|
|
22654
|
-
if (stream) {
|
|
22655
|
-
stream.cleanup();
|
|
22656
|
-
}
|
|
22657
|
-
}
|
|
22658
|
-
/**
|
|
22659
|
-
* Close the standalone GET SSE stream, triggering client reconnection.
|
|
22660
|
-
* Use this to implement polling behavior for server-initiated notifications.
|
|
22661
|
-
*/
|
|
22662
|
-
closeStandaloneSSEStream() {
|
|
22663
|
-
const stream = this._streamMapping.get(this._standaloneSseStreamId);
|
|
22664
|
-
if (stream) {
|
|
22665
|
-
stream.cleanup();
|
|
22666
|
-
}
|
|
22667
|
-
}
|
|
22668
|
-
async send(message, options) {
|
|
22669
|
-
let requestId = options?.relatedRequestId;
|
|
22670
|
-
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
22671
|
-
requestId = message.id;
|
|
22672
|
-
}
|
|
22673
|
-
if (requestId === void 0) {
|
|
22674
|
-
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
22675
|
-
throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");
|
|
22676
|
-
}
|
|
22677
|
-
let eventId;
|
|
22678
|
-
if (this._eventStore) {
|
|
22679
|
-
eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
|
|
22680
|
-
}
|
|
22681
|
-
const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
|
|
22682
|
-
if (standaloneSse === void 0) {
|
|
22683
|
-
return;
|
|
22684
|
-
}
|
|
22685
|
-
if (standaloneSse.controller && standaloneSse.encoder) {
|
|
22686
|
-
this.writeSSEEvent(standaloneSse.controller, standaloneSse.encoder, message, eventId);
|
|
22687
|
-
}
|
|
22688
|
-
return;
|
|
22689
|
-
}
|
|
22690
|
-
const streamId = this._requestToStreamMapping.get(requestId);
|
|
22691
|
-
if (!streamId) {
|
|
22692
|
-
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
22693
|
-
}
|
|
22694
|
-
const stream = this._streamMapping.get(streamId);
|
|
22695
|
-
if (!this._enableJsonResponse && stream?.controller && stream?.encoder) {
|
|
22696
|
-
let eventId;
|
|
22697
|
-
if (this._eventStore) {
|
|
22698
|
-
eventId = await this._eventStore.storeEvent(streamId, message);
|
|
22699
|
-
}
|
|
22700
|
-
this.writeSSEEvent(stream.controller, stream.encoder, message, eventId);
|
|
22701
|
-
}
|
|
22702
|
-
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
22703
|
-
this._requestResponseMap.set(requestId, message);
|
|
22704
|
-
const relatedIds = Array.from(this._requestToStreamMapping.entries()).filter(([_, sid]) => sid === streamId).map(([id]) => id);
|
|
22705
|
-
const allResponsesReady = relatedIds.every((id) => this._requestResponseMap.has(id));
|
|
22706
|
-
if (allResponsesReady) {
|
|
22707
|
-
if (!stream) {
|
|
22708
|
-
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
22709
|
-
}
|
|
22710
|
-
if (this._enableJsonResponse && stream.resolveJson) {
|
|
22711
|
-
const headers = {
|
|
22712
|
-
"Content-Type": "application/json"
|
|
22713
|
-
};
|
|
22714
|
-
if (this.sessionId !== void 0) {
|
|
22715
|
-
headers["mcp-session-id"] = this.sessionId;
|
|
22716
|
-
}
|
|
22717
|
-
const responses = relatedIds.map((id) => this._requestResponseMap.get(id));
|
|
22718
|
-
if (responses.length === 1) {
|
|
22719
|
-
stream.resolveJson(new Response(JSON.stringify(responses[0]), { status: 200, headers }));
|
|
22720
|
-
} else {
|
|
22721
|
-
stream.resolveJson(new Response(JSON.stringify(responses), { status: 200, headers }));
|
|
22722
|
-
}
|
|
22723
|
-
} else {
|
|
22724
|
-
stream.cleanup();
|
|
22725
|
-
}
|
|
22726
|
-
for (const id of relatedIds) {
|
|
22727
|
-
this._requestResponseMap.delete(id);
|
|
22728
|
-
this._requestToStreamMapping.delete(id);
|
|
22729
|
-
}
|
|
22730
|
-
}
|
|
22731
|
-
}
|
|
22732
|
-
}
|
|
22733
|
-
};
|
|
22734
|
-
}
|
|
22735
|
-
});
|
|
22736
|
-
|
|
22737
22174
|
// src/api.ts
|
|
22738
22175
|
var SquirrelApiClient;
|
|
22739
22176
|
var init_api2 = __esm({
|
|
@@ -22987,24 +22424,11 @@ var server_exports = {};
|
|
|
22987
22424
|
__export(server_exports, {
|
|
22988
22425
|
MARKDOWN_CAPABILITIES_DESCRIPTION: () => MARKDOWN_CAPABILITIES_DESCRIPTION,
|
|
22989
22426
|
createMcpServer: () => createMcpServer,
|
|
22990
|
-
handleRequest: () => handleRequest,
|
|
22991
22427
|
inferMimeType: () => inferMimeType
|
|
22992
22428
|
});
|
|
22993
22429
|
import { readFile } from "node:fs/promises";
|
|
22994
22430
|
import { basename, extname, resolve, sep } from "node:path";
|
|
22995
22431
|
import { tmpdir } from "node:os";
|
|
22996
|
-
function emitAuditEvent(event) {
|
|
22997
|
-
if (!AUDIT_INTERNAL_SECRET) return;
|
|
22998
|
-
fetch(`${SQUIRREL_API_BASE_URL}/audit/internal`, {
|
|
22999
|
-
method: "POST",
|
|
23000
|
-
headers: {
|
|
23001
|
-
"Content-Type": "application/json",
|
|
23002
|
-
"x-audit-internal-secret": AUDIT_INTERNAL_SECRET
|
|
23003
|
-
},
|
|
23004
|
-
body: JSON.stringify(event)
|
|
23005
|
-
}).catch(() => {
|
|
23006
|
-
});
|
|
23007
|
-
}
|
|
23008
22432
|
async function buildContext(creds) {
|
|
23009
22433
|
const client = new SquirrelApiClient(SQUIRREL_API_BASE_URL, creds.apiKey);
|
|
23010
22434
|
const { salt, keyCheck, wrappedDek, dekIv } = await client.getEncryption();
|
|
@@ -23020,7 +22444,6 @@ async function buildContext(creds) {
|
|
|
23020
22444
|
} else {
|
|
23021
22445
|
throw new Error("Inconsistent encryption metadata from server: wrappedDek/dekIv mismatch");
|
|
23022
22446
|
}
|
|
23023
|
-
emitAuditEvent({ eventType: "auth.mcp.access", authMethod: "mcp", outcome: "success" });
|
|
23024
22447
|
return { client, key };
|
|
23025
22448
|
}
|
|
23026
22449
|
function decryptNoteTitle(note, key) {
|
|
@@ -23979,29 +23402,11 @@ function createMcpServer(creds, opts = {}) {
|
|
|
23979
23402
|
}
|
|
23980
23403
|
return server2;
|
|
23981
23404
|
}
|
|
23982
|
-
|
|
23983
|
-
const server2 = createMcpServer(creds);
|
|
23984
|
-
const transport2 = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
23985
|
-
await server2.connect(transport2);
|
|
23986
|
-
const request = new Request("https://lambda/mcp", {
|
|
23987
|
-
method: "POST",
|
|
23988
|
-
headers: event.headers,
|
|
23989
|
-
body: event.body ?? ""
|
|
23990
|
-
});
|
|
23991
|
-
const response = await transport2.handleRequest(request);
|
|
23992
|
-
const responseBody = await response.text();
|
|
23993
|
-
const responseHeaders = {};
|
|
23994
|
-
response.headers.forEach((value, key) => {
|
|
23995
|
-
responseHeaders[key] = value;
|
|
23996
|
-
});
|
|
23997
|
-
return { statusCode: response.status, headers: responseHeaders, body: responseBody };
|
|
23998
|
-
}
|
|
23999
|
-
var SQUIRREL_API_BASE_URL, AUDIT_INTERNAL_SECRET, MIME_TYPES, MARKDOWN_CAPABILITIES_DESCRIPTION;
|
|
23405
|
+
var SQUIRREL_API_BASE_URL, MIME_TYPES, MARKDOWN_CAPABILITIES_DESCRIPTION;
|
|
24000
23406
|
var init_server3 = __esm({
|
|
24001
23407
|
"src/server.ts"() {
|
|
24002
23408
|
"use strict";
|
|
24003
23409
|
init_mcp();
|
|
24004
|
-
init_webStandardStreamableHttp();
|
|
24005
23410
|
init_zod();
|
|
24006
23411
|
init_api2();
|
|
24007
23412
|
init_crypto();
|
|
@@ -24010,7 +23415,6 @@ var init_server3 = __esm({
|
|
|
24010
23415
|
if (!SQUIRREL_API_BASE_URL) {
|
|
24011
23416
|
throw new Error("SQUIRREL_API_BASE_URL environment variable is required");
|
|
24012
23417
|
}
|
|
24013
|
-
AUDIT_INTERNAL_SECRET = process.env.AUDIT_INTERNAL_SECRET;
|
|
24014
23418
|
MIME_TYPES = {
|
|
24015
23419
|
png: "image/png",
|
|
24016
23420
|
jpg: "image/jpeg",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@squirrelnotes.app/mcp",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "MCP stdio CLI for Squirrel Notes — bridges AI agents to the encrypted note-taking API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,10 +14,8 @@
|
|
|
14
14
|
"access": "public"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "
|
|
17
|
+
"build": "npm run build:stdio",
|
|
18
18
|
"build:stdio": "esbuild src/stdio-handler.ts --bundle --platform=node --target=node20 --outfile=dist/stdio-handler.js --format=esm --banner:js='#!/usr/bin/env node'",
|
|
19
|
-
"build:all": "npm run build && npm run build:stdio",
|
|
20
|
-
"dev": "SQUIRREL_API_BASE_URL=http://localhost:3001 tsx src/dev-server.ts",
|
|
21
19
|
"test": "node --experimental-vm-modules node_modules/.bin/jest",
|
|
22
20
|
"lint": "tsc --noEmit"
|
|
23
21
|
},
|
|
@@ -26,13 +24,11 @@
|
|
|
26
24
|
"zod": "^3.23.8"
|
|
27
25
|
},
|
|
28
26
|
"devDependencies": {
|
|
29
|
-
"@types/aws-lambda": "^8.10.145",
|
|
30
27
|
"@types/jest": "^30.0.0",
|
|
31
28
|
"@types/node": "^20.0.0",
|
|
32
29
|
"esbuild": "^0.28.1",
|
|
33
30
|
"jest": "^29.7.0",
|
|
34
31
|
"ts-jest": "^29.1.5",
|
|
35
|
-
"tsx": "^4.21.0",
|
|
36
32
|
"typescript": "^5.4.5"
|
|
37
33
|
},
|
|
38
34
|
"jest": {
|