@pollychrome/joan-mcp 1.3.0 → 1.4.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../../src/resources/attachments.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAiN1F"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * MCP resources for attachment data
3
+ */
4
+ export function registerAttachmentResources(server, client) {
5
+ // Project attachments
6
+ server.resource('joan://projects/{projectId}/attachments', 'Get file attachments for a project', async (uri) => {
7
+ const match = uri.pathname.match(/^\/\/projects\/([^/]+)\/attachments$/);
8
+ const projectId = match?.[1];
9
+ if (!projectId) {
10
+ throw new Error('Invalid project attachments URI');
11
+ }
12
+ const attachments = await client.listAttachmentsByEntity('project', projectId);
13
+ return {
14
+ contents: [{
15
+ uri: uri.toString(),
16
+ mimeType: 'application/json',
17
+ text: JSON.stringify(attachments, null, 2),
18
+ }],
19
+ };
20
+ });
21
+ // Project attachment hierarchy
22
+ server.resource('joan://projects/{projectId}/attachments/hierarchy', 'Get all attachments organized by project hierarchy (project → milestones → tasks)', async (uri) => {
23
+ const match = uri.pathname.match(/^\/\/projects\/([^/]+)\/attachments\/hierarchy$/);
24
+ const projectId = match?.[1];
25
+ if (!projectId) {
26
+ throw new Error('Invalid project attachments hierarchy URI');
27
+ }
28
+ const hierarchy = await client.getProjectAttachmentHierarchy(projectId);
29
+ return {
30
+ contents: [{
31
+ uri: uri.toString(),
32
+ mimeType: 'application/json',
33
+ text: JSON.stringify(hierarchy, null, 2),
34
+ }],
35
+ };
36
+ });
37
+ // Milestone attachments
38
+ server.resource('joan://projects/{projectId}/milestones/{milestoneId}/attachments', 'Get file attachments for a milestone', async (uri) => {
39
+ const match = uri.pathname.match(/^\/\/projects\/([^/]+)\/milestones\/([^/]+)\/attachments$/);
40
+ const milestoneId = match?.[2];
41
+ if (!milestoneId) {
42
+ throw new Error('Invalid milestone attachments URI');
43
+ }
44
+ const attachments = await client.listAttachmentsByEntity('milestone', milestoneId);
45
+ return {
46
+ contents: [{
47
+ uri: uri.toString(),
48
+ mimeType: 'application/json',
49
+ text: JSON.stringify(attachments, null, 2),
50
+ }],
51
+ };
52
+ });
53
+ // Task attachments
54
+ server.resource('joan://tasks/{taskId}/attachments', 'Get file attachments for a task', async (uri) => {
55
+ const match = uri.pathname.match(/^\/\/tasks\/([^/]+)\/attachments$/);
56
+ const taskId = match?.[1];
57
+ if (!taskId) {
58
+ throw new Error('Invalid task attachments URI');
59
+ }
60
+ const attachments = await client.listAttachmentsByEntity('task', taskId);
61
+ return {
62
+ contents: [{
63
+ uri: uri.toString(),
64
+ mimeType: 'application/json',
65
+ text: JSON.stringify(attachments, null, 2),
66
+ }],
67
+ };
68
+ });
69
+ // Note attachments
70
+ server.resource('joan://notes/{noteId}/attachments', 'Get file attachments for a note', async (uri) => {
71
+ const match = uri.pathname.match(/^\/\/notes\/([^/]+)\/attachments$/);
72
+ const noteId = match?.[1];
73
+ if (!noteId) {
74
+ throw new Error('Invalid note attachments URI');
75
+ }
76
+ const attachments = await client.listAttachmentsByEntity('note', noteId);
77
+ return {
78
+ contents: [{
79
+ uri: uri.toString(),
80
+ mimeType: 'application/json',
81
+ text: JSON.stringify(attachments, null, 2),
82
+ }],
83
+ };
84
+ });
85
+ // Single attachment metadata
86
+ server.resource('joan://attachments/{attachmentId}', 'Get metadata for a specific attachment', async (uri) => {
87
+ const match = uri.pathname.match(/^\/\/attachments\/([^/]+)$/);
88
+ const attachmentId = match?.[1];
89
+ if (!attachmentId) {
90
+ throw new Error('Invalid attachment URI');
91
+ }
92
+ const attachment = await client.getAttachmentMetadata(attachmentId);
93
+ return {
94
+ contents: [{
95
+ uri: uri.toString(),
96
+ mimeType: 'application/json',
97
+ text: JSON.stringify(attachment, null, 2),
98
+ }],
99
+ };
100
+ });
101
+ // Storage usage
102
+ server.resource('joan://attachments/usage', 'Get storage usage statistics for the current user', async (uri) => {
103
+ const usage = await client.getStorageUsage();
104
+ return {
105
+ contents: [{
106
+ uri: uri.toString(),
107
+ mimeType: 'application/json',
108
+ text: JSON.stringify(usage, null, 2),
109
+ }],
110
+ };
111
+ });
112
+ // Task resources (links/notes)
113
+ server.resource('joan://tasks/{taskId}/resources', 'Get resources (links, notes, references) attached to a task', async (uri) => {
114
+ const match = uri.pathname.match(/^\/\/tasks\/([^/]+)\/resources$/);
115
+ const taskId = match?.[1];
116
+ if (!taskId) {
117
+ throw new Error('Invalid task resources URI');
118
+ }
119
+ const resources = await client.listTaskResources(taskId);
120
+ return {
121
+ contents: [{
122
+ uri: uri.toString(),
123
+ mimeType: 'application/json',
124
+ text: JSON.stringify(resources, null, 2),
125
+ }],
126
+ };
127
+ });
128
+ // Project resources (links/notes)
129
+ server.resource('joan://projects/{projectId}/resources', 'Get resources (links, notes) attached to a project', async (uri) => {
130
+ const match = uri.pathname.match(/^\/\/projects\/([^/]+)\/resources$/);
131
+ const projectId = match?.[1];
132
+ if (!projectId) {
133
+ throw new Error('Invalid project resources URI');
134
+ }
135
+ const resources = await client.listProjectResources(projectId);
136
+ return {
137
+ contents: [{
138
+ uri: uri.toString(),
139
+ mimeType: 'application/json',
140
+ text: JSON.stringify(resources, null, 2),
141
+ }],
142
+ };
143
+ });
144
+ }
145
+ //# sourceMappingURL=attachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["../../src/resources/attachments.ts"],"names":[],"mappings":"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"}
@@ -9,9 +9,10 @@ import { registerMilestoneResources } from './milestones.js';
9
9
  import { registerGoalResources } from './goals.js';
10
10
  import { registerNoteResources } from './notes.js';
11
11
  import { registerCommentResources } from './comments.js';
12
+ import { registerAttachmentResources } from './attachments.js';
12
13
  /**
13
14
  * Register all resources with the MCP server
14
15
  */
15
16
  export declare function registerAllResources(server: McpServer, client: JoanApiClient): void;
16
- export { registerProjectResources, registerTaskResources, registerMilestoneResources, registerGoalResources, registerNoteResources, registerCommentResources, };
17
+ export { registerProjectResources, registerTaskResources, registerMilestoneResources, registerGoalResources, registerNoteResources, registerCommentResources, registerAttachmentResources, };
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAEzD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAOnF;AAED,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,wBAAwB,GACzB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAE/D;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAQnF;AAED,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,wBAAwB,EACxB,2BAA2B,GAC5B,CAAC"}
@@ -7,6 +7,7 @@ import { registerMilestoneResources } from './milestones.js';
7
7
  import { registerGoalResources } from './goals.js';
8
8
  import { registerNoteResources } from './notes.js';
9
9
  import { registerCommentResources } from './comments.js';
10
+ import { registerAttachmentResources } from './attachments.js';
10
11
  /**
11
12
  * Register all resources with the MCP server
12
13
  */
@@ -17,6 +18,7 @@ export function registerAllResources(server, client) {
17
18
  registerGoalResources(server, client);
18
19
  registerNoteResources(server, client);
19
20
  registerCommentResources(server, client);
21
+ registerAttachmentResources(server, client);
20
22
  }
21
- export { registerProjectResources, registerTaskResources, registerMilestoneResources, registerGoalResources, registerNoteResources, registerCommentResources, };
23
+ export { registerProjectResources, registerTaskResources, registerMilestoneResources, registerGoalResources, registerNoteResources, registerCommentResources, registerAttachmentResources, };
22
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAEzD;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,MAAqB;IAC3E,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,0BAA0B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,wBAAwB,GACzB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAE/D;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,MAAqB;IAC3E,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,0BAA0B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,2BAA2B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC9C,CAAC;AAED,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,wBAAwB,EACxB,2BAA2B,GAC5B,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * MCP tools for file attachment management
3
+ */
4
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
+ import type { JoanApiClient } from '../client/api-client.js';
6
+ export declare function registerAttachmentTools(server: McpServer, client: JoanApiClient): void;
7
+ //# sourceMappingURL=attachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../../src/tools/attachments.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AA4E7D,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CA4atF"}
@@ -0,0 +1,428 @@
1
+ /**
2
+ * MCP tools for file attachment management
3
+ */
4
+ import { readFile } from 'fs/promises';
5
+ import { basename, extname } from 'path';
6
+ import { z } from 'zod';
7
+ import { formatErrorForMcp } from '../utils/errors.js';
8
+ // Common MIME type mappings
9
+ const MIME_TYPES = {
10
+ // Documents
11
+ '.pdf': 'application/pdf',
12
+ '.doc': 'application/msword',
13
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
14
+ '.xls': 'application/vnd.ms-excel',
15
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
16
+ '.ppt': 'application/vnd.ms-powerpoint',
17
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
18
+ '.txt': 'text/plain',
19
+ '.rtf': 'application/rtf',
20
+ '.csv': 'text/csv',
21
+ '.md': 'text/markdown',
22
+ // Images
23
+ '.png': 'image/png',
24
+ '.jpg': 'image/jpeg',
25
+ '.jpeg': 'image/jpeg',
26
+ '.gif': 'image/gif',
27
+ '.svg': 'image/svg+xml',
28
+ '.webp': 'image/webp',
29
+ '.bmp': 'image/bmp',
30
+ // Videos
31
+ '.mp4': 'video/mp4',
32
+ '.mov': 'video/quicktime',
33
+ '.avi': 'video/x-msvideo',
34
+ '.mkv': 'video/x-matroska',
35
+ '.webm': 'video/webm',
36
+ // Audio
37
+ '.mp3': 'audio/mpeg',
38
+ '.wav': 'audio/wav',
39
+ '.ogg': 'audio/ogg',
40
+ '.m4a': 'audio/mp4',
41
+ '.flac': 'audio/flac',
42
+ // Archives
43
+ '.zip': 'application/zip',
44
+ '.tar': 'application/x-tar',
45
+ '.gz': 'application/gzip',
46
+ '.rar': 'application/vnd.rar',
47
+ '.7z': 'application/x-7z-compressed',
48
+ // Code
49
+ '.json': 'application/json',
50
+ '.js': 'text/javascript',
51
+ '.ts': 'text/typescript',
52
+ '.html': 'text/html',
53
+ '.css': 'text/css',
54
+ '.py': 'text/x-python',
55
+ '.java': 'text/x-java-source',
56
+ '.go': 'text/x-go',
57
+ '.rs': 'text/x-rust',
58
+ '.rb': 'text/x-ruby',
59
+ '.php': 'text/x-php',
60
+ '.c': 'text/x-c',
61
+ '.cpp': 'text/x-c++src',
62
+ '.h': 'text/x-c',
63
+ '.yaml': 'text/yaml',
64
+ '.yml': 'text/yaml',
65
+ '.xml': 'application/xml',
66
+ };
67
+ function getMimeType(filename) {
68
+ const ext = extname(filename).toLowerCase();
69
+ return MIME_TYPES[ext] || 'application/octet-stream';
70
+ }
71
+ function formatFileSize(bytes) {
72
+ if (bytes < 1024)
73
+ return `${bytes} B`;
74
+ if (bytes < 1024 * 1024)
75
+ return `${(bytes / 1024).toFixed(1)} KB`;
76
+ if (bytes < 1024 * 1024 * 1024)
77
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
78
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
79
+ }
80
+ export function registerAttachmentTools(server, client) {
81
+ // Upload Attachment
82
+ server.tool('upload_attachment', 'Upload a file attachment to Joan. Supports local file paths, base64-encoded content, or URLs to fetch from.', {
83
+ // File source (one of these is required)
84
+ file_path: z.string().optional().describe('Local file path to upload'),
85
+ file_base64: z.string().optional().describe('Base64-encoded file content'),
86
+ file_url: z.string().url().optional().describe('URL to fetch file from'),
87
+ // Required filename for base64/URL sources
88
+ filename: z.string().optional().describe('Filename (required for base64/URL, auto-detected for file_path)'),
89
+ // Attachment target (entity)
90
+ entity_type: z.enum(['project', 'milestone', 'task', 'note', 'folder', 'user']).optional()
91
+ .describe('Entity type to attach to'),
92
+ entity_id: z.string().uuid().optional().describe('Entity ID to attach to'),
93
+ // Metadata
94
+ display_name: z.string().optional().describe('Custom display name'),
95
+ description: z.string().optional().describe('Description of the attachment'),
96
+ category: z.enum(['document', 'image', 'video', 'audio', 'archive', 'code', 'other']).optional()
97
+ .describe('Category (auto-detected from MIME type if not specified)'),
98
+ tags: z.array(z.string()).optional().describe('Tags for organization'),
99
+ }, async (input) => {
100
+ try {
101
+ let fileBuffer;
102
+ let filename;
103
+ let mimeType;
104
+ // Determine file source and load content
105
+ if (input.file_path) {
106
+ // Local file path
107
+ fileBuffer = await readFile(input.file_path);
108
+ filename = input.filename || basename(input.file_path);
109
+ mimeType = getMimeType(filename);
110
+ }
111
+ else if (input.file_base64) {
112
+ // Base64 encoded content
113
+ if (!input.filename) {
114
+ return {
115
+ content: [{
116
+ type: 'text',
117
+ text: 'Error: filename is required when using file_base64',
118
+ }],
119
+ };
120
+ }
121
+ fileBuffer = Buffer.from(input.file_base64, 'base64');
122
+ filename = input.filename;
123
+ mimeType = getMimeType(filename);
124
+ }
125
+ else if (input.file_url) {
126
+ // Fetch from URL
127
+ const response = await fetch(input.file_url);
128
+ if (!response.ok) {
129
+ return {
130
+ content: [{
131
+ type: 'text',
132
+ text: `Error: Failed to fetch file from URL (${response.status})`,
133
+ }],
134
+ };
135
+ }
136
+ const arrayBuffer = await response.arrayBuffer();
137
+ fileBuffer = Buffer.from(arrayBuffer);
138
+ // Try to get filename from URL or Content-Disposition
139
+ const contentDisposition = response.headers.get('Content-Disposition');
140
+ if (input.filename) {
141
+ filename = input.filename;
142
+ }
143
+ else if (contentDisposition) {
144
+ const match = contentDisposition.match(/filename="?([^";\n]+)"?/);
145
+ filename = match?.[1] || basename(new URL(input.file_url).pathname) || 'downloaded_file';
146
+ }
147
+ else {
148
+ filename = basename(new URL(input.file_url).pathname) || 'downloaded_file';
149
+ }
150
+ // Try to get MIME type from response or filename
151
+ mimeType = response.headers.get('Content-Type')?.split(';')[0] || getMimeType(filename);
152
+ }
153
+ else {
154
+ return {
155
+ content: [{
156
+ type: 'text',
157
+ text: 'Error: Must provide one of file_path, file_base64, or file_url',
158
+ }],
159
+ };
160
+ }
161
+ // Validate entity_type and entity_id are both provided or both omitted
162
+ if ((input.entity_type && !input.entity_id) || (!input.entity_type && input.entity_id)) {
163
+ return {
164
+ content: [{
165
+ type: 'text',
166
+ text: 'Error: Both entity_type and entity_id must be provided together',
167
+ }],
168
+ };
169
+ }
170
+ // Build metadata
171
+ const metadata = {
172
+ entity_type: input.entity_type,
173
+ entity_id: input.entity_id,
174
+ display_name: input.display_name,
175
+ description: input.description,
176
+ category: input.category,
177
+ tags: input.tags,
178
+ };
179
+ const attachment = await client.uploadAttachment(fileBuffer, filename, mimeType, metadata);
180
+ return {
181
+ content: [{
182
+ type: 'text',
183
+ text: `Uploaded "${attachment.display_name}" (ID: ${attachment.id})\n` +
184
+ ` File: ${attachment.filename}\n` +
185
+ ` Size: ${formatFileSize(attachment.size)}\n` +
186
+ ` Type: ${attachment.mime_type}\n` +
187
+ ` Category: ${attachment.category}` +
188
+ (attachment.entity_type ? `\n Attached to: ${attachment.entity_type} (${attachment.entity_id})` : ''),
189
+ }],
190
+ };
191
+ }
192
+ catch (error) {
193
+ return { content: formatErrorForMcp(error) };
194
+ }
195
+ });
196
+ // List Attachments
197
+ server.tool('list_attachments', 'List file attachments for a specific entity (project, milestone, task, note, folder, or user).', {
198
+ entity_type: z.enum(['project', 'milestone', 'task', 'note', 'folder', 'user'])
199
+ .describe('Entity type'),
200
+ entity_id: z.string().uuid().describe('Entity ID'),
201
+ }, async (input) => {
202
+ try {
203
+ const attachments = await client.listAttachmentsByEntity(input.entity_type, input.entity_id);
204
+ if (attachments.length === 0) {
205
+ return {
206
+ content: [{
207
+ type: 'text',
208
+ text: `No attachments found for ${input.entity_type} ${input.entity_id}`,
209
+ }],
210
+ };
211
+ }
212
+ const lines = attachments.map(a => `- ${a.display_name} (ID: ${a.id})\n` +
213
+ ` File: ${a.filename} | Size: ${formatFileSize(a.size)} | Type: ${a.category}`);
214
+ return {
215
+ content: [{
216
+ type: 'text',
217
+ text: `Attachments for ${input.entity_type} ${input.entity_id}:\n\n${lines.join('\n\n')}`,
218
+ }],
219
+ };
220
+ }
221
+ catch (error) {
222
+ return { content: formatErrorForMcp(error) };
223
+ }
224
+ });
225
+ // Get Attachment
226
+ server.tool('get_attachment', 'Get metadata for a specific attachment.', {
227
+ attachment_id: z.string().uuid().describe('Attachment ID'),
228
+ }, async (input) => {
229
+ try {
230
+ const attachment = await client.getAttachmentMetadata(input.attachment_id);
231
+ const info = [
232
+ `Attachment: ${attachment.display_name}`,
233
+ `ID: ${attachment.id}`,
234
+ `Filename: ${attachment.filename}`,
235
+ `Size: ${formatFileSize(attachment.size)}`,
236
+ `MIME Type: ${attachment.mime_type}`,
237
+ `Category: ${attachment.category}`,
238
+ ];
239
+ if (attachment.description) {
240
+ info.push(`Description: ${attachment.description}`);
241
+ }
242
+ if (attachment.tags && attachment.tags.length > 0) {
243
+ info.push(`Tags: ${attachment.tags.join(', ')}`);
244
+ }
245
+ if (attachment.entity_type) {
246
+ info.push(`Attached to: ${attachment.entity_type} (${attachment.entity_id})`);
247
+ }
248
+ info.push(`Uploaded: ${attachment.uploaded_at}`);
249
+ info.push(`Download URL: ${attachment.url}`);
250
+ return {
251
+ content: [{
252
+ type: 'text',
253
+ text: info.join('\n'),
254
+ }],
255
+ };
256
+ }
257
+ catch (error) {
258
+ return { content: formatErrorForMcp(error) };
259
+ }
260
+ });
261
+ // Update Attachment
262
+ server.tool('update_attachment', 'Update metadata for an attachment.', {
263
+ attachment_id: z.string().uuid().describe('Attachment ID'),
264
+ display_name: z.string().optional().describe('New display name'),
265
+ description: z.string().optional().describe('New description'),
266
+ category: z.enum(['document', 'image', 'video', 'audio', 'archive', 'code', 'other']).optional()
267
+ .describe('New category'),
268
+ tags: z.array(z.string()).optional().describe('New tags'),
269
+ }, async (input) => {
270
+ try {
271
+ const updateData = {};
272
+ if (input.display_name !== undefined)
273
+ updateData.display_name = input.display_name;
274
+ if (input.description !== undefined)
275
+ updateData.description = input.description;
276
+ if (input.category !== undefined)
277
+ updateData.category = input.category;
278
+ if (input.tags !== undefined)
279
+ updateData.tags = input.tags;
280
+ const attachment = await client.updateAttachment(input.attachment_id, updateData);
281
+ return {
282
+ content: [{
283
+ type: 'text',
284
+ text: `Updated attachment "${attachment.display_name}" (ID: ${attachment.id})`,
285
+ }],
286
+ };
287
+ }
288
+ catch (error) {
289
+ return { content: formatErrorForMcp(error) };
290
+ }
291
+ });
292
+ // Delete Attachment
293
+ server.tool('delete_attachment', 'Delete an attachment.', {
294
+ attachment_id: z.string().uuid().describe('Attachment ID'),
295
+ }, async (input) => {
296
+ try {
297
+ await client.deleteAttachment(input.attachment_id);
298
+ return {
299
+ content: [{
300
+ type: 'text',
301
+ text: `Attachment ${input.attachment_id} deleted`,
302
+ }],
303
+ };
304
+ }
305
+ catch (error) {
306
+ return { content: formatErrorForMcp(error) };
307
+ }
308
+ });
309
+ // Get Attachment Download URL
310
+ server.tool('get_attachment_download_url', 'Get a download URL for an attachment.', {
311
+ attachment_id: z.string().uuid().describe('Attachment ID'),
312
+ expires_in: z.number().optional().describe('URL expiration time in seconds (default: 3600)'),
313
+ }, async (input) => {
314
+ try {
315
+ const info = await client.getAttachmentDownloadUrl(input.attachment_id, input.expires_in);
316
+ return {
317
+ content: [{
318
+ type: 'text',
319
+ text: `Download URL for "${info.display_name}":\n` +
320
+ ` URL: ${info.download_url}\n` +
321
+ ` Expires: ${info.expires_at}\n` +
322
+ ` Size: ${formatFileSize(info.file_size)}`,
323
+ }],
324
+ };
325
+ }
326
+ catch (error) {
327
+ return { content: formatErrorForMcp(error) };
328
+ }
329
+ });
330
+ // Get Project Attachment Hierarchy
331
+ server.tool('get_project_attachment_hierarchy', 'Get all attachments organized by project hierarchy (project → milestones → tasks).', {
332
+ project_id: z.string().uuid().describe('Project ID'),
333
+ }, async (input) => {
334
+ try {
335
+ const hierarchy = await client.getProjectAttachmentHierarchy(input.project_id);
336
+ const lines = [];
337
+ lines.push(`Project Attachments (${hierarchy.total_files} total files)`);
338
+ lines.push('');
339
+ // Project level
340
+ if (hierarchy.project.attachments.length > 0) {
341
+ lines.push(`📁 Project (${hierarchy.project.attachments.length} files)`);
342
+ for (const a of hierarchy.project.attachments) {
343
+ lines.push(` - ${a.display_name} (${formatFileSize(a.size)})`);
344
+ }
345
+ lines.push('');
346
+ }
347
+ // Milestones
348
+ for (const milestone of hierarchy.milestones) {
349
+ const milestoneFiles = milestone.attachments.length +
350
+ milestone.tasks.reduce((sum, t) => sum + t.attachments.length, 0);
351
+ if (milestoneFiles > 0) {
352
+ lines.push(`🎯 ${milestone.name} [${milestone.status}] (${milestoneFiles} files)`);
353
+ for (const a of milestone.attachments) {
354
+ lines.push(` - ${a.display_name} (${formatFileSize(a.size)})`);
355
+ }
356
+ for (const task of milestone.tasks) {
357
+ if (task.attachments.length > 0) {
358
+ lines.push(` 📋 ${task.title} [${task.status}]`);
359
+ for (const a of task.attachments) {
360
+ lines.push(` - ${a.display_name} (${formatFileSize(a.size)})`);
361
+ }
362
+ }
363
+ }
364
+ lines.push('');
365
+ }
366
+ }
367
+ // Unassigned tasks
368
+ if (hierarchy.unassigned_tasks.length > 0) {
369
+ const unassignedFiles = hierarchy.unassigned_tasks.reduce((sum, t) => sum + t.attachments.length, 0);
370
+ if (unassignedFiles > 0) {
371
+ lines.push(`📋 Unassigned Tasks (${unassignedFiles} files)`);
372
+ for (const task of hierarchy.unassigned_tasks) {
373
+ if (task.attachments.length > 0) {
374
+ lines.push(` ${task.title} [${task.status}]`);
375
+ for (const a of task.attachments) {
376
+ lines.push(` - ${a.display_name} (${formatFileSize(a.size)})`);
377
+ }
378
+ }
379
+ }
380
+ }
381
+ }
382
+ return {
383
+ content: [{
384
+ type: 'text',
385
+ text: lines.join('\n'),
386
+ }],
387
+ };
388
+ }
389
+ catch (error) {
390
+ return { content: formatErrorForMcp(error) };
391
+ }
392
+ });
393
+ // Get Storage Usage
394
+ server.tool('get_storage_usage', 'Get storage usage statistics for your account.', {}, async () => {
395
+ try {
396
+ const usage = await client.getStorageUsage();
397
+ const lines = [
398
+ 'Storage Usage',
399
+ '─'.repeat(30),
400
+ `Total Files: ${usage.total_files}`,
401
+ `Total Size: ${usage.total_size_mb.toFixed(2)} MB`,
402
+ `Usage: ${usage.usage_percentage}% of ${usage.storage_limit_mb} MB limit`,
403
+ '',
404
+ 'By Entity Type:',
405
+ ];
406
+ for (const [type, count] of Object.entries(usage.by_entity)) {
407
+ lines.push(` ${type}: ${count} files`);
408
+ }
409
+ if (usage.by_type.length > 0) {
410
+ lines.push('');
411
+ lines.push('By File Type:');
412
+ for (const item of usage.by_type) {
413
+ lines.push(` ${item.file_type}: ${item.count} files (${formatFileSize(item.total_size)})`);
414
+ }
415
+ }
416
+ return {
417
+ content: [{
418
+ type: 'text',
419
+ text: lines.join('\n'),
420
+ }],
421
+ };
422
+ }
423
+ catch (error) {
424
+ return { content: formatErrorForMcp(error) };
425
+ }
426
+ });
427
+ }
428
+ //# sourceMappingURL=attachments.js.map