ofw-mcp 1.0.0 → 1.0.2
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/README.md +6 -0
- package/dist/client.js +12 -6
- package/dist/index.js +2 -1
- package/dist/tools/messages.js +89 -6
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A [Model Context Protocol](https://modelcontextprotocol.io) server that connects Claude to [OurFamilyWizard](https://www.ourfamilywizard.com), giving you natural-language access to your co-parenting messages, calendar, expenses, and journal.
|
|
4
4
|
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> **AI-developed project.** This codebase was entirely built and is actively maintained by [Claude Sonnet 4.6](https://www.anthropic.com/claude). No human has audited the implementation. Review all code and tool permissions before use.
|
|
7
|
+
|
|
5
8
|
## What you can do
|
|
6
9
|
|
|
7
10
|
Ask Claude things like:
|
|
@@ -101,6 +104,9 @@ Read-only tools run automatically. Write tools ask for your confirmation first.
|
|
|
101
104
|
| `ofw_list_messages` | Messages in a folder | Auto |
|
|
102
105
|
| `ofw_get_message` | Full content of a single message | Auto |
|
|
103
106
|
| `ofw_send_message` | Send a message | Confirm |
|
|
107
|
+
| `ofw_list_drafts` | Draft messages | Auto |
|
|
108
|
+
| `ofw_save_draft` | Create or update a draft | Confirm |
|
|
109
|
+
| `ofw_delete_draft` | Delete a draft | Confirm |
|
|
104
110
|
| `ofw_list_events` | Calendar events in a date range | Auto |
|
|
105
111
|
| `ofw_create_event` | Create a calendar event | Confirm |
|
|
106
112
|
| `ofw_update_event` | Update a calendar event | Confirm |
|
package/dist/client.js
CHANGED
|
@@ -14,13 +14,19 @@ export class OFWClient {
|
|
|
14
14
|
return this.doRequest(method, path, body, false);
|
|
15
15
|
}
|
|
16
16
|
async doRequest(method, path, body, isRetry) {
|
|
17
|
+
const isFormData = body instanceof FormData;
|
|
18
|
+
const headers = {
|
|
19
|
+
'ofw-client': 'WebApplication',
|
|
20
|
+
'ofw-version': '1.0.0',
|
|
21
|
+
Accept: 'application/json',
|
|
22
|
+
Authorization: `Bearer ${this.token}`,
|
|
23
|
+
};
|
|
24
|
+
if (!isFormData)
|
|
25
|
+
headers['Content-Type'] = 'application/json';
|
|
17
26
|
const response = await fetch(`${BASE_URL}${path}`, {
|
|
18
27
|
method,
|
|
19
|
-
headers
|
|
20
|
-
|
|
21
|
-
...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
|
|
22
|
-
},
|
|
23
|
-
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
28
|
+
headers,
|
|
29
|
+
...(body !== undefined ? { body: isFormData ? body : JSON.stringify(body) } : {}),
|
|
24
30
|
});
|
|
25
31
|
if (response.status === 401 && !isRetry) {
|
|
26
32
|
this.token = null;
|
|
@@ -59,7 +65,7 @@ export class OFWClient {
|
|
|
59
65
|
});
|
|
60
66
|
// Extract just the SESSION=value part (strip attributes like Path, Secure, etc.)
|
|
61
67
|
const setCookie = initResponse.headers.get('set-cookie') ?? '';
|
|
62
|
-
const sessionCookie = setCookie.split(';')[0]
|
|
68
|
+
const sessionCookie = setCookie.split(';')[0]; // split always returns a string; empty string is falsy
|
|
63
69
|
const response = await fetch(`${BASE_URL}/ofw/login`, {
|
|
64
70
|
method: 'POST',
|
|
65
71
|
headers: {
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ for (const tool of expenseTools)
|
|
|
26
26
|
handlers[tool.name] = (n, a) => handleExpenses(n, a, client);
|
|
27
27
|
for (const tool of journalTools)
|
|
28
28
|
handlers[tool.name] = (n, a) => handleJournal(n, a, client);
|
|
29
|
-
const server = new Server({ name: 'ofw', version: '1.0.
|
|
29
|
+
const server = new Server({ name: 'ofw', version: '1.0.1' }, { capabilities: { tools: {} } });
|
|
30
30
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: allTools }));
|
|
31
31
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
32
32
|
const { name, arguments: args = {} } = request.params;
|
|
@@ -48,5 +48,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
|
+
console.error('[ofw-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.');
|
|
51
52
|
const transport = new StdioServerTransport();
|
|
52
53
|
await server.connect(transport);
|
package/dist/tools/messages.js
CHANGED
|
@@ -40,13 +40,64 @@ export const toolDefinitions = [
|
|
|
40
40
|
properties: {
|
|
41
41
|
subject: { type: 'string', description: 'Message subject' },
|
|
42
42
|
body: { type: 'string', description: 'Message body text' },
|
|
43
|
-
|
|
43
|
+
recipientIds: {
|
|
44
44
|
type: 'array',
|
|
45
45
|
items: { type: 'number' },
|
|
46
|
-
description: 'Array of recipient
|
|
46
|
+
description: 'Array of recipient user IDs (get from ofw_get_profile)',
|
|
47
47
|
},
|
|
48
48
|
},
|
|
49
|
-
required: ['subject', 'body', '
|
|
49
|
+
required: ['subject', 'body', 'recipientIds'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'ofw_list_drafts',
|
|
54
|
+
description: 'List all draft messages in OurFamilyWizard',
|
|
55
|
+
annotations: { readOnlyHint: true },
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
page: { type: 'number', description: 'Page number (default 1)' },
|
|
60
|
+
size: { type: 'number', description: 'Drafts per page (default 50)' },
|
|
61
|
+
},
|
|
62
|
+
required: [],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'ofw_save_draft',
|
|
67
|
+
description: 'Save a message as a draft in OurFamilyWizard. Recipients are optional — a draft can be saved without them. To update an existing draft, provide its messageId.',
|
|
68
|
+
annotations: { readOnlyHint: false },
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
subject: { type: 'string', description: 'Message subject' },
|
|
73
|
+
body: { type: 'string', description: 'Message body text' },
|
|
74
|
+
recipientIds: {
|
|
75
|
+
type: 'array',
|
|
76
|
+
items: { type: 'number' },
|
|
77
|
+
description: 'Array of recipient user IDs (optional for drafts)',
|
|
78
|
+
},
|
|
79
|
+
messageId: {
|
|
80
|
+
type: 'number',
|
|
81
|
+
description: 'ID of an existing draft to update (omit to create a new draft)',
|
|
82
|
+
},
|
|
83
|
+
replyToId: {
|
|
84
|
+
type: 'number',
|
|
85
|
+
description: 'ID of the message this draft is replying to (omit for new messages)',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
required: ['subject', 'body'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'ofw_delete_draft',
|
|
93
|
+
description: 'Delete a draft message from OurFamilyWizard',
|
|
94
|
+
annotations: { destructiveHint: true },
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
messageId: { type: 'number', description: 'Draft message ID to delete' },
|
|
99
|
+
},
|
|
100
|
+
required: ['messageId'],
|
|
50
101
|
},
|
|
51
102
|
},
|
|
52
103
|
];
|
|
@@ -68,9 +119,41 @@ export async function handleTool(name, args, client) {
|
|
|
68
119
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
69
120
|
}
|
|
70
121
|
case 'ofw_send_message': {
|
|
71
|
-
const { subject, body,
|
|
72
|
-
|
|
73
|
-
|
|
122
|
+
const { subject, body, recipientIds } = args;
|
|
123
|
+
const data = await client.request('POST', '/pub/v3/messages', {
|
|
124
|
+
subject, body, recipientIds,
|
|
125
|
+
attachments: { myFileIDs: [] },
|
|
126
|
+
draft: false,
|
|
127
|
+
includeOriginal: false,
|
|
128
|
+
});
|
|
129
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
130
|
+
}
|
|
131
|
+
case 'ofw_list_drafts': {
|
|
132
|
+
const { page = 1, size = 50 } = args;
|
|
133
|
+
// 13471259 is the system Drafts folder (folderType: DRAFTS)
|
|
134
|
+
const path = `/pub/v3/messages?folders=13471259&page=${page}&size=${size}&sort=date&sortDirection=desc`;
|
|
135
|
+
const data = await client.request('GET', path);
|
|
136
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
137
|
+
}
|
|
138
|
+
case 'ofw_save_draft': {
|
|
139
|
+
const { subject, body, recipientIds = [], messageId, replyToId = null } = args;
|
|
140
|
+
const payload = {
|
|
141
|
+
subject, body, recipientIds,
|
|
142
|
+
attachments: { myFileIDs: [] },
|
|
143
|
+
draft: true,
|
|
144
|
+
includeOriginal: false,
|
|
145
|
+
replyToId,
|
|
146
|
+
};
|
|
147
|
+
if (messageId !== undefined)
|
|
148
|
+
payload.messageId = messageId;
|
|
149
|
+
const data = await client.request('POST', '/pub/v3/messages', payload);
|
|
150
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
151
|
+
}
|
|
152
|
+
case 'ofw_delete_draft': {
|
|
153
|
+
const { messageId } = args;
|
|
154
|
+
const form = new FormData();
|
|
155
|
+
form.append('messageIds', String(messageId));
|
|
156
|
+
const data = await client.request('DELETE', '/pub/v1/messages', form);
|
|
74
157
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
75
158
|
}
|
|
76
159
|
default:
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofw-mcp",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "OurFamilyWizard MCP server for Claude",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "OurFamilyWizard MCP server for Claude — developed and maintained by AI (Claude Sonnet 4.6)",
|
|
5
|
+
"author": "Claude Sonnet 4.6 (AI) <https://www.anthropic.com/claude>",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"bin": {
|
|
7
8
|
"ofw-mcp": "dist/index.js"
|
|
@@ -16,12 +17,13 @@
|
|
|
16
17
|
"test:watch": "vitest"
|
|
17
18
|
},
|
|
18
19
|
"dependencies": {
|
|
19
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
20
|
-
"dotenv": "^
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
21
|
+
"dotenv": "^17.3.1"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
|
-
"@types/node": "^
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"@types/node": "^25.5.0",
|
|
25
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"vitest": "^4.1.0"
|
|
26
28
|
}
|
|
27
29
|
}
|