@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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AuroraDocs MCP Server
4
+ *
5
+ * Exposes AuroraDocs workspace data to Claude Desktop and other MCP clients.
6
+ * Uses the MCP stdio transport — add it to claude_desktop_config.json.
7
+ *
8
+ * SECURITY:
9
+ * - On startup the server verifies the authenticated user is a member
10
+ * of AURORA_WORKSPACE_ID. All queries are scoped to that workspace.
11
+ * - Object reads/writes verify workspace ownership before proceeding.
12
+ * - No cross-workspace or cross-user data leakage is possible.
13
+ *
14
+ * Environment variables:
15
+ * AURORA_WORKSPACE_ID Workspace ID to expose
16
+ * AURORA_API_URL AuroraCloud API URL, e.g. http://127.0.0.1:3000
17
+ * AURORA_API_TOKEN AuroraCloud bearer token (recommended: workspace MCP token `aur_mcp_...`)
18
+ * AURORA_API_EMAIL AuroraCloud user email (legacy/dev fallback)
19
+ * AURORA_API_PASSWORD AuroraCloud user password (legacy/dev fallback)
20
+ */
21
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
22
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
23
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
24
+ import { authenticate } from './auroraClient.js';
25
+ import { executeToolCall, formatToolResult } from './tools.js';
26
+ import { getToolDefinitions } from './toolCatalog.js';
27
+ import { SERVER_VERSION } from './version.js';
28
+ // ── Main ─────────────────────────────────────────────────────────────────────
29
+ async function main() {
30
+ const workspaceId = process.env['AURORA_WORKSPACE_ID'];
31
+ if (!workspaceId) {
32
+ process.stderr.write('Error: AURORA_WORKSPACE_ID environment variable is required\n');
33
+ process.exit(1);
34
+ }
35
+ try {
36
+ await authenticate();
37
+ }
38
+ catch (err) {
39
+ process.stderr.write(`Authentication failed: ${err instanceof Error ? err.message : err}\n`);
40
+ process.exit(1);
41
+ }
42
+ const server = new Server({ name: 'auroradocs-mcp', version: SERVER_VERSION }, { capabilities: { tools: {} } });
43
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
44
+ tools: getToolDefinitions(),
45
+ }));
46
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
47
+ const { name, arguments: args } = request.params;
48
+ const result = await executeToolCall(name, (args ?? {}), workspaceId);
49
+ const text = formatToolResult(result);
50
+ return {
51
+ content: [{ type: 'text', text }],
52
+ isError: result.type === 'error',
53
+ };
54
+ });
55
+ const transport = new StdioServerTransport();
56
+ await server.connect(transport);
57
+ process.stderr.write(`AuroraDocs MCP server running (workspace: ${workspaceId})\n`);
58
+ }
59
+ main().catch((err) => {
60
+ process.stderr.write(`Fatal: ${err}\n`);
61
+ process.exit(1);
62
+ });
@@ -0,0 +1,76 @@
1
+ export type McpPlanningTask = {
2
+ id: string;
3
+ title: string | null;
4
+ status: string | null;
5
+ due_date: string | null;
6
+ updated_at: string | null;
7
+ labels: string[];
8
+ };
9
+ export type BuildWeekPlanInput = {
10
+ anchorDate: string;
11
+ includeUnscheduled: boolean;
12
+ unscheduledLimit: number;
13
+ };
14
+ export type McpWeekPlanTask = McpPlanningTask & {
15
+ timeBlock: {
16
+ isTimeBlock: boolean;
17
+ durationMinutes: number | null;
18
+ };
19
+ };
20
+ export type McpWeekPlan = {
21
+ type: 'week_plan';
22
+ range: {
23
+ start: string;
24
+ end: string;
25
+ };
26
+ days: Array<{
27
+ date: string;
28
+ tasks: McpWeekPlanTask[];
29
+ }>;
30
+ unscheduled: McpWeekPlanTask[];
31
+ };
32
+ export type McpCanvasObject = {
33
+ id: string;
34
+ title: string | null;
35
+ type: string;
36
+ };
37
+ export type McpCanvasCard = {
38
+ id: string;
39
+ type: string;
40
+ x: number | null;
41
+ y: number | null;
42
+ width: number | null;
43
+ height: number | null;
44
+ text: string | null;
45
+ color: string | null;
46
+ objectId: string | null;
47
+ objectTitle: string | null;
48
+ };
49
+ export type McpCanvasEdge = {
50
+ id: string;
51
+ fromCard: string | null;
52
+ toCard: string | null;
53
+ fromSide: string | null;
54
+ toSide: string | null;
55
+ label: string | null;
56
+ color: string | null;
57
+ style: string | null;
58
+ arrow: string | null;
59
+ arrowMode: string | null;
60
+ strokeWidth: number | null;
61
+ };
62
+ export type McpCanvasReadResult = {
63
+ type: 'canvas';
64
+ canvas: {
65
+ id: string;
66
+ title: string | null;
67
+ };
68
+ cards: McpCanvasCard[];
69
+ edges: McpCanvasEdge[];
70
+ frames: Array<Record<string, unknown>>;
71
+ warnings: string[];
72
+ };
73
+ export declare function buildWeekPlan(tasks: McpPlanningTask[], input: BuildWeekPlanInput): McpWeekPlan;
74
+ export declare function normalizeCanvasContent(object: McpCanvasObject, contentJson: unknown, options?: {
75
+ includeText?: boolean;
76
+ }): McpCanvasReadResult;
@@ -0,0 +1,159 @@
1
+ export function buildWeekPlan(tasks, input) {
2
+ const days = getWeekPlanningDays(input.anchorDate);
3
+ const buckets = getWeekPlanningTaskBuckets(tasks, days);
4
+ return {
5
+ type: 'week_plan',
6
+ range: getWeekPlanningRange(input.anchorDate),
7
+ days: days.map((date) => ({
8
+ date,
9
+ tasks: (buckets[date] ?? []).map(toWeekPlanTask),
10
+ })),
11
+ unscheduled: input.includeUnscheduled
12
+ ? getUnscheduledWeekPlanningTasks(tasks).slice(0, clampLimit(input.unscheduledLimit)).map(toWeekPlanTask)
13
+ : [],
14
+ };
15
+ }
16
+ export function normalizeCanvasContent(object, contentJson, options = {}) {
17
+ const includeText = options.includeText !== false;
18
+ const content = isRecord(contentJson) ? contentJson : {};
19
+ const cardsRaw = Array.isArray(content['cards']) ? content['cards'] : [];
20
+ const edgesRaw = Array.isArray(content['edges']) ? content['edges'] : [];
21
+ const framesRaw = Array.isArray(content['frames']) ? content['frames'] : [];
22
+ const warnings = [];
23
+ if (!Array.isArray(content['cards']))
24
+ warnings.push('Canvas content did not include a cards array.');
25
+ if (!Array.isArray(content['edges']))
26
+ warnings.push('Canvas content did not include an edges array.');
27
+ return {
28
+ type: 'canvas',
29
+ canvas: { id: object.id, title: object.title },
30
+ cards: cardsRaw.filter(isRecord).map((card) => ({
31
+ id: readString(card['id']) ?? '(missing-id)',
32
+ type: readString(card['type']) ?? 'unknown',
33
+ x: readNumber(card['x']),
34
+ y: readNumber(card['y']),
35
+ width: readNumber(card['w']),
36
+ height: readNumber(card['h']),
37
+ text: includeText ? readString(card['text']) : null,
38
+ color: readString(card['color']),
39
+ objectId: readString(card['objectId']),
40
+ objectTitle: readString(card['objectTitle']),
41
+ })),
42
+ edges: edgesRaw.filter(isRecord).map((edge) => ({
43
+ id: readString(edge['id']) ?? '(missing-id)',
44
+ fromCard: readString(edge['fromCard']),
45
+ toCard: readString(edge['toCard']),
46
+ fromSide: readString(edge['fromSide']),
47
+ toSide: readString(edge['toSide']),
48
+ label: readString(edge['label']),
49
+ color: readString(edge['color']),
50
+ style: readString(edge['style']),
51
+ arrow: readString(edge['arrow']),
52
+ arrowMode: readString(edge['arrowMode']),
53
+ strokeWidth: readNumber(edge['strokeWidth']),
54
+ })),
55
+ frames: framesRaw.filter(isRecord),
56
+ warnings,
57
+ };
58
+ }
59
+ function toWeekPlanTask(task) {
60
+ return {
61
+ ...task,
62
+ timeBlock: parseWeekPlanningTimeBlock(task),
63
+ };
64
+ }
65
+ function clampLimit(value) {
66
+ return Math.max(0, Math.min(50, Number.isFinite(value) ? Math.round(value) : 12));
67
+ }
68
+ function getWeekPlanningRange(anchorDateKey) {
69
+ const start = getWeekPlanningStart(anchorDateKey);
70
+ return {
71
+ start,
72
+ end: addDaysToDateKey(start, 6),
73
+ };
74
+ }
75
+ function getWeekPlanningDays(anchorDateKey) {
76
+ const start = getWeekPlanningStart(anchorDateKey);
77
+ return Array.from({ length: 7 }, (_, index) => addDaysToDateKey(start, index));
78
+ }
79
+ function getWeekPlanningStart(anchorDateKey) {
80
+ const day = parseDateKey(anchorDateKey).getDay();
81
+ const offsetToMonday = day === 0 ? -6 : 1 - day;
82
+ return addDaysToDateKey(anchorDateKey, offsetToMonday);
83
+ }
84
+ function getWeekPlanningTaskBuckets(tasks, days) {
85
+ const daySet = new Set(days);
86
+ const buckets = Object.fromEntries(days.map((day) => [day, []]));
87
+ for (const task of tasks) {
88
+ if (isDoneTask(task))
89
+ continue;
90
+ const day = task.due_date?.slice(0, 10);
91
+ if (!day || !daySet.has(day))
92
+ continue;
93
+ buckets[day].push(task);
94
+ }
95
+ for (const day of days) {
96
+ buckets[day].sort(compareWeekPlanningTasks);
97
+ }
98
+ return buckets;
99
+ }
100
+ function getUnscheduledWeekPlanningTasks(tasks) {
101
+ return tasks
102
+ .filter((task) => !isDoneTask(task) && !task.due_date)
103
+ .sort(compareWeekPlanningTasks);
104
+ }
105
+ function parseWeekPlanningTimeBlock(task) {
106
+ const labels = task.labels ?? [];
107
+ if (!labels.includes('time-block'))
108
+ return { isTimeBlock: false, durationMinutes: null };
109
+ const durationLabel = labels.find((label) => /^duration:\d+m$/.test(label));
110
+ const durationMinutes = durationLabel ? Number(durationLabel.match(/^duration:(\d+)m$/)?.[1] ?? '') : NaN;
111
+ return {
112
+ isTimeBlock: true,
113
+ durationMinutes: Number.isFinite(durationMinutes) && durationMinutes > 0 ? durationMinutes : null,
114
+ };
115
+ }
116
+ function isRecord(value) {
117
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
118
+ }
119
+ function readString(value) {
120
+ return typeof value === 'string' && value.trim() ? value : null;
121
+ }
122
+ function readNumber(value) {
123
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
124
+ }
125
+ function addDaysToDateKey(dateKey, days) {
126
+ const date = parseDateKey(dateKey);
127
+ date.setDate(date.getDate() + days);
128
+ return formatDateKey(date);
129
+ }
130
+ function parseDateKey(dateKey) {
131
+ const [year, month, day] = dateKey.split('-').map((part) => Number(part));
132
+ return new Date(year, month - 1, day);
133
+ }
134
+ function formatDateKey(date) {
135
+ const year = date.getFullYear();
136
+ const month = String(date.getMonth() + 1).padStart(2, '0');
137
+ const day = String(date.getDate()).padStart(2, '0');
138
+ return `${year}-${month}-${day}`;
139
+ }
140
+ function isDoneTask(task) {
141
+ return task.status === 'Done';
142
+ }
143
+ function compareWeekPlanningTasks(a, b) {
144
+ const aTime = taskTime(a.due_date);
145
+ const bTime = taskTime(b.due_date);
146
+ if (aTime && bTime && aTime !== bTime)
147
+ return aTime.localeCompare(bTime);
148
+ if (aTime && !bTime)
149
+ return -1;
150
+ if (!aTime && bTime)
151
+ return 1;
152
+ const aTitle = a.title?.trim() || a.id;
153
+ const bTitle = b.title?.trim() || b.id;
154
+ return aTitle.localeCompare(bTitle);
155
+ }
156
+ function taskTime(dueDate) {
157
+ const match = dueDate?.match(/^\d{4}-\d{2}-\d{2}T(\d{2}:\d{2})/);
158
+ return match?.[1] ?? null;
159
+ }
@@ -0,0 +1,39 @@
1
+ export type McpToolDefinition = {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: 'object';
6
+ properties: Record<string, unknown>;
7
+ required?: string[];
8
+ };
9
+ };
10
+ export type McpCoveragePriority = 'high' | 'medium' | 'low';
11
+ export type McpCoverageStatus = 'covered' | 'partial' | 'gap';
12
+ export type McpToolEffect = 'read' | 'write';
13
+ export type McpToolCoverageAudit = {
14
+ generatedAt: string;
15
+ areas: Array<{
16
+ id: string;
17
+ label: string;
18
+ status: McpCoverageStatus;
19
+ implementedTools: string[];
20
+ missingTools: Array<{
21
+ name: string;
22
+ priority: McpCoveragePriority;
23
+ reason: string;
24
+ }>;
25
+ }>;
26
+ };
27
+ export type McpWorkflowRecipe = {
28
+ id: string;
29
+ title: string;
30
+ goal: string;
31
+ requiredScopes: string[];
32
+ toolSteps: string[];
33
+ prompt: string;
34
+ };
35
+ export declare function getToolDefinitions(): McpToolDefinition[];
36
+ export declare function getToolEffect(name: string): McpToolEffect | undefined;
37
+ export declare function getToolEffects(): Readonly<Record<string, McpToolEffect>>;
38
+ export declare function getMcpToolCoverageAudit(): McpToolCoverageAudit;
39
+ export declare function getMcpWorkflowRecipes(): McpWorkflowRecipe[];