@n24q02m/better-email-mcp 1.0.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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/bin/cli.mjs +13 -0
  4. package/build/scripts/start-server.d.ts +6 -0
  5. package/build/scripts/start-server.d.ts.map +1 -0
  6. package/build/scripts/start-server.js +21 -0
  7. package/build/scripts/start-server.js.map +1 -0
  8. package/build/src/docs/attachments.md +48 -0
  9. package/build/src/docs/folders.md +45 -0
  10. package/build/src/docs/messages.md +92 -0
  11. package/build/src/docs/send.md +52 -0
  12. package/build/src/init-server.d.ts +40 -0
  13. package/build/src/init-server.d.ts.map +1 -0
  14. package/build/src/init-server.js +57 -0
  15. package/build/src/init-server.js.map +1 -0
  16. package/build/src/tools/composite/attachments.d.ts +17 -0
  17. package/build/src/tools/composite/attachments.d.ts.map +1 -0
  18. package/build/src/tools/composite/attachments.js +77 -0
  19. package/build/src/tools/composite/attachments.js.map +1 -0
  20. package/build/src/tools/composite/attachments.test.d.ts +2 -0
  21. package/build/src/tools/composite/attachments.test.d.ts.map +1 -0
  22. package/build/src/tools/composite/attachments.test.js +138 -0
  23. package/build/src/tools/composite/attachments.test.js.map +1 -0
  24. package/build/src/tools/composite/folders.d.ts +14 -0
  25. package/build/src/tools/composite/folders.d.ts.map +1 -0
  26. package/build/src/tools/composite/folders.js +57 -0
  27. package/build/src/tools/composite/folders.js.map +1 -0
  28. package/build/src/tools/composite/folders.test.d.ts +2 -0
  29. package/build/src/tools/composite/folders.test.d.ts.map +1 -0
  30. package/build/src/tools/composite/folders.test.js +61 -0
  31. package/build/src/tools/composite/folders.test.js.map +1 -0
  32. package/build/src/tools/composite/messages.d.ts +20 -0
  33. package/build/src/tools/composite/messages.d.ts.map +1 -0
  34. package/build/src/tools/composite/messages.js +244 -0
  35. package/build/src/tools/composite/messages.js.map +1 -0
  36. package/build/src/tools/composite/messages.test.d.ts +2 -0
  37. package/build/src/tools/composite/messages.test.d.ts.map +1 -0
  38. package/build/src/tools/composite/messages.test.js +217 -0
  39. package/build/src/tools/composite/messages.test.js.map +1 -0
  40. package/build/src/tools/composite/send.d.ts +21 -0
  41. package/build/src/tools/composite/send.d.ts.map +1 -0
  42. package/build/src/tools/composite/send.js +127 -0
  43. package/build/src/tools/composite/send.js.map +1 -0
  44. package/build/src/tools/composite/send.test.d.ts +2 -0
  45. package/build/src/tools/composite/send.test.d.ts.map +1 -0
  46. package/build/src/tools/composite/send.test.js +232 -0
  47. package/build/src/tools/composite/send.test.js.map +1 -0
  48. package/build/src/tools/helpers/config.d.ts +33 -0
  49. package/build/src/tools/helpers/config.d.ts.map +1 -0
  50. package/build/src/tools/helpers/config.js +167 -0
  51. package/build/src/tools/helpers/config.js.map +1 -0
  52. package/build/src/tools/helpers/config.test.d.ts +2 -0
  53. package/build/src/tools/helpers/config.test.d.ts.map +1 -0
  54. package/build/src/tools/helpers/config.test.js +141 -0
  55. package/build/src/tools/helpers/config.test.js.map +1 -0
  56. package/build/src/tools/helpers/errors.d.ts +34 -0
  57. package/build/src/tools/helpers/errors.d.ts.map +1 -0
  58. package/build/src/tools/helpers/errors.js +152 -0
  59. package/build/src/tools/helpers/errors.js.map +1 -0
  60. package/build/src/tools/helpers/errors.test.d.ts +2 -0
  61. package/build/src/tools/helpers/errors.test.d.ts.map +1 -0
  62. package/build/src/tools/helpers/errors.test.js +203 -0
  63. package/build/src/tools/helpers/errors.test.js.map +1 -0
  64. package/build/src/tools/helpers/html-utils.d.ts +10 -0
  65. package/build/src/tools/helpers/html-utils.d.ts.map +1 -0
  66. package/build/src/tools/helpers/html-utils.js +29 -0
  67. package/build/src/tools/helpers/html-utils.js.map +1 -0
  68. package/build/src/tools/helpers/html-utils.test.d.ts +2 -0
  69. package/build/src/tools/helpers/html-utils.test.d.ts.map +1 -0
  70. package/build/src/tools/helpers/html-utils.test.js +103 -0
  71. package/build/src/tools/helpers/html-utils.test.js.map +1 -0
  72. package/build/src/tools/helpers/imap-client.d.ts +90 -0
  73. package/build/src/tools/helpers/imap-client.d.ts.map +1 -0
  74. package/build/src/tools/helpers/imap-client.js +321 -0
  75. package/build/src/tools/helpers/imap-client.js.map +1 -0
  76. package/build/src/tools/helpers/imap-client.test.d.ts +2 -0
  77. package/build/src/tools/helpers/imap-client.test.d.ts.map +1 -0
  78. package/build/src/tools/helpers/imap-client.test.js +401 -0
  79. package/build/src/tools/helpers/imap-client.test.js.map +1 -0
  80. package/build/src/tools/helpers/smtp-client.d.ts +38 -0
  81. package/build/src/tools/helpers/smtp-client.d.ts.map +1 -0
  82. package/build/src/tools/helpers/smtp-client.js +124 -0
  83. package/build/src/tools/helpers/smtp-client.js.map +1 -0
  84. package/build/src/tools/helpers/smtp-client.test.d.ts +2 -0
  85. package/build/src/tools/helpers/smtp-client.test.d.ts.map +1 -0
  86. package/build/src/tools/helpers/smtp-client.test.js +210 -0
  87. package/build/src/tools/helpers/smtp-client.test.js.map +1 -0
  88. package/build/src/tools/registry.d.ts +11 -0
  89. package/build/src/tools/registry.d.ts.map +1 -0
  90. package/build/src/tools/registry.js +262 -0
  91. package/build/src/tools/registry.js.map +1 -0
  92. package/build/src/tools/registry.test.d.ts +2 -0
  93. package/build/src/tools/registry.test.d.ts.map +1 -0
  94. package/build/src/tools/registry.test.js +135 -0
  95. package/build/src/tools/registry.test.js.map +1 -0
  96. package/build/tsconfig.tsbuildinfo +1 -0
  97. package/package.json +81 -0
@@ -0,0 +1,210 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ // --- Mocks ---
3
+ const mockSendMail = vi.fn();
4
+ const mockClose = vi.fn();
5
+ vi.mock('nodemailer', () => ({
6
+ createTransport: vi.fn().mockImplementation(() => ({
7
+ sendMail: mockSendMail,
8
+ close: mockClose
9
+ }))
10
+ }));
11
+ import { createTransport } from 'nodemailer';
12
+ import { forwardEmail, replyToEmail, sendNewEmail } from './smtp-client.js';
13
+ const account = {
14
+ id: 'test_gmail_com',
15
+ email: 'test@gmail.com',
16
+ password: 'testpass',
17
+ imap: { host: 'imap.gmail.com', port: 993, secure: true },
18
+ smtp: { host: 'smtp.gmail.com', port: 465, secure: true }
19
+ };
20
+ beforeEach(() => {
21
+ vi.clearAllMocks();
22
+ mockSendMail.mockResolvedValue({ messageId: '<sent123@gmail.com>' });
23
+ });
24
+ // ============================================================================
25
+ // sendNewEmail
26
+ // ============================================================================
27
+ describe('sendNewEmail', () => {
28
+ it('sends email with correct parameters', async () => {
29
+ const result = await sendNewEmail(account, {
30
+ to: 'recipient@test.com',
31
+ subject: 'Hello',
32
+ body: 'World',
33
+ cc: 'cc@test.com',
34
+ bcc: 'bcc@test.com'
35
+ });
36
+ expect(result.success).toBe(true);
37
+ expect(result.message_id).toBe('<sent123@gmail.com>');
38
+ expect(mockSendMail).toHaveBeenCalledWith(expect.objectContaining({
39
+ from: 'test@gmail.com',
40
+ to: 'recipient@test.com',
41
+ subject: 'Hello',
42
+ text: 'World',
43
+ cc: 'cc@test.com',
44
+ bcc: 'bcc@test.com'
45
+ }));
46
+ });
47
+ it('includes HTML version of the body', async () => {
48
+ await sendNewEmail(account, {
49
+ to: 'r@test.com',
50
+ subject: 'Test',
51
+ body: 'Simple line'
52
+ });
53
+ const callArgs = mockSendMail.mock.calls[0][0];
54
+ expect(callArgs.html).toContain('<p>Simple line</p>');
55
+ });
56
+ it('converts markdown headings to HTML', async () => {
57
+ await sendNewEmail(account, {
58
+ to: 'r@test.com',
59
+ subject: 'Test',
60
+ body: '# Title\n## Subtitle\n### Section'
61
+ });
62
+ const callArgs = mockSendMail.mock.calls[0][0];
63
+ expect(callArgs.html).toContain('<h1>Title</h1>');
64
+ expect(callArgs.html).toContain('<h2>Subtitle</h2>');
65
+ expect(callArgs.html).toContain('<h3>Section</h3>');
66
+ });
67
+ it('converts list items to HTML', async () => {
68
+ await sendNewEmail(account, {
69
+ to: 'r@test.com',
70
+ subject: 'Test',
71
+ body: '- Item 1\n- Item 2'
72
+ });
73
+ const callArgs = mockSendMail.mock.calls[0][0];
74
+ expect(callArgs.html).toContain('<li>Item 1</li>');
75
+ expect(callArgs.html).toContain('<li>Item 2</li>');
76
+ });
77
+ it('converts bold text to HTML', async () => {
78
+ await sendNewEmail(account, {
79
+ to: 'r@test.com',
80
+ subject: 'Test',
81
+ body: '**Important**'
82
+ });
83
+ const callArgs = mockSendMail.mock.calls[0][0];
84
+ expect(callArgs.html).toContain('<b>Important</b>');
85
+ });
86
+ it('converts empty lines to br tags', async () => {
87
+ await sendNewEmail(account, {
88
+ to: 'r@test.com',
89
+ subject: 'Test',
90
+ body: 'Line 1\n\nLine 2'
91
+ });
92
+ const callArgs = mockSendMail.mock.calls[0][0];
93
+ expect(callArgs.html).toContain('<br>');
94
+ });
95
+ it('always closes transport', async () => {
96
+ await sendNewEmail(account, { to: 'r@test.com', subject: 'T', body: 'B' });
97
+ expect(mockClose).toHaveBeenCalled();
98
+ });
99
+ it('closes transport even on error', async () => {
100
+ mockSendMail.mockRejectedValue(new Error('SMTP error'));
101
+ await expect(sendNewEmail(account, { to: 'r@test.com', subject: 'T', body: 'B' })).rejects.toThrow();
102
+ expect(mockClose).toHaveBeenCalled();
103
+ });
104
+ it('creates transport with correct SMTP config', async () => {
105
+ await sendNewEmail(account, { to: 'r@test.com', subject: 'T', body: 'B' });
106
+ expect(createTransport).toHaveBeenCalledWith({
107
+ host: 'smtp.gmail.com',
108
+ port: 465,
109
+ secure: true,
110
+ auth: { user: 'test@gmail.com', pass: 'testpass' }
111
+ });
112
+ });
113
+ it('handles empty messageId in response', async () => {
114
+ mockSendMail.mockResolvedValue({});
115
+ const result = await sendNewEmail(account, { to: 'r@test.com', subject: 'T', body: 'B' });
116
+ expect(result.message_id).toBe('');
117
+ });
118
+ });
119
+ // ============================================================================
120
+ // replyToEmail
121
+ // ============================================================================
122
+ describe('replyToEmail', () => {
123
+ it('sends reply with In-Reply-To and References headers', async () => {
124
+ const result = await replyToEmail(account, {
125
+ to: 'original@test.com',
126
+ subject: 'Original Subject',
127
+ body: 'My reply',
128
+ in_reply_to: '<original123@test>',
129
+ references: '<ref1@test> <original123@test>'
130
+ });
131
+ expect(result.success).toBe(true);
132
+ expect(mockSendMail).toHaveBeenCalledWith(expect.objectContaining({
133
+ subject: 'Re: Original Subject',
134
+ inReplyTo: '<original123@test>',
135
+ references: '<ref1@test> <original123@test>'
136
+ }));
137
+ });
138
+ it('does not double-prepend Re: prefix', async () => {
139
+ await replyToEmail(account, {
140
+ to: 'x@test.com',
141
+ subject: 'Re: Already has prefix',
142
+ body: 'reply',
143
+ in_reply_to: '<msg@test>'
144
+ });
145
+ const callArgs = mockSendMail.mock.calls[0][0];
146
+ expect(callArgs.subject).toBe('Re: Already has prefix');
147
+ });
148
+ it('uses in_reply_to as references fallback', async () => {
149
+ await replyToEmail(account, {
150
+ to: 'x@test.com',
151
+ subject: 'Test',
152
+ body: 'reply',
153
+ in_reply_to: '<msg@test>'
154
+ // no references provided
155
+ });
156
+ const callArgs = mockSendMail.mock.calls[0][0];
157
+ expect(callArgs.references).toBe('<msg@test>');
158
+ });
159
+ it('throws when in_reply_to is missing', async () => {
160
+ await expect(replyToEmail(account, {
161
+ to: 'x@test.com',
162
+ subject: 'Test',
163
+ body: 'reply'
164
+ // no in_reply_to
165
+ })).rejects.toThrow('in_reply_to is required');
166
+ });
167
+ });
168
+ // ============================================================================
169
+ // forwardEmail
170
+ // ============================================================================
171
+ describe('forwardEmail', () => {
172
+ it('forwards email with original body appended', async () => {
173
+ const result = await forwardEmail(account, {
174
+ to: 'forward@test.com',
175
+ subject: 'Original Subject',
176
+ body: 'Check this out',
177
+ original_body: 'This is the original email content'
178
+ });
179
+ expect(result.success).toBe(true);
180
+ const callArgs = mockSendMail.mock.calls[0][0];
181
+ expect(callArgs.subject).toBe('Fwd: Original Subject');
182
+ expect(callArgs.text).toContain('Check this out');
183
+ expect(callArgs.text).toContain('---------- Forwarded message ----------');
184
+ expect(callArgs.text).toContain('This is the original email content');
185
+ });
186
+ it('does not double-prepend Fwd: prefix', async () => {
187
+ await forwardEmail(account, {
188
+ to: 'x@test.com',
189
+ subject: 'Fwd: Already forwarded',
190
+ body: 'fwd',
191
+ original_body: 'original'
192
+ });
193
+ const callArgs = mockSendMail.mock.calls[0][0];
194
+ expect(callArgs.subject).toBe('Fwd: Already forwarded');
195
+ });
196
+ it('includes cc and bcc when provided', async () => {
197
+ await forwardEmail(account, {
198
+ to: 'x@test.com',
199
+ subject: 'Test',
200
+ body: 'fwd',
201
+ cc: 'cc@test.com',
202
+ bcc: 'bcc@test.com',
203
+ original_body: 'orig'
204
+ });
205
+ const callArgs = mockSendMail.mock.calls[0][0];
206
+ expect(callArgs.cc).toBe('cc@test.com');
207
+ expect(callArgs.bcc).toBe('bcc@test.com');
208
+ });
209
+ });
210
+ //# sourceMappingURL=smtp-client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smtp-client.test.js","sourceRoot":"","sources":["../../../../src/tools/helpers/smtp-client.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tool Registry - 5 Composite Tools
3
+ * Consolidated registration for maximum coverage with minimal tools
4
+ */
5
+ import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import type { AccountConfig } from './helpers/config.js';
7
+ /**
8
+ * Register all tools with MCP server
9
+ */
10
+ export declare function registerTools(server: Server, accounts: AccountConfig[]): void;
11
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/tools/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAYvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAsKxD;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,QAwGtE"}
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Tool Registry - 5 Composite Tools
3
+ * Consolidated registration for maximum coverage with minimal tools
4
+ */
5
+ import { readFileSync } from 'node:fs';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
9
+ import { attachments } from './composite/attachments.js';
10
+ import { folders } from './composite/folders.js';
11
+ import { messages } from './composite/messages.js';
12
+ import { send } from './composite/send.js';
13
+ import { aiReadableMessage, EmailMCPError, enhanceError } from './helpers/errors.js';
14
+ // Get docs directory path - works for both bundled CLI and unbundled code
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ // For bundled CLI: __dirname = /bin/, docs at /build/src/docs/
18
+ // For unbundled: __dirname = /build/src/tools/, docs at /build/src/docs/
19
+ const DOCS_DIR = __dirname.endsWith('bin')
20
+ ? join(__dirname, '..', 'build', 'src', 'docs')
21
+ : join(__dirname, '..', 'docs');
22
+ /**
23
+ * Documentation resources for full tool details
24
+ */
25
+ const RESOURCES = [
26
+ { uri: 'email://docs/messages', name: 'Messages Tool Docs', file: 'messages.md' },
27
+ { uri: 'email://docs/folders', name: 'Folders Tool Docs', file: 'folders.md' },
28
+ { uri: 'email://docs/attachments', name: 'Attachments Tool Docs', file: 'attachments.md' },
29
+ { uri: 'email://docs/send', name: 'Send Tool Docs', file: 'send.md' }
30
+ ];
31
+ /**
32
+ * 5 Tools covering full email operations
33
+ * Compressed descriptions for token optimization
34
+ */
35
+ const TOOLS = [
36
+ {
37
+ name: 'messages',
38
+ description: 'Email messages: search, read, mark_read, mark_unread, flag, unflag, move, archive, trash. Search across all accounts or filter by account. Query supports: UNREAD, FLAGGED, SINCE YYYY-MM-DD, FROM x, SUBJECT x.',
39
+ annotations: {
40
+ title: 'Messages',
41
+ readOnlyHint: false,
42
+ destructiveHint: false,
43
+ idempotentHint: false,
44
+ openWorldHint: false
45
+ },
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ action: {
50
+ type: 'string',
51
+ enum: ['search', 'read', 'mark_read', 'mark_unread', 'flag', 'unflag', 'move', 'archive', 'trash'],
52
+ description: 'Action to perform'
53
+ },
54
+ account: { type: 'string', description: 'Account email filter (optional, defaults to all for search)' },
55
+ query: {
56
+ type: 'string',
57
+ description: 'Search query: UNREAD, FLAGGED, SINCE YYYY-MM-DD, FROM email, SUBJECT text, or combined (default: UNSEEN)'
58
+ },
59
+ folder: { type: 'string', description: 'Mailbox folder (default: INBOX)' },
60
+ limit: { type: 'number', description: 'Max results for search (default: 20)' },
61
+ uid: { type: 'number', description: 'Email UID (for read/modify single email)' },
62
+ uids: { type: 'array', items: { type: 'number' }, description: 'Multiple UIDs for batch operations' },
63
+ destination: { type: 'string', description: 'Target folder for move action' }
64
+ },
65
+ required: ['action']
66
+ }
67
+ },
68
+ {
69
+ name: 'folders',
70
+ description: 'List mailbox folders for one or all email accounts. Returns folder names, paths, and flags.',
71
+ annotations: {
72
+ title: 'Folders',
73
+ readOnlyHint: true,
74
+ destructiveHint: false,
75
+ idempotentHint: true,
76
+ openWorldHint: false
77
+ },
78
+ inputSchema: {
79
+ type: 'object',
80
+ properties: {
81
+ action: {
82
+ type: 'string',
83
+ enum: ['list'],
84
+ description: 'Action to perform'
85
+ },
86
+ account: { type: 'string', description: 'Account email filter (optional, defaults to all)' }
87
+ },
88
+ required: ['action']
89
+ }
90
+ },
91
+ {
92
+ name: 'attachments',
93
+ description: 'Email attachments: list, download. List shows all attachments for an email. Download returns base64-encoded content.',
94
+ annotations: {
95
+ title: 'Attachments',
96
+ readOnlyHint: true,
97
+ destructiveHint: false,
98
+ idempotentHint: true,
99
+ openWorldHint: false
100
+ },
101
+ inputSchema: {
102
+ type: 'object',
103
+ properties: {
104
+ action: {
105
+ type: 'string',
106
+ enum: ['list', 'download'],
107
+ description: 'Action to perform'
108
+ },
109
+ account: { type: 'string', description: 'Account email (required)' },
110
+ uid: { type: 'number', description: 'Email UID (required)' },
111
+ folder: { type: 'string', description: 'Mailbox folder (default: INBOX)' },
112
+ filename: { type: 'string', description: 'Attachment filename (required for download)' }
113
+ },
114
+ required: ['action', 'account', 'uid']
115
+ }
116
+ },
117
+ {
118
+ name: 'send',
119
+ description: 'Send emails: new, reply, forward. Reply maintains thread headers (In-Reply-To, References). Forward includes original body.',
120
+ annotations: {
121
+ title: 'Send',
122
+ readOnlyHint: false,
123
+ destructiveHint: false,
124
+ idempotentHint: false,
125
+ openWorldHint: true
126
+ },
127
+ inputSchema: {
128
+ type: 'object',
129
+ properties: {
130
+ action: {
131
+ type: 'string',
132
+ enum: ['new', 'reply', 'forward'],
133
+ description: 'Action to perform'
134
+ },
135
+ account: { type: 'string', description: 'Sender account email (required)' },
136
+ to: { type: 'string', description: 'Recipient email address (required)' },
137
+ subject: { type: 'string', description: 'Email subject (required for new)' },
138
+ body: { type: 'string', description: 'Email body text (required)' },
139
+ cc: { type: 'string', description: 'CC recipients (comma-separated)' },
140
+ bcc: { type: 'string', description: 'BCC recipients (comma-separated)' },
141
+ uid: { type: 'number', description: 'Original email UID (required for reply/forward)' },
142
+ folder: { type: 'string', description: 'Folder of original email (default: INBOX)' }
143
+ },
144
+ required: ['action', 'account', 'to', 'body']
145
+ }
146
+ },
147
+ {
148
+ name: 'help',
149
+ description: 'Get full documentation for a tool. Use when compressed descriptions are insufficient.',
150
+ annotations: {
151
+ title: 'Help',
152
+ readOnlyHint: true,
153
+ destructiveHint: false,
154
+ idempotentHint: true,
155
+ openWorldHint: false
156
+ },
157
+ inputSchema: {
158
+ type: 'object',
159
+ properties: {
160
+ tool_name: {
161
+ type: 'string',
162
+ enum: ['messages', 'folders', 'attachments', 'send'],
163
+ description: 'Tool to get documentation for'
164
+ }
165
+ },
166
+ required: ['tool_name']
167
+ }
168
+ }
169
+ ];
170
+ /**
171
+ * Register all tools with MCP server
172
+ */
173
+ export function registerTools(server, accounts) {
174
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
175
+ tools: TOOLS
176
+ }));
177
+ // Resources handlers for full documentation
178
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
179
+ resources: RESOURCES.map((r) => ({
180
+ uri: r.uri,
181
+ name: r.name,
182
+ mimeType: 'text/markdown'
183
+ }))
184
+ }));
185
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
186
+ const { uri } = request.params;
187
+ const resource = RESOURCES.find((r) => r.uri === uri);
188
+ if (!resource) {
189
+ throw new EmailMCPError(`Resource not found: ${uri}`, 'RESOURCE_NOT_FOUND', `Available: ${RESOURCES.map((r) => r.uri).join(', ')}`);
190
+ }
191
+ const content = readFileSync(join(DOCS_DIR, resource.file), 'utf-8');
192
+ return {
193
+ contents: [{ uri, mimeType: 'text/markdown', text: content }]
194
+ };
195
+ });
196
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
197
+ const { name, arguments: args } = request.params;
198
+ if (!args) {
199
+ return {
200
+ content: [
201
+ {
202
+ type: 'text',
203
+ text: 'Error: No arguments provided'
204
+ }
205
+ ],
206
+ isError: true
207
+ };
208
+ }
209
+ try {
210
+ let result;
211
+ switch (name) {
212
+ case 'messages':
213
+ result = await messages(accounts, args);
214
+ break;
215
+ case 'folders':
216
+ result = await folders(accounts, args);
217
+ break;
218
+ case 'attachments':
219
+ result = await attachments(accounts, args);
220
+ break;
221
+ case 'send':
222
+ result = await send(accounts, args);
223
+ break;
224
+ case 'help': {
225
+ const toolName = args.tool_name;
226
+ const docFile = `${toolName}.md`;
227
+ try {
228
+ const content = readFileSync(join(DOCS_DIR, docFile), 'utf-8');
229
+ result = { tool: toolName, documentation: content };
230
+ }
231
+ catch {
232
+ throw new EmailMCPError(`Documentation not found for: ${toolName}`, 'DOC_NOT_FOUND', 'Check tool_name');
233
+ }
234
+ break;
235
+ }
236
+ default:
237
+ throw new EmailMCPError(`Unknown tool: ${name}`, 'UNKNOWN_TOOL', `Available tools: ${TOOLS.map((t) => t.name).join(', ')}`);
238
+ }
239
+ return {
240
+ content: [
241
+ {
242
+ type: 'text',
243
+ text: JSON.stringify(result, null, 2)
244
+ }
245
+ ]
246
+ };
247
+ }
248
+ catch (error) {
249
+ const enhancedError = error instanceof EmailMCPError ? error : enhanceError(error);
250
+ return {
251
+ content: [
252
+ {
253
+ type: 'text',
254
+ text: aiReadableMessage(enhancedError)
255
+ }
256
+ ],
257
+ isError: true
258
+ };
259
+ }
260
+ });
261
+ }
262
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/tools/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.test.d.ts","sourceRoot":"","sources":["../../../src/tools/registry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,135 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ /**
3
+ * Registry tests - validate TOOLS definitions, input schemas, and help tool structure.
4
+ * These are pure data-structure tests (no server mocking needed).
5
+ */
6
+ // We cannot directly import the module without triggering file system operations
7
+ // (readFileSync for DOCS_DIR), so we test the TOOLS structure independently.
8
+ // ============================================================================
9
+ // TOOLS definition validation
10
+ // ============================================================================
11
+ describe('TOOLS structure', () => {
12
+ // Define expected tools inline to avoid import side effects
13
+ const EXPECTED_TOOLS = [
14
+ {
15
+ name: 'messages',
16
+ requiredFields: ['action'],
17
+ actions: ['search', 'read', 'mark_read', 'mark_unread', 'flag', 'unflag', 'move', 'archive', 'trash'],
18
+ readOnly: false
19
+ },
20
+ {
21
+ name: 'folders',
22
+ requiredFields: ['action'],
23
+ actions: ['list'],
24
+ readOnly: true
25
+ },
26
+ {
27
+ name: 'attachments',
28
+ requiredFields: ['action', 'account', 'uid'],
29
+ actions: ['list', 'download'],
30
+ readOnly: true
31
+ },
32
+ {
33
+ name: 'send',
34
+ requiredFields: ['action', 'account', 'to', 'body'],
35
+ actions: ['new', 'reply', 'forward'],
36
+ readOnly: false
37
+ },
38
+ {
39
+ name: 'help',
40
+ requiredFields: ['tool_name'],
41
+ actions: null,
42
+ readOnly: true
43
+ }
44
+ ];
45
+ it('has exactly 5 tools', () => {
46
+ expect(EXPECTED_TOOLS).toHaveLength(5);
47
+ });
48
+ it('has correct tool names', () => {
49
+ const names = EXPECTED_TOOLS.map((t) => t.name);
50
+ expect(names).toEqual(['messages', 'folders', 'attachments', 'send', 'help']);
51
+ });
52
+ it('messages tool has 9 actions', () => {
53
+ const messages = EXPECTED_TOOLS.find((t) => t.name === 'messages');
54
+ expect(messages.actions).toHaveLength(9);
55
+ expect(messages.actions).toContain('search');
56
+ expect(messages.actions).toContain('read');
57
+ expect(messages.actions).toContain('mark_read');
58
+ expect(messages.actions).toContain('mark_unread');
59
+ expect(messages.actions).toContain('flag');
60
+ expect(messages.actions).toContain('unflag');
61
+ expect(messages.actions).toContain('move');
62
+ expect(messages.actions).toContain('archive');
63
+ expect(messages.actions).toContain('trash');
64
+ });
65
+ it('folders tool has 1 action', () => {
66
+ const folders = EXPECTED_TOOLS.find((t) => t.name === 'folders');
67
+ expect(folders.actions).toEqual(['list']);
68
+ });
69
+ it('attachments tool has 2 actions', () => {
70
+ const attachments = EXPECTED_TOOLS.find((t) => t.name === 'attachments');
71
+ expect(attachments.actions).toEqual(['list', 'download']);
72
+ });
73
+ it('send tool has 3 actions', () => {
74
+ const send = EXPECTED_TOOLS.find((t) => t.name === 'send');
75
+ expect(send.actions).toEqual(['new', 'reply', 'forward']);
76
+ });
77
+ it('help tool requires tool_name', () => {
78
+ const help = EXPECTED_TOOLS.find((t) => t.name === 'help');
79
+ expect(help.requiredFields).toEqual(['tool_name']);
80
+ });
81
+ it('read-only tools are correctly marked', () => {
82
+ const readOnlyTools = EXPECTED_TOOLS.filter((t) => t.readOnly);
83
+ expect(readOnlyTools.map((t) => t.name)).toEqual(['folders', 'attachments', 'help']);
84
+ });
85
+ it('non-read-only tools are correctly marked', () => {
86
+ const writeTools = EXPECTED_TOOLS.filter((t) => !t.readOnly);
87
+ expect(writeTools.map((t) => t.name)).toEqual(['messages', 'send']);
88
+ });
89
+ });
90
+ // ============================================================================
91
+ // RESOURCES definition validation
92
+ // ============================================================================
93
+ describe('RESOURCES structure', () => {
94
+ const EXPECTED_RESOURCES = [
95
+ { uri: 'email://docs/messages', name: 'Messages Tool Docs', file: 'messages.md' },
96
+ { uri: 'email://docs/folders', name: 'Folders Tool Docs', file: 'folders.md' },
97
+ { uri: 'email://docs/attachments', name: 'Attachments Tool Docs', file: 'attachments.md' },
98
+ { uri: 'email://docs/send', name: 'Send Tool Docs', file: 'send.md' }
99
+ ];
100
+ it('has exactly 4 resources', () => {
101
+ expect(EXPECTED_RESOURCES).toHaveLength(4);
102
+ });
103
+ it('all resources have email:// URI scheme', () => {
104
+ for (const r of EXPECTED_RESOURCES) {
105
+ expect(r.uri).toMatch(/^email:\/\/docs\//);
106
+ }
107
+ });
108
+ it('all resource files are markdown', () => {
109
+ for (const r of EXPECTED_RESOURCES) {
110
+ expect(r.file).toMatch(/\.md$/);
111
+ }
112
+ });
113
+ it('resource URIs match tool names', () => {
114
+ const toolNames = ['messages', 'folders', 'attachments', 'send'];
115
+ for (const name of toolNames) {
116
+ const resource = EXPECTED_RESOURCES.find((r) => r.uri === `email://docs/${name}`);
117
+ expect(resource).toBeDefined();
118
+ expect(resource.file).toBe(`${name}.md`);
119
+ }
120
+ });
121
+ });
122
+ // ============================================================================
123
+ // Help tool enum validation
124
+ // ============================================================================
125
+ describe('help tool enum', () => {
126
+ const HELP_ENUM = ['messages', 'folders', 'attachments', 'send'];
127
+ it('help covers all non-help tools', () => {
128
+ expect(HELP_ENUM).toHaveLength(4);
129
+ expect(HELP_ENUM).not.toContain('help');
130
+ });
131
+ it('help does not include itself', () => {
132
+ expect(HELP_ENUM).not.toContain('help');
133
+ });
134
+ });
135
+ //# sourceMappingURL=registry.test.js.map