@henrikogard/auroradocs-mcp 0.1.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.
- package/LICENSE +201 -0
- package/NOTICE +3 -0
- package/README.md +239 -0
- package/dist/auroraClient.d.ts +165 -0
- package/dist/auroraClient.js +706 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +62 -0
- package/dist/planningTools.d.ts +76 -0
- package/dist/planningTools.js +159 -0
- package/dist/toolCatalog.d.ts +39 -0
- package/dist/toolCatalog.js +423 -0
- package/dist/tools.d.ts +105 -0
- package/dist/tools.js +640 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/package.json +54 -0
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auroraClient.ts — AuroraCloud client helpers for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* SECURITY: Every query is scoped to the configured workspace.
|
|
5
|
+
* On authenticate(), the server verifies the caller is a member of
|
|
6
|
+
* that workspace — if not, the process exits.
|
|
7
|
+
*/
|
|
8
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
9
|
+
export const WORKSPACE_KNOWLEDGE_SOURCE_KINDS = [
|
|
10
|
+
'object',
|
|
11
|
+
'content_chunk',
|
|
12
|
+
'property',
|
|
13
|
+
'comment',
|
|
14
|
+
'attachment_metadata',
|
|
15
|
+
'relationship',
|
|
16
|
+
];
|
|
17
|
+
export const WORKSPACE_KNOWLEDGE_AVAILABILITY_STATES = [
|
|
18
|
+
'available',
|
|
19
|
+
'encrypted_locked',
|
|
20
|
+
'not_indexed',
|
|
21
|
+
'unsupported_type',
|
|
22
|
+
'permission_denied',
|
|
23
|
+
];
|
|
24
|
+
export const WORKSPACE_KNOWLEDGE_RELATIONSHIP_TYPES = [
|
|
25
|
+
'parent',
|
|
26
|
+
'child',
|
|
27
|
+
'link',
|
|
28
|
+
'backlink',
|
|
29
|
+
'tag',
|
|
30
|
+
'task_project',
|
|
31
|
+
];
|
|
32
|
+
// ── Singleton backend instance ───────────────────────────────────────────────
|
|
33
|
+
let _client = null;
|
|
34
|
+
export function resetAuroraClientForTests() {
|
|
35
|
+
_client = null;
|
|
36
|
+
}
|
|
37
|
+
export function getAuroraClient() {
|
|
38
|
+
if (_client)
|
|
39
|
+
return _client;
|
|
40
|
+
_client = createAuroraCloudClient(getAuroraApiUrl());
|
|
41
|
+
return _client;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Authenticate and verify workspace membership.
|
|
45
|
+
* Exits the process if the user is not a member of the target workspace.
|
|
46
|
+
*/
|
|
47
|
+
export async function authenticate() {
|
|
48
|
+
const client = getAuroraClient();
|
|
49
|
+
const workspaceId = process.env['AURORA_WORKSPACE_ID'];
|
|
50
|
+
const token = process.env['AURORA_API_TOKEN'];
|
|
51
|
+
if (token) {
|
|
52
|
+
client.authStore.save(token, null);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const email = process.env['AURORA_API_EMAIL'];
|
|
56
|
+
const password = process.env['AURORA_API_PASSWORD'];
|
|
57
|
+
const users = client.collection('users');
|
|
58
|
+
if (email && password && users.authWithPassword) {
|
|
59
|
+
await users.authWithPassword(email, password);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
throw new Error('Provide AURORA_API_TOKEN (recommended: workspace MCP token `aur_mcp_...`), or AURORA_API_EMAIL + AURORA_API_PASSWORD for local development only.');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
await ensureAuthRecord();
|
|
66
|
+
// Verify workspace membership
|
|
67
|
+
if (workspaceId) {
|
|
68
|
+
const userId = getAuthUserId();
|
|
69
|
+
if (!userId)
|
|
70
|
+
throw new Error('Could not determine authenticated user ID');
|
|
71
|
+
const members = await client.collection('workspace_members').list({
|
|
72
|
+
filter: client.filter('workspace_id = {:wid} && user_id = {:uid}', { wid: workspaceId, uid: userId }),
|
|
73
|
+
});
|
|
74
|
+
if (!members.length) {
|
|
75
|
+
throw new Error(`Authenticated user ${userId} is not a member of workspace ${workspaceId}`);
|
|
76
|
+
}
|
|
77
|
+
process.stderr.write(`Authenticated as ${userId} (role: ${members[0]['role']})\n`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Get the authenticated user's ID from the auth store. */
|
|
81
|
+
function getAuthUserId() {
|
|
82
|
+
const client = getAuroraClient();
|
|
83
|
+
const record = client.authStore.record;
|
|
84
|
+
return record?.['id'] ?? null;
|
|
85
|
+
}
|
|
86
|
+
// ── Object CRUD ──────────────────────────────────────────────────────────────
|
|
87
|
+
export async function listObjects(workspaceId, type) {
|
|
88
|
+
const client = getAuroraClient();
|
|
89
|
+
const filter = type
|
|
90
|
+
? client.filter('workspace_id = {:wid} && is_deleted = false && type = {:type}', { wid: workspaceId, type })
|
|
91
|
+
: client.filter('workspace_id = {:wid} && is_deleted = false', { wid: workspaceId });
|
|
92
|
+
const records = await client.collection('objects').list({ filter, sort: '-updated_at' });
|
|
93
|
+
return records.map(mapObject);
|
|
94
|
+
}
|
|
95
|
+
export async function getObject(id, workspaceId) {
|
|
96
|
+
const client = getAuroraClient();
|
|
97
|
+
try {
|
|
98
|
+
const r = await client.collection('objects').get(id);
|
|
99
|
+
const obj = mapObject(r);
|
|
100
|
+
// SECURITY: reject objects outside the configured workspace
|
|
101
|
+
if (obj.workspace_id !== workspaceId)
|
|
102
|
+
return null;
|
|
103
|
+
return obj;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export async function createObject(workspaceId, type, title) {
|
|
110
|
+
const client = getAuroraClient();
|
|
111
|
+
const r = await client.collection('objects').create({
|
|
112
|
+
workspace_id: workspaceId,
|
|
113
|
+
type,
|
|
114
|
+
title,
|
|
115
|
+
is_deleted: false,
|
|
116
|
+
is_template: false,
|
|
117
|
+
});
|
|
118
|
+
return mapObject(r);
|
|
119
|
+
}
|
|
120
|
+
export async function updateObjectTitle(id, title, workspaceId) {
|
|
121
|
+
// Verify ownership before update
|
|
122
|
+
const obj = await getObject(id, workspaceId);
|
|
123
|
+
if (!obj)
|
|
124
|
+
throw new Error(`Object ${id} not found in this workspace`);
|
|
125
|
+
const client = getAuroraClient();
|
|
126
|
+
await client.collection('objects').update(id, { title });
|
|
127
|
+
}
|
|
128
|
+
export async function deleteObject(id, workspaceId) {
|
|
129
|
+
const obj = await getObject(id, workspaceId);
|
|
130
|
+
if (!obj)
|
|
131
|
+
throw new Error(`Object ${id} not found in this workspace`);
|
|
132
|
+
const client = getAuroraClient();
|
|
133
|
+
await client.collection('objects').update(id, { is_deleted: true });
|
|
134
|
+
}
|
|
135
|
+
// ── Content ──────────────────────────────────────────────────────────────────
|
|
136
|
+
export async function getContent(objectId, workspaceId) {
|
|
137
|
+
// Verify object belongs to workspace
|
|
138
|
+
const obj = await getObject(objectId, workspaceId);
|
|
139
|
+
if (!obj)
|
|
140
|
+
return null;
|
|
141
|
+
const client = getAuroraClient();
|
|
142
|
+
try {
|
|
143
|
+
const records = await client.collection('content').list({
|
|
144
|
+
filter: client.filter('object_id = {:id}', { id: objectId }),
|
|
145
|
+
});
|
|
146
|
+
const r = records[0];
|
|
147
|
+
if (!r)
|
|
148
|
+
return null;
|
|
149
|
+
const json = r['content_json'];
|
|
150
|
+
if (!json)
|
|
151
|
+
return null;
|
|
152
|
+
return extractTextFromDoc(typeof json === 'string' ? JSON.parse(json) : json);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export async function getContentJson(objectId, workspaceId) {
|
|
159
|
+
const obj = await getObject(objectId, workspaceId);
|
|
160
|
+
if (!obj)
|
|
161
|
+
return null;
|
|
162
|
+
const client = getAuroraClient();
|
|
163
|
+
const records = await client.collection('content').list({
|
|
164
|
+
filter: client.filter('object_id = {:id}', { id: objectId }),
|
|
165
|
+
});
|
|
166
|
+
const r = records[0];
|
|
167
|
+
if (!r)
|
|
168
|
+
return null;
|
|
169
|
+
const json = r['content_json'];
|
|
170
|
+
if (!json)
|
|
171
|
+
return null;
|
|
172
|
+
if (typeof json === 'string') {
|
|
173
|
+
if (json.startsWith('v1:') || json.startsWith('v2:')) {
|
|
174
|
+
throw new Error('Object content is end-to-end encrypted and cannot be modified by the MCP server');
|
|
175
|
+
}
|
|
176
|
+
return JSON.parse(json);
|
|
177
|
+
}
|
|
178
|
+
return json;
|
|
179
|
+
}
|
|
180
|
+
export async function searchWorkspaceKnowledgeServer(workspaceId, query, limit = 20) {
|
|
181
|
+
const trimmedWorkspaceId = workspaceId.trim();
|
|
182
|
+
const trimmedQuery = query.trim();
|
|
183
|
+
if (!trimmedWorkspaceId || !trimmedQuery)
|
|
184
|
+
return [];
|
|
185
|
+
const payload = await getAuroraClient().request(`/api/knowledge/workspaces/${trimmedWorkspaceId}/search?q=${encodeURIComponent(trimmedQuery)}&limit=${Math.max(1, Math.min(50, limit))}`, { method: 'GET' });
|
|
186
|
+
return normalizeWorkspaceKnowledgeResponse(payload).items;
|
|
187
|
+
}
|
|
188
|
+
export async function getWorkspaceKnowledgeObjectServer(workspaceId, objectId, includeFullText = false) {
|
|
189
|
+
const trimmedWorkspaceId = workspaceId.trim();
|
|
190
|
+
const trimmedObjectId = objectId.trim();
|
|
191
|
+
if (!trimmedWorkspaceId || !trimmedObjectId)
|
|
192
|
+
return null;
|
|
193
|
+
const payload = await getAuroraClient().request(`/api/knowledge/workspaces/${trimmedWorkspaceId}/objects/${trimmedObjectId}`, { method: 'GET' });
|
|
194
|
+
const source = normalizeWorkspaceKnowledgeResponse(payload).items[0] ?? null;
|
|
195
|
+
if (!source)
|
|
196
|
+
return null;
|
|
197
|
+
return includeFullText ? source : { ...source, plainText: null };
|
|
198
|
+
}
|
|
199
|
+
export async function listWorkspaceRelatedKnowledgeServer(workspaceId, objectId, limit = 6) {
|
|
200
|
+
const trimmedWorkspaceId = workspaceId.trim();
|
|
201
|
+
const trimmedObjectId = objectId.trim();
|
|
202
|
+
if (!trimmedWorkspaceId || !trimmedObjectId)
|
|
203
|
+
return [];
|
|
204
|
+
const payload = await getAuroraClient().request(`/api/knowledge/workspaces/${trimmedWorkspaceId}/related/${trimmedObjectId}?limit=${Math.max(1, Math.min(10, limit))}`, { method: 'GET' });
|
|
205
|
+
return normalizeWorkspaceKnowledgeResponse(payload).items;
|
|
206
|
+
}
|
|
207
|
+
export async function listWorkspaceRecentKnowledgeServer(workspaceId, limit = 6) {
|
|
208
|
+
const trimmedWorkspaceId = workspaceId.trim();
|
|
209
|
+
if (!trimmedWorkspaceId)
|
|
210
|
+
return [];
|
|
211
|
+
const payload = await getAuroraClient().request(`/api/knowledge/workspaces/${trimmedWorkspaceId}/recent?limit=${Math.max(1, Math.min(10, limit))}`, { method: 'GET' });
|
|
212
|
+
return normalizeWorkspaceKnowledgeResponse(payload).items;
|
|
213
|
+
}
|
|
214
|
+
export async function setContent(objectId, workspaceId, contentJson) {
|
|
215
|
+
const obj = await getObject(objectId, workspaceId);
|
|
216
|
+
if (!obj)
|
|
217
|
+
throw new Error(`Object ${objectId} not found in this workspace`);
|
|
218
|
+
const client = getAuroraClient();
|
|
219
|
+
const existing = await client.collection('content').list({
|
|
220
|
+
filter: client.filter('object_id = {:id}', { id: objectId }),
|
|
221
|
+
});
|
|
222
|
+
if (existing.length > 0) {
|
|
223
|
+
await client.collection('content').update(String(existing[0].id), { content_json: contentJson });
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
await client.collection('content').create({ object_id: objectId, content_json: contentJson });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
export async function appendContentText(objectId, workspaceId, text) {
|
|
230
|
+
const current = await getContentJson(objectId, workspaceId);
|
|
231
|
+
const currentContent = Array.isArray(current?.['content']) ? current['content'] : [];
|
|
232
|
+
const appended = text.split('\n').map((line) => ({
|
|
233
|
+
type: 'paragraph',
|
|
234
|
+
content: line ? [{ type: 'text', text: line }] : [],
|
|
235
|
+
}));
|
|
236
|
+
await setContent(objectId, workspaceId, {
|
|
237
|
+
...(current ?? { type: 'doc' }),
|
|
238
|
+
type: 'doc',
|
|
239
|
+
content: [...currentContent, ...appended],
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
// ── Properties (parameterized queries) ───────────────────────────────────────
|
|
243
|
+
export async function listProperties(objectIds, workspaceId) {
|
|
244
|
+
if (!objectIds.length)
|
|
245
|
+
return [];
|
|
246
|
+
const client = getAuroraClient();
|
|
247
|
+
const results = [];
|
|
248
|
+
// Batch in groups of 50, using parameterized filters
|
|
249
|
+
for (let i = 0; i < objectIds.length; i += 50) {
|
|
250
|
+
const batch = objectIds.slice(i, i + 50);
|
|
251
|
+
// Build parameterized OR filter for AuroraCloud collection queries.
|
|
252
|
+
const conditions = batch.map((_, idx) => `object_id = {:id${idx}}`);
|
|
253
|
+
const params = {};
|
|
254
|
+
batch.forEach((id, idx) => { params[`id${idx}`] = id; });
|
|
255
|
+
const filter = client.filter(conditions.join(' || '), params);
|
|
256
|
+
const records = await client.collection('object_properties').list({ filter });
|
|
257
|
+
for (const r of records) {
|
|
258
|
+
results.push({
|
|
259
|
+
id: r['id'],
|
|
260
|
+
object_id: r['object_id'],
|
|
261
|
+
key: r['key'],
|
|
262
|
+
value_type: r['value_type'],
|
|
263
|
+
value_text: r['value_text'] ?? null,
|
|
264
|
+
value_num: r['value_num'] ?? null,
|
|
265
|
+
value_date: r['value_date'] ?? null,
|
|
266
|
+
value_bool: r['value_bool'] != null ? Boolean(r['value_bool']) : null,
|
|
267
|
+
value_ref: r['value_ref'] ?? null,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return results;
|
|
272
|
+
}
|
|
273
|
+
export async function upsertProperty(objectId, workspaceId, key, valueType, value) {
|
|
274
|
+
// Verify object belongs to workspace
|
|
275
|
+
const obj = await getObject(objectId, workspaceId);
|
|
276
|
+
if (!obj)
|
|
277
|
+
throw new Error(`Object ${objectId} not found in this workspace`);
|
|
278
|
+
const client = getAuroraClient();
|
|
279
|
+
const existing = await client.collection('object_properties').list({
|
|
280
|
+
filter: client.filter('object_id = {:oid} && key = {:key}', { oid: objectId, key }),
|
|
281
|
+
});
|
|
282
|
+
const valueField = valueType === 'number' ? 'value_num' :
|
|
283
|
+
valueType === 'date' ? 'value_date' :
|
|
284
|
+
valueType === 'boolean' ? 'value_bool' :
|
|
285
|
+
'value_text';
|
|
286
|
+
const parsedValue = valueType === 'number' ? Number(value) :
|
|
287
|
+
valueType === 'boolean' ? (value === 'true') :
|
|
288
|
+
value;
|
|
289
|
+
if (existing.length > 0) {
|
|
290
|
+
await client.collection('object_properties').update(String(existing[0].id), { value_type: valueType, [valueField]: parsedValue });
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
await client.collection('object_properties').create({ object_id: objectId, key, value_type: valueType, [valueField]: parsedValue });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// ── Members ──────────────────────────────────────────────────────────────────
|
|
297
|
+
export async function listMembers(workspaceId) {
|
|
298
|
+
const response = await getAuroraClient().request(`/workspaces/${workspaceId}/members`, {
|
|
299
|
+
method: 'GET',
|
|
300
|
+
});
|
|
301
|
+
if (Array.isArray(response.members)) {
|
|
302
|
+
return response.members.map((member) => ({
|
|
303
|
+
id: member.userId ?? '',
|
|
304
|
+
name: member.name,
|
|
305
|
+
email: member.email,
|
|
306
|
+
role: member.role,
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
if (Array.isArray(response.items)) {
|
|
310
|
+
return response.items.map((member) => ({
|
|
311
|
+
id: member.userId ?? '',
|
|
312
|
+
name: member.user?.displayName ?? null,
|
|
313
|
+
email: member.user?.email ?? '',
|
|
314
|
+
role: member.role ?? 'viewer',
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
// ── Task lists & statuses ────────────────────────────────────────────────────
|
|
320
|
+
export async function listTaskLists(workspaceId) {
|
|
321
|
+
const client = getAuroraClient();
|
|
322
|
+
try {
|
|
323
|
+
const records = await client.collection('task_lists').list({
|
|
324
|
+
filter: client.filter('workspace_id = {:wid}', { wid: workspaceId }),
|
|
325
|
+
sort: 'position',
|
|
326
|
+
});
|
|
327
|
+
return records.map((r) => ({
|
|
328
|
+
id: r['id'],
|
|
329
|
+
name: r['name'],
|
|
330
|
+
default_status: r['default_status'] ?? null,
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
export async function listTaskStatuses(_workspaceId) {
|
|
338
|
+
// Task statuses are defined in the schema, not per-workspace.
|
|
339
|
+
// Return the standard set.
|
|
340
|
+
return ['Backlog', 'To Do', 'In Progress', 'In Review', 'Done', 'Cancelled'];
|
|
341
|
+
}
|
|
342
|
+
export async function getTaskProps(objectId, workspaceId) {
|
|
343
|
+
const props = await listProperties([objectId], workspaceId);
|
|
344
|
+
const get = (key) => props.find((p) => p.object_id === objectId && p.key === key);
|
|
345
|
+
return {
|
|
346
|
+
status: get('status')?.value_text ?? null,
|
|
347
|
+
priority: get('priority')?.value_text ?? null,
|
|
348
|
+
due_date: get('due_date')?.value_date ?? get('due_date')?.value_text ?? null,
|
|
349
|
+
assignees: (() => {
|
|
350
|
+
const raw = get('assignees')?.value_text;
|
|
351
|
+
if (!raw)
|
|
352
|
+
return [];
|
|
353
|
+
try {
|
|
354
|
+
return JSON.parse(raw);
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
return [];
|
|
358
|
+
}
|
|
359
|
+
})(),
|
|
360
|
+
labels: parseTaskListProperty(get('tags')?.value_text, get('labels')?.value_text),
|
|
361
|
+
description: get('description')?.value_text ?? null,
|
|
362
|
+
task_list_id: get('task_list_id')?.value_ref ?? get('task_list_id')?.value_text ?? null,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function parseTaskListProperty(...values) {
|
|
366
|
+
const items = [];
|
|
367
|
+
for (const raw of values) {
|
|
368
|
+
if (!raw)
|
|
369
|
+
continue;
|
|
370
|
+
try {
|
|
371
|
+
const parsed = JSON.parse(raw);
|
|
372
|
+
if (Array.isArray(parsed)) {
|
|
373
|
+
items.push(...parsed.map((entry) => String(entry).trim()).filter(Boolean));
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
// Fall through to comma-separated legacy/current property storage.
|
|
379
|
+
}
|
|
380
|
+
items.push(...raw.split(',').map((entry) => entry.trim()).filter(Boolean));
|
|
381
|
+
}
|
|
382
|
+
return [...new Set(items)];
|
|
383
|
+
}
|
|
384
|
+
export async function updateTaskProps(objectId, workspaceId, patch) {
|
|
385
|
+
const obj = await getObject(objectId, workspaceId);
|
|
386
|
+
if (!obj)
|
|
387
|
+
throw new Error(`Object ${objectId} not found in this workspace`);
|
|
388
|
+
if (obj.type !== 'task')
|
|
389
|
+
throw new Error(`${objectId} is not a task`);
|
|
390
|
+
const client = getAuroraClient();
|
|
391
|
+
const upsert = async (key, valueType, value) => {
|
|
392
|
+
if (value === null || value === '')
|
|
393
|
+
return;
|
|
394
|
+
const existing = await client.collection('object_properties').list({
|
|
395
|
+
filter: client.filter('object_id = {:oid} && key = {:key}', { oid: objectId, key }),
|
|
396
|
+
});
|
|
397
|
+
const valueField = valueType === 'number' ? 'value_num' :
|
|
398
|
+
valueType === 'date' ? 'value_date' :
|
|
399
|
+
valueType === 'boolean' ? 'value_bool' :
|
|
400
|
+
valueType === 'ref' ? 'value_ref' :
|
|
401
|
+
'value_text';
|
|
402
|
+
if (existing.length > 0) {
|
|
403
|
+
await client.collection('object_properties').update(String(existing[0].id), { value_type: valueType, [valueField]: value });
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
await client.collection('object_properties').create({ object_id: objectId, key, value_type: valueType, [valueField]: value });
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
if (patch.status !== undefined)
|
|
410
|
+
await upsert('status', 'text', patch.status);
|
|
411
|
+
if (patch.priority !== undefined)
|
|
412
|
+
await upsert('priority', 'text', patch.priority);
|
|
413
|
+
if (patch.due_date !== undefined)
|
|
414
|
+
await upsert('due_date', 'date', patch.due_date);
|
|
415
|
+
if (patch.description !== undefined)
|
|
416
|
+
await upsert('description', 'text', patch.description);
|
|
417
|
+
if (patch.task_list_id !== undefined)
|
|
418
|
+
await upsert('task_list_id', 'ref', patch.task_list_id);
|
|
419
|
+
if (patch.assignees !== undefined)
|
|
420
|
+
await upsert('assignees', 'text', JSON.stringify(patch.assignees));
|
|
421
|
+
if (patch.labels !== undefined)
|
|
422
|
+
await upsert('labels', 'text', JSON.stringify(patch.labels));
|
|
423
|
+
}
|
|
424
|
+
export async function listPlanningTasks(workspaceId) {
|
|
425
|
+
const tasks = await listObjects(workspaceId, 'task');
|
|
426
|
+
const results = [];
|
|
427
|
+
for (const task of tasks) {
|
|
428
|
+
const props = await getTaskProps(task.id, workspaceId);
|
|
429
|
+
results.push({
|
|
430
|
+
id: task.id,
|
|
431
|
+
title: task.title,
|
|
432
|
+
updated_at: task.updated_at,
|
|
433
|
+
...props,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
return results;
|
|
437
|
+
}
|
|
438
|
+
export async function readCanvasContent(workspaceId, objectId) {
|
|
439
|
+
const object = await getObject(objectId, workspaceId);
|
|
440
|
+
if (!object)
|
|
441
|
+
return null;
|
|
442
|
+
if (object.type !== 'canvas')
|
|
443
|
+
throw new Error(`${objectId} is not a canvas`);
|
|
444
|
+
const contentJson = await getContentJson(objectId, workspaceId);
|
|
445
|
+
return { object, contentJson };
|
|
446
|
+
}
|
|
447
|
+
export async function createPlanningTimeBlock(workspaceId, input) {
|
|
448
|
+
const object = await createObject(workspaceId, 'task', input.title);
|
|
449
|
+
await updateTaskProps(object.id, workspaceId, {
|
|
450
|
+
due_date: input.dueDate,
|
|
451
|
+
labels: input.labels,
|
|
452
|
+
description: input.description,
|
|
453
|
+
});
|
|
454
|
+
return object;
|
|
455
|
+
}
|
|
456
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
457
|
+
function mapObject(r) {
|
|
458
|
+
return {
|
|
459
|
+
id: r['id'],
|
|
460
|
+
workspace_id: r['workspace_id'],
|
|
461
|
+
type: r['type'],
|
|
462
|
+
title: r['title'] ?? null,
|
|
463
|
+
icon: r['icon'] ?? null,
|
|
464
|
+
parent_id: r['parent_id'] ?? null,
|
|
465
|
+
is_deleted: Boolean(r['is_deleted']),
|
|
466
|
+
is_template: Boolean(r['is_template']),
|
|
467
|
+
created_at: r['created_at'],
|
|
468
|
+
updated_at: r['updated_at'],
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function extractTextFromDoc(doc) {
|
|
472
|
+
const parts = [];
|
|
473
|
+
function walk(node) {
|
|
474
|
+
if (typeof node['text'] === 'string')
|
|
475
|
+
parts.push(node['text']);
|
|
476
|
+
const content = node['content'];
|
|
477
|
+
if (content)
|
|
478
|
+
content.forEach(walk);
|
|
479
|
+
}
|
|
480
|
+
walk(doc);
|
|
481
|
+
return parts.join(' ').slice(0, 3000);
|
|
482
|
+
}
|
|
483
|
+
function normalizeWorkspaceKnowledgeResponse(payload) {
|
|
484
|
+
const items = Array.isArray(payload?.items)
|
|
485
|
+
? payload.items
|
|
486
|
+
.map((item) => normalizeWorkspaceKnowledgeSource(item))
|
|
487
|
+
.filter((item) => item !== null)
|
|
488
|
+
: [];
|
|
489
|
+
return {
|
|
490
|
+
workspaceId: typeof payload?.workspaceId === 'string' ? payload.workspaceId : '',
|
|
491
|
+
query: typeof payload?.query === 'string' ? payload.query : null,
|
|
492
|
+
limit: typeof payload?.limit === 'number' && Number.isFinite(payload.limit) ? payload.limit : items.length,
|
|
493
|
+
items,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
function normalizeWorkspaceKnowledgeSource(value) {
|
|
497
|
+
if (!value || typeof value !== 'object')
|
|
498
|
+
return null;
|
|
499
|
+
const kind = normalizeWorkspaceKnowledgeKind(value.kind);
|
|
500
|
+
const objectId = typeof value.objectId === 'string' ? value.objectId.trim() : '';
|
|
501
|
+
const workspaceId = typeof value.workspaceId === 'string' ? value.workspaceId.trim() : '';
|
|
502
|
+
const title = typeof value.title === 'string' ? value.title : null;
|
|
503
|
+
const objectType = typeof value.objectType === 'string' ? value.objectType : 'page';
|
|
504
|
+
const icon = typeof value.icon === 'string' ? value.icon : null;
|
|
505
|
+
const breadcrumb = Array.isArray(value.breadcrumb)
|
|
506
|
+
? value.breadcrumb.filter((entry) => typeof entry === 'string')
|
|
507
|
+
: [];
|
|
508
|
+
const deepLink = typeof value.deepLink === 'string' ? value.deepLink : objectId ? `/object/${objectId}` : '';
|
|
509
|
+
const snippet = typeof value.snippet === 'string' ? value.snippet : null;
|
|
510
|
+
const plainText = typeof value.plainText === 'string' ? value.plainText : null;
|
|
511
|
+
const blockId = typeof value.blockId === 'string' ? value.blockId : null;
|
|
512
|
+
const updatedAt = typeof value.updatedAt === 'string' ? value.updatedAt : new Date().toISOString();
|
|
513
|
+
const score = typeof value.score === 'number' && Number.isFinite(value.score) ? value.score : null;
|
|
514
|
+
const matchedFields = Array.isArray(value.matchedFields)
|
|
515
|
+
? value.matchedFields.filter((field) => field === 'title' || field === 'content' || field === 'properties' || field === 'relationships')
|
|
516
|
+
: [];
|
|
517
|
+
const availability = normalizeWorkspaceKnowledgeAvailability(value.availability);
|
|
518
|
+
const relationships = Array.isArray(value.relationships)
|
|
519
|
+
? value.relationships
|
|
520
|
+
.map((relationship) => normalizeWorkspaceKnowledgeRelationship(relationship))
|
|
521
|
+
.filter((relationship) => relationship !== null)
|
|
522
|
+
: [];
|
|
523
|
+
const sourceId = typeof value.sourceId === 'string' && value.sourceId.trim().length
|
|
524
|
+
? value.sourceId.trim()
|
|
525
|
+
: buildWorkspaceKnowledgeSourceId(kind, objectId);
|
|
526
|
+
if (!workspaceId || !objectId || !deepLink)
|
|
527
|
+
return null;
|
|
528
|
+
return {
|
|
529
|
+
sourceId,
|
|
530
|
+
workspaceId,
|
|
531
|
+
objectId,
|
|
532
|
+
kind,
|
|
533
|
+
title,
|
|
534
|
+
objectType,
|
|
535
|
+
icon,
|
|
536
|
+
breadcrumb,
|
|
537
|
+
deepLink,
|
|
538
|
+
snippet,
|
|
539
|
+
plainText,
|
|
540
|
+
blockId,
|
|
541
|
+
updatedAt,
|
|
542
|
+
score,
|
|
543
|
+
matchedFields,
|
|
544
|
+
availability,
|
|
545
|
+
relationships,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function normalizeWorkspaceKnowledgeKind(value) {
|
|
549
|
+
return value === 'content_chunk'
|
|
550
|
+
|| value === 'property'
|
|
551
|
+
|| value === 'comment'
|
|
552
|
+
|| value === 'attachment_metadata'
|
|
553
|
+
|| value === 'relationship'
|
|
554
|
+
? value
|
|
555
|
+
: 'object';
|
|
556
|
+
}
|
|
557
|
+
function normalizeWorkspaceKnowledgeAvailability(value) {
|
|
558
|
+
return WORKSPACE_KNOWLEDGE_AVAILABILITY_STATES.includes(value)
|
|
559
|
+
? value
|
|
560
|
+
: 'available';
|
|
561
|
+
}
|
|
562
|
+
function normalizeWorkspaceKnowledgeRelationship(value) {
|
|
563
|
+
if (!value || typeof value !== 'object')
|
|
564
|
+
return null;
|
|
565
|
+
const type = value.type;
|
|
566
|
+
const objectId = typeof value.objectId === 'string' ? value.objectId.trim() : '';
|
|
567
|
+
if (!WORKSPACE_KNOWLEDGE_RELATIONSHIP_TYPES.includes(type) || !objectId) {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
type: type,
|
|
572
|
+
objectId,
|
|
573
|
+
title: typeof value.title === 'string' ? value.title : null,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function buildWorkspaceKnowledgeSourceId(kind, objectId, suffix) {
|
|
577
|
+
const normalizedSuffix = suffix === undefined || suffix === null ? '' : `:${String(suffix)}`;
|
|
578
|
+
switch (kind) {
|
|
579
|
+
case 'object':
|
|
580
|
+
return `object:${objectId}`;
|
|
581
|
+
case 'content_chunk':
|
|
582
|
+
return `chunk:${objectId}${normalizedSuffix}`;
|
|
583
|
+
case 'property':
|
|
584
|
+
return `property:${objectId}${normalizedSuffix}`;
|
|
585
|
+
case 'relationship':
|
|
586
|
+
return `relationship:${objectId}${normalizedSuffix}`;
|
|
587
|
+
case 'comment':
|
|
588
|
+
return `comment:${objectId}${normalizedSuffix}`;
|
|
589
|
+
case 'attachment_metadata':
|
|
590
|
+
return `attachment:${objectId}${normalizedSuffix}`;
|
|
591
|
+
default:
|
|
592
|
+
return `${kind}:${objectId}${normalizedSuffix}`;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function getAuroraApiUrl() {
|
|
596
|
+
const value = process.env['AURORA_API_URL']?.trim();
|
|
597
|
+
if (!value)
|
|
598
|
+
throw new Error('AURORA_API_URL environment variable is required for AuroraCloud MCP mode');
|
|
599
|
+
return value.replace(/\/+$/, '');
|
|
600
|
+
}
|
|
601
|
+
async function ensureAuthRecord() {
|
|
602
|
+
const client = getAuroraClient();
|
|
603
|
+
if (client.authStore.record?.['id'])
|
|
604
|
+
return;
|
|
605
|
+
if (!client.authStore.token)
|
|
606
|
+
throw new Error('Authenticated token missing.');
|
|
607
|
+
const response = await client.request('/auth/me', { method: 'GET' });
|
|
608
|
+
client.authStore.save(client.authStore.token, response.user);
|
|
609
|
+
}
|
|
610
|
+
function createAuroraCloudClient(baseUrl) {
|
|
611
|
+
const authStore = {
|
|
612
|
+
token: '',
|
|
613
|
+
record: null,
|
|
614
|
+
save(token, record) {
|
|
615
|
+
this.token = token;
|
|
616
|
+
this.record = record;
|
|
617
|
+
},
|
|
618
|
+
};
|
|
619
|
+
const filter = (template, params) => template.replace(/\{:(\w+)\}/g, (_match, key) => {
|
|
620
|
+
const value = params[key];
|
|
621
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
622
|
+
return String(value);
|
|
623
|
+
return `"${String(value ?? '').replaceAll('"', '\\"')}"`;
|
|
624
|
+
});
|
|
625
|
+
const request = async (path, options = {}) => {
|
|
626
|
+
const headers = new Headers();
|
|
627
|
+
if (authStore.token)
|
|
628
|
+
headers.set('authorization', `Bearer ${authStore.token}`);
|
|
629
|
+
if (options.body !== undefined)
|
|
630
|
+
headers.set('content-type', 'application/json');
|
|
631
|
+
const response = await fetch(`${baseUrl}${path.startsWith('/') ? path : `/${path}`}`, {
|
|
632
|
+
method: options.method ?? 'GET',
|
|
633
|
+
headers,
|
|
634
|
+
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
635
|
+
});
|
|
636
|
+
if (!response.ok) {
|
|
637
|
+
let message = `${response.status} ${response.statusText}`;
|
|
638
|
+
try {
|
|
639
|
+
const json = await response.json();
|
|
640
|
+
message = json.error ?? json.message ?? message;
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
// ignore parse failure
|
|
644
|
+
}
|
|
645
|
+
throw new Error(message);
|
|
646
|
+
}
|
|
647
|
+
if (response.status === 204)
|
|
648
|
+
return undefined;
|
|
649
|
+
return await response.json();
|
|
650
|
+
};
|
|
651
|
+
const collection = (name) => ({
|
|
652
|
+
async list(options = {}) {
|
|
653
|
+
const pageSize = options.batch ?? 500;
|
|
654
|
+
const first = await request(`/api/collections/${name}/records?${toQueryString(1, pageSize, options)}`, { method: 'GET' });
|
|
655
|
+
const items = [...first.items];
|
|
656
|
+
for (let page = 2; page <= (first.totalPages ?? 1); page += 1) {
|
|
657
|
+
const next = await request(`/api/collections/${name}/records?${toQueryString(page, pageSize, options)}`, { method: 'GET' });
|
|
658
|
+
items.push(...next.items);
|
|
659
|
+
}
|
|
660
|
+
return items;
|
|
661
|
+
},
|
|
662
|
+
async get(id) {
|
|
663
|
+
return request(`/api/collections/${name}/records/${encodeURIComponent(id)}`, { method: 'GET' });
|
|
664
|
+
},
|
|
665
|
+
async create(data) {
|
|
666
|
+
if (name === 'users') {
|
|
667
|
+
const result = await request('/auth/register', {
|
|
668
|
+
method: 'POST',
|
|
669
|
+
body: {
|
|
670
|
+
email: data['email'],
|
|
671
|
+
password: data['password'],
|
|
672
|
+
displayName: data['name'] ?? data['display_name'] ?? null,
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
authStore.save(result.accessToken, result.user);
|
|
676
|
+
return result.user;
|
|
677
|
+
}
|
|
678
|
+
return request(`/api/collections/${name}/records`, { method: 'POST', body: data });
|
|
679
|
+
},
|
|
680
|
+
async update(id, data) {
|
|
681
|
+
return request(`/api/collections/${name}/records/${encodeURIComponent(id)}`, {
|
|
682
|
+
method: 'PATCH',
|
|
683
|
+
body: data,
|
|
684
|
+
});
|
|
685
|
+
},
|
|
686
|
+
async authWithPassword(email, password) {
|
|
687
|
+
const result = await request('/auth/login', { method: 'POST', body: { email, password } });
|
|
688
|
+
authStore.save(result.accessToken, result.user);
|
|
689
|
+
return { record: result.user, token: result.accessToken };
|
|
690
|
+
},
|
|
691
|
+
});
|
|
692
|
+
return { authStore, filter, collection, request };
|
|
693
|
+
}
|
|
694
|
+
function toQueryString(page, perPage, options) {
|
|
695
|
+
const params = new URLSearchParams({
|
|
696
|
+
page: String(page),
|
|
697
|
+
perPage: String(perPage),
|
|
698
|
+
});
|
|
699
|
+
if (options.filter)
|
|
700
|
+
params.set('filter', options.filter);
|
|
701
|
+
if (options.sort)
|
|
702
|
+
params.set('sort', options.sort);
|
|
703
|
+
if (options.expand)
|
|
704
|
+
params.set('expand', options.expand);
|
|
705
|
+
return params.toString();
|
|
706
|
+
}
|