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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # whooing-mcp
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/whooing-mcp.svg)](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. Install
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
- node dist/cli.js
31
+ npx whooing-mcp
39
32
  ```
40
33
 
41
34
  ### HTTP mode (daemon)
42
35
 
43
36
  ```bash
44
- node dist/cli.js --http --port 8182
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": "node",
54
- "args": ["/path/to/whooing-mcp/dist/cli.js"],
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": "node",
73
- "args": ["/path/to/whooing-mcp/dist/cli.js"],
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/node</string>
112
- <string>/path/to/whooing-mcp/dist/cli.js</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.1.0",
33
+ version: "0.2.0",
34
34
  }, {
35
35
  instructions: "Whooing (후잉) is a Korean personal finance tracking service. " +
36
- "This server provides read-only access to financial data: " +
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
  }
@@ -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;
@@ -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.1.0",
4
- "description": "MCP server for Whooing (후잉) personal finance — read-only queries for spending, transactions, budgets, and balance sheets",
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": {