@pollychrome/joan-mcp 1.3.0 → 1.3.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/dist/client/api-client.d.ts +47 -1
- package/dist/client/api-client.d.ts.map +1 -1
- package/dist/client/api-client.js +152 -3
- package/dist/client/api-client.js.map +1 -1
- package/dist/client/types.d.ts +131 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/resources/attachments.d.ts +7 -0
- package/dist/resources/attachments.d.ts.map +1 -0
- package/dist/resources/attachments.js +145 -0
- package/dist/resources/attachments.js.map +1 -0
- package/dist/resources/index.d.ts +2 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +3 -1
- package/dist/resources/index.js.map +1 -1
- package/dist/tools/attachments.d.ts +7 -0
- package/dist/tools/attachments.d.ts.map +1 -0
- package/dist/tools/attachments.js +428 -0
- package/dist/tools/attachments.js.map +1 -0
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/resources.d.ts +7 -0
- package/dist/tools/resources.d.ts.map +1 -0
- package/dist/tools/resources.js +380 -0
- package/dist/tools/resources.js.map +1 -0
- package/dist/tools/tasks.js +4 -4
- package/dist/tools/tasks.js.map +1 -1
- package/package.json +1 -1
|
@@ -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":"AAAA;;GAEG;AAMH,MAAM,UAAU,2BAA2B,CAAC,MAAiB,EAAE,MAAqB;IAClF,sBAAsB;IACtB,MAAM,CAAC,QAAQ,CACb,yCAAyC,EACzC,oCAAoC,EACpC,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;QACzE,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAE7B,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACrD,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAE/E,OAAO;YACL,QAAQ,EAAE,CAAC;oBACT,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;oBACnB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;iBAC3C,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,+BAA+B;IAC/B,MAAM,CAAC,QAAQ,CACb,mDAAmD,EACnD,mFAAmF,EACnF,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACpF,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAE7B,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC/D,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,6BAA6B,CAAC,SAAS,CAAC,CAAC;QAExE,OAAO;YACL,QAAQ,EAAE,CAAC;oBACT,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;oBACnB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;iBACzC,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,wBAAwB;IACxB,MAAM,CAAC,QAAQ,CACb,kEAAkE,EAClE,sCAAsC,EACtC,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;QAC9F,MAAM,WAAW,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAE/B,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACvD,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;QAEnF,OAAO;YACL,QAAQ,EAAE,CAAC;oBACT,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;oBACnB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;iBAC3C,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,mBAAmB;IACnB,MAAM,CAAC,QAAQ,CACb,mCAAmC,EACnC,iCAAiC,EACjC,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAE1B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAClD,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAEzE,OAAO;YACL,QAAQ,EAAE,CAAC;oBACT,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;oBACnB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;iBAC3C,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,mBAAmB;IACnB,MAAM,CAAC,QAAQ,CACb,mCAAmC,EACnC,iCAAiC,EACjC,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAE1B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAClD,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAEzE,OAAO;YACL,QAAQ,EAAE,CAAC;oBACT,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;oBACnB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;iBAC3C,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,6BAA6B;IAC7B,MAAM,CAAC,QAAQ,CACb,mCAAmC,EACnC,wCAAwC,EACxC,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAC/D,MAAM,YAAY,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAEhC,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC;QAEpE,OAAO;YACL,QAAQ,EAAE,CAAC;oBACT,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;oBACnB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;iBAC1C,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,gBAAgB;IAChB,MAAM,CAAC,QAAQ,CACb,0BAA0B,EAC1B,mDAAmD,EACnD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,eAAe,EAAE,CAAC;QAE7C,OAAO;YACL,QAAQ,EAAE,CAAC;oBACT,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;oBACnB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;iBACrC,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,+BAA+B;IAC/B,MAAM,CAAC,QAAQ,CACb,iCAAiC,EACjC,6DAA6D,EAC7D,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAE1B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAChD,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAEzD,OAAO;YACL,QAAQ,EAAE,CAAC;oBACT,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;oBACnB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;iBACzC,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,kCAAkC;IAClC,MAAM,CAAC,QAAQ,CACb,uCAAuC,EACvC,oDAAoD,EACpD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAE7B,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACnD,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAE/D,OAAO;YACL,QAAQ,EAAE,CAAC;oBACT,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE;oBACnB,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;iBACzC,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -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;
|
|
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"}
|
package/dist/resources/index.js
CHANGED
|
@@ -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;
|
|
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
|