ofw-mcp 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,53 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { ListToolsRequestSchema, CallToolRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
4
  import { client } from './client.js';
6
- import { toolDefinitions as userTools, handleTool as handleUser } from './tools/user.js';
7
- import { toolDefinitions as messageTools, handleTool as handleMessages } from './tools/messages.js';
8
- import { toolDefinitions as calendarTools, handleTool as handleCalendar } from './tools/calendar.js';
9
- import { toolDefinitions as expenseTools, handleTool as handleExpenses } from './tools/expenses.js';
10
- import { toolDefinitions as journalTools, handleTool as handleJournal } from './tools/journal.js';
11
- const allTools = [
12
- ...userTools,
13
- ...messageTools,
14
- ...calendarTools,
15
- ...expenseTools,
16
- ...journalTools,
17
- ];
18
- const handlers = {};
19
- for (const tool of userTools)
20
- handlers[tool.name] = (n, a) => handleUser(n, a, client);
21
- for (const tool of messageTools)
22
- handlers[tool.name] = (n, a) => handleMessages(n, a, client);
23
- for (const tool of calendarTools)
24
- handlers[tool.name] = (n, a) => handleCalendar(n, a, client);
25
- for (const tool of expenseTools)
26
- handlers[tool.name] = (n, a) => handleExpenses(n, a, client);
27
- for (const tool of journalTools)
28
- handlers[tool.name] = (n, a) => handleJournal(n, a, client);
29
- const server = new Server({ name: 'ofw', version: '2.0.1' }, { capabilities: { tools: {} } });
30
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: allTools }));
31
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
32
- const { name, arguments: args = {} } = request.params;
33
- const handler = handlers[name];
34
- if (!handler) {
35
- return {
36
- content: [{ type: 'text', text: `Unknown tool: ${name}` }],
37
- isError: true,
38
- };
39
- }
40
- try {
41
- return await handler(name, args);
42
- }
43
- catch (err) {
44
- const message = err instanceof Error ? err.message : String(err);
45
- return {
46
- content: [{ type: 'text', text: `Error: ${message}` }],
47
- isError: true,
48
- };
49
- }
50
- });
5
+ import { registerUserTools } from './tools/user.js';
6
+ import { registerMessageTools } from './tools/messages.js';
7
+ import { registerCalendarTools } from './tools/calendar.js';
8
+ import { registerExpenseTools } from './tools/expenses.js';
9
+ import { registerJournalTools } from './tools/journal.js';
10
+ const server = new McpServer({ name: 'ofw', version: '2.0.3' });
11
+ registerUserTools(server, client);
12
+ registerMessageTools(server, client);
13
+ registerCalendarTools(server, client);
14
+ registerExpenseTools(server, client);
15
+ registerJournalTools(server, client);
51
16
  console.error('[ofw-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.');
52
17
  const transport = new StdioServerTransport();
53
18
  await server.connect(transport);
@@ -1,94 +1,64 @@
1
- export const toolDefinitions = [
2
- {
3
- name: 'ofw_list_events',
1
+ import { z } from 'zod';
2
+ export function registerCalendarTools(server, client) {
3
+ server.registerTool('ofw_list_events', {
4
4
  description: 'List OurFamilyWizard calendar events in a date range',
5
5
  annotations: { readOnlyHint: true },
6
6
  inputSchema: {
7
- type: 'object',
8
- properties: {
9
- startDate: { type: 'string', description: 'Start date YYYY-MM-DD' },
10
- endDate: { type: 'string', description: 'End date YYYY-MM-DD' },
11
- detailed: { type: 'boolean', description: 'Return full event details (default false)' },
12
- },
13
- required: ['startDate', 'endDate'],
7
+ startDate: z.string().describe('Start date YYYY-MM-DD'),
8
+ endDate: z.string().describe('End date YYYY-MM-DD'),
9
+ detailed: z.boolean().describe('Return full event details (default false)').optional(),
14
10
  },
15
- },
16
- {
17
- name: 'ofw_create_event',
11
+ }, async (args) => {
12
+ const variant = args.detailed ? 'detailed' : 'basic';
13
+ const data = await client.request('GET', `/pub/v1/calendar/${variant}?startDate=${encodeURIComponent(args.startDate)}&endDate=${encodeURIComponent(args.endDate)}`);
14
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
15
+ });
16
+ server.registerTool('ofw_create_event', {
18
17
  description: 'Create a calendar event in OurFamilyWizard',
19
18
  annotations: { destructiveHint: false },
20
19
  inputSchema: {
21
- type: 'object',
22
- properties: {
23
- title: { type: 'string' },
24
- startDate: { type: 'string', description: 'ISO datetime string' },
25
- endDate: { type: 'string', description: 'ISO datetime string' },
26
- allDay: { type: 'boolean' },
27
- location: { type: 'string' },
28
- reminder: { type: 'string', description: 'Reminder setting (e.g. "1 hour before")' },
29
- privateEvent: { type: 'boolean' },
30
- eventFor: { type: 'string', description: 'neither | parent1 | parent2' },
31
- dropOffParent: { type: 'string' },
32
- pickUpParent: { type: 'string' },
33
- children: { type: 'array', items: { type: 'number' }, description: 'Array of child IDs' },
34
- },
35
- required: ['title', 'startDate', 'endDate'],
20
+ title: z.string(),
21
+ startDate: z.string().describe('ISO datetime string'),
22
+ endDate: z.string().describe('ISO datetime string'),
23
+ allDay: z.boolean().optional(),
24
+ location: z.string().optional(),
25
+ reminder: z.string().describe('Reminder setting (e.g. "1 hour before")').optional(),
26
+ privateEvent: z.boolean().optional(),
27
+ eventFor: z.string().describe('neither | parent1 | parent2').optional(),
28
+ dropOffParent: z.string().optional(),
29
+ pickUpParent: z.string().optional(),
30
+ children: z.array(z.number()).describe('Array of child IDs').optional(),
36
31
  },
37
- },
38
- {
39
- name: 'ofw_update_event',
32
+ }, async (args) => {
33
+ const data = await client.request('POST', '/pub/v1/calendar/events', args);
34
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
35
+ });
36
+ server.registerTool('ofw_update_event', {
40
37
  description: 'Update an existing OurFamilyWizard calendar event',
41
38
  annotations: { destructiveHint: false },
42
39
  inputSchema: {
43
- type: 'object',
44
- properties: {
45
- eventId: { type: 'string' },
46
- title: { type: 'string' },
47
- startDate: { type: 'string' },
48
- endDate: { type: 'string' },
49
- allDay: { type: 'boolean' },
50
- location: { type: 'string' },
51
- reminder: { type: 'string' },
52
- privateEvent: { type: 'boolean' },
53
- },
54
- required: ['eventId'],
40
+ eventId: z.string(),
41
+ title: z.string().optional(),
42
+ startDate: z.string().optional(),
43
+ endDate: z.string().optional(),
44
+ allDay: z.boolean().optional(),
45
+ location: z.string().optional(),
46
+ reminder: z.string().optional(),
47
+ privateEvent: z.boolean().optional(),
55
48
  },
56
- },
57
- {
58
- name: 'ofw_delete_event',
49
+ }, async (args) => {
50
+ const { eventId, ...updateData } = args;
51
+ const data = await client.request('PUT', `/pub/v1/calendar/events/${encodeURIComponent(eventId)}`, updateData);
52
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
53
+ });
54
+ server.registerTool('ofw_delete_event', {
59
55
  description: 'Delete an OurFamilyWizard calendar event',
60
56
  annotations: { destructiveHint: true },
61
57
  inputSchema: {
62
- type: 'object',
63
- properties: { eventId: { type: 'string', description: 'Event ID to delete' } },
64
- required: ['eventId'],
58
+ eventId: z.string().describe('Event ID to delete'),
65
59
  },
66
- },
67
- ];
68
- export async function handleTool(name, args, client) {
69
- switch (name) {
70
- case 'ofw_list_events': {
71
- const { startDate, endDate, detailed = false } = args;
72
- const variant = detailed ? 'detailed' : 'basic';
73
- const data = await client.request('GET', `/pub/v1/calendar/${variant}?startDate=${encodeURIComponent(startDate)}&endDate=${encodeURIComponent(endDate)}`);
74
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
75
- }
76
- case 'ofw_create_event': {
77
- // Field names are best-guess; confirm via DevTools capture and update if needed (see pre-task note)
78
- const data = await client.request('POST', '/pub/v1/calendar/events', args);
79
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
80
- }
81
- case 'ofw_update_event': {
82
- const { eventId, ...updateData } = args;
83
- const data = await client.request('PUT', `/pub/v1/calendar/events/${encodeURIComponent(eventId)}`, updateData);
84
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
85
- }
86
- case 'ofw_delete_event': {
87
- const { eventId } = args;
88
- await client.request('DELETE', `/pub/v1/calendar/events/${encodeURIComponent(eventId)}`);
89
- return { content: [{ type: 'text', text: `Event ${eventId} deleted` }] };
90
- }
91
- default:
92
- throw new Error(`Unknown tool: ${name}`);
93
- }
60
+ }, async (args) => {
61
+ await client.request('DELETE', `/pub/v1/calendar/events/${encodeURIComponent(args.eventId)}`);
62
+ return { content: [{ type: 'text', text: `Event ${args.eventId} deleted` }] };
63
+ });
94
64
  }
@@ -1,55 +1,34 @@
1
- export const toolDefinitions = [
2
- {
3
- name: 'ofw_get_expense_totals',
1
+ import { z } from 'zod';
2
+ export function registerExpenseTools(server, client) {
3
+ server.registerTool('ofw_get_expense_totals', {
4
4
  description: 'Get OurFamilyWizard expense summary totals (owed/paid)',
5
5
  annotations: { readOnlyHint: true },
6
- inputSchema: { type: 'object', properties: {}, required: [] },
7
- },
8
- {
9
- name: 'ofw_list_expenses',
6
+ }, async () => {
7
+ const data = await client.request('GET', '/pub/v2/expense/expenses/totals');
8
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
9
+ });
10
+ server.registerTool('ofw_list_expenses', {
10
11
  description: 'List OurFamilyWizard expenses with pagination',
11
12
  annotations: { readOnlyHint: true },
12
13
  inputSchema: {
13
- type: 'object',
14
- properties: {
15
- start: { type: 'number', description: 'Start offset (default 0)' },
16
- max: { type: 'number', description: 'Max results (default 20)' },
17
- },
18
- required: [],
14
+ start: z.number().describe('Start offset (default 0)').optional(),
15
+ max: z.number().describe('Max results (default 20)').optional(),
19
16
  },
20
- },
21
- {
22
- name: 'ofw_create_expense',
17
+ }, async (args) => {
18
+ const start = args.start ?? 0;
19
+ const max = args.max ?? 20;
20
+ const data = await client.request('GET', `/pub/v2/expense/expenses?start=${start}&max=${max}`);
21
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
22
+ });
23
+ server.registerTool('ofw_create_expense', {
23
24
  description: 'Log a new expense in OurFamilyWizard',
24
25
  annotations: { destructiveHint: false },
25
26
  inputSchema: {
26
- type: 'object',
27
- properties: {
28
- amount: { type: 'number', description: 'Expense amount' },
29
- description: { type: 'string', description: 'Expense description' },
30
- // Additional fields TBD — add after DevTools capture (see pre-task note)
31
- },
32
- required: ['amount', 'description'],
27
+ amount: z.number().describe('Expense amount'),
28
+ description: z.string().describe('Expense description'),
33
29
  },
34
- },
35
- ];
36
- export async function handleTool(name, args, client) {
37
- switch (name) {
38
- case 'ofw_get_expense_totals': {
39
- const data = await client.request('GET', '/pub/v2/expense/expenses/totals');
40
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
41
- }
42
- case 'ofw_list_expenses': {
43
- const { start = 0, max = 20 } = args;
44
- const data = await client.request('GET', `/pub/v2/expense/expenses?start=${start}&max=${max}`);
45
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
46
- }
47
- case 'ofw_create_expense': {
48
- // Field names are best-guess; confirm via DevTools capture and update if needed (see pre-task note)
49
- const data = await client.request('POST', '/pub/v2/expense/expenses', args);
50
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
51
- }
52
- default:
53
- throw new Error(`Unknown tool: ${name}`);
54
- }
30
+ }, async (args) => {
31
+ const data = await client.request('POST', '/pub/v2/expense/expenses', args);
32
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
33
+ });
55
34
  }
@@ -1,46 +1,28 @@
1
- export const toolDefinitions = [
2
- {
3
- name: 'ofw_list_journal_entries',
1
+ import { z } from 'zod';
2
+ export function registerJournalTools(server, client) {
3
+ server.registerTool('ofw_list_journal_entries', {
4
4
  description: 'List OurFamilyWizard journal entries',
5
5
  annotations: { readOnlyHint: true },
6
6
  inputSchema: {
7
- type: 'object',
8
- properties: {
9
- start: { type: 'number', description: 'Start offset (default 1)' },
10
- max: { type: 'number', description: 'Max results (default 10)' },
11
- },
12
- required: [],
7
+ start: z.number().describe('Start offset (default 1)').optional(),
8
+ max: z.number().describe('Max results (default 10)').optional(),
13
9
  },
14
- },
15
- {
16
- name: 'ofw_create_journal_entry',
10
+ }, async (args) => {
11
+ // Journal API uses 1-based offset (unlike expenses which start at 0)
12
+ const start = args.start ?? 1;
13
+ const max = args.max ?? 10;
14
+ const data = await client.request('GET', `/pub/v1/journals?start=${start}&max=${max}`);
15
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
16
+ });
17
+ server.registerTool('ofw_create_journal_entry', {
17
18
  description: 'Create a new journal entry in OurFamilyWizard',
18
19
  annotations: { destructiveHint: false },
19
20
  inputSchema: {
20
- type: 'object',
21
- properties: {
22
- title: { type: 'string', description: 'Entry title' },
23
- body: { type: 'string', description: 'Entry text content' },
24
- // Additional fields TBD — add after DevTools capture (see pre-task note)
25
- },
26
- required: ['title', 'body'],
21
+ title: z.string().describe('Entry title'),
22
+ body: z.string().describe('Entry text content'),
27
23
  },
28
- },
29
- ];
30
- export async function handleTool(name, args, client) {
31
- switch (name) {
32
- case 'ofw_list_journal_entries': {
33
- // Journal API uses 1-based offset (unlike expenses which start at 0)
34
- const { start = 1, max = 10 } = args;
35
- const data = await client.request('GET', `/pub/v1/journals?start=${start}&max=${max}`);
36
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
37
- }
38
- case 'ofw_create_journal_entry': {
39
- // Field names are best-guess; confirm via DevTools capture and update if needed (see pre-task note)
40
- const data = await client.request('POST', '/pub/v1/journals', args);
41
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
42
- }
43
- default:
44
- throw new Error(`Unknown tool: ${name}`);
45
- }
24
+ }, async (args) => {
25
+ const data = await client.request('POST', '/pub/v1/journals', args);
26
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
27
+ });
46
28
  }