@aaronsb/google-workspace-mcp 2.1.0 → 2.3.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.
- package/build/__tests__/executor/workspace.test.js +39 -13
- package/build/__tests__/executor/workspace.test.js.map +1 -1
- package/build/__tests__/services/gmail/mime.test.js +1 -161
- package/build/__tests__/services/gmail/mime.test.js.map +1 -1
- package/build/executor/file-output.d.ts +12 -1
- package/build/executor/file-output.js +52 -1
- package/build/executor/file-output.js.map +1 -1
- package/build/executor/workspace.d.ts +8 -2
- package/build/executor/workspace.js +22 -3
- package/build/executor/workspace.js.map +1 -1
- package/build/factory/manifest.yaml +32 -3
- package/build/server/formatting/markdown.d.ts +8 -0
- package/build/server/formatting/markdown.js.map +1 -1
- package/build/server/handlers/workspace.d.ts +1 -1
- package/build/server/handlers/workspace.js +223 -23
- package/build/server/handlers/workspace.js.map +1 -1
- package/build/server/scratchpad/adapters/send-email-draft.d.ts +1 -0
- package/build/server/scratchpad/adapters/send-email-draft.js +21 -20
- package/build/server/scratchpad/adapters/send-email-draft.js.map +1 -1
- package/build/server/scratchpad/adapters/send-email.d.ts +2 -1
- package/build/server/scratchpad/adapters/send-email.js +25 -37
- package/build/server/scratchpad/adapters/send-email.js.map +1 -1
- package/build/server/server.js +7 -3
- package/build/server/server.js.map +1 -1
- package/build/server/tools.js +8 -5
- package/build/server/tools.js.map +1 -1
- package/build/services/drive/patch.js +47 -1
- package/build/services/drive/patch.js.map +1 -1
- package/build/services/gmail/attachments.d.ts +9 -10
- package/build/services/gmail/attachments.js +39 -17
- package/build/services/gmail/attachments.js.map +1 -1
- package/build/services/gmail/mime.d.ts +4 -24
- package/build/services/gmail/mime.js +4 -93
- package/build/services/gmail/mime.js.map +1 -1
- package/build/services/gmail/patch.js +31 -38
- package/build/services/gmail/patch.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { validateWorkspaceDir, resolveWorkspacePath, getWorkspaceDir, checkWorkspaceStatus } from '../../executor/workspace.js';
|
|
1
|
+
import { validateWorkspaceDir, resolveWorkspacePath, getWorkspaceDir, checkWorkspaceStatus, sanitizePath } from '../../executor/workspace.js';
|
|
2
2
|
import * as os from 'node:os';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
const HOME = os.homedir();
|
|
@@ -33,6 +33,36 @@ describe('validateWorkspaceDir', () => {
|
|
|
33
33
|
expect(() => validateWorkspaceDir(path.join(HOME, '.local', 'share', 'google-workspace-mcp', 'workspace'))).not.toThrow();
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
|
+
describe('sanitizePath', () => {
|
|
37
|
+
it('preserves directory separators', () => {
|
|
38
|
+
expect(sanitizePath('reports/q1/summary.csv')).toBe(path.join('reports', 'q1', 'summary.csv'));
|
|
39
|
+
});
|
|
40
|
+
it('sanitizes each segment individually', () => {
|
|
41
|
+
expect(sanitizePath('reports/<bad>/file.txt')).toBe(path.join('reports', '_bad_', 'file.txt'));
|
|
42
|
+
});
|
|
43
|
+
it('rejects .. traversal segments', () => {
|
|
44
|
+
expect(() => sanitizePath('../etc/passwd')).toThrow(/traversal/);
|
|
45
|
+
expect(() => sanitizePath('reports/../../etc/passwd')).toThrow(/traversal/);
|
|
46
|
+
});
|
|
47
|
+
it('rejects . segments', () => {
|
|
48
|
+
expect(() => sanitizePath('./file.txt')).toThrow(/traversal/);
|
|
49
|
+
});
|
|
50
|
+
it('handles empty input', () => {
|
|
51
|
+
expect(sanitizePath('')).toBe('unnamed');
|
|
52
|
+
});
|
|
53
|
+
it('collapses empty segments from double slashes', () => {
|
|
54
|
+
expect(sanitizePath('reports//q1///file.txt')).toBe(path.join('reports', 'q1', 'file.txt'));
|
|
55
|
+
});
|
|
56
|
+
it('normalizes backslashes', () => {
|
|
57
|
+
expect(sanitizePath('reports\\q1\\file.txt')).toBe(path.join('reports', 'q1', 'file.txt'));
|
|
58
|
+
});
|
|
59
|
+
it('strips leading dots from segments', () => {
|
|
60
|
+
expect(sanitizePath('.hidden/file.txt')).toBe(path.join('hidden', 'file.txt'));
|
|
61
|
+
});
|
|
62
|
+
it('handles single filename (no separators)', () => {
|
|
63
|
+
expect(sanitizePath('report.csv')).toBe('report.csv');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
36
66
|
describe('resolveWorkspacePath', () => {
|
|
37
67
|
const origEnv = process.env.WORKSPACE_DIR;
|
|
38
68
|
beforeEach(() => {
|
|
@@ -50,20 +80,16 @@ describe('resolveWorkspacePath', () => {
|
|
|
50
80
|
const resolved = resolveWorkspacePath('report.csv');
|
|
51
81
|
expect(resolved).toBe('/tmp/test-workspace/report.csv');
|
|
52
82
|
});
|
|
53
|
-
it('resolves
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
expect(resolved).toBe('/tmp/test-workspace/report.csv');
|
|
83
|
+
it('resolves nested path within workspace', () => {
|
|
84
|
+
const resolved = resolveWorkspacePath('reports/q1/summary.csv');
|
|
85
|
+
expect(resolved).toBe('/tmp/test-workspace/reports/q1/summary.csv');
|
|
57
86
|
});
|
|
58
|
-
it('
|
|
59
|
-
|
|
60
|
-
const resolved = resolveWorkspacePath('../../etc/passwd');
|
|
61
|
-
// Leading dots stripped, slashes become underscores
|
|
62
|
-
expect(resolved).toBe('/tmp/test-workspace/_.._etc_passwd');
|
|
87
|
+
it('rejects path traversal via ..', () => {
|
|
88
|
+
expect(() => resolveWorkspacePath('../../etc/passwd')).toThrow(/traversal/);
|
|
63
89
|
});
|
|
64
|
-
it('sanitizes
|
|
65
|
-
const resolved = resolveWorkspacePath('
|
|
66
|
-
expect(resolved).toBe('/tmp/test-workspace/
|
|
90
|
+
it('sanitizes dangerous characters in path segments', () => {
|
|
91
|
+
const resolved = resolveWorkspacePath('reports/<bad>/file.txt');
|
|
92
|
+
expect(resolved).toBe('/tmp/test-workspace/reports/_bad_/file.txt');
|
|
67
93
|
});
|
|
68
94
|
it('sanitizes null bytes and control characters', () => {
|
|
69
95
|
const resolved = resolveWorkspacePath('file\x00.txt');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.test.js","sourceRoot":"","sources":["../../../src/__tests__/executor/workspace.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"workspace.test.js","sourceRoot":"","sources":["../../../src/__tests__/executor/workspace.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/** Decode a base64url string back to UTF-8. */
|
|
3
|
-
function decodeRaw(raw) {
|
|
4
|
-
const base64 = raw.replace(/-/g, '+').replace(/_/g, '/');
|
|
5
|
-
return Buffer.from(base64, 'base64').toString('utf-8');
|
|
6
|
-
}
|
|
1
|
+
import { lookupMimeType } from '../../../services/gmail/mime.js';
|
|
7
2
|
describe('lookupMimeType', () => {
|
|
8
3
|
it('returns correct MIME type for known extensions', () => {
|
|
9
4
|
expect(lookupMimeType('report.pdf')).toBe('application/pdf');
|
|
@@ -20,164 +15,9 @@ describe('lookupMimeType', () => {
|
|
|
20
15
|
expect(lookupMimeType('FILE.PDF')).toBe('application/pdf');
|
|
21
16
|
expect(lookupMimeType('image.PNG')).toBe('image/png');
|
|
22
17
|
});
|
|
23
|
-
});
|
|
24
|
-
describe('buildMimeMessage', () => {
|
|
25
|
-
const plainAttachment = {
|
|
26
|
-
filename: 'invoice.md',
|
|
27
|
-
mimeType: 'text/markdown',
|
|
28
|
-
content: Buffer.from('# Invoice\n\nAmount: $100'),
|
|
29
|
-
};
|
|
30
|
-
it('produces valid base64url (no +, /, or = characters)', () => {
|
|
31
|
-
const raw = buildMimeMessage({
|
|
32
|
-
to: 'test@example.com',
|
|
33
|
-
subject: 'Test',
|
|
34
|
-
body: 'Hello',
|
|
35
|
-
attachments: [plainAttachment],
|
|
36
|
-
});
|
|
37
|
-
expect(raw).not.toMatch(/[+/=]/);
|
|
38
|
-
});
|
|
39
|
-
it('decodes to a valid MIME multipart message', () => {
|
|
40
|
-
const raw = buildMimeMessage({
|
|
41
|
-
to: 'test@example.com',
|
|
42
|
-
subject: 'Test subject',
|
|
43
|
-
body: 'Hello world',
|
|
44
|
-
attachments: [plainAttachment],
|
|
45
|
-
});
|
|
46
|
-
const message = decodeRaw(raw);
|
|
47
|
-
expect(message).toContain('MIME-Version: 1.0');
|
|
48
|
-
expect(message).toContain('Content-Type: multipart/mixed; boundary=');
|
|
49
|
-
expect(message).toContain('To: test@example.com');
|
|
50
|
-
expect(message).toContain('Subject: Test subject');
|
|
51
|
-
expect(message).toContain('Content-Type: text/plain; charset=UTF-8');
|
|
52
|
-
expect(message).toContain('Content-Disposition: attachment; filename="invoice.md"');
|
|
53
|
-
});
|
|
54
|
-
it('includes From, Cc, Bcc headers when provided', () => {
|
|
55
|
-
const raw = buildMimeMessage({
|
|
56
|
-
to: 'to@example.com',
|
|
57
|
-
subject: 'Test',
|
|
58
|
-
body: 'Hi',
|
|
59
|
-
from: 'from@example.com',
|
|
60
|
-
cc: 'cc@example.com',
|
|
61
|
-
bcc: 'bcc@example.com',
|
|
62
|
-
attachments: [plainAttachment],
|
|
63
|
-
});
|
|
64
|
-
const message = decodeRaw(raw);
|
|
65
|
-
expect(message).toContain('From: from@example.com');
|
|
66
|
-
expect(message).toContain('Cc: cc@example.com');
|
|
67
|
-
expect(message).toContain('Bcc: bcc@example.com');
|
|
68
|
-
});
|
|
69
|
-
it('uses text/html content type when html flag is set', () => {
|
|
70
|
-
const raw = buildMimeMessage({
|
|
71
|
-
to: 'test@example.com',
|
|
72
|
-
subject: 'Test',
|
|
73
|
-
body: '<b>Bold</b>',
|
|
74
|
-
html: true,
|
|
75
|
-
attachments: [plainAttachment],
|
|
76
|
-
});
|
|
77
|
-
const message = decodeRaw(raw);
|
|
78
|
-
expect(message).toContain('Content-Type: text/html; charset=UTF-8');
|
|
79
|
-
});
|
|
80
|
-
it('handles multiple attachments', () => {
|
|
81
|
-
const raw = buildMimeMessage({
|
|
82
|
-
to: 'test@example.com',
|
|
83
|
-
subject: 'Test',
|
|
84
|
-
body: 'See attached',
|
|
85
|
-
attachments: [
|
|
86
|
-
plainAttachment,
|
|
87
|
-
{
|
|
88
|
-
filename: 'data.csv',
|
|
89
|
-
mimeType: 'text/csv',
|
|
90
|
-
content: Buffer.from('a,b,c\n1,2,3'),
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
});
|
|
94
|
-
const message = decodeRaw(raw);
|
|
95
|
-
expect(message).toContain('filename="invoice.md"');
|
|
96
|
-
expect(message).toContain('filename="data.csv"');
|
|
97
|
-
});
|
|
98
|
-
it('encodes non-ASCII subjects with RFC 2047', () => {
|
|
99
|
-
const raw = buildMimeMessage({
|
|
100
|
-
to: 'test@example.com',
|
|
101
|
-
subject: 'Factura de febrero',
|
|
102
|
-
body: 'Hello',
|
|
103
|
-
attachments: [plainAttachment],
|
|
104
|
-
});
|
|
105
|
-
const message = decodeRaw(raw);
|
|
106
|
-
// ASCII-only subjects should not be encoded
|
|
107
|
-
expect(message).not.toContain('=?UTF-8?B?');
|
|
108
|
-
});
|
|
109
|
-
it('encodes subjects with non-ASCII characters using RFC 2047', () => {
|
|
110
|
-
const raw = buildMimeMessage({
|
|
111
|
-
to: 'test@example.com',
|
|
112
|
-
subject: 'Rechnung für Februar',
|
|
113
|
-
body: 'Hello',
|
|
114
|
-
attachments: [plainAttachment],
|
|
115
|
-
});
|
|
116
|
-
const message = decodeRaw(raw);
|
|
117
|
-
expect(message).toContain('=?UTF-8?B?');
|
|
118
|
-
// Verify the encoded subject decodes correctly
|
|
119
|
-
const match = message.match(/Subject: =\?UTF-8\?B\?(.+?)\?=/);
|
|
120
|
-
expect(match).toBeTruthy();
|
|
121
|
-
const decoded = Buffer.from(match[1], 'base64').toString('utf-8');
|
|
122
|
-
expect(decoded).toBe('Rechnung für Februar');
|
|
123
|
-
});
|
|
124
|
-
it('preserves binary attachment content through round-trip', () => {
|
|
125
|
-
// Create a buffer with all byte values 0-255
|
|
126
|
-
const binaryContent = Buffer.alloc(256);
|
|
127
|
-
for (let i = 0; i < 256; i++)
|
|
128
|
-
binaryContent[i] = i;
|
|
129
|
-
const raw = buildMimeMessage({
|
|
130
|
-
to: 'test@example.com',
|
|
131
|
-
subject: 'Binary test',
|
|
132
|
-
body: 'See attached',
|
|
133
|
-
attachments: [{
|
|
134
|
-
filename: 'binary.bin',
|
|
135
|
-
mimeType: 'application/octet-stream',
|
|
136
|
-
content: binaryContent,
|
|
137
|
-
}],
|
|
138
|
-
});
|
|
139
|
-
const message = decodeRaw(raw);
|
|
140
|
-
// Find the attachment's base64 content (after the empty line following headers)
|
|
141
|
-
const parts = message.split(/----=_Part_/);
|
|
142
|
-
const attachmentPart = parts.find(p => p.includes('filename="binary.bin"'));
|
|
143
|
-
expect(attachmentPart).toBeTruthy();
|
|
144
|
-
// Extract base64 data (after the blank line that separates headers from body)
|
|
145
|
-
const attachmentBody = attachmentPart.split('\r\n\r\n')[1];
|
|
146
|
-
// Remove trailing boundary marker if present
|
|
147
|
-
const base64Data = attachmentBody.split('\r\n--')[0].replace(/\r\n/g, '');
|
|
148
|
-
const decoded = Buffer.from(base64Data, 'base64');
|
|
149
|
-
expect(decoded).toEqual(binaryContent);
|
|
150
|
-
});
|
|
151
|
-
it('strips CRLF from header values to prevent injection', () => {
|
|
152
|
-
const raw = buildMimeMessage({
|
|
153
|
-
to: 'victim@example.com\r\nBcc: attacker@evil.com',
|
|
154
|
-
subject: 'Injected\r\nBcc: attacker@evil.com',
|
|
155
|
-
body: 'Hello',
|
|
156
|
-
from: 'sender@example.com\r\nBcc: attacker@evil.com',
|
|
157
|
-
attachments: [plainAttachment],
|
|
158
|
-
});
|
|
159
|
-
const message = decodeRaw(raw);
|
|
160
|
-
// The injected Bcc should not appear as a separate header
|
|
161
|
-
expect(message).not.toContain('attacker@evil.com');
|
|
162
|
-
// The legitimate To header should be sanitized
|
|
163
|
-
expect(message).toContain('To: victim@example.com');
|
|
164
|
-
});
|
|
165
18
|
it('handles filenames without extensions', () => {
|
|
166
19
|
expect(lookupMimeType('Makefile')).toBe('application/octet-stream');
|
|
167
20
|
expect(lookupMimeType('README')).toBe('application/octet-stream');
|
|
168
21
|
});
|
|
169
|
-
it('throws on messages exceeding 5MB', () => {
|
|
170
|
-
const largeContent = Buffer.alloc(4 * 1024 * 1024); // 4MB → ~5.3MB after base64
|
|
171
|
-
expect(() => buildMimeMessage({
|
|
172
|
-
to: 'test@example.com',
|
|
173
|
-
subject: 'Large',
|
|
174
|
-
body: 'See attached',
|
|
175
|
-
attachments: [{
|
|
176
|
-
filename: 'large.bin',
|
|
177
|
-
mimeType: 'application/octet-stream',
|
|
178
|
-
content: largeContent,
|
|
179
|
-
}],
|
|
180
|
-
})).toThrow(/exceeds Gmail's 5MB limit/);
|
|
181
|
-
});
|
|
182
22
|
});
|
|
183
23
|
//# sourceMappingURL=mime.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mime.test.js","sourceRoot":"","sources":["../../../../src/__tests__/services/gmail/mime.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"mime.test.js","sourceRoot":"","sources":["../../../../src/__tests__/services/gmail/mime.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC7D,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACpE,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACpE,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -4,16 +4,27 @@
|
|
|
4
4
|
* sandboxed environments (Claude Desktop) can't read the MCP server's
|
|
5
5
|
* local filesystem, so text content must be included in the response.
|
|
6
6
|
*
|
|
7
|
-
* Used by: getAttachment (gmail), download (drive), export (drive)
|
|
7
|
+
* Used by: getAttachment (gmail), download (drive), export (drive), workspace read
|
|
8
8
|
*/
|
|
9
|
+
import type { ContentBlock } from '../server/formatting/markdown.js';
|
|
9
10
|
/** Check if a file should have its content returned inline. */
|
|
10
11
|
export declare function isTextFile(filename: string, mimeType?: string): boolean;
|
|
12
|
+
/** Check if a file is an image that can be returned as an MCP image block. */
|
|
13
|
+
export declare function isImageFile(filename: string, mimeType?: string): boolean;
|
|
14
|
+
/** Get the image MIME type for a filename or explicit MIME. */
|
|
15
|
+
export declare function getImageMimeType(filename: string, mimeType?: string): string;
|
|
16
|
+
/** Build an MCP image content block from a buffer. Returns undefined if too large. */
|
|
17
|
+
export declare function buildImageBlock(buffer: Buffer, filename: string, mimeType?: string): ContentBlock | undefined;
|
|
18
|
+
/** Build an MCP image content block from a file on disk. Returns undefined if not an image or too large. */
|
|
19
|
+
export declare function buildImageBlockFromFile(filePath: string, filename: string, mimeType?: string): Promise<ContentBlock | undefined>;
|
|
11
20
|
export interface FileOutputResult {
|
|
12
21
|
filename: string;
|
|
13
22
|
path: string;
|
|
14
23
|
size: number;
|
|
15
24
|
/** Text content included inline for containerized agents. Undefined for binary files. */
|
|
16
25
|
content?: string;
|
|
26
|
+
/** Image content block for visual files. */
|
|
27
|
+
imageBlock?: ContentBlock;
|
|
17
28
|
}
|
|
18
29
|
/**
|
|
19
30
|
* Save a buffer to the workspace directory and optionally return text content inline.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* sandboxed environments (Claude Desktop) can't read the MCP server's
|
|
5
5
|
* local filesystem, so text content must be included in the response.
|
|
6
6
|
*
|
|
7
|
-
* Used by: getAttachment (gmail), download (drive), export (drive)
|
|
7
|
+
* Used by: getAttachment (gmail), download (drive), export (drive), workspace read
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from 'node:fs/promises';
|
|
10
10
|
import * as path from 'node:path';
|
|
@@ -19,7 +19,18 @@ const TEXT_EXTENSIONS = [
|
|
|
19
19
|
'.htm', '.eml', '.log', '.ini', '.toml', '.js', '.ts', '.py',
|
|
20
20
|
'.sh', '.bash', '.zsh', '.css', '.svg',
|
|
21
21
|
];
|
|
22
|
+
/** Image MIME types supported by MCP image content blocks. */
|
|
23
|
+
/** Raster image types returned as MCP image blocks. SVG excluded — it's text/XML. */
|
|
24
|
+
const IMAGE_MIME_MAP = {
|
|
25
|
+
'.png': 'image/png',
|
|
26
|
+
'.jpg': 'image/jpeg',
|
|
27
|
+
'.jpeg': 'image/jpeg',
|
|
28
|
+
'.gif': 'image/gif',
|
|
29
|
+
'.webp': 'image/webp',
|
|
30
|
+
'.bmp': 'image/bmp',
|
|
31
|
+
};
|
|
22
32
|
const MAX_INLINE_SIZE = 100_000; // 100KB — larger text files are saved only
|
|
33
|
+
const MAX_IMAGE_SIZE = 5_000_000; // 5MB — larger images are path-only
|
|
23
34
|
/** Check if a file should have its content returned inline. */
|
|
24
35
|
export function isTextFile(filename, mimeType) {
|
|
25
36
|
if (mimeType && TEXT_MIME_PREFIXES.some(p => mimeType.startsWith(p)))
|
|
@@ -27,6 +38,40 @@ export function isTextFile(filename, mimeType) {
|
|
|
27
38
|
const ext = path.extname(filename).toLowerCase();
|
|
28
39
|
return TEXT_EXTENSIONS.includes(ext);
|
|
29
40
|
}
|
|
41
|
+
/** Check if a file is an image that can be returned as an MCP image block. */
|
|
42
|
+
export function isImageFile(filename, mimeType) {
|
|
43
|
+
if (mimeType && mimeType.startsWith('image/'))
|
|
44
|
+
return true;
|
|
45
|
+
const ext = path.extname(filename).toLowerCase();
|
|
46
|
+
return ext in IMAGE_MIME_MAP;
|
|
47
|
+
}
|
|
48
|
+
/** Get the image MIME type for a filename or explicit MIME. */
|
|
49
|
+
export function getImageMimeType(filename, mimeType) {
|
|
50
|
+
if (mimeType && mimeType.startsWith('image/'))
|
|
51
|
+
return mimeType;
|
|
52
|
+
const ext = path.extname(filename).toLowerCase();
|
|
53
|
+
return IMAGE_MIME_MAP[ext] ?? 'image/png';
|
|
54
|
+
}
|
|
55
|
+
/** Build an MCP image content block from a buffer. Returns undefined if too large. */
|
|
56
|
+
export function buildImageBlock(buffer, filename, mimeType) {
|
|
57
|
+
if (buffer.length > MAX_IMAGE_SIZE)
|
|
58
|
+
return undefined;
|
|
59
|
+
return {
|
|
60
|
+
type: 'image',
|
|
61
|
+
data: buffer.toString('base64'),
|
|
62
|
+
mimeType: getImageMimeType(filename, mimeType),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Build an MCP image content block from a file on disk. Returns undefined if not an image or too large. */
|
|
66
|
+
export async function buildImageBlockFromFile(filePath, filename, mimeType) {
|
|
67
|
+
if (!isImageFile(filename, mimeType))
|
|
68
|
+
return undefined;
|
|
69
|
+
const stat = await fs.stat(filePath);
|
|
70
|
+
if (stat.size > MAX_IMAGE_SIZE)
|
|
71
|
+
return undefined;
|
|
72
|
+
const buffer = await fs.readFile(filePath);
|
|
73
|
+
return buildImageBlock(buffer, filename, mimeType);
|
|
74
|
+
}
|
|
30
75
|
/**
|
|
31
76
|
* Save a buffer to the workspace directory and optionally return text content inline.
|
|
32
77
|
*/
|
|
@@ -47,6 +92,9 @@ export async function saveToWorkspace(filename, buffer, mimeType) {
|
|
|
47
92
|
if (isTextFile(filename, mimeType) && buffer.length < MAX_INLINE_SIZE) {
|
|
48
93
|
result.content = buffer.toString('utf-8');
|
|
49
94
|
}
|
|
95
|
+
else if (isImageFile(filename, mimeType)) {
|
|
96
|
+
result.imageBlock = buildImageBlock(buffer, filename, mimeType);
|
|
97
|
+
}
|
|
50
98
|
return result;
|
|
51
99
|
}
|
|
52
100
|
/** Format a file output result as markdown for the MCP response. */
|
|
@@ -62,6 +110,9 @@ export function formatFileOutput(result) {
|
|
|
62
110
|
const safeContent = result.content.replace(/```/g, '` ` `');
|
|
63
111
|
parts.push('', '---', '', '```', safeContent, '```');
|
|
64
112
|
}
|
|
113
|
+
else if (result.imageBlock) {
|
|
114
|
+
parts.push('', '_Image included inline below._');
|
|
115
|
+
}
|
|
65
116
|
return parts.join('\n');
|
|
66
117
|
}
|
|
67
118
|
//# sourceMappingURL=file-output.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-output.js","sourceRoot":"","sources":["../../src/executor/file-output.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-output.js","sourceRoot":"","sources":["../../src/executor/file-output.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,13 +22,19 @@ export declare function checkWorkspaceStatus(): WorkspaceStatus;
|
|
|
22
22
|
/** Ensure the workspace directory exists and is validated. Returns status instead of throwing. */
|
|
23
23
|
export declare function ensureWorkspaceDir(): Promise<WorkspaceStatus>;
|
|
24
24
|
/**
|
|
25
|
-
* Sanitize a filename
|
|
25
|
+
* Sanitize a single filename segment (no path separators).
|
|
26
26
|
* Strips null bytes, control characters, path separators, and other dangerous chars.
|
|
27
27
|
*/
|
|
28
28
|
export declare function sanitizeFilename(filename: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Sanitize a path that may contain directory separators.
|
|
31
|
+
* Each segment is sanitized individually; empty and traversal segments are rejected.
|
|
32
|
+
*/
|
|
33
|
+
export declare function sanitizePath(inputPath: string): string;
|
|
29
34
|
/**
|
|
30
35
|
* Resolve a file path within the workspace directory.
|
|
31
|
-
*
|
|
36
|
+
* Supports nested paths (e.g. "reports/q1/summary.csv").
|
|
37
|
+
* Prevents path traversal and sanitizes each path segment.
|
|
32
38
|
*/
|
|
33
39
|
export declare function resolveWorkspacePath(filename: string): string;
|
|
34
40
|
/**
|
|
@@ -90,7 +90,7 @@ export async function ensureWorkspaceDir() {
|
|
|
90
90
|
return status;
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
93
|
-
* Sanitize a filename
|
|
93
|
+
* Sanitize a single filename segment (no path separators).
|
|
94
94
|
* Strips null bytes, control characters, path separators, and other dangerous chars.
|
|
95
95
|
*/
|
|
96
96
|
export function sanitizeFilename(filename) {
|
|
@@ -110,13 +110,32 @@ export function sanitizeFilename(filename) {
|
|
|
110
110
|
// Fallback if nothing remains
|
|
111
111
|
|| 'unnamed';
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Sanitize a path that may contain directory separators.
|
|
115
|
+
* Each segment is sanitized individually; empty and traversal segments are rejected.
|
|
116
|
+
*/
|
|
117
|
+
export function sanitizePath(inputPath) {
|
|
118
|
+
// Normalize separators to forward slash, then split
|
|
119
|
+
const segments = inputPath.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
120
|
+
if (segments.length === 0)
|
|
121
|
+
return 'unnamed';
|
|
122
|
+
const sanitized = segments.map(segment => {
|
|
123
|
+
// Reject traversal segments before sanitization
|
|
124
|
+
if (segment === '..' || segment === '.') {
|
|
125
|
+
throw new Error(`Path traversal segment rejected: "${segment}"`);
|
|
126
|
+
}
|
|
127
|
+
return sanitizeFilename(segment);
|
|
128
|
+
});
|
|
129
|
+
return sanitized.join(path.sep);
|
|
130
|
+
}
|
|
113
131
|
/**
|
|
114
132
|
* Resolve a file path within the workspace directory.
|
|
115
|
-
*
|
|
133
|
+
* Supports nested paths (e.g. "reports/q1/summary.csv").
|
|
134
|
+
* Prevents path traversal and sanitizes each path segment.
|
|
116
135
|
*/
|
|
117
136
|
export function resolveWorkspacePath(filename) {
|
|
118
137
|
const dir = getWorkspaceDir();
|
|
119
|
-
const sanitized =
|
|
138
|
+
const sanitized = sanitizePath(filename);
|
|
120
139
|
const resolved = path.resolve(dir, sanitized);
|
|
121
140
|
// Ensure the resolved path is still inside the workspace
|
|
122
141
|
const resolvedDir = path.resolve(dir);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/executor/workspace.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/executor/workspace.ts"],"names":[],"mappings":"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"}
|
|
@@ -53,7 +53,7 @@ services:
|
|
|
53
53
|
|
|
54
54
|
send:
|
|
55
55
|
type: action
|
|
56
|
-
description: "compose and send a new email"
|
|
56
|
+
description: "compose and send a new email (creates draft when attachments present, 35MB attachment limit via upload endpoint)"
|
|
57
57
|
helper: "+send"
|
|
58
58
|
params:
|
|
59
59
|
to:
|
|
@@ -76,10 +76,13 @@ services:
|
|
|
76
76
|
description: "BCC email(s), comma-separated"
|
|
77
77
|
attachments:
|
|
78
78
|
type: string
|
|
79
|
-
description: "Workspace filenames to attach, comma-separated (files must exist in workspace via manage_workspace)"
|
|
79
|
+
description: "Workspace filenames to attach, comma-separated (files must exist in workspace via manage_workspace). Creates draft when present."
|
|
80
80
|
html:
|
|
81
81
|
type: boolean
|
|
82
82
|
description: "Treat body as HTML content (default: plain text)"
|
|
83
|
+
draft:
|
|
84
|
+
type: boolean
|
|
85
|
+
description: "Save as draft instead of sending (default: false, forced true when attachments present)"
|
|
83
86
|
cli_args:
|
|
84
87
|
to: "--to"
|
|
85
88
|
subject: "--subject"
|
|
@@ -127,7 +130,7 @@ services:
|
|
|
127
130
|
|
|
128
131
|
forward:
|
|
129
132
|
type: action
|
|
130
|
-
description: "forward a message to new recipients"
|
|
133
|
+
description: "forward a message to new recipients (includes original attachments by default)"
|
|
131
134
|
helper: "+forward"
|
|
132
135
|
params:
|
|
133
136
|
messageId:
|
|
@@ -195,6 +198,22 @@ services:
|
|
|
195
198
|
defaults:
|
|
196
199
|
userId: me
|
|
197
200
|
|
|
201
|
+
viewAttachment:
|
|
202
|
+
type: detail
|
|
203
|
+
description: "view an image attachment inline without saving to workspace (png, jpg, gif, webp). Use for quick preview — use getAttachment to save."
|
|
204
|
+
resource: users.messages.attachments.get
|
|
205
|
+
params:
|
|
206
|
+
messageId:
|
|
207
|
+
type: string
|
|
208
|
+
description: "Message ID containing the attachment"
|
|
209
|
+
required: true
|
|
210
|
+
filename:
|
|
211
|
+
type: string
|
|
212
|
+
description: "Image filename from the read response"
|
|
213
|
+
required: true
|
|
214
|
+
defaults:
|
|
215
|
+
userId: me
|
|
216
|
+
|
|
198
217
|
modify:
|
|
199
218
|
type: action
|
|
200
219
|
description: "add or remove labels on a message (e.g. archive, mark read/unread)"
|
|
@@ -476,6 +495,16 @@ services:
|
|
|
476
495
|
name: "--name"
|
|
477
496
|
parentFolderId: "--parent"
|
|
478
497
|
|
|
498
|
+
viewImage:
|
|
499
|
+
type: detail
|
|
500
|
+
description: "view an image file inline without saving to workspace (png, jpg, gif, webp). Use for quick preview — use download to save."
|
|
501
|
+
resource: files.get
|
|
502
|
+
params:
|
|
503
|
+
fileId:
|
|
504
|
+
type: string
|
|
505
|
+
description: "File ID of the image to view"
|
|
506
|
+
required: true
|
|
507
|
+
|
|
479
508
|
download:
|
|
480
509
|
type: detail
|
|
481
510
|
description: "download file content to local path"
|
|
@@ -8,10 +8,18 @@
|
|
|
8
8
|
* - Each formatter returns { text, refs } where refs are the
|
|
9
9
|
* structured values queue $N.field resolution needs
|
|
10
10
|
*/
|
|
11
|
+
/** MCP content block for inline image/audio return. */
|
|
12
|
+
export interface ContentBlock {
|
|
13
|
+
type: 'image' | 'audio';
|
|
14
|
+
data: string;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
}
|
|
11
17
|
/** Shared response shape — markdown text for agents, structured refs for queue $N.field. */
|
|
12
18
|
export interface HandlerResponse {
|
|
13
19
|
text: string;
|
|
14
20
|
refs: Record<string, unknown>;
|
|
21
|
+
/** Optional content blocks (images, audio) returned alongside text. */
|
|
22
|
+
content?: ContentBlock[];
|
|
15
23
|
}
|
|
16
24
|
/**
|
|
17
25
|
* Walk Gmail MIME payload parts to extract the message body.
|