@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.
Files changed (37) hide show
  1. package/build/__tests__/executor/workspace.test.js +39 -13
  2. package/build/__tests__/executor/workspace.test.js.map +1 -1
  3. package/build/__tests__/services/gmail/mime.test.js +1 -161
  4. package/build/__tests__/services/gmail/mime.test.js.map +1 -1
  5. package/build/executor/file-output.d.ts +12 -1
  6. package/build/executor/file-output.js +52 -1
  7. package/build/executor/file-output.js.map +1 -1
  8. package/build/executor/workspace.d.ts +8 -2
  9. package/build/executor/workspace.js +22 -3
  10. package/build/executor/workspace.js.map +1 -1
  11. package/build/factory/manifest.yaml +32 -3
  12. package/build/server/formatting/markdown.d.ts +8 -0
  13. package/build/server/formatting/markdown.js.map +1 -1
  14. package/build/server/handlers/workspace.d.ts +1 -1
  15. package/build/server/handlers/workspace.js +223 -23
  16. package/build/server/handlers/workspace.js.map +1 -1
  17. package/build/server/scratchpad/adapters/send-email-draft.d.ts +1 -0
  18. package/build/server/scratchpad/adapters/send-email-draft.js +21 -20
  19. package/build/server/scratchpad/adapters/send-email-draft.js.map +1 -1
  20. package/build/server/scratchpad/adapters/send-email.d.ts +2 -1
  21. package/build/server/scratchpad/adapters/send-email.js +25 -37
  22. package/build/server/scratchpad/adapters/send-email.js.map +1 -1
  23. package/build/server/server.js +7 -3
  24. package/build/server/server.js.map +1 -1
  25. package/build/server/tools.js +8 -5
  26. package/build/server/tools.js.map +1 -1
  27. package/build/services/drive/patch.js +47 -1
  28. package/build/services/drive/patch.js.map +1 -1
  29. package/build/services/gmail/attachments.d.ts +9 -10
  30. package/build/services/gmail/attachments.js +39 -17
  31. package/build/services/gmail/attachments.js.map +1 -1
  32. package/build/services/gmail/mime.d.ts +4 -24
  33. package/build/services/gmail/mime.js +4 -93
  34. package/build/services/gmail/mime.js.map +1 -1
  35. package/build/services/gmail/patch.js +31 -38
  36. package/build/services/gmail/patch.js.map +1 -1
  37. 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 filename within workspace (sanitized)', () => {
54
- // Slashes in filenames get sanitized to underscores
55
- const resolved = resolveWorkspacePath('report.csv');
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('sanitizes path separators in filenames', () => {
59
- // Path separators become underscores — no directory traversal
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 absolute paths', () => {
65
- const resolved = resolveWorkspacePath('/etc/passwd');
66
- expect(resolved).toBe('/tmp/test-workspace/_etc_passwd');
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":"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"}
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 { buildMimeMessage, lookupMimeType } from '../../../services/gmail/mime.js';
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":"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"}
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":"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"}
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;AAG5F,wEAAwE;AACxE,MAAM,kBAAkB,GAAG;IACzB,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,wBAAwB;IACxE,oBAAoB,EAAE,kBAAkB,EAAE,iBAAiB;CAC5D,CAAC;AAEF,MAAM,eAAe,GAAG;IACtB,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO;IAChE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK;IAC5D,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CACvC,CAAC;AAEF,8DAA8D;AAC9D,qFAAqF;AACrF,MAAM,cAAc,GAA2B;IAC7C,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;CACpB,CAAC;AAEF,MAAM,eAAe,GAAG,OAAO,CAAC,CAAC,2CAA2C;AAC5E,MAAM,cAAc,GAAG,SAAS,CAAC,CAAC,oCAAoC;AAEtE,+DAA+D;AAC/D,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,QAAiB;IAC5D,IAAI,QAAQ,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAClF,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,OAAO,eAAe,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACvC,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,QAAiB;IAC7D,IAAI,QAAQ,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,OAAO,GAAG,IAAI,cAAc,CAAC;AAC/B,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,QAAiB;IAClE,IAAI,QAAQ,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC;AAC5C,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,QAAgB,EAAE,QAAiB;IACjF,IAAI,MAAM,CAAC,MAAM,GAAG,cAAc;QAAE,OAAO,SAAS,CAAC;IACrD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC/B,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC;KAC/C,CAAC;AACJ,CAAC;AAED,4GAA4G;AAC5G,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,QAAgB,EAAE,QAAgB,EAAE,QAAiB;IACjG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IACvD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,IAAI,CAAC,IAAI,GAAG,cAAc;QAAE,OAAO,SAAS,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3C,OAAO,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACrD,CAAC;AAYD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,MAAc,EACd,QAAiB;IAEjB,MAAM,QAAQ,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAC5C,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,gCAAgC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,UAAU,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACnC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAEvC,MAAM,MAAM,GAAqB;QAC/B,QAAQ;QACR,IAAI,EAAE,UAAU;QAChB,IAAI,EAAE,MAAM,CAAC,MAAM;KACpB,CAAC;IAEF,IAAI,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QACtE,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;SAAM,IAAI,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC3C,MAAM,CAAC,UAAU,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,gBAAgB,CAAC,MAAwB;IACvD,MAAM,KAAK,GAAG;QACZ,KAAK,MAAM,CAAC,QAAQ,uBAAuB;QAC3C,EAAE;QACF,aAAa,MAAM,CAAC,IAAI,EAAE;QAC1B,aAAa,MAAM,CAAC,IAAI,QAAQ;KACjC,CAAC;IAEF,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,iFAAiF;QACjF,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5D,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;IACvD,CAAC;SAAM,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,gCAAgC,CAAC,CAAC;IACnD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -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 from external sources (email attachments, Drive metadata).
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
- * Prevents path traversal (e.g. ../../etc/passwd) and sanitizes the filename.
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 from external sources (email attachments, Drive metadata).
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
- * Prevents path traversal (e.g. ../../etc/passwd) and sanitizes the filename.
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 = sanitizeFilename(filename);
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.