whooing-mcp 0.1.0 → 0.2.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.
- package/README.md +22 -18
- package/dist/cli.js +0 -0
- package/dist/server.js +79 -3
- package/dist/whooing-client.d.ts +1 -0
- package/dist/whooing-client.js +19 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# whooing-mcp
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/whooing-mcp)
|
|
4
|
+
|
|
3
5
|
MCP server for [Whooing (후잉)](https://whooing.com) personal finance — read-only queries for spending, transactions, balance sheets, and accounts.
|
|
4
6
|
|
|
5
7
|
## Setup
|
|
@@ -10,16 +12,7 @@ MCP server for [Whooing (후잉)](https://whooing.com) personal finance — read
|
|
|
10
12
|
2. Note your `app_id`, `token`, and `signature`
|
|
11
13
|
3. Find your `section_id` from the API or URL
|
|
12
14
|
|
|
13
|
-
### 2.
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
git clone https://github.com/jmjeong/whooing-mcp.git
|
|
17
|
-
cd whooing-mcp
|
|
18
|
-
npm install
|
|
19
|
-
npm run build
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### 3. Configure Environment
|
|
15
|
+
### 2. Configure Environment
|
|
23
16
|
|
|
24
17
|
```bash
|
|
25
18
|
export WHOOING_APP_ID=3
|
|
@@ -35,13 +28,13 @@ Or create a `.env` file (see `.env.example`).
|
|
|
35
28
|
### stdio mode (Claude Code, Claude Desktop)
|
|
36
29
|
|
|
37
30
|
```bash
|
|
38
|
-
|
|
31
|
+
npx whooing-mcp
|
|
39
32
|
```
|
|
40
33
|
|
|
41
34
|
### HTTP mode (daemon)
|
|
42
35
|
|
|
43
36
|
```bash
|
|
44
|
-
|
|
37
|
+
npx whooing-mcp --http --port 8182
|
|
45
38
|
```
|
|
46
39
|
|
|
47
40
|
### Claude Code config (`~/.mcp.json`)
|
|
@@ -50,8 +43,8 @@ node dist/cli.js --http --port 8182
|
|
|
50
43
|
{
|
|
51
44
|
"mcpServers": {
|
|
52
45
|
"whooing": {
|
|
53
|
-
"command": "
|
|
54
|
-
"args": ["
|
|
46
|
+
"command": "npx",
|
|
47
|
+
"args": ["whooing-mcp"],
|
|
55
48
|
"env": {
|
|
56
49
|
"WHOOING_APP_ID": "3",
|
|
57
50
|
"WHOOING_TOKEN": "...",
|
|
@@ -69,8 +62,8 @@ node dist/cli.js --http --port 8182
|
|
|
69
62
|
{
|
|
70
63
|
"mcpServers": {
|
|
71
64
|
"whooing": {
|
|
72
|
-
"command": "
|
|
73
|
-
"args": ["
|
|
65
|
+
"command": "npx",
|
|
66
|
+
"args": ["whooing-mcp"],
|
|
74
67
|
"env": {
|
|
75
68
|
"WHOOING_APP_ID": "3",
|
|
76
69
|
"WHOOING_TOKEN": "...",
|
|
@@ -108,8 +101,8 @@ Create `~/Library/LaunchAgents/com.whooing.mcp.plist`:
|
|
|
108
101
|
<key>Label</key><string>com.whooing.mcp</string>
|
|
109
102
|
<key>ProgramArguments</key>
|
|
110
103
|
<array>
|
|
111
|
-
<string>/opt/homebrew/bin/
|
|
112
|
-
<string
|
|
104
|
+
<string>/opt/homebrew/bin/npx</string>
|
|
105
|
+
<string>whooing-mcp</string>
|
|
113
106
|
<string>--http</string>
|
|
114
107
|
<string>--port</string>
|
|
115
108
|
<string>8182</string>
|
|
@@ -120,6 +113,7 @@ Create `~/Library/LaunchAgents/com.whooing.mcp.plist`:
|
|
|
120
113
|
<key>WHOOING_TOKEN</key><string>YOUR_TOKEN</string>
|
|
121
114
|
<key>WHOOING_SIGNATURE</key><string>YOUR_SIGNATURE</string>
|
|
122
115
|
<key>WHOOING_SECTION_ID</key><string>YOUR_SECTION_ID</string>
|
|
116
|
+
<key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
123
117
|
</dict>
|
|
124
118
|
<key>KeepAlive</key><true/>
|
|
125
119
|
<key>RunAtLoad</key><true/>
|
|
@@ -134,6 +128,16 @@ chmod 600 ~/Library/LaunchAgents/com.whooing.mcp.plist
|
|
|
134
128
|
launchctl load ~/Library/LaunchAgents/com.whooing.mcp.plist
|
|
135
129
|
```
|
|
136
130
|
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
git clone https://github.com/jmjeong/whooing-mcp.git
|
|
135
|
+
cd whooing-mcp
|
|
136
|
+
npm install
|
|
137
|
+
npm run build
|
|
138
|
+
node dist/cli.js
|
|
139
|
+
```
|
|
140
|
+
|
|
137
141
|
## License
|
|
138
142
|
|
|
139
143
|
MIT
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/server.js
CHANGED
|
@@ -30,12 +30,12 @@ const dateRangeSchema = {
|
|
|
30
30
|
export function createWhooingMcpServer(client) {
|
|
31
31
|
const server = new McpServer({
|
|
32
32
|
name: "whooing-mcp",
|
|
33
|
-
version: "0.
|
|
33
|
+
version: "0.2.0",
|
|
34
34
|
}, {
|
|
35
35
|
instructions: "Whooing (후잉) is a Korean personal finance tracking service. " +
|
|
36
|
-
"This server provides
|
|
36
|
+
"This server provides access to financial data: " +
|
|
37
37
|
"spending/income summaries (P&L), transaction lists, balance sheets, " +
|
|
38
|
-
"and account listings. " +
|
|
38
|
+
"and account listings, and can create new expense entries. " +
|
|
39
39
|
"Dates use YYYYMMDD format. All amounts are in KRW (원). " +
|
|
40
40
|
"If no dates are specified, the current month is used.",
|
|
41
41
|
});
|
|
@@ -137,5 +137,81 @@ export function createWhooingMcpServer(client) {
|
|
|
137
137
|
const text = formatSections(results);
|
|
138
138
|
return { content: [{ type: "text", text }] };
|
|
139
139
|
});
|
|
140
|
+
// whooing_add_entry — Create a new entry
|
|
141
|
+
server.registerTool("whooing_add_entry", {
|
|
142
|
+
description: "Create a new transaction entry in Whooing (e.g. expense, income). " +
|
|
143
|
+
"Use whooing_accounts first to look up account IDs.",
|
|
144
|
+
inputSchema: {
|
|
145
|
+
entry_date: z
|
|
146
|
+
.string()
|
|
147
|
+
.regex(/^\d{8}$/)
|
|
148
|
+
.describe("Transaction date in YYYYMMDD format"),
|
|
149
|
+
l_account_id: z
|
|
150
|
+
.string()
|
|
151
|
+
.describe("Left account ID (e.g. expense category like x11 for 식비)"),
|
|
152
|
+
r_account_id: z
|
|
153
|
+
.string()
|
|
154
|
+
.describe("Right account ID (e.g. payment method like x24 for 삼성카드)"),
|
|
155
|
+
item: z.string().describe("Item description (store name or item)"),
|
|
156
|
+
money: z.number().positive().describe("Amount in KRW"),
|
|
157
|
+
memo: z.string().optional().describe("Optional memo"),
|
|
158
|
+
section_id: z
|
|
159
|
+
.string()
|
|
160
|
+
.optional()
|
|
161
|
+
.describe("Section ID. Defaults to WHOOING_SECTION_ID env var."),
|
|
162
|
+
},
|
|
163
|
+
annotations: { readOnlyHint: false },
|
|
164
|
+
}, async (args) => {
|
|
165
|
+
const sectionId = args.section_id ?? client.defaultSectionId;
|
|
166
|
+
// Load accounts to resolve account types
|
|
167
|
+
await client.loadAccounts(sectionId);
|
|
168
|
+
const lInfo = client.getAccountInfo(args.l_account_id);
|
|
169
|
+
const rInfo = client.getAccountInfo(args.r_account_id);
|
|
170
|
+
if (!lInfo) {
|
|
171
|
+
return {
|
|
172
|
+
content: [
|
|
173
|
+
{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: `Error: Unknown left account ID "${args.l_account_id}". Use whooing_accounts to look up valid IDs.`,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
isError: true,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (!rInfo) {
|
|
182
|
+
return {
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: `Error: Unknown right account ID "${args.r_account_id}". Use whooing_accounts to look up valid IDs.`,
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
isError: true,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const body = {
|
|
193
|
+
section_id: sectionId,
|
|
194
|
+
entry_date: args.entry_date,
|
|
195
|
+
l_account: lInfo.type,
|
|
196
|
+
l_account_id: args.l_account_id,
|
|
197
|
+
r_account: rInfo.type,
|
|
198
|
+
r_account_id: args.r_account_id,
|
|
199
|
+
item: args.item,
|
|
200
|
+
money: String(args.money),
|
|
201
|
+
};
|
|
202
|
+
if (args.memo) {
|
|
203
|
+
body.memo = args.memo;
|
|
204
|
+
}
|
|
205
|
+
await client.apiPost("entries.json", body);
|
|
206
|
+
const formattedDate = `${args.entry_date.slice(0, 4)}-${args.entry_date.slice(4, 6)}-${args.entry_date.slice(6, 8)}`;
|
|
207
|
+
const text = `Entry created successfully.\n` +
|
|
208
|
+
` Date: ${formattedDate}\n` +
|
|
209
|
+
` Left: ${lInfo.name} (${lInfo.type})\n` +
|
|
210
|
+
` Right: ${rInfo.name} (${rInfo.type})\n` +
|
|
211
|
+
` Item: ${args.item}\n` +
|
|
212
|
+
` Amount: ${args.money.toLocaleString()}원` +
|
|
213
|
+
(args.memo ? `\n Memo: ${args.memo}` : "");
|
|
214
|
+
return { content: [{ type: "text", text }] };
|
|
215
|
+
});
|
|
140
216
|
return server;
|
|
141
217
|
}
|
package/dist/whooing-client.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class WhooingClient {
|
|
|
15
15
|
get defaultSectionId(): string;
|
|
16
16
|
private getApiKey;
|
|
17
17
|
apiGet(endpoint: string, params?: Record<string, string>): Promise<unknown>;
|
|
18
|
+
apiPost(endpoint: string, body: Record<string, string>): Promise<unknown>;
|
|
18
19
|
loadAccounts(sectionId?: string): Promise<Map<string, AccountInfo>>;
|
|
19
20
|
getAccountName(accountId: string): string;
|
|
20
21
|
getAccountInfo(accountId: string): AccountInfo | undefined;
|
package/dist/whooing-client.js
CHANGED
|
@@ -34,6 +34,25 @@ export class WhooingClient {
|
|
|
34
34
|
}
|
|
35
35
|
return json.results;
|
|
36
36
|
}
|
|
37
|
+
async apiPost(endpoint, body) {
|
|
38
|
+
const res = await fetch(`https://whooing.com/api/${endpoint}`, {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: {
|
|
41
|
+
"X-API-KEY": this.getApiKey(),
|
|
42
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
43
|
+
},
|
|
44
|
+
body: new URLSearchParams(body).toString(),
|
|
45
|
+
});
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
const text = await res.text();
|
|
48
|
+
throw new Error(`Whooing API error ${res.status}: ${text}`);
|
|
49
|
+
}
|
|
50
|
+
const json = (await res.json());
|
|
51
|
+
if (json.code !== 200) {
|
|
52
|
+
throw new Error(`Whooing API error ${json.code}: ${json.message}`);
|
|
53
|
+
}
|
|
54
|
+
return json.results;
|
|
55
|
+
}
|
|
37
56
|
async loadAccounts(sectionId) {
|
|
38
57
|
const sid = sectionId || this.config.defaultSectionId;
|
|
39
58
|
const results = (await this.apiGet("accounts.json", {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whooing-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Whooing (후잉) personal finance —
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for Whooing (후잉) personal finance — queries and entry creation for spending, transactions, budgets, and balance sheets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/server.js",
|
|
7
7
|
"scripts": {
|