vikunja-mcp-ng 0.6.2 → 0.7.0-beta.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/LICENSE +1 -0
- package/README.md +52 -69
- package/dist/auth/CredentialSource.d.ts +92 -0
- package/dist/auth/CredentialSource.d.ts.map +1 -0
- package/dist/auth/CredentialSource.js +99 -0
- package/dist/auth/CredentialSource.js.map +1 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +5 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oidc/joseLoader.d.ts +25 -0
- package/dist/auth/oidc/joseLoader.d.ts.map +1 -0
- package/dist/auth/oidc/joseLoader.js +36 -0
- package/dist/auth/oidc/joseLoader.js.map +1 -0
- package/dist/auth/oidc/jwtValidator.d.ts +46 -0
- package/dist/auth/oidc/jwtValidator.d.ts.map +1 -0
- package/dist/auth/oidc/jwtValidator.js +141 -0
- package/dist/auth/oidc/jwtValidator.js.map +1 -0
- package/dist/auth/oidc/types.d.ts +79 -0
- package/dist/auth/oidc/types.d.ts.map +1 -0
- package/dist/auth/oidc/types.js +11 -0
- package/dist/auth/oidc/types.js.map +1 -0
- package/dist/client.d.ts +47 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +59 -0
- package/dist/client.js.map +1 -1
- package/dist/config/ConfigurationManager.d.ts +11 -1
- package/dist/config/ConfigurationManager.d.ts.map +1 -1
- package/dist/config/ConfigurationManager.js +91 -1
- package/dist/config/ConfigurationManager.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/secrets.d.ts +1 -1
- package/dist/config/secrets.d.ts.map +1 -1
- package/dist/config/secrets.js +1 -1
- package/dist/config/secrets.js.map +1 -1
- package/dist/config/types.d.ts +140 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +88 -1
- package/dist/config/types.js.map +1 -1
- package/dist/context/requestContext.d.ts +77 -0
- package/dist/context/requestContext.d.ts.map +1 -0
- package/dist/context/requestContext.js +110 -0
- package/dist/context/requestContext.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware/simplified-rate-limit.d.ts.map +1 -1
- package/dist/middleware/simplified-rate-limit.js +15 -1
- package/dist/middleware/simplified-rate-limit.js.map +1 -1
- package/dist/storage/vaultFileStore.d.ts +167 -0
- package/dist/storage/vaultFileStore.d.ts.map +1 -0
- package/dist/storage/vaultFileStore.js +428 -0
- package/dist/storage/vaultFileStore.js.map +1 -0
- package/dist/tools/admin.d.ts.map +1 -1
- package/dist/tools/admin.js +7 -1
- package/dist/tools/admin.js.map +1 -1
- package/dist/tools/auth.d.ts +1 -1
- package/dist/tools/auth.d.ts.map +1 -1
- package/dist/tools/auth.js +152 -7
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/batch-import.d.ts.map +1 -1
- package/dist/tools/batch-import.js +8 -2
- package/dist/tools/batch-import.js.map +1 -1
- package/dist/tools/caldav-tokens.d.ts.map +1 -1
- package/dist/tools/caldav-tokens.js +7 -1
- package/dist/tools/caldav-tokens.js.map +1 -1
- package/dist/tools/export.d.ts.map +1 -1
- package/dist/tools/export.js +7 -1
- package/dist/tools/export.js.map +1 -1
- package/dist/tools/filters.d.ts.map +1 -1
- package/dist/tools/filters.js +10 -2
- package/dist/tools/filters.js.map +1 -1
- package/dist/tools/labels.d.ts.map +1 -1
- package/dist/tools/labels.js +7 -1
- package/dist/tools/labels.js.map +1 -1
- package/dist/tools/notifications.d.ts.map +1 -1
- package/dist/tools/notifications.js +6 -1
- package/dist/tools/notifications.js.map +1 -1
- package/dist/tools/projects/index.d.ts.map +1 -1
- package/dist/tools/projects/index.js +7 -1
- package/dist/tools/projects/index.js.map +1 -1
- package/dist/tools/reactions.d.ts.map +1 -1
- package/dist/tools/reactions.js +6 -1
- package/dist/tools/reactions.js.map +1 -1
- package/dist/tools/subscriptions.d.ts.map +1 -1
- package/dist/tools/subscriptions.js +6 -1
- package/dist/tools/subscriptions.js.map +1 -1
- package/dist/tools/task-assignees.d.ts.map +1 -1
- package/dist/tools/task-assignees.js +7 -2
- package/dist/tools/task-assignees.js.map +1 -1
- package/dist/tools/task-bulk.d.ts.map +1 -1
- package/dist/tools/task-bulk.js +7 -2
- package/dist/tools/task-bulk.js.map +1 -1
- package/dist/tools/task-comments.d.ts.map +1 -1
- package/dist/tools/task-comments.js +7 -2
- package/dist/tools/task-comments.js.map +1 -1
- package/dist/tools/task-labels.d.ts.map +1 -1
- package/dist/tools/task-labels.js +7 -2
- package/dist/tools/task-labels.js.map +1 -1
- package/dist/tools/task-relations.d.ts.map +1 -1
- package/dist/tools/task-relations.js +7 -2
- package/dist/tools/task-relations.js.map +1 -1
- package/dist/tools/task-reminders.d.ts.map +1 -1
- package/dist/tools/task-reminders.js +7 -2
- package/dist/tools/task-reminders.js.map +1 -1
- package/dist/tools/tasks/attachments.d.ts.map +1 -1
- package/dist/tools/tasks/attachments.js +8 -1
- package/dist/tools/tasks/attachments.js.map +1 -1
- package/dist/tools/tasks/index.d.ts.map +1 -1
- package/dist/tools/tasks/index.js +22 -6
- package/dist/tools/tasks/index.js.map +1 -1
- package/dist/tools/teams.d.ts.map +1 -1
- package/dist/tools/teams.js +7 -1
- package/dist/tools/teams.js.map +1 -1
- package/dist/tools/templates.d.ts.map +1 -1
- package/dist/tools/templates.js +23 -6
- package/dist/tools/templates.js.map +1 -1
- package/dist/tools/tokens.d.ts.map +1 -1
- package/dist/tools/tokens.js +7 -1
- package/dist/tools/tokens.js.map +1 -1
- package/dist/tools/user-deletion.d.ts.map +1 -1
- package/dist/tools/user-deletion.js +7 -1
- package/dist/tools/user-deletion.js.map +1 -1
- package/dist/tools/users.d.ts.map +1 -1
- package/dist/tools/users.js +7 -1
- package/dist/tools/users.js.map +1 -1
- package/dist/tools/webhooks.d.ts.map +1 -1
- package/dist/tools/webhooks.js +6 -1
- package/dist/tools/webhooks.js.map +1 -1
- package/dist/transport/httpTransport.d.ts +85 -0
- package/dist/transport/httpTransport.d.ts.map +1 -0
- package/dist/transport/httpTransport.js +273 -0
- package/dist/transport/httpTransport.js.map +1 -0
- package/dist/transport/oidcHttpAuth.d.ts +83 -0
- package/dist/transport/oidcHttpAuth.d.ts.map +1 -0
- package/dist/transport/oidcHttpAuth.js +214 -0
- package/dist/transport/oidcHttpAuth.js.map +1 -0
- package/dist/transport/oidcMiddlewareSeam.d.ts +52 -0
- package/dist/transport/oidcMiddlewareSeam.d.ts.map +1 -0
- package/dist/transport/oidcMiddlewareSeam.js +46 -0
- package/dist/transport/oidcMiddlewareSeam.js.map +1 -0
- package/dist/transport/resourceMetadata.d.ts +61 -0
- package/dist/transport/resourceMetadata.d.ts.map +1 -0
- package/dist/transport/resourceMetadata.js +85 -0
- package/dist/transport/resourceMetadata.js.map +1 -0
- package/dist/types/errors.d.ts +8 -0
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js.map +1 -1
- package/dist/utils/read-only.d.ts +10 -5
- package/dist/utils/read-only.d.ts.map +1 -1
- package/dist/utils/read-only.js +17 -5
- package/dist/utils/read-only.js.map +1 -1
- package/dist/utils/retry.d.ts +13 -0
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +13 -0
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/vikunja-rest.d.ts +10 -0
- package/dist/utils/vikunja-rest.d.ts.map +1 -1
- package/dist/utils/vikunja-rest.js +40 -2
- package/dist/utils/vikunja-rest.js.map +1 -1
- package/docs/CONFIGURATION.md +188 -16
- package/docs/DOCKER-DESKTOP-MCP.md +21 -16
- package/docs/TOOLS.md +126 -117
- package/package.json +19 -8
package/LICENSE
CHANGED
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
|
[](https://www.npmjs.com/package/vikunja-mcp-ng)
|
|
6
|
-
[](LICENSE)
|
|
7
|
-
[](https://github.com/netadvanced/vikunja-mcp-ng/blob/main/LICENSE)
|
|
7
|
+
[](https://github.com/netadvanced/vikunja-mcp-ng/blob/main/package.json)
|
|
8
8
|
[](https://modelcontextprotocol.io)
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
114
|
-
| **Projects** | `vikunja_projects` | CRUD, hierarchy, views, Kanban buckets, one-call
|
|
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
|
|
102
|
+
| **Automate & move data** | `vikunja_webhooks`, `vikunja_batch_import`, `vikunja_export_project`\* | Webhooks, CSV/JSON import, project export |
|
|
118
103
|
|
|
119
|
-
\* JWT authentication only
|
|
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
|
|
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
|
|
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
|
|
132
|
-
- [Full tool reference](docs/TOOLS.md)
|
|
133
|
-
- [Configuration guide](docs/CONFIGURATION.md)
|
|
134
|
-
- [
|
|
135
|
-
- [
|
|
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"}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -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
|
package/dist/auth/index.d.ts.map
CHANGED
|
@@ -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"}
|
package/dist/auth/index.js
CHANGED
|
@@ -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
|
package/dist/auth/index.js.map
CHANGED
|
@@ -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,CA+DlB"}
|