vikunja-mcp-ng 0.6.2 → 0.7.0-beta.1

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.
Files changed (176) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +52 -69
  3. package/dist/auth/CredentialSource.d.ts +92 -0
  4. package/dist/auth/CredentialSource.d.ts.map +1 -0
  5. package/dist/auth/CredentialSource.js +99 -0
  6. package/dist/auth/CredentialSource.js.map +1 -0
  7. package/dist/auth/index.d.ts +3 -0
  8. package/dist/auth/index.d.ts.map +1 -1
  9. package/dist/auth/index.js +5 -1
  10. package/dist/auth/index.js.map +1 -1
  11. package/dist/auth/oidc/joseLoader.d.ts +25 -0
  12. package/dist/auth/oidc/joseLoader.d.ts.map +1 -0
  13. package/dist/auth/oidc/joseLoader.js +36 -0
  14. package/dist/auth/oidc/joseLoader.js.map +1 -0
  15. package/dist/auth/oidc/jwtValidator.d.ts +46 -0
  16. package/dist/auth/oidc/jwtValidator.d.ts.map +1 -0
  17. package/dist/auth/oidc/jwtValidator.js +145 -0
  18. package/dist/auth/oidc/jwtValidator.js.map +1 -0
  19. package/dist/auth/oidc/types.d.ts +85 -0
  20. package/dist/auth/oidc/types.d.ts.map +1 -0
  21. package/dist/auth/oidc/types.js +11 -0
  22. package/dist/auth/oidc/types.js.map +1 -0
  23. package/dist/client.d.ts +47 -0
  24. package/dist/client.d.ts.map +1 -1
  25. package/dist/client.js +59 -0
  26. package/dist/client.js.map +1 -1
  27. package/dist/config/ConfigurationManager.d.ts +11 -1
  28. package/dist/config/ConfigurationManager.d.ts.map +1 -1
  29. package/dist/config/ConfigurationManager.js +102 -1
  30. package/dist/config/ConfigurationManager.js.map +1 -1
  31. package/dist/config/index.d.ts +1 -1
  32. package/dist/config/index.d.ts.map +1 -1
  33. package/dist/config/index.js +3 -1
  34. package/dist/config/index.js.map +1 -1
  35. package/dist/config/secrets.d.ts +1 -1
  36. package/dist/config/secrets.d.ts.map +1 -1
  37. package/dist/config/secrets.js +1 -1
  38. package/dist/config/secrets.js.map +1 -1
  39. package/dist/config/types.d.ts +462 -1
  40. package/dist/config/types.d.ts.map +1 -1
  41. package/dist/config/types.js +159 -1
  42. package/dist/config/types.js.map +1 -1
  43. package/dist/context/requestContext.d.ts +85 -0
  44. package/dist/context/requestContext.d.ts.map +1 -0
  45. package/dist/context/requestContext.js +110 -0
  46. package/dist/context/requestContext.js.map +1 -0
  47. package/dist/index.d.ts +22 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +55 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/middleware/simplified-rate-limit.d.ts.map +1 -1
  52. package/dist/middleware/simplified-rate-limit.js +15 -1
  53. package/dist/middleware/simplified-rate-limit.js.map +1 -1
  54. package/dist/storage/vaultFileStore.d.ts +167 -0
  55. package/dist/storage/vaultFileStore.d.ts.map +1 -0
  56. package/dist/storage/vaultFileStore.js +428 -0
  57. package/dist/storage/vaultFileStore.js.map +1 -0
  58. package/dist/tools/admin.d.ts.map +1 -1
  59. package/dist/tools/admin.js +7 -1
  60. package/dist/tools/admin.js.map +1 -1
  61. package/dist/tools/auth.d.ts +1 -1
  62. package/dist/tools/auth.d.ts.map +1 -1
  63. package/dist/tools/auth.js +197 -7
  64. package/dist/tools/auth.js.map +1 -1
  65. package/dist/tools/batch-import.d.ts.map +1 -1
  66. package/dist/tools/batch-import.js +8 -2
  67. package/dist/tools/batch-import.js.map +1 -1
  68. package/dist/tools/caldav-tokens.d.ts.map +1 -1
  69. package/dist/tools/caldav-tokens.js +7 -1
  70. package/dist/tools/caldav-tokens.js.map +1 -1
  71. package/dist/tools/export.d.ts.map +1 -1
  72. package/dist/tools/export.js +7 -1
  73. package/dist/tools/export.js.map +1 -1
  74. package/dist/tools/filters.d.ts.map +1 -1
  75. package/dist/tools/filters.js +10 -2
  76. package/dist/tools/filters.js.map +1 -1
  77. package/dist/tools/labels.d.ts.map +1 -1
  78. package/dist/tools/labels.js +7 -1
  79. package/dist/tools/labels.js.map +1 -1
  80. package/dist/tools/notifications.d.ts.map +1 -1
  81. package/dist/tools/notifications.js +6 -1
  82. package/dist/tools/notifications.js.map +1 -1
  83. package/dist/tools/projects/index.d.ts.map +1 -1
  84. package/dist/tools/projects/index.js +7 -1
  85. package/dist/tools/projects/index.js.map +1 -1
  86. package/dist/tools/reactions.d.ts.map +1 -1
  87. package/dist/tools/reactions.js +6 -1
  88. package/dist/tools/reactions.js.map +1 -1
  89. package/dist/tools/subscriptions.d.ts.map +1 -1
  90. package/dist/tools/subscriptions.js +6 -1
  91. package/dist/tools/subscriptions.js.map +1 -1
  92. package/dist/tools/task-assignees.d.ts.map +1 -1
  93. package/dist/tools/task-assignees.js +7 -2
  94. package/dist/tools/task-assignees.js.map +1 -1
  95. package/dist/tools/task-bulk.d.ts.map +1 -1
  96. package/dist/tools/task-bulk.js +7 -2
  97. package/dist/tools/task-bulk.js.map +1 -1
  98. package/dist/tools/task-comments.d.ts.map +1 -1
  99. package/dist/tools/task-comments.js +7 -2
  100. package/dist/tools/task-comments.js.map +1 -1
  101. package/dist/tools/task-labels.d.ts.map +1 -1
  102. package/dist/tools/task-labels.js +7 -2
  103. package/dist/tools/task-labels.js.map +1 -1
  104. package/dist/tools/task-relations.d.ts.map +1 -1
  105. package/dist/tools/task-relations.js +7 -2
  106. package/dist/tools/task-relations.js.map +1 -1
  107. package/dist/tools/task-reminders.d.ts.map +1 -1
  108. package/dist/tools/task-reminders.js +7 -2
  109. package/dist/tools/task-reminders.js.map +1 -1
  110. package/dist/tools/tasks/attachments.d.ts.map +1 -1
  111. package/dist/tools/tasks/attachments.js +8 -1
  112. package/dist/tools/tasks/attachments.js.map +1 -1
  113. package/dist/tools/tasks/index.d.ts.map +1 -1
  114. package/dist/tools/tasks/index.js +22 -6
  115. package/dist/tools/tasks/index.js.map +1 -1
  116. package/dist/tools/teams.d.ts.map +1 -1
  117. package/dist/tools/teams.js +7 -1
  118. package/dist/tools/teams.js.map +1 -1
  119. package/dist/tools/templates.d.ts.map +1 -1
  120. package/dist/tools/templates.js +23 -6
  121. package/dist/tools/templates.js.map +1 -1
  122. package/dist/tools/tokens.d.ts.map +1 -1
  123. package/dist/tools/tokens.js +7 -1
  124. package/dist/tools/tokens.js.map +1 -1
  125. package/dist/tools/user-deletion.d.ts.map +1 -1
  126. package/dist/tools/user-deletion.js +7 -1
  127. package/dist/tools/user-deletion.js.map +1 -1
  128. package/dist/tools/users.d.ts.map +1 -1
  129. package/dist/tools/users.js +7 -1
  130. package/dist/tools/users.js.map +1 -1
  131. package/dist/tools/webhooks.d.ts.map +1 -1
  132. package/dist/tools/webhooks.js +6 -1
  133. package/dist/tools/webhooks.js.map +1 -1
  134. package/dist/transport/enrollment.d.ts +169 -0
  135. package/dist/transport/enrollment.d.ts.map +1 -0
  136. package/dist/transport/enrollment.js +540 -0
  137. package/dist/transport/enrollment.js.map +1 -0
  138. package/dist/transport/enrollmentTickets.d.ts +60 -0
  139. package/dist/transport/enrollmentTickets.d.ts.map +1 -0
  140. package/dist/transport/enrollmentTickets.js +159 -0
  141. package/dist/transport/enrollmentTickets.js.map +1 -0
  142. package/dist/transport/httpTransport.d.ts +85 -0
  143. package/dist/transport/httpTransport.d.ts.map +1 -0
  144. package/dist/transport/httpTransport.js +299 -0
  145. package/dist/transport/httpTransport.js.map +1 -0
  146. package/dist/transport/oidcHttpAuth.d.ts +83 -0
  147. package/dist/transport/oidcHttpAuth.d.ts.map +1 -0
  148. package/dist/transport/oidcHttpAuth.js +222 -0
  149. package/dist/transport/oidcHttpAuth.js.map +1 -0
  150. package/dist/transport/oidcMiddlewareSeam.d.ts +52 -0
  151. package/dist/transport/oidcMiddlewareSeam.d.ts.map +1 -0
  152. package/dist/transport/oidcMiddlewareSeam.js +46 -0
  153. package/dist/transport/oidcMiddlewareSeam.js.map +1 -0
  154. package/dist/transport/resourceMetadata.d.ts +61 -0
  155. package/dist/transport/resourceMetadata.d.ts.map +1 -0
  156. package/dist/transport/resourceMetadata.js +85 -0
  157. package/dist/transport/resourceMetadata.js.map +1 -0
  158. package/dist/types/errors.d.ts +8 -0
  159. package/dist/types/errors.d.ts.map +1 -1
  160. package/dist/types/errors.js.map +1 -1
  161. package/dist/utils/read-only.d.ts +10 -5
  162. package/dist/utils/read-only.d.ts.map +1 -1
  163. package/dist/utils/read-only.js +17 -5
  164. package/dist/utils/read-only.js.map +1 -1
  165. package/dist/utils/retry.d.ts +13 -0
  166. package/dist/utils/retry.d.ts.map +1 -1
  167. package/dist/utils/retry.js +13 -0
  168. package/dist/utils/retry.js.map +1 -1
  169. package/dist/utils/vikunja-rest.d.ts +10 -0
  170. package/dist/utils/vikunja-rest.d.ts.map +1 -1
  171. package/dist/utils/vikunja-rest.js +40 -2
  172. package/dist/utils/vikunja-rest.js.map +1 -1
  173. package/docs/CONFIGURATION.md +212 -16
  174. package/docs/DOCKER-DESKTOP-MCP.md +21 -16
  175. package/docs/TOOLS.md +127 -117
  176. package/package.json +19 -8
package/LICENSE CHANGED
@@ -1,6 +1,7 @@
1
1
  MIT License
2
2
 
3
3
  Copyright (c) 2025 Jeremy Green
4
+ Copyright (c) 2026 Pierre Christen
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -3,33 +3,19 @@
3
3
  **Give your AI assistant real hands on your Vikunja instance** — create and triage tasks, manage projects and Kanban boards, assign teammates, and more, through natural conversation.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/vikunja-mcp-ng.svg)](https://www.npmjs.com/package/vikunja-mcp-ng)
6
- [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
- [![node: 20+](https://img.shields.io/badge/node-20%2B-brightgreen.svg)](package.json)
6
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/netadvanced/vikunja-mcp-ng/blob/main/LICENSE)
7
+ [![node: 22+](https://img.shields.io/badge/node-22%2B-brightgreen.svg)](https://github.com/netadvanced/vikunja-mcp-ng/blob/main/package.json)
8
8
  [![MCP](https://img.shields.io/badge/MCP-server-purple.svg)](https://modelcontextprotocol.io)
9
9
 
10
- > 👋 **Why this fork exists:** we rely on this project and noticed the [upstream repo](https://github.com/democratize-technology/vikunja-mcp) had gone quiet, with a growing backlog of open PRs and issues. So we've taken over active maintenance here — triaging and resolving most of that backlog (tracked in [this issue](https://github.com/netadvanced/vikunja-mcp-ng/issues/19)). Full credit to the original authors for the foundation. If they'd like to pick it back up, we'll gladly hand the reins back — or work together.
10
+ This server exposes Vikunja as **27 tools**, each covering one entity (tasks, projects, labels, teams…) with a consistent `subcommand` pattern — not a 1:1 REST proxy, but composite operations built for how an AI actually works: resolve a username instead of demanding a user ID, verify that tricky writes actually stuck instead of trusting a `200`, and require explicit confirmation on destructive operations. Your assistant reasons in natural language; the server turns that into correct Vikunja API calls and reports partial failures honestly instead of pretending success.
11
11
 
12
- ---
12
+ ## Requirements
13
13
 
14
- ## What this gives your AI assistant
14
+ - **Node.js 22+** (Node 20 reached end-of-life in April 2026)
15
+ - A Vikunja instance, **2.3.0 or newer** — tested against 2.4.0, with 2.3.0 as the supported floor
16
+ - An API token (`tk_…`) or JWT from that instance
15
17
 
16
- This server exposes Vikunja as **27 tools** (a session tool, 22 available by default or by auth type, and 4 sensitive ones that are off until an operator opts in), each covering one entity (tasks, projects, labels, teams…) with a consistent `subcommand` pattern — not a 1:1 REST proxy, but composite operations built for how an AI actually works: resolve a username instead of demanding a user ID, verify that tricky writes actually stuck instead of trusting a 200, and explicit confirmation gates on destructive operations. Your assistant reasons in natural language; the server turns that into correct Vikunja API calls and reports partial failures honestly instead of pretending success.
17
-
18
- ## See it in action
19
-
20
- > **You:** "Move 'Fix login redirect bug' to In Review and show me the board."
21
-
22
- ```typescript
23
- vikunja_tasks({ subcommand: "set-bucket", id: 342, bucketId: 43 })
24
- ```
25
-
26
- `projectId`/`viewId` auto-resolve from the task — no need to know which view is the Kanban one. The task card slides from *Backlog* into *In Review* on the Kanban board, instantly visible to anyone else looking at the board.
27
-
28
- More end-to-end scenarios — daily triage, team sharing, project planning, staying informed, bulk imports, admin ops — each paired with the exact tool call and the resulting Vikunja UI state, live in [`docs/samples/`](docs/samples/).
29
-
30
- ## Quick Start
31
-
32
- ### From npm (recommended)
18
+ ## Quick start
33
19
 
34
20
  No install step needed — point your MCP client at `npx`:
35
21
 
@@ -40,7 +26,7 @@ No install step needed — point your MCP client at `npx`:
40
26
  "command": "npx",
41
27
  "args": ["-y", "vikunja-mcp-ng"],
42
28
  "env": {
43
- "VIKUNJA_URL": "https://your-vikunja-instance.com/api/v1",
29
+ "VIKUNJA_URL": "https://your-vikunja-instance.com",
44
30
  "VIKUNJA_API_TOKEN": "your-api-token"
45
31
  }
46
32
  }
@@ -48,35 +34,13 @@ No install step needed — point your MCP client at `npx`:
48
34
  }
49
35
  ```
50
36
 
51
- Or install globally (`npm install -g vikunja-mcp-ng`) and use `"command": "vikunja-mcp-ng"` with no args.
52
-
53
- ### From source
54
-
55
- ```bash
56
- git clone https://github.com/netadvanced/vikunja-mcp-ng.git
57
- cd vikunja-mcp-ng
58
- npm ci
59
- npm run build
60
- ```
37
+ Use the bare instance URL for `VIKUNJA_URL` — the server resolves the right API path itself (today that's always `/api/v1`; an explicit `/api/v1` suffix still works too). The bare form is also the future-proof choice: it's the same URL the server will use to pick between v1 and v2 automatically once v2 support lands.
61
38
 
62
- ```json
63
- {
64
- "mcpServers": {
65
- "vikunja": {
66
- "command": "node",
67
- "args": ["/path/to/vikunja-mcp/dist/index.js"],
68
- "env": {
69
- "VIKUNJA_URL": "https://your-vikunja-instance.com/api/v1",
70
- "VIKUNJA_API_TOKEN": "your-api-token"
71
- }
72
- }
73
- }
74
- }
75
- ```
39
+ Or install globally (`npm install -g vikunja-mcp-ng`) and use `"command": "vikunja-mcp-ng"` with no args.
76
40
 
77
41
  ### Docker
78
42
 
79
- Images are published to GHCR on every release (also tagged `X.Y.Z` and `X.Y.Z-vikunja<A.B.C>` for Vikunja compatibility):
43
+ Multi-architecture images (`linux/amd64` + `linux/arm64`) are published to GHCR on every release, with `X.Y.Z`, `latest`, and `X.Y.Z-vikunja<A.B.C>` compatibility tags:
80
44
 
81
45
  ```bash
82
46
  docker pull ghcr.io/netadvanced/vikunja-mcp-ng:latest
@@ -94,7 +58,7 @@ docker pull ghcr.io/netadvanced/vikunja-mcp-ng:latest
94
58
  "ghcr.io/netadvanced/vikunja-mcp-ng:latest"
95
59
  ],
96
60
  "env": {
97
- "VIKUNJA_URL": "https://your-vikunja-instance.com/api/v1",
61
+ "VIKUNJA_URL": "https://your-vikunja-instance.com",
98
62
  "VIKUNJA_API_TOKEN": "your-api-token"
99
63
  }
100
64
  }
@@ -102,42 +66,61 @@ docker pull ghcr.io/netadvanced/vikunja-mcp-ng:latest
102
66
  }
103
67
  ```
104
68
 
105
- Using Docker Desktop's MCP Toolkit instead of a bare `docker run`? See [docs/DOCKER-DESKTOP-MCP.md](docs/DOCKER-DESKTOP-MCP.md) for the tested, step-by-step path.
69
+ Using Docker Desktop's MCP Toolkit rather than a bare `docker run`? There's a tested, step-by-step path in the [Docker Desktop guide](https://github.com/netadvanced/vikunja-mcp-ng/blob/main/docs/DOCKER-DESKTOP-MCP.md).
70
+
71
+ ## What it looks like in use
72
+
73
+ > **You:** "Move 'Fix login redirect bug' to In Review and show me the board."
74
+
75
+ ```typescript
76
+ vikunja_tasks({ subcommand: "set-bucket", id: 342, bucketId: 43 })
77
+ ```
78
+
79
+ `projectId`/`viewId` resolve from the task itself — no need to know which view is the Kanban one. The card slides from *Backlog* into *In Review*, instantly visible to anyone else watching the board.
106
80
 
107
- Full install options, JWT vs. API-token auth, module gating, and every environment variable live in the [Configuration guide](docs/CONFIGURATION.md).
81
+ Setting up a whole board is one call too:
82
+
83
+ ```typescript
84
+ vikunja_projects({
85
+ subcommand: "setup-kanban",
86
+ title: "Q3 Offsite",
87
+ columns: ["To Do", "Doing", "Done"],
88
+ tasks: [{ title: "Book venue", column: "To Do", priority: 4 }]
89
+ })
90
+ ```
91
+
92
+ Omit `columns` entirely and it becomes a plain "create a project with its tasks" call, touching no Kanban structure at all.
108
93
 
109
94
  ## Capabilities
110
95
 
111
96
  | Group | Tools | Covers |
112
97
  |---|---|---|
113
- | **Tasks** | `vikunja_tasks`, `vikunja_task_bulk`, `vikunja_task_assignees`, `vikunja_task_comments`, `vikunja_task_labels`, `vikunja_task_relations`, `vikunja_task_reminders` | CRUD, filtering, bulk ops, Kanban placement, subtasks, duplication, mark-read, comments, relations |
114
- | **Projects** | `vikunja_projects` | CRUD, hierarchy, views, Kanban buckets, one-call Kanban board setup (`setup-kanban`), sharing, duplication, opt-in backgrounds |
115
- | **Organize** | `vikunja_labels`, `vikunja_filters`, `vikunja_templates` | Labels, saved filters, reusable task templates |
98
+ | **Tasks** | `vikunja_tasks`, `vikunja_task_bulk`, `vikunja_task_assignees`, `vikunja_task_comments`, `vikunja_task_labels`, `vikunja_task_relations`, `vikunja_task_reminders` | CRUD, filtering, bulk ops, Kanban placement, subtasks, duplication, comments, relations, reminders |
99
+ | **Projects** | `vikunja_projects` | CRUD, hierarchy, views, Kanban buckets, one-call board setup (`setup-kanban`), sharing, duplication |
100
+ | **Organize** | `vikunja_labels`, `vikunja_filters`, `vikunja_templates` | Labels (including attach-by-title), saved filters, reusable task templates |
116
101
  | **Collaborate** | `vikunja_teams`, `vikunja_users`\*, `vikunja_notifications`, `vikunja_subscriptions`, `vikunja_reactions` | Team membership, user search, avatar settings, notifications, watch/react |
117
- | **Automate & move data** | `vikunja_webhooks`, `vikunja_batch_import`, `vikunja_export_project`\* | Webhooks (per-project and account-wide), CSV/JSON import, project export |
102
+ | **Automate & move data** | `vikunja_webhooks`, `vikunja_batch_import`, `vikunja_export_project`\* | Webhooks, CSV/JSON import, project export |
118
103
 
119
- \* JWT authentication only. User data export also has request/status/download tools (`vikunja_request_user_export`, `vikunja_user_export_status`, `vikunja_download_user_export`), all JWT-only. (`vikunja_webhooks`' account-wide `scope: 'user'` is JWT-only too; its default `scope: 'project'` works with either auth type.)
104
+ \* JWT authentication only, along with the three user-data-export tools.
120
105
 
121
- A session tool, `vikunja_auth` (connect / status / info / refresh / disconnect), rounds out the always-on surface. Four more tools — `vikunja_tokens`, `vikunja_caldav_tokens`, `vikunja_admin`, and `vikunja_user_deletion` — exist for API-token management, CalDAV-token management, instance administration, and self account deletion. All are **disabled by default**; an operator opts in explicitly (see Configuration). `vikunja_user_deletion` is the most sensitive of the four — it can delete the connected account — so read its [Configuration guide entry](docs/CONFIGURATION.md#known-modules) before enabling it. `vikunja_projects` also has three opt-in cosmetic subcommands (project backgrounds) behind a `backgrounds` module toggle, off by default for the opposite reason: low value, not danger.
106
+ A session tool, `vikunja_auth` (connect / status / info / refresh / disconnect), rounds out the always-on surface. Four further tools — `vikunja_tokens`, `vikunja_caldav_tokens`, `vikunja_admin`, `vikunja_user_deletion` — are **disabled by default** and require an operator to opt in explicitly.
122
107
 
123
- Full subcommand-by-subcommand reference: [`docs/TOOLS.md`](docs/TOOLS.md).
108
+ Full subcommand-by-subcommand reference: [`docs/TOOLS.md`](https://github.com/netadvanced/vikunja-mcp-ng/blob/main/docs/TOOLS.md).
124
109
 
125
110
  ## Safety by design
126
111
 
127
- Every entity is a toggle you can disable in config, `vikunja_admin`/`vikunja_tokens`/`vikunja_caldav_tokens`/`vikunja_user_deletion` ship off until an operator opts in (and `vikunja_admin`/`vikunja_caldav_tokens`/`vikunja_user_deletion` additionally require an active JWT session), and a global read-only mode can reject every write/destructive subcommand while reads keep working. Full details: [Configuration guide](docs/CONFIGURATION.md#module-gating).
112
+ Every entity group is a toggle you can disable in config. The four sensitive tools ship off until an operator opts in — `vikunja_admin`, `vikunja_caldav_tokens`, and `vikunja_user_deletion` additionally require an active JWT session. A global **read-only mode** rejects every write and destructive subcommand while reads keep working.
113
+
114
+ Details, plus auth, secrets handling, and rate limits: [Configuration guide](https://github.com/netadvanced/vikunja-mcp-ng/blob/main/docs/CONFIGURATION.md).
128
115
 
129
116
  ## Links
130
117
 
131
- - [Sample walkthroughs](docs/samples/) — real conversations paired with the tool calls and UI results behind them
132
- - [Full tool reference](docs/TOOLS.md) — every tool, subcommand, and argument
133
- - [Configuration guide](docs/CONFIGURATION.md) — auth, secrets, module gating, rate limits
134
- - [Roadmap](docs/ROADMAP.md) — where the project is headed and why
135
- - [Contributing / endpoint playbook](docs/ENDPOINT-PLAYBOOK.md) — conventions for adding new coverage
136
- - [Local test stack](docs/LOCAL-TESTING.md) — disposable Vikunja+Postgres via Docker for trying this out safely
137
- - [Agent battle-testing harness](docs/BATTLE-TESTING.md) — spawns a real AI agent against the tool surface and grades it on correctness and ergonomics (manual, costs real money — see the doc before running)
138
- - [Docker Desktop MCP Toolkit how-to](docs/DOCKER-DESKTOP-MCP.md) — registering this server with `docker mcp`
139
- - [Releasing](docs/RELEASING.md) — versioning policy and the release checklist · [CHANGELOG](CHANGELOG.md)
118
+ - [Sample walkthroughs](https://github.com/netadvanced/vikunja-mcp-ng/tree/main/docs/samples) — real conversations paired with the tool calls and UI results behind them
119
+ - [Full tool reference](https://github.com/netadvanced/vikunja-mcp-ng/blob/main/docs/TOOLS.md)
120
+ - [Configuration guide](https://github.com/netadvanced/vikunja-mcp-ng/blob/main/docs/CONFIGURATION.md)
121
+ - [Changelog](https://github.com/netadvanced/vikunja-mcp-ng/blob/main/CHANGELOG.md)
122
+ - [Source, issues, and contributing](https://github.com/netadvanced/vikunja-mcp-ng)
140
123
 
141
124
  ## License
142
125
 
143
- MIT — see [LICENSE](LICENSE).
126
+ MIT — see [LICENSE](https://github.com/netadvanced/vikunja-mcp-ng/blob/main/LICENSE).
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Credential source: identity -> Vikunja credential.
3
+ *
4
+ * Spec: docs/OIDC-RESOURCE-SERVER.md §3(c)/§3(d). A Keycloak/OIDC access
5
+ * token authenticates a *person*, it is not itself a Vikunja credential —
6
+ * the server needs a lookup from the validated identity to a Vikunja `tk_`
7
+ * token. §3(c) specifies that lookup as an encrypted-JSON-file vault, but
8
+ * that is explicitly H2 scope (wave plan, H2-1/H2-3). H1's job is only to
9
+ * shape the seam so H2 plugs in without touching a single call site:
10
+ *
11
+ * - `VikunjaCredentialSource` — the interface every call site programs
12
+ * against. `getCredential` returns `null` (never throws) when an
13
+ * identity has no linked credential; callers turn that into the
14
+ * structured `AUTH_REQUIRED` "provision" error (`createOidcAuthRequiredError`
15
+ * below), never a 500, and never anything that reveals whether some
16
+ * *other* identity is provisioned.
17
+ * - `StdioCredentialSource` — today's behaviour: one static credential
18
+ * (from env/config, `src/index.ts`'s existing bootstrap), identity-
19
+ * independent, because `stdio` mode is single-tenant. This is not a
20
+ * stub — it's the permanent stdio-mode implementation.
21
+ * - `OidcStubCredentialSource` — the H1 stand-in for the real vault.
22
+ * Always returns `null`, so every `oidc-http` caller gets the
23
+ * provisioning prompt until H2 lands `src/storage/vaultFileStore.ts` and
24
+ * a vault-backed implementation of this same interface (H2-3). Retained
25
+ * (not deleted) after H2 lands — still used by tests that want a
26
+ * deterministic "nobody is ever provisioned" source without touching a
27
+ * real vault file.
28
+ * - `VaultCredentialSource` — H2's real implementation, a thin adapter over
29
+ * `VaultFileStore` (`src/storage/vaultFileStore.ts`). Replaces
30
+ * `OidcStubCredentialSource` in the production `oidc-http` wiring
31
+ * (`src/transport/oidcHttpAuth.ts`'s `setupOidcHttpAuth`).
32
+ */
33
+ import type { Identity } from '../context/requestContext';
34
+ import { MCPError } from '../types/errors';
35
+ import type { VaultFileStore } from '../storage/vaultFileStore';
36
+ /** A Vikunja credential resolved for one identity. */
37
+ export interface VikunjaCredential {
38
+ readonly apiUrl: string;
39
+ readonly apiToken: string;
40
+ readonly authType?: 'api-token' | 'jwt';
41
+ }
42
+ /**
43
+ * Resolves the Vikunja credential for a validated identity. Implementations
44
+ * MUST derive the credential from `identity` alone (or, for `stdio`,
45
+ * ignore it entirely in favour of the one process-wide credential) — never
46
+ * from anything caller-supplied outside the validated request context. That
47
+ * is what closes the "claim to be someone else" spoofing vector (§4,
48
+ * isolation-matrix row "Vault lookup can't be spoofed").
49
+ */
50
+ export interface VikunjaCredentialSource {
51
+ getCredential(identity: Identity): VikunjaCredential | null;
52
+ }
53
+ /**
54
+ * `stdio` mode: the one static credential configured for the whole
55
+ * process (env `VIKUNJA_URL`/`VIKUNJA_API_TOKEN` today), identical for
56
+ * every call regardless of `identity`.
57
+ */
58
+ export declare class StdioCredentialSource implements VikunjaCredentialSource {
59
+ private readonly credential;
60
+ constructor(credential: VikunjaCredential | null);
61
+ getCredential(_identity: Identity): VikunjaCredential | null;
62
+ }
63
+ /**
64
+ * `oidc-http` mode, H1 scope: no vault yet (H2-1/H2-3). Every identity is
65
+ * unprovisioned until a real, vault-backed `VikunjaCredentialSource`
66
+ * replaces this stub.
67
+ */
68
+ export declare class OidcStubCredentialSource implements VikunjaCredentialSource {
69
+ getCredential(_identity: Identity): VikunjaCredential | null;
70
+ }
71
+ /**
72
+ * `oidc-http` mode, H2 scope: the real, vault-backed credential source.
73
+ * Delegates directly to a `VaultFileStore` (`src/storage/
74
+ * vaultFileStore.ts`) — `getCredential` is already synchronous and never
75
+ * throws there (a missing record and an undecryptable one both resolve to
76
+ * `null`), so this adapter adds no behaviour of its own beyond satisfying
77
+ * the interface type.
78
+ */
79
+ export declare class VaultCredentialSource implements VikunjaCredentialSource {
80
+ private readonly vault;
81
+ constructor(vault: Pick<VaultFileStore, 'getCredential'>);
82
+ getCredential(identity: Identity): VikunjaCredential | null;
83
+ }
84
+ /**
85
+ * The structured `AUTH_REQUIRED` error for a validly-authenticated identity
86
+ * that has no linked Vikunja credential — exact shape from §3(c)'s
87
+ * "Missing-credential behaviour": never a 500, and the message masks the
88
+ * `sub` (never echoes it in full) and never leaks whether any other
89
+ * identity is provisioned.
90
+ */
91
+ export declare function createOidcAuthRequiredError(identity: Identity): MCPError;
92
+ //# sourceMappingURL=CredentialSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CredentialSource.d.ts","sourceRoot":"","sources":["../../src/auth/CredentialSource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAa,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,iBAAiB,GAAG,IAAI,CAAC;CAC7D;AAED;;;;GAIG;AACH,qBAAa,qBAAsB,YAAW,uBAAuB;IACvD,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,iBAAiB,GAAG,IAAI;IAMjE,aAAa,CAAC,SAAS,EAAE,QAAQ,GAAG,iBAAiB,GAAG,IAAI;CAG7D;AAED;;;;GAIG;AACH,qBAAa,wBAAyB,YAAW,uBAAuB;IACtE,aAAa,CAAC,SAAS,EAAE,QAAQ,GAAG,iBAAiB,GAAG,IAAI;CAG7D;AAED;;;;;;;GAOG;AACH,qBAAa,qBAAsB,YAAW,uBAAuB;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC;IAEzE,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,iBAAiB,GAAG,IAAI;CAG5D;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAOxE"}
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ /**
3
+ * Credential source: identity -> Vikunja credential.
4
+ *
5
+ * Spec: docs/OIDC-RESOURCE-SERVER.md §3(c)/§3(d). A Keycloak/OIDC access
6
+ * token authenticates a *person*, it is not itself a Vikunja credential —
7
+ * the server needs a lookup from the validated identity to a Vikunja `tk_`
8
+ * token. §3(c) specifies that lookup as an encrypted-JSON-file vault, but
9
+ * that is explicitly H2 scope (wave plan, H2-1/H2-3). H1's job is only to
10
+ * shape the seam so H2 plugs in without touching a single call site:
11
+ *
12
+ * - `VikunjaCredentialSource` — the interface every call site programs
13
+ * against. `getCredential` returns `null` (never throws) when an
14
+ * identity has no linked credential; callers turn that into the
15
+ * structured `AUTH_REQUIRED` "provision" error (`createOidcAuthRequiredError`
16
+ * below), never a 500, and never anything that reveals whether some
17
+ * *other* identity is provisioned.
18
+ * - `StdioCredentialSource` — today's behaviour: one static credential
19
+ * (from env/config, `src/index.ts`'s existing bootstrap), identity-
20
+ * independent, because `stdio` mode is single-tenant. This is not a
21
+ * stub — it's the permanent stdio-mode implementation.
22
+ * - `OidcStubCredentialSource` — the H1 stand-in for the real vault.
23
+ * Always returns `null`, so every `oidc-http` caller gets the
24
+ * provisioning prompt until H2 lands `src/storage/vaultFileStore.ts` and
25
+ * a vault-backed implementation of this same interface (H2-3). Retained
26
+ * (not deleted) after H2 lands — still used by tests that want a
27
+ * deterministic "nobody is ever provisioned" source without touching a
28
+ * real vault file.
29
+ * - `VaultCredentialSource` — H2's real implementation, a thin adapter over
30
+ * `VaultFileStore` (`src/storage/vaultFileStore.ts`). Replaces
31
+ * `OidcStubCredentialSource` in the production `oidc-http` wiring
32
+ * (`src/transport/oidcHttpAuth.ts`'s `setupOidcHttpAuth`).
33
+ */
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.VaultCredentialSource = exports.OidcStubCredentialSource = exports.StdioCredentialSource = void 0;
36
+ exports.createOidcAuthRequiredError = createOidcAuthRequiredError;
37
+ const errors_1 = require("../types/errors");
38
+ const security_1 = require("../utils/security");
39
+ /**
40
+ * `stdio` mode: the one static credential configured for the whole
41
+ * process (env `VIKUNJA_URL`/`VIKUNJA_API_TOKEN` today), identical for
42
+ * every call regardless of `identity`.
43
+ */
44
+ class StdioCredentialSource {
45
+ credential;
46
+ constructor(credential) {
47
+ this.credential = credential;
48
+ }
49
+ // `identity` is intentionally unused — stdio is single-tenant, one
50
+ // credential for the one process, exactly as today. The parameter stays
51
+ // so this class satisfies the same interface as every oidc-mode source,
52
+ // and so no stdio call site is ever tempted to special-case identity.
53
+ getCredential(_identity) {
54
+ return this.credential;
55
+ }
56
+ }
57
+ exports.StdioCredentialSource = StdioCredentialSource;
58
+ /**
59
+ * `oidc-http` mode, H1 scope: no vault yet (H2-1/H2-3). Every identity is
60
+ * unprovisioned until a real, vault-backed `VikunjaCredentialSource`
61
+ * replaces this stub.
62
+ */
63
+ class OidcStubCredentialSource {
64
+ getCredential(_identity) {
65
+ return null;
66
+ }
67
+ }
68
+ exports.OidcStubCredentialSource = OidcStubCredentialSource;
69
+ /**
70
+ * `oidc-http` mode, H2 scope: the real, vault-backed credential source.
71
+ * Delegates directly to a `VaultFileStore` (`src/storage/
72
+ * vaultFileStore.ts`) — `getCredential` is already synchronous and never
73
+ * throws there (a missing record and an undecryptable one both resolve to
74
+ * `null`), so this adapter adds no behaviour of its own beyond satisfying
75
+ * the interface type.
76
+ */
77
+ class VaultCredentialSource {
78
+ vault;
79
+ constructor(vault) {
80
+ this.vault = vault;
81
+ }
82
+ getCredential(identity) {
83
+ return this.vault.getCredential(identity);
84
+ }
85
+ }
86
+ exports.VaultCredentialSource = VaultCredentialSource;
87
+ /**
88
+ * The structured `AUTH_REQUIRED` error for a validly-authenticated identity
89
+ * that has no linked Vikunja credential — exact shape from §3(c)'s
90
+ * "Missing-credential behaviour": never a 500, and the message masks the
91
+ * `sub` (never echoes it in full) and never leaks whether any other
92
+ * identity is provisioned.
93
+ */
94
+ function createOidcAuthRequiredError(identity) {
95
+ const maskedSub = (0, security_1.maskCredential)(identity.sub) || '[REDACTED]';
96
+ return new errors_1.MCPError(errors_1.ErrorCode.AUTH_REQUIRED, `You're authenticated as ${maskedSub} but haven't linked a Vikunja API token yet. ` +
97
+ `Run vikunja_auth provision with a token you create in Vikunja → Settings → API Tokens.`);
98
+ }
99
+ //# sourceMappingURL=CredentialSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CredentialSource.js","sourceRoot":"","sources":["../../src/auth/CredentialSource.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;;;AA6EH,kEAOC;AAjFD,4CAAsD;AACtD,gDAAmD;AAsBnD;;;;GAIG;AACH,MAAa,qBAAqB;IACH;IAA7B,YAA6B,UAAoC;QAApC,eAAU,GAAV,UAAU,CAA0B;IAAG,CAAC;IAErE,mEAAmE;IACnE,wEAAwE;IACxE,wEAAwE;IACxE,sEAAsE;IACtE,aAAa,CAAC,SAAmB;QAC/B,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;CACF;AAVD,sDAUC;AAED;;;;GAIG;AACH,MAAa,wBAAwB;IACnC,aAAa,CAAC,SAAmB;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAJD,4DAIC;AAED;;;;;;;GAOG;AACH,MAAa,qBAAqB;IACH;IAA7B,YAA6B,KAA4C;QAA5C,UAAK,GAAL,KAAK,CAAuC;IAAG,CAAC;IAE7E,aAAa,CAAC,QAAkB;QAC9B,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;CACF;AAND,sDAMC;AAED;;;;;;GAMG;AACH,SAAgB,2BAA2B,CAAC,QAAkB;IAC5D,MAAM,SAAS,GAAG,IAAA,yBAAc,EAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC;IAC/D,OAAO,IAAI,iBAAQ,CACjB,kBAAS,CAAC,aAAa,EACvB,2BAA2B,SAAS,+CAA+C;QACjF,wFAAwF,CAC3F,CAAC;AACJ,CAAC"}
@@ -4,4 +4,7 @@
4
4
  */
5
5
  export { AuthManager } from './AuthManager';
6
6
  export { Permission, PermissionManager, TOOL_PERMISSIONS, type PermissionCheckResult, } from './permissions';
7
+ export { createOidcJwtValidator, type OidcJwtValidator } from './oidc/jwtValidator';
8
+ export { loadJose } from './oidc/joseLoader';
9
+ export type { Identity, JoseDeps, JoseCreateRemoteJWKSet, JoseJwtVerify, OidcJwksCacheConfig, OidcJwtValidatorConfig, } from './oidc/types';
7
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,YAAY,EACV,QAAQ,EACR,QAAQ,EACR,sBAAsB,EACtB,aAAa,EACb,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,cAAc,CAAC"}
@@ -4,11 +4,15 @@
4
4
  * Eliminated over-engineered testing infrastructure
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.TOOL_PERMISSIONS = exports.PermissionManager = exports.Permission = exports.AuthManager = void 0;
7
+ exports.loadJose = exports.createOidcJwtValidator = exports.TOOL_PERMISSIONS = exports.PermissionManager = exports.Permission = exports.AuthManager = void 0;
8
8
  var AuthManager_1 = require("./AuthManager");
9
9
  Object.defineProperty(exports, "AuthManager", { enumerable: true, get: function () { return AuthManager_1.AuthManager; } });
10
10
  var permissions_1 = require("./permissions");
11
11
  Object.defineProperty(exports, "Permission", { enumerable: true, get: function () { return permissions_1.Permission; } });
12
12
  Object.defineProperty(exports, "PermissionManager", { enumerable: true, get: function () { return permissions_1.PermissionManager; } });
13
13
  Object.defineProperty(exports, "TOOL_PERMISSIONS", { enumerable: true, get: function () { return permissions_1.TOOL_PERMISSIONS; } });
14
+ var jwtValidator_1 = require("./oidc/jwtValidator");
15
+ Object.defineProperty(exports, "createOidcJwtValidator", { enumerable: true, get: function () { return jwtValidator_1.createOidcJwtValidator; } });
16
+ var joseLoader_1 = require("./oidc/joseLoader");
17
+ Object.defineProperty(exports, "loadJose", { enumerable: true, get: function () { return joseLoader_1.loadJose; } });
14
18
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AACpB,6CAKuB;AAJrB,yGAAA,UAAU,OAAA;AACV,gHAAA,iBAAiB,OAAA;AACjB,+GAAA,gBAAgB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AACpB,6CAKuB;AAJrB,yGAAA,UAAU,OAAA;AACV,gHAAA,iBAAiB,OAAA;AACjB,+GAAA,gBAAgB,OAAA;AAGlB,oDAAoF;AAA3E,sHAAA,sBAAsB,OAAA;AAC/B,gDAA6C;AAApC,sGAAA,QAAQ,OAAA"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Loads the `jose` package for production use.
3
+ *
4
+ * `jose@6` ships ESM-only (no CommonJS build); this project compiles to
5
+ * CommonJS (see tsconfig.json's `module: "NodeNext"` with no `"type": "module"`
6
+ * in package.json). A dynamic `import()` is the interop path the Node.js docs
7
+ * themselves recommend for a CommonJS module consuming an ESM-only package,
8
+ * and it works unmodified on every Node 20+ runtime this project targets —
9
+ * unlike newer `require(esm)` semantics, it needs no engine-version caveats.
10
+ *
11
+ * This function is intentionally the *only* place that dynamic import lives.
12
+ * Jest's CommonJS-mode test runner cannot execute a genuine dynamic `import()`
13
+ * of a real ES module without globally enabling `--experimental-vm-modules`
14
+ * (which, in turn, requires re-plumbing the whole suite's module handling and
15
+ * was rejected as disproportionate for a single dependency — see the PR
16
+ * description). So {@link createOidcJwtValidator} takes its `jose` functions
17
+ * as an explicit, fully unit-testable dependency instead of importing them
18
+ * itself; tests inject `jose`'s own statically-imported exports (which do
19
+ * load fine under Jest, see tests/auth/oidc/jwtValidator.test.ts) and never
20
+ * exercise this function. Only real Node execution (and the manual/e2e OIDC
21
+ * lane) exercises this path, hence the coverage exclusion below.
22
+ */
23
+ import type { JoseDeps } from './types';
24
+ export declare function loadJose(): Promise<JoseDeps>;
25
+ //# sourceMappingURL=joseLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joseLoader.d.ts","sourceRoot":"","sources":["../../../src/auth/oidc/joseLoader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAOxC,wBAAgB,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,CAK5C"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /**
3
+ * Loads the `jose` package for production use.
4
+ *
5
+ * `jose@6` ships ESM-only (no CommonJS build); this project compiles to
6
+ * CommonJS (see tsconfig.json's `module: "NodeNext"` with no `"type": "module"`
7
+ * in package.json). A dynamic `import()` is the interop path the Node.js docs
8
+ * themselves recommend for a CommonJS module consuming an ESM-only package,
9
+ * and it works unmodified on every Node 20+ runtime this project targets —
10
+ * unlike newer `require(esm)` semantics, it needs no engine-version caveats.
11
+ *
12
+ * This function is intentionally the *only* place that dynamic import lives.
13
+ * Jest's CommonJS-mode test runner cannot execute a genuine dynamic `import()`
14
+ * of a real ES module without globally enabling `--experimental-vm-modules`
15
+ * (which, in turn, requires re-plumbing the whole suite's module handling and
16
+ * was rejected as disproportionate for a single dependency — see the PR
17
+ * description). So {@link createOidcJwtValidator} takes its `jose` functions
18
+ * as an explicit, fully unit-testable dependency instead of importing them
19
+ * itself; tests inject `jose`'s own statically-imported exports (which do
20
+ * load fine under Jest, see tests/auth/oidc/jwtValidator.test.ts) and never
21
+ * exercise this function. Only real Node execution (and the manual/e2e OIDC
22
+ * lane) exercises this path, hence the coverage exclusion below.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.loadJose = loadJose;
26
+ let cachedDeps;
27
+ // See file header: only a genuine ESM dynamic import exercises this function;
28
+ // Jest cannot run one without --experimental-vm-modules, so no test calls it.
29
+ /* istanbul ignore next */
30
+ function loadJose() {
31
+ if (!cachedDeps) {
32
+ cachedDeps = import('jose');
33
+ }
34
+ return cachedDeps;
35
+ }
36
+ //# sourceMappingURL=joseLoader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joseLoader.js","sourceRoot":"","sources":["../../../src/auth/oidc/joseLoader.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;AASH,4BAKC;AAVD,IAAI,UAAyC,CAAC;AAE9C,8EAA8E;AAC9E,8EAA8E;AAC9E,0BAA0B;AAC1B,SAAgB,QAAQ;IACtB,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * OIDC resource-server JWT validation middleware.
3
+ *
4
+ * Implements docs/OIDC-RESOURCE-SERVER.md §3(b)'s validation contract: strict
5
+ * issuer/audience checking, an explicit `alg` allowlist (default `['RS256']`,
6
+ * rejecting `none` and unexpected HMAC algorithms), bounded clock skew, and a
7
+ * generic 401/403 failure contract that never echoes token material back to
8
+ * the caller and never logs a token at any level.
9
+ *
10
+ * Deliberately transport-agnostic: {@link createOidcJwtValidator} returns a
11
+ * `validate(authorizationHeaderValue) => Promise<Identity>` function with no
12
+ * dependency on Node's `http`, the MCP SDK transport, or any request/response
13
+ * object — so an HTTP transport seam can call it directly, and unit tests
14
+ * need no HTTP server (see tests/auth/oidc/jwtValidator.test.ts).
15
+ */
16
+ import type { Identity, JoseDeps, OidcJwtValidatorConfig } from './types';
17
+ export interface OidcJwtValidator {
18
+ /**
19
+ * Validates an `Authorization` header value and returns the caller's
20
+ * identity on success.
21
+ *
22
+ * On any failure, throws an {@link MCPError} carrying the generic,
23
+ * safe-to-return-verbatim message plus `details.statusCode` (401 or 403)
24
+ * and `details.wwwAuthenticateError` (`'invalid_token'` or
25
+ * `'insufficient_scope'`) for the transport to build its HTTP response.
26
+ * The specific failure reason is logged at `warn` — the token itself is
27
+ * never included in that log line.
28
+ */
29
+ validate(authorizationHeader: string | null | undefined): Promise<Identity>;
30
+ /**
31
+ * Forces an immediate JWKS refetch, bypassing the cooldown window. Not
32
+ * required for normal operation — jose's remote JWKS resolver already
33
+ * refetches automatically when it sees an unrecognized `kid` — but useful
34
+ * for an operator reacting to a known key rotation, or for tests.
35
+ */
36
+ reloadJwks(): Promise<void>;
37
+ }
38
+ /**
39
+ * Builds an {@link OidcJwtValidator} bound to the given config.
40
+ *
41
+ * `deps` is required rather than defaulted to a live `import('jose')` so this
42
+ * function stays synchronous and trivially unit-testable; see
43
+ * src/auth/oidc/joseLoader.ts for how production code obtains `deps`.
44
+ */
45
+ export declare function createOidcJwtValidator(config: OidcJwtValidatorConfig, deps: JoseDeps): OidcJwtValidator;
46
+ //# sourceMappingURL=jwtValidator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwtValidator.d.ts","sourceRoot":"","sources":["../../../src/auth/oidc/jwtValidator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAuB,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAU/F,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,mBAAmB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5E;;;;;OAKG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,sBAAsB,EAC9B,IAAI,EAAE,QAAQ,GACb,gBAAgB,CAmElB"}