budgety-mcp 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.
- package/README.md +154 -0
- package/index.js +210 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Budgety MCP
|
|
2
|
+
|
|
3
|
+
Budgety (家計簿アプリ) を Claude から操作するための MCP ブリッジ。
|
|
4
|
+
|
|
5
|
+
Claude → MCP server (Node.js) → macOS `shortcuts` CLI → Shortcuts.app レシピ → Budgety AppIntent → Core Data → CloudKit、という経路で支出の取得・追加を行う。
|
|
6
|
+
|
|
7
|
+
## できること
|
|
8
|
+
|
|
9
|
+
| ツール名 | 用途 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `mcp__budgety__get_expenses` | 期間指定で支出/収入一覧を JSON で取得 |
|
|
12
|
+
| `mcp__budgety__add_expense` | 支出を 1 件追加 (日付・カテゴリ AI 自動分類対応) |
|
|
13
|
+
|
|
14
|
+
## 前提
|
|
15
|
+
|
|
16
|
+
- macOS (Mac Catalyst で Budgety が動く環境)
|
|
17
|
+
- Budgety アプリがインストール済み・起動済み (= AppIntent が macOS に登録されている)
|
|
18
|
+
- iCloud に Budgety と同じ Apple ID でサインイン
|
|
19
|
+
- Node.js (v18 以上推奨)
|
|
20
|
+
- Claude Code CLI
|
|
21
|
+
|
|
22
|
+
## セットアップ
|
|
23
|
+
|
|
24
|
+
### 1. Budgety を起動
|
|
25
|
+
|
|
26
|
+
一度起動して AppIntent を macOS に登録させる。終了せず動かしっぱなしで OK。
|
|
27
|
+
|
|
28
|
+
### 2. Shortcuts.app でレシピを 2 つ作成
|
|
29
|
+
|
|
30
|
+
#### A. 「支出を取得」
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
[1] Budgety > 支出を取得
|
|
34
|
+
期間: ショートカットの入力
|
|
35
|
+
[2] 結果として返す: [1] の出力
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### B. 「クイック支出追加」
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
[1] ショートカットの入力 から辞書を取得 (= JSON parse)
|
|
42
|
+
[2] 辞書 内の amount の 値を取得
|
|
43
|
+
[3] 辞書 内の title の 値を取得
|
|
44
|
+
[4] 辞書 内の date の 値を取得
|
|
45
|
+
[5] Budgety > 支出を追加
|
|
46
|
+
シート: 家計簿 (固定選択)
|
|
47
|
+
タイトル: [3] の出力
|
|
48
|
+
金額: [2] の出力
|
|
49
|
+
カテゴリ: AI 提案 (= ドロップダウンで選択)
|
|
50
|
+
日付: 現在の日付 (= 変数バインド)
|
|
51
|
+
日付テキスト: [4] の出力 (= ISO8601 文字列を内部で parse)
|
|
52
|
+
メモ: (空)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> ⚠ Shortcuts.app で `[4]` の値を「日付」フィールドに直接バインドするとエラーになる。必ず「**日付テキスト (任意)**」フィールドに渡すこと (= AppIntent 内部で文字列 → Date 変換される)。「日付」自体は `現在の日付` 変数固定でフォールバック用。
|
|
56
|
+
|
|
57
|
+
### 3. MCP server をインストール
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd /path/to/budgety-mcp
|
|
61
|
+
npm install
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 4. Claude Code に登録
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
claude mcp add budgety -s user -- node $(pwd)/index.js
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 5. 起動確認
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
claude
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Claude セッション内で `mcp__budgety__get_expenses` が使えれば成功。
|
|
77
|
+
|
|
78
|
+
## 使い方 (Claude への依頼例)
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
「今月の支出を見せて」 → get_expenses(period: "thisMonth")
|
|
82
|
+
「コーヒー 350 円を追加」 → add_expense(amount: 350, title: "コーヒー")
|
|
83
|
+
「昨日のラーメン 1200 円」 → add_expense(amount: 1200, title: "ラーメン",
|
|
84
|
+
date: "2026-05-09T...")
|
|
85
|
+
「5/3 の焼肉 4200 円」 → add_expense(amount: 4200, title: "焼肉",
|
|
86
|
+
date: "2026-05-03T19:00:00+09:00")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
カテゴリは指定しない。AI が title から自動分類する。
|
|
90
|
+
|
|
91
|
+
## 仕様詳細
|
|
92
|
+
|
|
93
|
+
### `add_expense`
|
|
94
|
+
|
|
95
|
+
| パラメータ | 型 | 必須 | 説明 |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| `amount` | number | ✓ | 数字のみ (¥, 円 などは剥がす) |
|
|
98
|
+
| `title` | string | ✓ | 用途・店名・品目 (短く) |
|
|
99
|
+
| `date` | string | × | ISO8601。未指定 = 現在時刻 |
|
|
100
|
+
|
|
101
|
+
**date の例:**
|
|
102
|
+
- `"2026-05-08T15:00:00Z"` (UTC)
|
|
103
|
+
- `"2026-05-08T19:00:00+09:00"` (JST)
|
|
104
|
+
|
|
105
|
+
相対日付 (「昨日」「先週月曜」) は **Claude 側で絶対日付に変換してから渡す**。Shortcut は相対表現を理解しない。
|
|
106
|
+
|
|
107
|
+
### `get_expenses`
|
|
108
|
+
|
|
109
|
+
| パラメータ | 値 |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `period` | `today` / `yesterday` / `thisWeek` / `thisMonth` / `lastMonth` / `thisYear` / `last30Days` |
|
|
112
|
+
|
|
113
|
+
## トラブルシューティング
|
|
114
|
+
|
|
115
|
+
| 症状 | 原因 | 対処 |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `shortcut timed out (>8s)` | Shortcut が対話プロンプトでハング | Shortcuts.app で対象 Shortcut の各パラメータを「毎回尋ねる」から固定値/変数に変更 |
|
|
118
|
+
| `shortcut exited code=1` | パラメータの型不整合 (例: 文字列を Date 欄に渡した) | Shortcut の各フィールドのバインド先を確認 |
|
|
119
|
+
| 追加したのに反映されない | iCloud 同期遅延 | 数秒待つ |
|
|
120
|
+
| 日付指定したのに今日になる | 日付テキストが間違ったキーにバインド | Shortcut で `date` キーから値を取り出しているか確認 |
|
|
121
|
+
| 「支出を追加」が `shortcuts list` に出ない | Budgety を起動していない | アプリを起動 |
|
|
122
|
+
|
|
123
|
+
## アーキテクチャ
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
Claude
|
|
127
|
+
↓ JSON-RPC (stdio)
|
|
128
|
+
budgety-mcp (Node.js)
|
|
129
|
+
↓ execFile("shortcuts", ["run", ...])
|
|
130
|
+
macOS Shortcuts CLI
|
|
131
|
+
↓ 実行
|
|
132
|
+
Shortcuts.app レシピ (= 「クイック支出追加」)
|
|
133
|
+
↓ AppIntent
|
|
134
|
+
Budgety.app (Mac Catalyst)
|
|
135
|
+
↓ Core Data
|
|
136
|
+
CloudKit → iPhone / Watch / 他 Mac
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## ファイル構成
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
budgety-mcp/
|
|
143
|
+
├── index.js # MCP server (Node.js)
|
|
144
|
+
├── package.json
|
|
145
|
+
└── README.md # このファイル
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
AppIntent 側のコード (= Budgety リポジトリ内):
|
|
149
|
+
- `Budgety/Intents/AddExpenseIntent.swift`
|
|
150
|
+
- `Budgety/Intents/QuickAddExpenseIntent.swift`
|
|
151
|
+
- `Budgety/Intents/GetExpensesIntent.swift`
|
|
152
|
+
- `Budgety/Intents/ExpensoShortcuts.swift`
|
|
153
|
+
- `Budgety/Intents/ExpenseCategoryEntity.swift`
|
|
154
|
+
- `Budgety/Intents/ExpenseSheetEntity.swift`
|
package/index.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//
|
|
3
|
+
// Budgety MCP Bridge
|
|
4
|
+
//
|
|
5
|
+
// Exposes Budgety's data to Claude via MCP. Internally calls macOS `shortcuts`
|
|
6
|
+
// CLI which runs the `Budgety で支出を取得` AppShortcut → returns JSON to Claude.
|
|
7
|
+
//
|
|
8
|
+
// Setup:
|
|
9
|
+
// 1. Install Budgety on iPhone, sign in to iCloud
|
|
10
|
+
// 2. On Mac, ensure Shortcuts.app has "Budgety で支出を取得" available
|
|
11
|
+
// (auto-synced via iCloud once Budgety iOS App Intent is registered)
|
|
12
|
+
// 3. claude mcp add budgety -s user -- node /Users/ishinotento/budgety-mcp/index.js
|
|
13
|
+
// 4. Restart Claude Code: claude -c
|
|
14
|
+
//
|
|
15
|
+
|
|
16
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
17
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18
|
+
import {
|
|
19
|
+
CallToolRequestSchema,
|
|
20
|
+
ListToolsRequestSchema,
|
|
21
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
22
|
+
import { execFile } from "node:child_process";
|
|
23
|
+
import { promisify } from "node:util";
|
|
24
|
+
import { writeFile, readFile, unlink } from "node:fs/promises";
|
|
25
|
+
import { tmpdir } from "node:os";
|
|
26
|
+
import { join } from "node:path";
|
|
27
|
+
|
|
28
|
+
const exec = promisify(execFile);
|
|
29
|
+
|
|
30
|
+
const SHORTCUT_NAME_GET_EXPENSES = "支出を取得";
|
|
31
|
+
const SHORTCUT_NAME_ADD_EXPENSE = "クイック支出追加";
|
|
32
|
+
|
|
33
|
+
const server = new Server(
|
|
34
|
+
{
|
|
35
|
+
name: "budgety-mcp",
|
|
36
|
+
version: "0.1.0",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
capabilities: {
|
|
40
|
+
tools: {},
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// MARK: Tools
|
|
46
|
+
|
|
47
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
48
|
+
tools: [
|
|
49
|
+
{
|
|
50
|
+
name: "get_expenses",
|
|
51
|
+
description:
|
|
52
|
+
"Get expense / income records from Budgety for a specified period. Returns structured JSON with date, amount, category, sheet, etc. Useful for analyzing spending patterns.",
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: {
|
|
56
|
+
period: {
|
|
57
|
+
type: "string",
|
|
58
|
+
enum: [
|
|
59
|
+
"today",
|
|
60
|
+
"yesterday",
|
|
61
|
+
"thisWeek",
|
|
62
|
+
"thisMonth",
|
|
63
|
+
"lastMonth",
|
|
64
|
+
"thisYear",
|
|
65
|
+
"last30Days",
|
|
66
|
+
],
|
|
67
|
+
default: "thisMonth",
|
|
68
|
+
description: "Period to fetch expenses for",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "add_expense",
|
|
75
|
+
description: [
|
|
76
|
+
"Record an expense in Budgety (the user's family expense tracking app).",
|
|
77
|
+
"Use this whenever the user mentions a purchase, payment, or expense in conversation",
|
|
78
|
+
"(e.g. 'コーヒー 350 円', 'lunch was 1200 yen', 'add yesterday's groceries').",
|
|
79
|
+
"",
|
|
80
|
+
"Behavior:",
|
|
81
|
+
"- Saves to the user's primary sheet (家計簿).",
|
|
82
|
+
"- Currency = sheet's default (JPY for Japan).",
|
|
83
|
+
"- Category is auto-classified by AI from the title — DO NOT pass a category.",
|
|
84
|
+
"",
|
|
85
|
+
"Date handling (IMPORTANT):",
|
|
86
|
+
"- Resolve relative dates ('yesterday', '昨日', '先週月曜', etc.) to an absolute",
|
|
87
|
+
" ISO8601 string BEFORE calling. The shortcut does not understand relative dates.",
|
|
88
|
+
"- Format: 'YYYY-MM-DDTHH:MM:SSZ' (UTC) or 'YYYY-MM-DDTHH:MM:SS+09:00' (JST).",
|
|
89
|
+
"- Omit `date` to record at the current moment.",
|
|
90
|
+
"- If the user mentions a date but not a time, use 12:00 local time as a sensible default.",
|
|
91
|
+
].join("\n"),
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
amount: {
|
|
96
|
+
type: "number",
|
|
97
|
+
description:
|
|
98
|
+
"Amount as a plain number. Strip currency symbols (¥, $, 円) before passing.",
|
|
99
|
+
},
|
|
100
|
+
title: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description:
|
|
103
|
+
"Short label for the expense (店名・品目). Keep it concise — no need for full sentences.",
|
|
104
|
+
},
|
|
105
|
+
date: {
|
|
106
|
+
type: "string",
|
|
107
|
+
description: [
|
|
108
|
+
"ISO8601 date-time string. Optional — defaults to now.",
|
|
109
|
+
"Examples:",
|
|
110
|
+
" '2026-05-03T19:30:00Z' (UTC)",
|
|
111
|
+
" '2026-05-03T19:30:00+09:00' (JST)",
|
|
112
|
+
"Resolve relative dates ('yesterday', '昨日') to absolute before passing.",
|
|
113
|
+
].join("\n"),
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
required: ["amount", "title"],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
}));
|
|
121
|
+
|
|
122
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
123
|
+
const name = req.params.name;
|
|
124
|
+
const args = req.params.arguments ?? {};
|
|
125
|
+
try {
|
|
126
|
+
if (name === "get_expenses") {
|
|
127
|
+
const period = args.period ?? "thisMonth";
|
|
128
|
+
const json = await runShortcutWithInput(
|
|
129
|
+
SHORTCUT_NAME_GET_EXPENSES,
|
|
130
|
+
period
|
|
131
|
+
);
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: "text", text: json }],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (name === "add_expense") {
|
|
137
|
+
// date は常に送る (= 未指定なら現在時刻の ISO8601)。Shortcut 側は
|
|
138
|
+
// 「日付を取得」アクションで text → Date に変換する前提。
|
|
139
|
+
const dateISO = args.date && typeof args.date === "string"
|
|
140
|
+
? args.date
|
|
141
|
+
: new Date().toISOString();
|
|
142
|
+
const payload = JSON.stringify({
|
|
143
|
+
amount: args.amount,
|
|
144
|
+
title: args.title ?? "",
|
|
145
|
+
date: dateISO,
|
|
146
|
+
});
|
|
147
|
+
const result = await runShortcutWithInput(
|
|
148
|
+
SHORTCUT_NAME_ADD_EXPENSE,
|
|
149
|
+
payload
|
|
150
|
+
);
|
|
151
|
+
return {
|
|
152
|
+
content: [{ type: "text", text: result || "OK" }],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
return {
|
|
158
|
+
isError: true,
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: "text",
|
|
162
|
+
text: `${err.message ?? err}\n\nHint: ensure the shortcut is set up in Shortcuts.app on this Mac (signed into the same iCloud as Budgety).`,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
async function runShortcutWithInput(name, input) {
|
|
170
|
+
const inputPath = join(tmpdir(), `budgety-mcp-in-${Date.now()}.txt`);
|
|
171
|
+
const outputPath = join(tmpdir(), `budgety-mcp-out-${Date.now()}.txt`);
|
|
172
|
+
await writeFile(inputPath, input ?? "", "utf-8");
|
|
173
|
+
try {
|
|
174
|
+
// 8 秒以上かかったら shortcut が対話プロンプトでハングしている可能性大なので abort
|
|
175
|
+
const child = execFile("shortcuts", [
|
|
176
|
+
"run",
|
|
177
|
+
name,
|
|
178
|
+
"--input-path", inputPath,
|
|
179
|
+
"--output-path", outputPath,
|
|
180
|
+
]);
|
|
181
|
+
const promise = new Promise((resolve, reject) => {
|
|
182
|
+
child.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`shortcut exited code=${code}`)));
|
|
183
|
+
child.on("error", reject);
|
|
184
|
+
});
|
|
185
|
+
const timeout = new Promise((_, reject) =>
|
|
186
|
+
setTimeout(() => {
|
|
187
|
+
try { child.kill("SIGKILL"); } catch {}
|
|
188
|
+
reject(new Error("shortcut timed out (>8s) - probably waiting for user input. Configure the shortcut in Shortcuts.app to skip prompts."));
|
|
189
|
+
}, 8000)
|
|
190
|
+
);
|
|
191
|
+
await Promise.race([promise, timeout]);
|
|
192
|
+
} finally {
|
|
193
|
+
try { await unlink(inputPath); } catch {}
|
|
194
|
+
}
|
|
195
|
+
let result = "";
|
|
196
|
+
try {
|
|
197
|
+
result = await readFile(outputPath, "utf-8");
|
|
198
|
+
} catch {
|
|
199
|
+
result = "";
|
|
200
|
+
}
|
|
201
|
+
try { await unlink(outputPath); } catch {}
|
|
202
|
+
// 空 output は許容 (= shortcut 末尾に出力アクションがない場合の正常終了)。
|
|
203
|
+
// 本物の失敗は execFile の exit code 非 0 / timeout で既に throw されている。
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// MARK: Run
|
|
208
|
+
|
|
209
|
+
const transport = new StdioServerTransport();
|
|
210
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "budgety-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP bridge to control Budgety (家計簿アプリ) from Claude via macOS Shortcuts.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": { "budgety-mcp": "./index.js" },
|
|
8
|
+
"files": [
|
|
9
|
+
"index.js",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start": "node index.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"claude",
|
|
18
|
+
"budgety",
|
|
19
|
+
"expense",
|
|
20
|
+
"shortcuts",
|
|
21
|
+
"macos"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"os": [
|
|
27
|
+
"darwin"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"author": "Tento Ishino",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|