@imboard.ai/mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,92 @@
1
+ import { z } from 'zod';
2
+ import { boardIdParam, resourceIdParam, formatResult, handleToolError, RegisterToolsFn } from './shared.js';
3
+
4
+ export const registerSupportingTools: RegisterToolsFn = (server, client) => {
5
+ server.tool(
6
+ 'get_board_audit_log',
7
+ 'Gets the audit trail for a board. Returns a chronological list of actions taken on the board.',
8
+ {
9
+ boardId: boardIdParam,
10
+ },
11
+ async ({ boardId }) => {
12
+ try {
13
+ const response = await client.get(`/api/auditlog/${boardId}`);
14
+ return formatResult({ data: response.data });
15
+ } catch (error) {
16
+ return handleToolError(error);
17
+ }
18
+ },
19
+ );
20
+
21
+ server.tool(
22
+ 'get_portfolio_data',
23
+ 'Gets portfolio data aggregated across all boards. Useful for cross-board analytics.',
24
+ {
25
+ getOnlyMyPortfolio: z.enum(['true', 'false']).optional().describe('Only include boards where user is a member (default: false)'),
26
+ includeArchived: z.enum(['true', 'false']).optional().describe('Include archived boards (default: false)'),
27
+ },
28
+ async ({ getOnlyMyPortfolio, includeArchived }) => {
29
+ try {
30
+ const params: Record<string, string> = {};
31
+ if (getOnlyMyPortfolio !== undefined) params.getOnlyMyPortfolio = getOnlyMyPortfolio;
32
+ if (includeArchived !== undefined) params.includeArchived = includeArchived;
33
+ const response = await client.get('/api/portfolio/data', params);
34
+ return formatResult({ data: response.data });
35
+ } catch (error) {
36
+ return handleToolError(error);
37
+ }
38
+ },
39
+ );
40
+
41
+ server.tool(
42
+ 'get_fx_rates',
43
+ 'Gets current foreign exchange rates. Optionally specify base currency, targets, and date.',
44
+ {
45
+ base: z.string().length(3).optional().describe('Base currency (3-letter ISO 4217 code, e.g. USD)'),
46
+ targets: z.array(z.string().length(3)).optional().describe('Target currencies to convert to'),
47
+ date: z.string().optional().describe('Historical date in YYYY-MM-DD format'),
48
+ },
49
+ async ({ base, targets, date }) => {
50
+ try {
51
+ const params: Record<string, string> = {};
52
+ if (base !== undefined) params.base = base;
53
+ if (targets !== undefined) params['targets[]'] = targets.join(',');
54
+ if (date !== undefined) params.date = date;
55
+ const response = await client.get('/api/fx/rates', params);
56
+ return formatResult({ data: response.data });
57
+ } catch (error) {
58
+ return handleToolError(error);
59
+ }
60
+ },
61
+ );
62
+
63
+ server.tool(
64
+ 'list_unassigned_attachments',
65
+ 'Lists email attachments awaiting assignment to documents.',
66
+ {},
67
+ async () => {
68
+ try {
69
+ const response = await client.get('/api/unassigned-emails/');
70
+ return formatResult({ data: response.data });
71
+ } catch (error) {
72
+ return handleToolError(error);
73
+ }
74
+ },
75
+ );
76
+
77
+ server.tool(
78
+ 'reject_attachment',
79
+ 'Rejects an unassigned email attachment.',
80
+ {
81
+ attachmentId: resourceIdParam.describe('The attachment ID'),
82
+ },
83
+ async ({ attachmentId }) => {
84
+ try {
85
+ const response = await client.put(`/api/unassigned-emails/attachment/${attachmentId}`);
86
+ return formatResult({ data: response.data });
87
+ } catch (error) {
88
+ return handleToolError(error);
89
+ }
90
+ },
91
+ );
92
+ };
@@ -0,0 +1,31 @@
1
+ import { formatResult, handleToolError, RegisterToolsFn } from './shared.js';
2
+
3
+ export const registerUserTools: RegisterToolsFn = (server, client) => {
4
+ server.tool(
5
+ 'list_my_boards',
6
+ 'Lists all board memberships and pending invites for the authenticated user.',
7
+ {},
8
+ async () => {
9
+ try {
10
+ const response = await client.get('/api/user/allboards');
11
+ return formatResult({ data: response.data });
12
+ } catch (error) {
13
+ return handleToolError(error);
14
+ }
15
+ },
16
+ );
17
+
18
+ server.tool(
19
+ 'get_storage_usage',
20
+ 'Returns storage usage for unassigned email attachments.',
21
+ {},
22
+ async () => {
23
+ try {
24
+ const response = await client.get('/api/user/storage');
25
+ return formatResult({ data: response.data });
26
+ } catch (error) {
27
+ return handleToolError(error);
28
+ }
29
+ },
30
+ );
31
+ };
@@ -0,0 +1,59 @@
1
+ import { redact } from './redact.js';
2
+
3
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
4
+
5
+ const LOG_LEVELS: Record<LogLevel, number> = {
6
+ debug: 0,
7
+ info: 1,
8
+ warn: 2,
9
+ error: 3,
10
+ };
11
+
12
+ let minLevel: LogLevel = 'info';
13
+
14
+ export function setLogLevel(level: LogLevel): void {
15
+ minLevel = level;
16
+ }
17
+
18
+ function shouldLog(level: LogLevel): boolean {
19
+ return LOG_LEVELS[level] >= LOG_LEVELS[minLevel];
20
+ }
21
+
22
+ function formatMessage(level: LogLevel, message: string, context?: Record<string, unknown>): string {
23
+ const timestamp = new Date().toISOString();
24
+ const redacted = redact(message);
25
+ const parts = [`[${timestamp}] [${level.toUpperCase()}] ${redacted}`];
26
+
27
+ if (context && Object.keys(context).length > 0) {
28
+ const safeContext = redact(JSON.stringify(context));
29
+ parts.push(safeContext);
30
+ }
31
+
32
+ return parts.join(' ');
33
+ }
34
+
35
+ export const logger = {
36
+ debug(message: string, context?: Record<string, unknown>): void {
37
+ if (shouldLog('debug')) {
38
+ process.stderr.write(formatMessage('debug', message, context) + '\n');
39
+ }
40
+ },
41
+
42
+ info(message: string, context?: Record<string, unknown>): void {
43
+ if (shouldLog('info')) {
44
+ process.stderr.write(formatMessage('info', message, context) + '\n');
45
+ }
46
+ },
47
+
48
+ warn(message: string, context?: Record<string, unknown>): void {
49
+ if (shouldLog('warn')) {
50
+ process.stderr.write(formatMessage('warn', message, context) + '\n');
51
+ }
52
+ },
53
+
54
+ error(message: string, context?: Record<string, unknown>): void {
55
+ if (shouldLog('error')) {
56
+ process.stderr.write(formatMessage('error', message, context) + '\n');
57
+ }
58
+ },
59
+ };
@@ -0,0 +1,8 @@
1
+ const TOKEN_PATTERN = /\b([a-zA-Z0-9_-]{8})[a-zA-Z0-9_-]{24,}\b/g;
2
+ const BEARER_PATTERN = /(Bearer\s+)[a-zA-Z0-9_.\-/+=]{8,}/gi;
3
+
4
+ export function redact(value: string): string {
5
+ return value
6
+ .replace(BEARER_PATTERN, '$1[REDACTED]')
7
+ .replace(TOKEN_PATTERN, '$1***');
8
+ }