aiquila-mcp 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -31
- package/dist/auth/login.js +1 -0
- package/dist/auth/provider.js +1 -0
- package/dist/auth/store.js +12 -4
- package/dist/client/aiquila.js +1 -0
- package/dist/client/bookmarks.js +1 -0
- package/dist/client/caldav.js +1 -0
- package/dist/client/deck.js +1 -0
- package/dist/client/mail.js +1 -0
- package/dist/client/maps.js +1 -0
- package/dist/client/notes.js +1 -0
- package/dist/client/ocs.js +1 -0
- package/dist/client/webdav.js +1 -0
- package/dist/index.js +3 -2
- package/dist/logger.js +1 -0
- package/dist/server.js +1 -0
- package/dist/tool-registry.js +1 -0
- package/dist/tools/apps/absence.js +1 -0
- package/dist/tools/apps/aiquila.js +1 -0
- package/dist/tools/apps/assistant.js +1 -0
- package/dist/tools/apps/bookmarks.js +1 -0
- package/dist/tools/apps/calendar.js +2 -14
- package/dist/tools/apps/circles.js +1 -0
- package/dist/tools/apps/contacts.js +2 -14
- package/dist/tools/apps/cookbook.js +1 -0
- package/dist/tools/apps/deck.js +19 -38
- package/dist/tools/apps/groups.js +1 -0
- package/dist/tools/apps/mail.js +1 -0
- package/dist/tools/apps/maps.js +1 -0
- package/dist/tools/apps/notes.js +12 -36
- package/dist/tools/apps/notifications.js +1 -0
- package/dist/tools/apps/photos.js +1 -0
- package/dist/tools/apps/projects.js +1 -0
- package/dist/tools/apps/shares.js +1 -0
- package/dist/tools/apps/talk.js +1 -0
- package/dist/tools/apps/tasks.js +2 -10
- package/dist/tools/apps/translate.js +1 -0
- package/dist/tools/apps/trash.js +1 -0
- package/dist/tools/apps/user-status.js +1 -0
- package/dist/tools/apps/users.js +1 -0
- package/dist/tools/apps/versions.js +1 -0
- package/dist/tools/dav-utils.js +30 -0
- package/dist/tools/error-utils.js +30 -0
- package/dist/tools/system/apps.js +1 -0
- package/dist/tools/system/files.js +1 -0
- package/dist/tools/system/occ-redact.js +1 -0
- package/dist/tools/system/occ.js +1 -0
- package/dist/tools/system/search.js +1 -0
- package/dist/tools/system/security.js +1 -0
- package/dist/tools/system/status.js +1 -0
- package/dist/tools/system/tags.js +1 -0
- package/dist/tools/types.js +4 -0
- package/dist/transports/http.js +7 -6
- package/dist/transports/stdio.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# AIquila MCP Server
|
|
2
2
|
|
|
3
|
-
MCP (Model Context Protocol) server that gives
|
|
3
|
+
MCP (Model Context Protocol) server that gives any MCP client full access to your Nextcloud instance — files, calendar, tasks, contacts, mail, talk, maps, bookmarks, notes, and more. 198 tools across 31 categories.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
|
-
### Claude Desktop
|
|
7
|
+
### Stdio (Claude Desktop, Claude Code, Cursor, etc.)
|
|
8
8
|
|
|
9
|
-
Add to `~/.config/claude/claude_desktop_config.json
|
|
9
|
+
Add to your MCP client configuration (example for Claude Desktop `~/.config/claude/claude_desktop_config.json`):
|
|
10
10
|
|
|
11
11
|
```json
|
|
12
12
|
{
|
|
@@ -26,35 +26,58 @@ Add to `~/.config/claude/claude_desktop_config.json`:
|
|
|
26
26
|
|
|
27
27
|
Generate an App Password in Nextcloud: **Settings → Security → Devices & sessions**.
|
|
28
28
|
|
|
29
|
-
### Docker
|
|
29
|
+
### HTTP Transport (Docker, Claude.ai, remote clients)
|
|
30
30
|
|
|
31
|
-
See the [Docker setup guide](https://github.com/elgorro/aiquila/blob/main/docs/mcp/setup.md#docker--claudeai-http-transport) for running AIquila as an HTTP server with OAuth for
|
|
31
|
+
See the [Docker setup guide](https://github.com/elgorro/aiquila/blob/main/docs/mcp/setup.md#docker--claudeai-http-transport) for running AIquila as an HTTP server with OAuth for remote MCP clients.
|
|
32
32
|
|
|
33
33
|
## What It Can Do
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
35
|
+
### Core (always available)
|
|
36
|
+
|
|
37
|
+
| Category | Tools |
|
|
38
|
+
| ---------- | ----: |
|
|
39
|
+
| Files | 12 |
|
|
40
|
+
| Status | 3 |
|
|
41
|
+
| Apps | 6 |
|
|
42
|
+
| Tags | 6 |
|
|
43
|
+
| Search | 2 |
|
|
44
|
+
| Users | 4 |
|
|
45
|
+
| Groups | 4 |
|
|
46
|
+
| Shares | 10 |
|
|
47
|
+
| Absence | 3 |
|
|
48
|
+
| Trash | 3 |
|
|
49
|
+
| Versions | 2 |
|
|
50
|
+
|
|
51
|
+
### AIquila app
|
|
52
|
+
|
|
53
|
+
| Category | Tools |
|
|
54
|
+
| ---------- | ----: |
|
|
55
|
+
| AIquila | 3 |
|
|
56
|
+
| Security | 2 |
|
|
57
|
+
| OCC | 1 |
|
|
58
|
+
| Projects | 7 |
|
|
59
|
+
|
|
60
|
+
### Optional Nextcloud apps
|
|
61
|
+
|
|
62
|
+
| Category | Tools |
|
|
63
|
+
| ------------- | ----: |
|
|
64
|
+
| Calendar | 6 |
|
|
65
|
+
| Tasks | 6 |
|
|
66
|
+
| Contacts | 6 |
|
|
67
|
+
| Notes | 5 |
|
|
68
|
+
| Mail | 8 |
|
|
69
|
+
| Deck | 12 |
|
|
70
|
+
| Cookbook | 6 |
|
|
71
|
+
| Maps | 25 |
|
|
72
|
+
| Photos | 11 |
|
|
73
|
+
| Talk | 10 |
|
|
74
|
+
| Circles | 8 |
|
|
75
|
+
| Bookmarks | 13 |
|
|
76
|
+
| Assistant | 4 |
|
|
77
|
+
| Translate | 1 |
|
|
78
|
+
| User Status | 5 |
|
|
79
|
+
| Notifications | 4 |
|
|
80
|
+
| **Total** | **198** |
|
|
58
81
|
|
|
59
82
|
## Configuration
|
|
60
83
|
|
|
@@ -64,7 +87,7 @@ See the [Docker setup guide](https://github.com/elgorro/aiquila/blob/main/docs/m
|
|
|
64
87
|
| `NEXTCLOUD_USER` | Yes | |
|
|
65
88
|
| `NEXTCLOUD_PASSWORD` | Yes | use an App Password |
|
|
66
89
|
| `MCP_TRANSPORT` | No | `stdio` (default) or `http` |
|
|
67
|
-
| `MCP_AUTH_ENABLED` | No | `true` to enable OAuth for
|
|
90
|
+
| `MCP_AUTH_ENABLED` | No | `true` to enable OAuth for remote clients |
|
|
68
91
|
| `MCP_AUTH_SECRET` | If auth | `openssl rand -hex 32` |
|
|
69
92
|
| `MCP_AUTH_ISSUER` | If auth | public HTTPS URL of this server |
|
|
70
93
|
| `LOG_LEVEL` | No | `trace`/`debug`/`info`/`warn`/`error`/`fatal` |
|
|
@@ -74,12 +97,12 @@ See the [Docker setup guide](https://github.com/elgorro/aiquila/blob/main/docs/m
|
|
|
74
97
|
- Node.js 24+
|
|
75
98
|
- A Nextcloud instance with an App Password
|
|
76
99
|
|
|
77
|
-
Optional Nextcloud apps unlock additional tool categories: Tasks, Calendar, Contacts, Notes, Cookbook, Bookmarks, Mail, Maps.
|
|
100
|
+
Optional Nextcloud apps unlock additional tool categories: Tasks, Calendar, Contacts, Notes, Cookbook, Deck, Bookmarks, Mail, Maps, Photos, Talk, Circles, and more.
|
|
78
101
|
|
|
79
102
|
## Documentation
|
|
80
103
|
|
|
81
104
|
- [Setup Guide](https://github.com/elgorro/aiquila/blob/main/docs/mcp/setup.md) — detailed installation and configuration
|
|
82
|
-
- [Tools Reference](https://github.com/elgorro/aiquila/blob/main/docs/mcp/README.md) — all
|
|
105
|
+
- [Tools Reference](https://github.com/elgorro/aiquila/blob/main/docs/mcp/README.md) — all 198 tools documented
|
|
83
106
|
- [Architecture](https://github.com/elgorro/aiquila/blob/main/docs/dev/mcp-server-architecture.md) — design and internals
|
|
84
107
|
- [Full Documentation](https://github.com/elgorro/aiquila/blob/main/docs/) — complete docs index
|
|
85
108
|
|
package/dist/auth/login.js
CHANGED
package/dist/auth/provider.js
CHANGED
package/dist/auth/store.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { readFileSync, writeFileSync, mkdirSync, renameSync, unlinkSync } from 'node:fs';
|
|
3
4
|
import { join } from 'node:path';
|
|
@@ -92,12 +93,13 @@ export class ClientsStore {
|
|
|
92
93
|
}
|
|
93
94
|
/**
|
|
94
95
|
* Creates a ClientsStore configured from environment variables:
|
|
95
|
-
* MCP_CLIENT_ID
|
|
96
|
-
*
|
|
96
|
+
* MCP_CLIENT_ID → pre-seeded static public PKCE client (any MCP client)
|
|
97
|
+
* MCP_CLIENT_REDIRECT_URIS → comma-separated redirect URIs for the pre-seeded client
|
|
98
|
+
* MCP_REGISTRATION_ENABLED=true → enable dynamic POST /register
|
|
97
99
|
*
|
|
98
100
|
* Note: no client_secret is stored — the SDK's clientAuth middleware requires the caller
|
|
99
101
|
* to send client_secret whenever client.client_secret is set, which public OAuth clients
|
|
100
|
-
*
|
|
102
|
+
* never do. Security is enforced via PKCE instead.
|
|
101
103
|
*/
|
|
102
104
|
static fromEnv() {
|
|
103
105
|
const preseeded = [];
|
|
@@ -109,7 +111,13 @@ export class ClientsStore {
|
|
|
109
111
|
.split(',')
|
|
110
112
|
.map((u) => u.trim())
|
|
111
113
|
.filter(Boolean)
|
|
112
|
-
: [
|
|
114
|
+
: [];
|
|
115
|
+
if (redirectUris.length === 0) {
|
|
116
|
+
logger.warn({ clientId: id }, '[config] MCP_CLIENT_ID is set but MCP_CLIENT_REDIRECT_URIS is empty — ' +
|
|
117
|
+
'the pre-seeded client will not be able to complete the OAuth flow. ' +
|
|
118
|
+
"Set MCP_CLIENT_REDIRECT_URIS to your client's callback URL, or enable " +
|
|
119
|
+
'dynamic registration with MCP_REGISTRATION_ENABLED=true.');
|
|
120
|
+
}
|
|
113
121
|
preseeded.push({
|
|
114
122
|
client_id: id,
|
|
115
123
|
client_id_issued_at: Math.floor(Date.now() / 1000),
|
package/dist/client/aiquila.js
CHANGED
package/dist/client/bookmarks.js
CHANGED
package/dist/client/caldav.js
CHANGED
package/dist/client/deck.js
CHANGED
package/dist/client/mail.js
CHANGED
package/dist/client/maps.js
CHANGED
package/dist/client/notes.js
CHANGED
package/dist/client/ocs.js
CHANGED
package/dist/client/webdav.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
2
3
|
import { startStdio } from './transports/stdio.js';
|
|
3
4
|
import { startHttp } from './transports/http.js';
|
|
4
5
|
import { logger } from './logger.js';
|
|
@@ -7,8 +8,8 @@ import { logger } from './logger.js';
|
|
|
7
8
|
* Provides Model Context Protocol integration for Nextcloud
|
|
8
9
|
*
|
|
9
10
|
* Transport selection via MCP_TRANSPORT environment variable:
|
|
10
|
-
* - "stdio" (default): Standard
|
|
11
|
-
* - "http": Streamable HTTP transport for
|
|
11
|
+
* - "stdio" (default): Standard I/O transport for local MCP clients (e.g. Claude Desktop, Cursor)
|
|
12
|
+
* - "http": Streamable HTTP transport for remote/network MCP clients
|
|
12
13
|
*/
|
|
13
14
|
async function main() {
|
|
14
15
|
const transport = process.env.MCP_TRANSPORT || 'stdio';
|
package/dist/logger.js
CHANGED
package/dist/server.js
CHANGED
package/dist/tool-registry.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { decodeXmlEntities, fetchCalDAV, nsTagContent } from '../../client/caldav.js';
|
|
4
|
+
import { escapeICalValue, unescapeICalValue } from '../dav-utils.js';
|
|
3
5
|
import { getNextcloudConfig } from '../types.js';
|
|
4
6
|
// ---------------------------------------------------------------------------
|
|
5
7
|
// iCalendar helpers
|
|
@@ -7,20 +9,6 @@ import { getNextcloudConfig } from '../types.js';
|
|
|
7
9
|
function unfoldICalLines(text) {
|
|
8
10
|
return text.replace(/\r?\n[ \t]/g, '');
|
|
9
11
|
}
|
|
10
|
-
function escapeICalValue(value) {
|
|
11
|
-
return value
|
|
12
|
-
.replace(/\\/g, '\\\\')
|
|
13
|
-
.replace(/;/g, '\\;')
|
|
14
|
-
.replace(/,/g, '\\,')
|
|
15
|
-
.replace(/\n/g, '\\n');
|
|
16
|
-
}
|
|
17
|
-
function unescapeICalValue(value) {
|
|
18
|
-
return value
|
|
19
|
-
.replace(/\\n/g, '\n')
|
|
20
|
-
.replace(/\\,/g, ',')
|
|
21
|
-
.replace(/\\;/g, ';')
|
|
22
|
-
.replace(/\\\\/g, '\\');
|
|
23
|
-
}
|
|
24
12
|
function formatICalDate(icalDate) {
|
|
25
13
|
if (icalDate.length === 8) {
|
|
26
14
|
return `${icalDate.slice(0, 4)}-${icalDate.slice(4, 6)}-${icalDate.slice(6, 8)}`;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { decodeXmlEntities, fetchCalDAV, nsTagContent } from '../../client/caldav.js';
|
|
4
|
+
import { escapeVCardValue, unescapeVCardValue } from '../dav-utils.js';
|
|
3
5
|
import { getNextcloudConfig } from '../types.js';
|
|
4
6
|
// ---------------------------------------------------------------------------
|
|
5
7
|
// vCard helpers
|
|
@@ -7,20 +9,6 @@ import { getNextcloudConfig } from '../types.js';
|
|
|
7
9
|
function unfoldVCardLines(text) {
|
|
8
10
|
return text.replace(/\r?\n[ \t]/g, '');
|
|
9
11
|
}
|
|
10
|
-
function escapeVCardValue(value) {
|
|
11
|
-
return value
|
|
12
|
-
.replace(/\\/g, '\\\\')
|
|
13
|
-
.replace(/;/g, '\\;')
|
|
14
|
-
.replace(/,/g, '\\,')
|
|
15
|
-
.replace(/\n/g, '\\n');
|
|
16
|
-
}
|
|
17
|
-
function unescapeVCardValue(value) {
|
|
18
|
-
return value
|
|
19
|
-
.replace(/\\n/gi, '\n')
|
|
20
|
-
.replace(/\\,/g, ',')
|
|
21
|
-
.replace(/\\;/g, ';')
|
|
22
|
-
.replace(/\\\\/g, '\\');
|
|
23
|
-
}
|
|
24
12
|
/**
|
|
25
13
|
* Extract TYPE parameter from a vCard property line.
|
|
26
14
|
* Handles TYPE=WORK, TYPE="WORK", type=work, etc.
|
package/dist/tools/apps/deck.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { fetchDeckAPI, } from '../../client/deck.js';
|
|
3
|
-
import {
|
|
4
|
+
import { handleAppError } from '../error-utils.js';
|
|
4
5
|
/**
|
|
5
6
|
* Nextcloud Deck App Tools
|
|
6
7
|
* Uses the Deck REST API v1.0 (/index.php/apps/deck/api/v1.0)
|
|
7
8
|
*/
|
|
9
|
+
const deckStatusMap = {
|
|
10
|
+
404: 'Not found.',
|
|
11
|
+
403: 'Permission denied.',
|
|
12
|
+
400: (e) => `Bad request: ${e.responseBody}`,
|
|
13
|
+
};
|
|
8
14
|
function formatBoard(board) {
|
|
9
15
|
const flags = [
|
|
10
16
|
board.archived ? 'archived' : null,
|
|
@@ -62,31 +68,6 @@ function formatCardDetail(card) {
|
|
|
62
68
|
lines.push(`Modified: ${new Date(card.lastModified * 1000).toISOString()}`);
|
|
63
69
|
return lines.join('\n');
|
|
64
70
|
}
|
|
65
|
-
function handleError(error, context) {
|
|
66
|
-
if (error instanceof ApiError) {
|
|
67
|
-
if (error.statusCode === 404) {
|
|
68
|
-
return { content: [{ type: 'text', text: 'Not found.' }], isError: true };
|
|
69
|
-
}
|
|
70
|
-
if (error.statusCode === 403) {
|
|
71
|
-
return { content: [{ type: 'text', text: 'Permission denied.' }], isError: true };
|
|
72
|
-
}
|
|
73
|
-
if (error.statusCode === 400) {
|
|
74
|
-
return {
|
|
75
|
-
content: [{ type: 'text', text: `Bad request: ${error.responseBody}` }],
|
|
76
|
-
isError: true,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
content: [
|
|
82
|
-
{
|
|
83
|
-
type: 'text',
|
|
84
|
-
text: `${context}: ${error instanceof Error ? error.message : String(error)}`,
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
isError: true,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
71
|
export const listBoardsTool = {
|
|
91
72
|
name: 'deck_list_boards',
|
|
92
73
|
description: 'List all Deck boards. Returns id, title, owner, and label count for each board.',
|
|
@@ -107,7 +88,7 @@ export const listBoardsTool = {
|
|
|
107
88
|
};
|
|
108
89
|
}
|
|
109
90
|
catch (error) {
|
|
110
|
-
return
|
|
91
|
+
return handleAppError(error, 'Error listing boards', deckStatusMap);
|
|
111
92
|
}
|
|
112
93
|
},
|
|
113
94
|
};
|
|
@@ -151,7 +132,7 @@ export const getBoardTool = {
|
|
|
151
132
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
152
133
|
}
|
|
153
134
|
catch (error) {
|
|
154
|
-
return
|
|
135
|
+
return handleAppError(error, 'Error getting board', deckStatusMap);
|
|
155
136
|
}
|
|
156
137
|
},
|
|
157
138
|
};
|
|
@@ -176,7 +157,7 @@ export const createBoardTool = {
|
|
|
176
157
|
};
|
|
177
158
|
}
|
|
178
159
|
catch (error) {
|
|
179
|
-
return
|
|
160
|
+
return handleAppError(error, 'Error creating board', deckStatusMap);
|
|
180
161
|
}
|
|
181
162
|
},
|
|
182
163
|
};
|
|
@@ -216,7 +197,7 @@ export const listStacksTool = {
|
|
|
216
197
|
};
|
|
217
198
|
}
|
|
218
199
|
catch (error) {
|
|
219
|
-
return
|
|
200
|
+
return handleAppError(error, 'Error listing stacks', deckStatusMap);
|
|
220
201
|
}
|
|
221
202
|
},
|
|
222
203
|
};
|
|
@@ -239,7 +220,7 @@ export const createStackTool = {
|
|
|
239
220
|
};
|
|
240
221
|
}
|
|
241
222
|
catch (error) {
|
|
242
|
-
return
|
|
223
|
+
return handleAppError(error, 'Error creating stack', deckStatusMap);
|
|
243
224
|
}
|
|
244
225
|
},
|
|
245
226
|
};
|
|
@@ -257,7 +238,7 @@ export const getCardTool = {
|
|
|
257
238
|
return { content: [{ type: 'text', text: formatCardDetail(card) }] };
|
|
258
239
|
}
|
|
259
240
|
catch (error) {
|
|
260
|
-
return
|
|
241
|
+
return handleAppError(error, 'Error getting card', deckStatusMap);
|
|
261
242
|
}
|
|
262
243
|
},
|
|
263
244
|
};
|
|
@@ -288,7 +269,7 @@ export const createCardTool = {
|
|
|
288
269
|
};
|
|
289
270
|
}
|
|
290
271
|
catch (error) {
|
|
291
|
-
return
|
|
272
|
+
return handleAppError(error, 'Error creating card', deckStatusMap);
|
|
292
273
|
}
|
|
293
274
|
},
|
|
294
275
|
};
|
|
@@ -325,7 +306,7 @@ export const updateCardTool = {
|
|
|
325
306
|
};
|
|
326
307
|
}
|
|
327
308
|
catch (error) {
|
|
328
|
-
return
|
|
309
|
+
return handleAppError(error, 'Error updating card', deckStatusMap);
|
|
329
310
|
}
|
|
330
311
|
},
|
|
331
312
|
};
|
|
@@ -359,7 +340,7 @@ export const moveCardTool = {
|
|
|
359
340
|
};
|
|
360
341
|
}
|
|
361
342
|
catch (error) {
|
|
362
|
-
return
|
|
343
|
+
return handleAppError(error, 'Error moving card', deckStatusMap);
|
|
363
344
|
}
|
|
364
345
|
},
|
|
365
346
|
};
|
|
@@ -386,7 +367,7 @@ export const archiveCardTool = {
|
|
|
386
367
|
};
|
|
387
368
|
}
|
|
388
369
|
catch (error) {
|
|
389
|
-
return
|
|
370
|
+
return handleAppError(error, `Error ${args.archive ? 'archiving' : 'unarchiving'} card`, deckStatusMap);
|
|
390
371
|
}
|
|
391
372
|
},
|
|
392
373
|
};
|
|
@@ -412,7 +393,7 @@ export const assignLabelTool = {
|
|
|
412
393
|
};
|
|
413
394
|
}
|
|
414
395
|
catch (error) {
|
|
415
|
-
return
|
|
396
|
+
return handleAppError(error, 'Error assigning label', deckStatusMap);
|
|
416
397
|
}
|
|
417
398
|
},
|
|
418
399
|
};
|
|
@@ -438,7 +419,7 @@ export const assignUserTool = {
|
|
|
438
419
|
};
|
|
439
420
|
}
|
|
440
421
|
catch (error) {
|
|
441
|
-
return
|
|
422
|
+
return handleAppError(error, 'Error assigning user', deckStatusMap);
|
|
442
423
|
}
|
|
443
424
|
},
|
|
444
425
|
};
|
package/dist/tools/apps/mail.js
CHANGED
package/dist/tools/apps/maps.js
CHANGED
package/dist/tools/apps/notes.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { fetchNotesAPI } from '../../client/notes.js';
|
|
3
|
-
import {
|
|
4
|
+
import { handleAppError } from '../error-utils.js';
|
|
4
5
|
/**
|
|
5
6
|
* Nextcloud Notes App Tools
|
|
6
7
|
* Uses the Notes REST API v1 (/index.php/apps/notes/api/v1)
|
|
7
8
|
*/
|
|
9
|
+
const notesStatusMap = {
|
|
10
|
+
404: 'Note not found.',
|
|
11
|
+
403: 'Note is read-only.',
|
|
12
|
+
412: 'Conflict: note was modified by someone else. Fetch the latest version and retry.',
|
|
13
|
+
};
|
|
8
14
|
function formatNote(note) {
|
|
9
15
|
const date = new Date(note.modified * 1000).toISOString();
|
|
10
16
|
const flags = [note.favorite ? 'favorite' : null, note.readonly ? 'readonly' : null]
|
|
@@ -15,36 +21,6 @@ function formatNote(note) {
|
|
|
15
21
|
.join(' | ');
|
|
16
22
|
return `[${note.id}] ${note.title}${meta ? ` (${meta})` : ''} — modified: ${date}`;
|
|
17
23
|
}
|
|
18
|
-
function handleError(error, context) {
|
|
19
|
-
if (error instanceof ApiError) {
|
|
20
|
-
if (error.statusCode === 404) {
|
|
21
|
-
return { content: [{ type: 'text', text: `Note not found.` }], isError: true };
|
|
22
|
-
}
|
|
23
|
-
if (error.statusCode === 403) {
|
|
24
|
-
return { content: [{ type: 'text', text: `Note is read-only.` }], isError: true };
|
|
25
|
-
}
|
|
26
|
-
if (error.statusCode === 412) {
|
|
27
|
-
return {
|
|
28
|
-
content: [
|
|
29
|
-
{
|
|
30
|
-
type: 'text',
|
|
31
|
-
text: `Conflict: note was modified by someone else. Fetch the latest version and retry.`,
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
isError: true,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return {
|
|
39
|
-
content: [
|
|
40
|
-
{
|
|
41
|
-
type: 'text',
|
|
42
|
-
text: `${context}: ${error instanceof Error ? error.message : String(error)}`,
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
isError: true,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
24
|
export const listNotesTool = {
|
|
49
25
|
name: 'list_notes',
|
|
50
26
|
description: 'List all notes in Nextcloud Notes. Returns id, title, category, favorite flag, and modification date.',
|
|
@@ -75,7 +51,7 @@ export const listNotesTool = {
|
|
|
75
51
|
};
|
|
76
52
|
}
|
|
77
53
|
catch (error) {
|
|
78
|
-
return
|
|
54
|
+
return handleAppError(error, 'Error listing notes', notesStatusMap);
|
|
79
55
|
}
|
|
80
56
|
},
|
|
81
57
|
};
|
|
@@ -98,7 +74,7 @@ export const getNoteTool = {
|
|
|
98
74
|
};
|
|
99
75
|
}
|
|
100
76
|
catch (error) {
|
|
101
|
-
return
|
|
77
|
+
return handleAppError(error, 'Error getting note', notesStatusMap);
|
|
102
78
|
}
|
|
103
79
|
},
|
|
104
80
|
};
|
|
@@ -132,7 +108,7 @@ export const createNoteTool = {
|
|
|
132
108
|
};
|
|
133
109
|
}
|
|
134
110
|
catch (error) {
|
|
135
|
-
return
|
|
111
|
+
return handleAppError(error, 'Error creating note', notesStatusMap);
|
|
136
112
|
}
|
|
137
113
|
},
|
|
138
114
|
};
|
|
@@ -170,7 +146,7 @@ export const updateNoteTool = {
|
|
|
170
146
|
};
|
|
171
147
|
}
|
|
172
148
|
catch (error) {
|
|
173
|
-
return
|
|
149
|
+
return handleAppError(error, 'Error updating note', notesStatusMap);
|
|
174
150
|
}
|
|
175
151
|
},
|
|
176
152
|
};
|
|
@@ -193,7 +169,7 @@ export const deleteNoteTool = {
|
|
|
193
169
|
};
|
|
194
170
|
}
|
|
195
171
|
catch (error) {
|
|
196
|
-
return
|
|
172
|
+
return handleAppError(error, 'Error deleting note', notesStatusMap);
|
|
197
173
|
}
|
|
198
174
|
},
|
|
199
175
|
};
|
package/dist/tools/apps/talk.js
CHANGED
package/dist/tools/apps/tasks.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { decodeXmlEntities, fetchCalDAV, nsTagContent } from '../../client/caldav.js';
|
|
4
|
+
import { escapeICalValue } from '../dav-utils.js';
|
|
3
5
|
import { getNextcloudConfig } from '../types.js';
|
|
4
6
|
// ---------------------------------------------------------------------------
|
|
5
7
|
// iCalendar helpers
|
|
@@ -11,16 +13,6 @@ import { getNextcloudConfig } from '../types.js';
|
|
|
11
13
|
function unfoldICalLines(text) {
|
|
12
14
|
return text.replace(/\r?\n[ \t]/g, '');
|
|
13
15
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Escape special characters in iCalendar text values per RFC 5545.
|
|
16
|
-
*/
|
|
17
|
-
function escapeICalValue(value) {
|
|
18
|
-
return value
|
|
19
|
-
.replace(/\\/g, '\\\\')
|
|
20
|
-
.replace(/;/g, '\\;')
|
|
21
|
-
.replace(/,/g, '\\,')
|
|
22
|
-
.replace(/\n/g, '\\n');
|
|
23
|
-
}
|
|
24
16
|
/**
|
|
25
17
|
* Format an iCalendar date string for human-readable display.
|
|
26
18
|
* "20240115T103000Z" -> "2024-01-15 10:30"
|
package/dist/tools/apps/trash.js
CHANGED
package/dist/tools/apps/users.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
/**
|
|
3
|
+
* Shared DAV escape/unescape utilities for iCalendar and vCard values.
|
|
4
|
+
*
|
|
5
|
+
* iCalendar (RFC 5545) and vCard (RFC 6350) use nearly identical text escaping.
|
|
6
|
+
* The only difference: vCard allows uppercase \N for newlines, so unescape is
|
|
7
|
+
* case-insensitive for that sequence.
|
|
8
|
+
*/
|
|
9
|
+
export function escapeDavValue(value) {
|
|
10
|
+
return value
|
|
11
|
+
.replace(/\\/g, '\\\\')
|
|
12
|
+
.replace(/;/g, '\\;')
|
|
13
|
+
.replace(/,/g, '\\,')
|
|
14
|
+
.replace(/\n/g, '\\n');
|
|
15
|
+
}
|
|
16
|
+
export function unescapeDavValue(value, options) {
|
|
17
|
+
return value
|
|
18
|
+
.replace(options?.caseInsensitiveNewline ? /\\n/gi : /\\n/g, '\n')
|
|
19
|
+
.replace(/\\,/g, ',')
|
|
20
|
+
.replace(/\\;/g, ';')
|
|
21
|
+
.replace(/\\\\/g, '\\');
|
|
22
|
+
}
|
|
23
|
+
/** Escape for iCalendar (VEVENT, VTODO) properties. */
|
|
24
|
+
export const escapeICalValue = escapeDavValue;
|
|
25
|
+
/** Unescape iCalendar property values (case-sensitive \\n). */
|
|
26
|
+
export const unescapeICalValue = (value) => unescapeDavValue(value);
|
|
27
|
+
/** Escape for vCard properties. */
|
|
28
|
+
export const escapeVCardValue = escapeDavValue;
|
|
29
|
+
/** Unescape vCard property values (case-insensitive \\n per RFC 6350). */
|
|
30
|
+
export const unescapeVCardValue = (value) => unescapeDavValue(value, { caseInsensitiveNewline: true });
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
import { ApiError } from '../client/aiquila.js';
|
|
3
|
+
/**
|
|
4
|
+
* Shared error handler for app tool modules.
|
|
5
|
+
*
|
|
6
|
+
* @param error The caught error
|
|
7
|
+
* @param context Human-readable context prefixed to generic messages (e.g. "Error listing notes")
|
|
8
|
+
* @param statusMap Optional map of HTTP status codes to user-facing messages.
|
|
9
|
+
* When the error is an ApiError whose status matches a key, the corresponding
|
|
10
|
+
* message is returned. The message may be a string or a function receiving the
|
|
11
|
+
* ApiError for dynamic messages (e.g. including the response body).
|
|
12
|
+
*/
|
|
13
|
+
export function handleAppError(error, context, statusMap = {}) {
|
|
14
|
+
if (error instanceof ApiError) {
|
|
15
|
+
const entry = statusMap[error.statusCode];
|
|
16
|
+
if (entry !== undefined) {
|
|
17
|
+
const text = typeof entry === 'function' ? entry(error) : entry;
|
|
18
|
+
return { content: [{ type: 'text', text }], isError: true };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: 'text',
|
|
25
|
+
text: `${context}: ${error instanceof Error ? error.message : String(error)}`,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
isError: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
package/dist/tools/system/occ.js
CHANGED
package/dist/tools/types.js
CHANGED
package/dist/transports/http.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
1
2
|
import https from 'node:https';
|
|
2
3
|
import express from 'express';
|
|
3
4
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
@@ -26,8 +27,8 @@ const TLS_CERT_ERROR_CODES = new Set([
|
|
|
26
27
|
/**
|
|
27
28
|
* Advisory TLS certificate check — purely informational, never crashes the server.
|
|
28
29
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
30
|
+
* Most MCP clients require a CA-trusted cert. If the issuer URL still has a
|
|
31
|
+
* self-signed or untrusted cert at startup this is logged as a warning so
|
|
31
32
|
* operators know to fix it, but the MCP server continues running normally.
|
|
32
33
|
*
|
|
33
34
|
* Rationale: crashing or health-check-failing on a bad cert creates a deadlock on
|
|
@@ -62,8 +63,8 @@ async function checkIssuerTls(issuerUrl) {
|
|
|
62
63
|
const code = err.code ?? '';
|
|
63
64
|
if (TLS_CERT_ERROR_CODES.has(code)) {
|
|
64
65
|
logger.warn({ issuer: issuerUrl, code }, `[startup] TLS certificate not yet trusted (${code}). ` +
|
|
65
|
-
`
|
|
66
|
-
`
|
|
66
|
+
`Most MCP clients require a CA-trusted certificate — self-signed certs ` +
|
|
67
|
+
`may cause clients to refuse the connection. ` +
|
|
67
68
|
`Use Let's Encrypt (via Traefik or Caddy with a real domain) or mount a CA-signed cert. ` +
|
|
68
69
|
`The MCP server will keep running; re-deploy once the cert is valid.`);
|
|
69
70
|
}
|
|
@@ -149,8 +150,8 @@ export async function startHttp() {
|
|
|
149
150
|
}
|
|
150
151
|
// Stateless mode: create a new transport + server per request so each MCP
|
|
151
152
|
// call is handled independently. This is required by the SDK for stateless
|
|
152
|
-
// operation and allows distributed clients (like Claude.ai) to
|
|
153
|
-
// multiple IPs without needing a shared session.
|
|
153
|
+
// operation and allows distributed clients (like Claude.ai, Cursor, etc.) to
|
|
154
|
+
// connect from multiple IPs without needing a shared session.
|
|
154
155
|
const handleMcpRequest = async (req, res) => {
|
|
155
156
|
const mcpServer = await createServer();
|
|
156
157
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
package/dist/transports/stdio.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiquila-mcp",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "AIquila - MCP server for Nextcloud integration
|
|
3
|
+
"version": "0.2.18",
|
|
4
|
+
"description": "AIquila - MCP server for Nextcloud integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|