@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 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. The security implications depend on which transport you use:
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
- ### stdio (local process) — recommended
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 | Transport | Description |
69
- |------|-----------|-------------|
70
- | `list_attachments` | All | Lists all attachments for a note with decrypted filenames and metadata. |
71
- | `get_attachment_download_url` | All | Returns a presigned S3 download URL (expires in 5 minutes). |
72
- | `delete_attachment` | All | Permanently deletes an attachment from S3 and the database. |
73
- | `upload_attachment_from_path` | STDIO only | 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. |
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
- Every request requires two headers:
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
- > **Which setup should I use?**
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
- cd mcp
300
- npm install
301
- npm run dev
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
- This starts an HTTP MCP server at `http://localhost:3002` pointing at the local backend (`http://localhost:3001`). No build step needed — `tsx` runs TypeScript directly.
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
- curl -s http://localhost:3002 -X POST \
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.
@@ -4755,12 +4755,11 @@ function assertCompleteRequestResourceTemplate(request) {
4755
4755
  }
4756
4756
  void request;
4757
4757
  }
4758
- var LATEST_PROTOCOL_VERSION, DEFAULT_NEGOTIATED_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, isInitializeRequest, 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;
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
- * Time in milliseconds to keep task results available after completion.
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: union([number2(), _null3()]).optional(),
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
- this._readBuffer.append(chunk);
6322
- this.processReadBuffer();
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.delete(request.id);
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
- let methodValue;
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
- async function handleRequest(event, creds) {
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.1",
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": "esbuild src/handler.ts --bundle --platform=node --target=node20 --outfile=dist/handler.js --format=cjs --external:aws-sdk",
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": {