@doist/todoist-mcp 10.3.2 → 10.3.3

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.
@@ -0,0 +1,19 @@
1
+ import { Express } from 'express';
2
+ type CreateHttpAppOptions = {
3
+ todoistApiKey: string;
4
+ baseUrl?: string;
5
+ /** Hostnames trusted in the Host/Origin headers (DNS-rebinding protection). */
6
+ allowedHosts: string[];
7
+ };
8
+ /**
9
+ * Build the Express app for the MCP HTTP server.
10
+ *
11
+ * The DNS-rebinding guard (`requireTrustedHost`) is scoped to the sensitive
12
+ * `/mcp` routes and runs ahead of body parsing and token auth there. `/health`
13
+ * is intentionally unguarded so deployment health probes — which send the
14
+ * target's private IP in the Host header — keep working; it exposes no account
15
+ * data.
16
+ */
17
+ declare function createHttpApp({ todoistApiKey, baseUrl, allowedHosts }: CreateHttpAppOptions): Express;
18
+ export { createHttpApp, type CreateHttpAppOptions };
19
+ //# sourceMappingURL=http-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-app.d.ts","sourceRoot":"","sources":["../src/http-app.ts"],"names":[],"mappings":"AACA,OAAgB,EAAE,KAAK,OAAO,EAA+B,MAAM,SAAS,CAAA;AAK5E,KAAK,oBAAoB,GAAG;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,EAAE,CAAA;CACzB,CAAA;AAED;;;;;;;;GAQG;AACH,iBAAS,aAAa,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,oBAAoB,GAAG,OAAO,CAgD9F;AAED,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,CAAA"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { f as s, s as a, l as e, m as t, a as o, u as d, r as i, d as r, b as n, c as l, e as c, h as p, i as m, j as f, k, n as T, o as u, p as g, q as j, t as v, v as P, w as h, x as S, y as b, z as A, A as y, B as C, C as G, D as F, E as x, F as E, G as L, H as O, I as w, J as H, K as I, L as M, M as q, N as z, O as B, P as D, Q as N, R, S as W } from "./mcp-server-C_UexOyp.js";
2
- import { T as V, g as _ } from "./mcp-server-C_UexOyp.js";
3
- import { r as Y, v as Z } from "./require-valid-todoist-token-B0fNp1hu.js";
1
+ import { f as s, s as a, l as e, m as t, a as o, u as d, r as i, d as r, b as n, c as l, e as c, h as p, i as m, j as f, k, n as T, o as u, p as g, q as j, t as v, v as P, w as h, x as S, y as b, z as A, A as y, B as C, C as G, D as F, E as x, F as E, G as L, H as O, I as w, J as H, K as I, L as M, M as q, N as z, O as B, P as D, Q as N, R, S as W } from "./mcp-server-BiypwfCF.js";
2
+ import { T as V, g as _ } from "./mcp-server-BiypwfCF.js";
3
+ import { r as Y, v as Z } from "./require-valid-todoist-token-CMvIva7A.js";
4
4
  const K = {
5
5
  // Task management tools
6
6
  addTasks: W,
package/dist/main-http.js CHANGED
@@ -1,41 +1,100 @@
1
1
  #!/usr/bin/env node
2
- import { isIP as l } from "node:net";
3
- import { StreamableHTTPServerTransport as f } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
- import m from "dotenv";
5
- import a from "express";
6
- import { g as d } from "./mcp-server-C_UexOyp.js";
7
- import { r as T } from "./require-valid-todoist-token-B0fNp1hu.js";
8
- m.config({ quiet: !0 });
9
- const s = Number.parseInt(process.env.PORT || "3000", 10), v = process.env.HOST || "127.0.0.1", i = u(v);
10
- function u(r) {
11
- return r.startsWith("[") && r.endsWith("]") ? r.slice(1, -1) : r;
12
- }
13
- function O(r) {
14
- return r === "0.0.0.0" || r === "::" ? "localhost" : p(r);
15
- }
16
- function p(r) {
17
- return l(r) === 6 ? `[${r}]` : r;
18
- }
19
- function S(r) {
20
- return r = u(r), r === "localhost" || r === "::1" || r === "0:0:0:0:0:0:0:1" ? !0 : l(r) === 4 ? r.split(".")[0] === "127" : !1;
21
- }
22
- function I() {
23
- const r = process.env.TODOIST_BASE_URL, c = process.env.TODOIST_API_KEY;
24
- c || (console.error("Error: TODOIST_API_KEY environment variable is required"), process.exit(1));
25
- const o = a();
26
- o.use(a.json()), o.get("/health", (e, t) => {
27
- t.json({ status: "ok" });
28
- }), o.post(
2
+ import { isIP as p } from "node:net";
3
+ import O from "dotenv";
4
+ import { StreamableHTTPServerTransport as S } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
+ import m from "express";
6
+ import { g as v } from "./mcp-server-BiypwfCF.js";
7
+ import { r as w } from "./require-valid-todoist-token-CMvIva7A.js";
8
+ function f(t) {
9
+ return t.startsWith("[") && t.endsWith("]") ? t.slice(1, -1) : t;
10
+ }
11
+ function I(t) {
12
+ return t = f(t), t === "localhost" || t === "::1" || t === "0:0:0:0:0:0:0:1" ? !0 : p(t) === 4 ? t.split(".")[0] === "127" : !1;
13
+ }
14
+ const _ = ["localhost", "127.0.0.1", "[::1]"], g = /[@/\\?#\s]/;
15
+ function h(t) {
16
+ if (!g.test(t))
17
+ try {
18
+ return new URL(`http://${t}`).hostname.toLowerCase();
19
+ } catch {
20
+ return;
21
+ }
22
+ }
23
+ function L(t) {
24
+ let r;
25
+ try {
26
+ r = new URL(t);
27
+ } catch {
28
+ return;
29
+ }
30
+ if (!(r.username || r.password || r.pathname !== "/" || r.search || r.hash))
31
+ return r.hostname.toLowerCase();
32
+ }
33
+ function A(t, r) {
34
+ const o = new Set(_);
35
+ for (const s of (r ?? "").split(",")) {
36
+ const n = s.trim();
37
+ n && o.add(n);
38
+ }
39
+ const e = f(t);
40
+ return e && e !== "0.0.0.0" && e !== "::" && o.add(p(e) === 6 ? `[${e}]` : e), [...o];
41
+ }
42
+ function d(t, r) {
43
+ t.status(403).json({
44
+ jsonrpc: "2.0",
45
+ error: { code: -32e3, message: r },
46
+ id: null
47
+ });
48
+ }
49
+ function b(t) {
50
+ const r = /* @__PURE__ */ new Set();
51
+ for (const o of t.allowedHosts) {
52
+ const e = h(o);
53
+ if (!e)
54
+ throw new Error(
55
+ `Invalid allowed host ${JSON.stringify(o)}: must be a hostname or host:port (IPv6 literals must be bracketed, e.g. [::1])`
56
+ );
57
+ r.add(e);
58
+ }
59
+ return (o, e, s) => {
60
+ const n = o.headers.host;
61
+ if (!n) {
62
+ d(e, "Missing Host header");
63
+ return;
64
+ }
65
+ const i = h(n);
66
+ if (!i || !r.has(i)) {
67
+ d(e, `Invalid Host: ${n}`);
68
+ return;
69
+ }
70
+ const a = o.headers.origin;
71
+ if (a) {
72
+ const l = L(a);
73
+ if (!l || !r.has(l)) {
74
+ d(e, `Invalid Origin: ${a}`);
75
+ return;
76
+ }
77
+ }
78
+ s();
79
+ };
80
+ }
81
+ function E({ todoistApiKey: t, baseUrl: r, allowedHosts: o }) {
82
+ const e = m(), s = b({ allowedHosts: o });
83
+ return e.get("/health", (n, i) => {
84
+ i.json({ status: "ok" });
85
+ }), e.post(
29
86
  "/mcp",
30
- T({ type: "static", apiKey: c, baseUrl: r }),
31
- async (e, t) => {
87
+ s,
88
+ m.json(),
89
+ w({ type: "static", apiKey: t, baseUrl: r }),
90
+ async (n, i) => {
32
91
  try {
33
- const n = new f({
92
+ const a = new S({
34
93
  sessionIdGenerator: void 0
35
94
  });
36
- await d({ todoistApiKey: c, baseUrl: r }).connect(n), await n.handleRequest(e, t, e.body);
37
- } catch (n) {
38
- console.error("[Error] Request handling failed:", n), t.status(500).json({
95
+ await v({ todoistApiKey: t, baseUrl: r }).connect(a), await a.handleRequest(n, i, n.body);
96
+ } catch (a) {
97
+ console.error("[Error] Request handling failed:", a), i.status(500).json({
39
98
  jsonrpc: "2.0",
40
99
  error: {
41
100
  code: -32603,
@@ -45,13 +104,27 @@ function I() {
45
104
  });
46
105
  }
47
106
  }
48
- ), o.get("/mcp", (e, t) => {
49
- t.status(405).set("Allow", "POST").send("Method Not Allowed");
50
- }), o.listen(s, i, () => {
51
- const e = O(i);
52
- console.error(`Todoist MCP HTTP Server started on ${p(i)}:${s}`), console.error(`MCP endpoint: http://${e}:${s}/mcp`), console.error(`Health check: http://${e}:${s}/health`), S(i) || console.error(
53
- "Warning: todoist-mcp-http is reachable from other hosts. Protect this service with trusted network/auth controls because requests run with TODOIST_API_KEY."
107
+ ), e.get("/mcp", s, (n, i) => {
108
+ i.status(405).set("Allow", "POST").send("Method Not Allowed");
109
+ }), e;
110
+ }
111
+ O.config({ quiet: !0 });
112
+ const c = Number.parseInt(process.env.PORT || "3000", 10), T = process.env.HOST || "127.0.0.1", u = f(T);
113
+ function P(t) {
114
+ return t === "0.0.0.0" || t === "::" ? "localhost" : H(t);
115
+ }
116
+ function H(t) {
117
+ return p(t) === 6 ? `[${t}]` : t;
118
+ }
119
+ function $() {
120
+ const t = process.env.TODOIST_BASE_URL, r = process.env.TODOIST_API_KEY;
121
+ r || (console.error("Error: TODOIST_API_KEY environment variable is required"), process.exit(1));
122
+ const o = A(T, process.env.ALLOWED_HOSTS);
123
+ E({ todoistApiKey: r, baseUrl: t, allowedHosts: o }).listen(c, u, () => {
124
+ const s = P(u);
125
+ console.error(`Todoist MCP HTTP Server started on ${H(u)}:${c}`), console.error(`MCP endpoint: http://${s}:${c}/mcp`), console.error(`Health check: http://${s}:${c}/health`), I(u) || console.error(
126
+ "Warning: todoist-mcp-http is reachable from other hosts. Set ALLOWED_HOSTS to the hostnames clients use and protect this service with trusted network/auth controls because requests run with TODOIST_API_KEY."
54
127
  );
55
128
  });
56
129
  }
57
- I();
130
+ $();
package/dist/main.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport as s } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import c from "dotenv";
4
- import { g as i } from "./mcp-server-C_UexOyp.js";
4
+ import { g as i } from "./mcp-server-BiypwfCF.js";
5
5
  function p() {
6
6
  const e = process.env.TODOIST_BASE_URL, r = process.env.TODOIST_API_KEY;
7
7
  if (!r)
@@ -309,7 +309,7 @@ function Ns(e) {
309
309
  const t = _s(e);
310
310
  return t ? Rs(t) : Ls(e);
311
311
  }
312
- const Ms = "10.3.2", Fs = {
312
+ const Ms = "10.3.3", Fs = {
313
313
  version: Ms
314
314
  }, Hs = "todoist-mcp", Je = Fs.version, _t = new fs(), pt = bs(import.meta.url);
315
315
  let ye;
@@ -0,0 +1,44 @@
1
+ import { RequestHandler } from 'express';
2
+ type RequireTrustedHostOptions = {
3
+ /**
4
+ * Port-agnostic hostnames permitted in the `Host` and `Origin` headers.
5
+ * IPv6 entries must be bracketed, e.g. `[::1]`. Invalid entries cause
6
+ * {@link requireTrustedHost} to throw at construction time (fail fast).
7
+ */
8
+ allowedHosts: string[];
9
+ };
10
+ /**
11
+ * Loopback hostnames always trusted by {@link requireTrustedHost}. Stored
12
+ * bracketed for IPv6 because `new URL('http://[::1]').hostname` returns `[::1]`.
13
+ */
14
+ declare const DEFAULT_ALLOWED_HOSTS: string[];
15
+ /**
16
+ * Build the list of hostnames trusted in the Host and Origin headers. Always
17
+ * includes the loopback defaults, plus any names from `allowedHostsEnv`
18
+ * (comma-separated), plus the configured `host` (so an explicit bind — loopback
19
+ * alias like 127.0.1.1, a LAN IP, etc. — is always trusted). Bind wildcards
20
+ * (0.0.0.0/::) are never added because they are never valid Host header values —
21
+ * operators exposing on a wildcard must enumerate client hostnames via
22
+ * ALLOWED_HOSTS.
23
+ */
24
+ declare function buildAllowedHosts(host: string, allowedHostsEnv?: string): string[];
25
+ /**
26
+ * Express middleware that rejects requests whose `Host` or `Origin` header is
27
+ * not in the trusted allowlist. This is DNS-rebinding protection: a malicious
28
+ * website that rebinds its domain to a victim's loopback address keeps its own
29
+ * `Host`/`Origin`, so string-matching against the allowlist (we never DNS
30
+ * resolve) blocks the request before it can reach the MCP transport.
31
+ *
32
+ * - Missing `Host` → 403.
33
+ * - `Host` hostname not in the allowlist → 403.
34
+ * - `Origin` absent → allowed (non-browser clients such as `mcp-remote`/`curl`
35
+ * send none; the browser attack path always sends one).
36
+ * - `Origin` present but untrusted or unparseable → 403.
37
+ *
38
+ * Throws at construction time if `allowedHosts` contains an entry that isn't a
39
+ * valid authority, so misconfiguration fails fast instead of becoming opaque
40
+ * 403s at request time.
41
+ */
42
+ declare function requireTrustedHost(options: RequireTrustedHostOptions): RequestHandler;
43
+ export { DEFAULT_ALLOWED_HOSTS, buildAllowedHosts, requireTrustedHost, type RequireTrustedHostOptions, };
44
+ //# sourceMappingURL=require-trusted-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-trusted-host.d.ts","sourceRoot":"","sources":["../../src/middleware/require-trusted-host.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAA;AAG9E,KAAK,yBAAyB,GAAG;IAC7B;;;;OAIG;IACH,YAAY,EAAE,MAAM,EAAE,CAAA;CACzB,CAAA;AAED;;;GAGG;AACH,QAAA,MAAM,qBAAqB,UAAsC,CAAA;AA4CjE;;;;;;;;GAQG;AACH,iBAAS,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAkB3E;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,iBAAS,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,cAAc,CAyC9E;AAED,OAAO,EACH,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,yBAAyB,GACjC,CAAA"}
@@ -1,4 +1,4 @@
1
- import { U as A, V as v, W as T } from "./mcp-server-C_UexOyp.js";
1
+ import { U as A, V as v, W as T } from "./mcp-server-BiypwfCF.js";
2
2
  const y = /* @__PURE__ */ new Set([401, 403]);
3
3
  function C(e) {
4
4
  const t = T(e);
@@ -0,0 +1,6 @@
1
+ /** Strip the surrounding brackets from a bracketed IPv6 literal, if present. */
2
+ declare function normalizeListenHost(host: string): string;
3
+ /** Whether `host` refers to a loopback interface (localhost / 127.0.0.0/8 / ::1). */
4
+ declare function isLoopbackHost(host: string): boolean;
5
+ export { isLoopbackHost, normalizeListenHost };
6
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/utils/host.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,iBAAS,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED,qFAAqF;AACrF,iBAAS,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAY7C;AAED,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/todoist-mcp",
3
- "version": "10.3.2",
3
+ "version": "10.3.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",