@him0/freee-mcp 0.5.2 → 0.6.1
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 +71 -20
- package/bin/cli.js +43 -42
- package/dist/auth/server.d.ts +0 -20
- package/dist/auth/tokens.d.ts +20 -0
- package/dist/config/companies.d.ts +75 -13
- package/dist/config/companies.test.d.ts +1 -0
- package/dist/config/mcp-config.d.ts +34 -0
- package/dist/config/mcp-config.test.d.ts +1 -0
- package/dist/config.d.ts +2 -7
- package/dist/constants.d.ts +4 -0
- package/dist/e2e/configure.e2e.test.d.ts +5 -0
- package/dist/e2e/fixtures/api-responses.d.ts +0 -26
- package/dist/e2e/mock-api.d.ts +0 -27
- package/dist/index.cjs +43 -42
- package/dist/index.esm.js +43 -42
- package/dist/openapi/minimal-types.d.ts +902 -0
- package/dist/openapi/schema-loader.d.ts +0 -7
- package/dist/openapi/schema-loader.test.d.ts +1 -0
- package/dist/utils/error.d.ts +24 -0
- package/package.json +3 -3
- package/dist/api/types.d.ts +0 -57
- package/dist/e2e/fixtures/index.d.ts +0 -5
- package/dist/test-utils/index.d.ts +0 -5
package/README.md
CHANGED
|
@@ -6,15 +6,50 @@ MCP サーバー(API 呼び出し機能)と skill(API リファレンス
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@him0/freee-mcp)
|
|
8
8
|
|
|
9
|
-
>
|
|
9
|
+
> Note: このプロジェクトは開発中であり、予期せぬ不具合が発生する可能性があります。問題を発見された場合は [Issue](https://github.com/him0/freee-mcp/issues) として報告していただけると幸いです。プルリクエストも歓迎しています。
|
|
10
10
|
|
|
11
11
|
## 特徴
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
13
|
+
- MCP サーバー: freee API を Claude Desktop / Claude Code から直接呼び出し
|
|
14
|
+
- Claude Plugin: 詳細な API リファレンスドキュメント付きスキルを提供
|
|
15
|
+
- 複数 API 対応: 会計・人事労務・請求書・工数管理・販売の5つの freee API をサポート
|
|
16
|
+
- OAuth 2.0 + PKCE: セキュアな認証フロー、トークン自動更新
|
|
17
|
+
- 複数事業所対応: 事業所の動的切り替えが可能
|
|
18
|
+
|
|
19
|
+
## SKILL と MCP の通信の流れ
|
|
20
|
+
|
|
21
|
+
Claude Code では、SKILL(API リファレンス)と MCP サーバー(API 呼び出し)を組み合わせて利用します。
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
sequenceDiagram
|
|
25
|
+
participant User as ユーザー
|
|
26
|
+
participant Claude as Claude Code
|
|
27
|
+
participant Skill as Agent Skill<br/>(API リファレンス)
|
|
28
|
+
participant MCP as MCP サーバー<br/>(ローカル)
|
|
29
|
+
participant API as freee API
|
|
30
|
+
|
|
31
|
+
User->>Claude: リクエスト<br/>「取引一覧を取得して」
|
|
32
|
+
|
|
33
|
+
Note over Claude,Skill: 1. SKILL からリファレンスを取得
|
|
34
|
+
Claude->>Skill: freee-api-skill 呼び出し
|
|
35
|
+
Skill-->>Claude: API リファレンス注入<br/>(エンドポイント、パラメータ仕様)
|
|
36
|
+
|
|
37
|
+
Note over Claude,MCP: 2. MCP Tool で API を実行
|
|
38
|
+
Claude->>MCP: freee_api_get 呼び出し<br/>path: /api/1/deals
|
|
39
|
+
MCP->>MCP: OpenAPI スキーマで検証
|
|
40
|
+
MCP->>MCP: 認証トークン付与
|
|
41
|
+
|
|
42
|
+
Note over MCP,API: 3. freee API への通信
|
|
43
|
+
MCP->>API: GET /api/1/deals<br/>Authorization: Bearer xxx
|
|
44
|
+
API-->>MCP: JSON レスポンス
|
|
45
|
+
|
|
46
|
+
MCP-->>Claude: 取引データ
|
|
47
|
+
Claude-->>User: 結果を整形して表示
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
この仕組みにより:
|
|
51
|
+
- SKILL: 必要な API リファレンスを段階的にコンテキストに注入(コンテキスト効率化)
|
|
52
|
+
- MCP: 認証・リクエスト検証・API 呼び出しを担当
|
|
18
53
|
|
|
19
54
|
## クイックスタート
|
|
20
55
|
|
|
@@ -22,8 +57,8 @@ MCP サーバー(API 呼び出し機能)と skill(API リファレンス
|
|
|
22
57
|
|
|
23
58
|
[freee アプリストア](https://app.secure.freee.co.jp/developers) で新しいアプリを作成:
|
|
24
59
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
60
|
+
- コールバックURL: `http://127.0.0.1:54321/callback`
|
|
61
|
+
- Client ID と Client Secret を取得
|
|
27
62
|
- 必要な権限にチェック
|
|
28
63
|
|
|
29
64
|
### 2. セットアップ
|
|
@@ -55,7 +90,7 @@ npx @him0/freee-mcp configure
|
|
|
55
90
|
}
|
|
56
91
|
```
|
|
57
92
|
|
|
58
|
-
>
|
|
93
|
+
> ⚠️ 環境変数での設定について
|
|
59
94
|
> 環境変数(`FREEE_CLIENT_ID`、`FREEE_CLIENT_SECRET` など)を使った設定は非推奨です。
|
|
60
95
|
> 代わりに `npx @him0/freee-mcp configure` を実行して設定ファイルに移行してください。
|
|
61
96
|
> 環境変数設定は将来のバージョンで削除される予定です。
|
|
@@ -85,9 +120,9 @@ Claude との会話中に API の使い方を質問すると、これらのリ
|
|
|
85
120
|
|
|
86
121
|
請求書や経費精算など、同じ形式のデータを繰り返し作成する場合は、以前に作成したデータを参照することで効率的に作業できます:
|
|
87
122
|
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
123
|
+
- 請求書作成: 過去の請求書を取得して、取引先・品目・税区分などを参考にする
|
|
124
|
+
- 経費精算: 過去の申請を参照して、勘定科目や部門の指定を正確に行う
|
|
125
|
+
- 取引登録: 類似の取引を参考にして、入力ミスを防ぐ
|
|
91
126
|
|
|
92
127
|
```
|
|
93
128
|
例: 「先月の○○社への請求書を参考に、今月分を作成して」
|
|
@@ -97,14 +132,15 @@ Claude との会話中に API の使い方を質問すると、これらのリ
|
|
|
97
132
|
|
|
98
133
|
### 管理ツール
|
|
99
134
|
|
|
100
|
-
| ツール
|
|
101
|
-
|
|
|
102
|
-
| `freee_authenticate`
|
|
103
|
-
| `freee_auth_status`
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
135
|
+
| ツール | 説明 |
|
|
136
|
+
| -------------------------- | ------------------ |
|
|
137
|
+
| `freee_authenticate` | OAuth 認証を実行 |
|
|
138
|
+
| `freee_auth_status` | 認証状態を確認 |
|
|
139
|
+
| `freee_clear_auth` | 認証情報をクリア |
|
|
140
|
+
| `freee_set_current_company`| 事業所を切り替え |
|
|
141
|
+
| `freee_get_current_company`| 現在の事業所を表示 |
|
|
142
|
+
| `freee_list_companies` | 事業所一覧を取得 |
|
|
143
|
+
| `freee_current_user` | 現在のユーザー情報 |
|
|
108
144
|
|
|
109
145
|
### API ツール
|
|
110
146
|
|
|
@@ -121,6 +157,14 @@ HTTPメソッドごとのシンプルなツール構成:
|
|
|
121
157
|
|
|
122
158
|
パスは OpenAPI スキーマに対して自動検証されます。
|
|
123
159
|
|
|
160
|
+
### company_id の取り扱い
|
|
161
|
+
|
|
162
|
+
リクエスト(パラメータまたはボディ)に `company_id` を含める場合、現在の事業所と一致している必要があります。不一致の場合はエラーになります。
|
|
163
|
+
|
|
164
|
+
- 事業所の確認: `freee_get_current_company`
|
|
165
|
+
- 事業所の切り替え: `freee_set_company`
|
|
166
|
+
- company_id を含まない API(例: `/api/1/companies`)はそのまま実行可能
|
|
167
|
+
|
|
124
168
|
## 開発者向け
|
|
125
169
|
|
|
126
170
|
```bash
|
|
@@ -150,7 +194,14 @@ TypeScript / Model Context Protocol SDK / OAuth 2.0 + PKCE / Zod / esbuild
|
|
|
150
194
|
|
|
151
195
|
ISC
|
|
152
196
|
|
|
197
|
+
## コミュニティ
|
|
198
|
+
|
|
199
|
+
質問や情報交換は Discord サーバーで行っています。お気軽にご参加ください。
|
|
200
|
+
|
|
201
|
+
- [Discord サーバー](https://discord.gg/fPA75nHp)
|
|
202
|
+
|
|
153
203
|
## 関連リンク
|
|
154
204
|
|
|
205
|
+
- [紹介記事: Public API を MCP化するとき Agent Skill 併用が良さそう with freee-mcp](https://zenn.dev/him0/articles/766798ca1315e0)
|
|
155
206
|
- [freee API ドキュメント](https://developer.freee.co.jp/docs)
|
|
156
207
|
- [Model Context Protocol](https://modelcontextprotocol.io)
|
package/bin/cli.js
CHANGED
|
@@ -1,79 +1,80 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
`),e=process.env.FREEE_CLIENT_ID||"",n=process.env.FREEE_CLIENT_SECRET||"",r=process.env.FREEE_CALLBACK_PORT?parseInt(process.env.FREEE_CALLBACK_PORT,10):T;else{if(!t.clientId||!t.clientSecret)throw new Error("\u8A8D\u8A3C\u60C5\u5831\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\n`freee-mcp configure` \u3092\u5B9F\u884C\u3057\u3066\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3057\u3066\u304F\u3060\u3055\u3044\u3002");e=t.clientId,n=t.clientSecret,r=t.callbackPort||T}let o=process.env.FREEE_DEFAULT_COMPANY_ID||"0";return process.env.FREEE_DEFAULT_COMPANY_ID&&(console.error("Warning: FREEE_DEFAULT_COMPANY_ID \u74B0\u5883\u5909\u6570\u306F\u975E\u63A8\u5968\u3067\u3059\u3002"),console.error(" \u4E8B\u696D\u6240ID\u306F `freee_set_company` \u30C4\u30FC\u30EB\u3067\u52D5\u7684\u306B\u5909\u66F4\u3067\u304D\u307E\u3059\u3002\n")),w={freee:{clientId:e,clientSecret:n,companyId:o,apiUrl:"https://api.freee.co.jp"},oauth:{callbackPort:r,redirectUri:`http://127.0.0.1:${r}/callback`,authorizationEndpoint:"https://accounts.secure.freee.co.jp/public_api/authorize",tokenEndpoint:"https://accounts.secure.freee.co.jp/public_api/token",scope:"read write"},server:{name:"freee",version:"1.0.0"},auth:{timeoutMs:M}},w}function Je(){if(!w)throw new Error("Config not loaded. Call loadConfig() first.");return w}var w,l,h=z(()=>{"use strict";P();b();w=null;l=new Proxy({},{get(t,e){if(!w)throw new Error("Config not loaded. Call loadConfig() first in async context.");return w[e]}})});h();import{McpServer as st}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as ct}from"@modelcontextprotocol/sdk/server/stdio.js";h();import Ke from"http";import{URL as We}from"url";import Ye from"net";h();import ke from"crypto";h();b();import C from"fs/promises";import I from"path";async function y(t){return t.json().catch(()=>({}))}function Q(){return I.join(A(),"tokens.json")}async function H(t){let e=Q(),n=I.dirname(e);try{console.error(`[info] Creating directory: ${n}`),await C.mkdir(n,{recursive:!0}),console.error(`[info] Writing tokens to: ${e}`),await C.writeFile(e,JSON.stringify(t,null,2),{mode:N}),console.error("[info] Tokens saved successfully")}catch(r){throw console.error("[error] Failed to save tokens:",r),r}}async function Z(){let t=Q();try{let e=await C.readFile(t,"utf8");return JSON.parse(e)}catch(e){if(e.code==="ENOENT"){let n=await qe();return n||null}throw console.error("[error] Failed to load tokens:",e),e}}function Ve(t){return Date.now()<t.expires_at}async function qe(){let t=A();try{let n=(await C.readdir(t)).filter(r=>r.startsWith("tokens-")&&r.endsWith(".json"));if(n.length>0){let r=I.join(t,n[0]),o=await C.readFile(r,"utf8"),i=JSON.parse(o);return await H(i),await Promise.all(n.map(a=>C.unlink(I.join(t,a)).catch(s=>{console.error(`[warn] Failed to clean up legacy token file ${a}:`,s)}))),console.error("[info] Migrated legacy company-specific tokens to user-based tokens"),i}}catch(e){console.error("[warn] Error during legacy token migration attempt:",e)}return null}async function ze(t){let e=await fetch(l.oauth.tokenEndpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"refresh_token",refresh_token:t,client_id:l.freee.clientId,client_secret:l.freee.clientSecret})});if(!e.ok){let o=await y(e);throw new Error(`Token refresh failed: ${e.status} ${JSON.stringify(o)}`)}let n=await e.json(),r={access_token:n.access_token,refresh_token:n.refresh_token||t,expires_at:Date.now()+n.expires_in*1e3,token_type:n.token_type||"Bearer",scope:n.scope||l.oauth.scope};return await H(r),r}async function Ce(){let t=Q();try{await C.unlink(t),console.error("[info] Tokens cleared successfully")}catch(e){if(e.code==="ENOENT"){console.error("[info] No tokens to clear (file does not exist)");return}throw console.error("[error] Failed to clear tokens:",e),e}await Ge()}async function Ge(){let t=A();try{let n=(await C.readdir(t)).filter(r=>r.startsWith("tokens-")&&r.endsWith(".json"));await Promise.all(n.map(r=>C.unlink(I.join(t,r)).catch(o=>{console.error(`[warn] Failed to clear legacy token file ${r}:`,o)}))),n.length>0&&console.error("[info] Cleared legacy company-specific token files")}catch(e){console.error("[warn] Error during legacy token cleanup:",e)}}async function we(){let t=await Z();if(!t)return null;if(Ve(t))return t.access_token;try{return(await ze(t.refresh_token)).access_token}catch(e){return console.error("[warn] Failed to refresh token:",e),null}}function _e(){let t=ke.randomBytes(32).toString("base64url"),e=ke.createHash("sha256").update(t).digest("base64url");return{codeVerifier:t,codeChallenge:e}}function B(t,e,n){let r=new URLSearchParams({response_type:"code",client_id:l.freee.clientId,redirect_uri:n,scope:l.oauth.scope,state:e,code_challenge:t,code_challenge_method:"S256"});return`${l.oauth.authorizationEndpoint}?${r.toString()}`}async function J(t,e,n){let r=await fetch(l.oauth.tokenEndpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:l.freee.clientId,client_secret:l.freee.clientSecret,code:t,redirect_uri:n,code_verifier:e})});if(!r.ok){let a=await y(r);throw new Error(`Token exchange failed: ${r.status} ${JSON.stringify(a)}`)}let o=await r.json(),i={access_token:o.access_token,refresh_token:o.refresh_token,expires_at:Date.now()+o.expires_in*1e3,token_type:o.token_type||"Bearer",scope:o.scope||l.oauth.scope};return await H(i),i}var ee=class{pendingAuthentications=new Map;cliAuthHandlers=new Map;registerAuthentication(e,n){console.error(`Registering authentication request with state: ${e.substring(0,10)}...`),console.error(`Code verifier: ${n.substring(0,10)}...`);let r=setTimeout(()=>{this.pendingAuthentications.delete(e),console.error(`Authentication timeout for state: ${e.substring(0,10)}...`)},l.auth.timeoutMs);this.pendingAuthentications.set(e,{codeVerifier:n,resolve:o=>{console.error("Authentication completed successfully!")},reject:o=>{console.error("Authentication failed:",o)},timeout:r}),console.error(`Registration complete. Total pending: ${this.pendingAuthentications.size}`)}getPendingAuthentication(e){return this.pendingAuthentications.get(e)}removePendingAuthentication(e){let n=this.pendingAuthentications.get(e);n&&(clearTimeout(n.timeout),this.pendingAuthentications.delete(e))}clearAllPending(){for(let[e,n]of this.pendingAuthentications)clearTimeout(n.timeout),n.reject(new Error("Server shutdown"));this.pendingAuthentications.clear()}get pendingCount(){return this.pendingAuthentications.size}registerCliAuthHandler(e,n){this.cliAuthHandlers.set(e,n)}getCliAuthHandler(e){return this.cliAuthHandlers.get(e)}removeCliAuthHandler(e){this.cliAuthHandlers.delete(e)}},te=class{server=null;port=null;authManager;constructor(e){this.authManager=e}getRedirectUri(){if(this.port===null)throw new Error("Callback server not started. Call start() first.");return`http://127.0.0.1:${this.port}/callback`}getPort(){return this.port}isRunning(){return this.server!==null}async start(){if(this.server)return;let e=l.oauth.callbackPort,n=await this.findAvailablePort(e);return this.port=n,n!==e&&console.error(`Warning: Port ${e} is in use. Using fallback port ${n} for OAuth callback server.`),new Promise((r,o)=>{this.server=Ke.createServer((i,a)=>{console.error(`Callback request: ${i.method} ${i.url}`);let s=new We(i.url,`http://127.0.0.1:${n}`);s.pathname==="/callback"?this.handleCallback(s,a):s.pathname==="/"?(a.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),a.end("<h1>freee MCP OAuth Server</h1><p>\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u30B5\u30FC\u30D0\u30FC\u304C\u7A3C\u50CD\u4E2D\u3067\u3059\u3002</p>")):(a.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),a.end("<h1>404 Not Found</h1><p>\u3053\u306E\u30D1\u30B9\u306F\u5B58\u5728\u3057\u307E\u305B\u3093\u3002</p>"))}),this.server.on("error",i=>{console.error("Callback server error:",i),o(i)}),this.server.listen(n,"127.0.0.1",()=>{console.error(`OAuth callback server listening on http://127.0.0.1:${n}`),r()})})}stop(){this.server&&(this.authManager.clearAllPending(),this.server.close(()=>{console.error("OAuth callback server stopped")}),this.server=null,this.port=null)}async checkPortAvailable(e){return new Promise(n=>{let r=Ye.createServer();r.listen(e,"127.0.0.1",()=>{r.close(()=>{n(!0)})}),r.on("error",()=>{n(!1)})})}async findAvailablePort(e,n=50){for(let r=e;r<e+n;r++)if(await this.checkPortAvailable(r))return r;throw new Error(`No available port found after checking ${n} ports starting from ${e}`)}handleCallback(e,n){let r=e.searchParams.get("code"),o=e.searchParams.get("state"),i=e.searchParams.get("error"),a=e.searchParams.get("error_description");console.error(`Callback received - URL: ${e.toString()}`),console.error("Callback parameters:",{code:r?`${r.substring(0,10)}...`:null,state:o?`${o.substring(0,10)}...`:null,error:i,errorDescription:a}),console.error(`Pending authentications count: ${this.authManager.pendingCount}`);let s=o?this.authManager.getCliAuthHandler(o):void 0;if(i){let u=a||i;if(console.error(`OAuth error: ${i} - ${a}`),n.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),n.end(`<h1>\u8A8D\u8A3C\u30A8\u30E9\u30FC</h1><p>\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${u}</p>`),s)s.reject(new Error(`OAuth error: ${i} - ${a}`));else if(o){let k=this.authManager.getPendingAuthentication(o);k&&(clearTimeout(k.timeout),k.reject(new Error(`OAuth error: ${i} - ${a}`)),this.authManager.removePendingAuthentication(o))}return}if(!r||!o){console.error("Missing code or state"),n.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),n.end("<h1>\u8A8D\u8A3C\u30A8\u30E9\u30FC</h1><p>\u8A8D\u8A3C\u30B3\u30FC\u30C9\u307E\u305F\u306F\u72B6\u614B\u30D1\u30E9\u30E1\u30FC\u30BF\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002</p>");return}if(s){console.error("Valid CLI callback received"),n.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),n.end("<h1>\u8A8D\u8A3C\u5B8C\u4E86</h1><p>\u8A8D\u8A3C\u304C\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002\u3053\u306E\u30DA\u30FC\u30B8\u3092\u9589\u3058\u3066\u30BF\u30FC\u30DF\u30CA\u30EB\u306B\u623B\u3063\u3066\u304F\u3060\u3055\u3044\u3002</p>"),s.resolve(r);return}let p=this.authManager.getPendingAuthentication(o);if(!p){console.error(`Unknown state: ${o}`),n.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),n.end("<h1>\u8A8D\u8A3C\u30A8\u30E9\u30FC</h1><p>\u4E0D\u660E\u306A\u8A8D\u8A3C\u72B6\u614B\u3067\u3059\u3002\u8A8D\u8A3C\u3092\u518D\u958B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>");return}console.error("Valid callback received, exchanging code for tokens..."),n.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),n.end("<h1>\u8A8D\u8A3C\u5B8C\u4E86</h1><p>\u8A8D\u8A3C\u304C\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002\u3053\u306E\u30DA\u30FC\u30B8\u3092\u9589\u3058\u3066\u304F\u3060\u3055\u3044\u3002</p>"),this.authManager.removePendingAuthentication(o),J(r,p.codeVerifier,this.getRedirectUri()).then(u=>{console.error("Token exchange successful!"),p.resolve(u)}).catch(u=>{console.error("Token exchange failed:",u),p.reject(u)})}},ne=new ee,re=new te(ne);function S(){return re.getRedirectUri()}async function V(){return re.start()}function Ee(t,e){ne.registerAuthentication(t,e)}function $(){re.stop()}function Ae(){return ne}h();import tt from"crypto";import{z as oe}from"zod";h();P();import Xe from"fs/promises";import Qe from"path";function Ze(t){return["application/pdf","application/octet-stream","image/"].some(n=>t.includes(n))}function et(t){let e={"application/pdf":".pdf","image/png":".png","image/jpeg":".jpg","image/gif":".gif","image/webp":".webp","text/csv":".csv"};for(let[n,r]of Object.entries(e))if(t.includes(n))return r;return t.includes("image/"),".bin"}async function R(t,e,n,r,o){let i=o||l.freee.apiUrl,a=await E(),s=await we();if(!s)throw new Error(`\u8A8D\u8A3C\u304C\u5FC5\u8981\u3067\u3059\u3002freee_authenticate \u30C4\u30FC\u30EB\u3092\u4F7F\u7528\u3057\u3066\u8A8D\u8A3C\u3092\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002
|
|
5
|
-
\u73FE\u5728\u306E\u4E8B\u696D\u6240ID: ${
|
|
6
|
-
\u307E\u305F\u306F\u3001FREEE_CLIENT_ID\u74B0\u5883\u5909\u6570\u304C\u6B63\u3057\u304F\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u308B\u304B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);let p=
|
|
7
|
-
\u73FE\u5728\u306E\u4E8B\u696D\
|
|
8
|
-
\u30A8\
|
|
3
|
+
var ot=Object.defineProperty;var se=(t,e)=>()=>(t&&(e=t(t=0)),e);var Te=(t,e)=>{for(var n in e)ot(t,n,{get:e[n],enumerable:!0})};import Ie from"path";import it from"os";function b(){return process.env.XDG_CONFIG_HOME?Ie.join(process.env.XDG_CONFIG_HOME,Se):Ie.join(it.homedir(),".config",Se)}var Se,M,G,K,W,F=se(()=>{"use strict";Se="freee-mcp";M=54321,G=300*1e3,K=384,W="https://api.freee.co.jp"});var Me={};Te(Me,{CompanyConfigSchema:()=>Re,FullConfigSchema:()=>$e,getCompanyInfo:()=>D,getCurrentCompanyId:()=>A,getDownloadDir:()=>ue,loadFullConfig:()=>P,saveFullConfig:()=>T,setCurrentCompany:()=>pe});import ce from"fs/promises";import xe from"path";import at from"os";import{z as u}from"zod";function le(){return xe.join(b(),"config.json")}async function st(){let t=xe.dirname(le());await ce.mkdir(t,{recursive:!0})}function ct(t){return t!=null&&typeof t=="object"&&"defaultCompanyId"in t&&"currentCompanyId"in t&&"companies"in t&&!("clientId"in t)}function lt(t){return console.error("\u{1F4E6} \u53E4\u3044\u8A2D\u5B9A\u5F62\u5F0F\u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u3002\u65B0\u3057\u3044\u5F62\u5F0F\u306B\u79FB\u884C\u3057\u307E\u3059..."),{clientId:void 0,clientSecret:void 0,callbackPort:void 0,defaultCompanyId:t.defaultCompanyId,currentCompanyId:t.currentCompanyId,companies:t.companies}}async function P(){let t=le();try{let e=await ce.readFile(t,"utf8"),n=JSON.parse(e);if(ct(n)){let o=lt(n);return await T(o),o}let r=$e.safeParse(n);if(!r.success)throw new Error(`Invalid config file: ${r.error.message}`);return r.data}catch(e){if(e.code==="ENOENT"){let n={clientId:void 0,clientSecret:void 0,callbackPort:void 0,defaultCompanyId:"0",currentCompanyId:"0",companies:{}};return await T(n),n}throw e}}async function T(t){await st();let e=le();await ce.writeFile(e,JSON.stringify(t,null,2),{mode:K})}async function A(){return(await P()).currentCompanyId}async function pe(t,e,n){let r=await P();r.companies[t]?(e||n)&&(e&&(r.companies[t].name=e),n&&(r.companies[t].description=n)):r.companies[t]={id:t,name:e||`Company ${t}`,description:n||void 0,addedAt:Date.now()},r.companies[t].lastUsed=Date.now(),r.currentCompanyId=t,await T(r)}async function D(t){return(await P()).companies[t]||null}async function ue(){return(await P()).downloadDir||at.tmpdir()}var Re,$e,I=se(()=>{"use strict";F();Re=u.object({id:u.string(),name:u.string().optional(),description:u.string().optional(),addedAt:u.number(),lastUsed:u.number().optional()}),$e=u.object({clientId:u.string().optional(),clientSecret:u.string().optional(),callbackPort:u.number().optional(),defaultCompanyId:u.string(),currentCompanyId:u.string(),companies:u.record(u.string(),Re),downloadDir:u.string().optional()})});var Fe={};Te(Fe,{getConfig:()=>d,loadConfig:()=>fe});function pt(){return!!(process.env.FREEE_CLIENT_ID||process.env.FREEE_CLIENT_SECRET)}async function fe(){if(S)return S;let t=await P(),e,n,r;if(pt())console.error("Warning: \u74B0\u5883\u5909\u6570\u3067\u306E\u8A8D\u8A3C\u60C5\u5831\u8A2D\u5B9A\u306F\u975E\u63A8\u5968\u3067\u3059\u3002"),console.error(" `freee-mcp configure` \u3092\u5B9F\u884C\u3057\u3066\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u306B\u79FB\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"),console.error(` \u74B0\u5883\u5909\u6570\u8A2D\u5B9A\u306F\u5C06\u6765\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3067\u524A\u9664\u3055\u308C\u308B\u4E88\u5B9A\u3067\u3059\u3002
|
|
4
|
+
`),e=process.env.FREEE_CLIENT_ID||"",n=process.env.FREEE_CLIENT_SECRET||"",r=process.env.FREEE_CALLBACK_PORT?parseInt(process.env.FREEE_CALLBACK_PORT,10):M;else{if(!t.clientId||!t.clientSecret)throw new Error("\u8A8D\u8A3C\u60C5\u5831\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\n`freee-mcp configure` \u3092\u5B9F\u884C\u3057\u3066\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3057\u3066\u304F\u3060\u3055\u3044\u3002");e=t.clientId,n=t.clientSecret,r=t.callbackPort||M}return S={freee:{clientId:e,clientSecret:n,companyId:"0",apiUrl:W},oauth:{callbackPort:r,redirectUri:`http://127.0.0.1:${r}/callback`,authorizationEndpoint:"https://accounts.secure.freee.co.jp/public_api/authorize",tokenEndpoint:"https://accounts.secure.freee.co.jp/public_api/token",scope:"read write"},server:{name:"freee",version:"1.0.0"},auth:{timeoutMs:G}},S}function d(){if(!S)throw new Error("Config not loaded. Call loadConfig() first in async context.");return S}var S,k=se(()=>{"use strict";I();F();S=null});k();import{McpServer as Rt}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as $t}from"@modelcontextprotocol/sdk/server/stdio.js";k();import gt from"http";import{URL as ht}from"url";import yt from"net";k();import Ue from"crypto";k();F();import _ from"fs/promises";import O from"path";import{z as x}from"zod";async function w(t){return t.json().catch(()=>({}))}function l(t){return{content:[{type:"text",text:t}]}}function C(t){return t instanceof Error?t.message:String(t)}var De=x.object({access_token:x.string(),refresh_token:x.string(),expires_at:x.number(),token_type:x.string(),scope:x.string()});function me(){return O.join(b(),"tokens.json")}async function X(t){let e=me(),n=O.dirname(e);try{console.error(`[info] Creating directory: ${n}`),await _.mkdir(n,{recursive:!0}),console.error(`[info] Writing tokens to: ${e}`),await _.writeFile(e,JSON.stringify(t,null,2),{mode:K}),console.error("[info] Tokens saved successfully")}catch(r){throw console.error("[error] Failed to save tokens:",r),r}}async function de(){let t=me();try{let e=await _.readFile(t,"utf8"),n=JSON.parse(e),r=De.safeParse(n);return r.success?r.data:(console.error("[error] Invalid token file:",r.error.message),null)}catch(e){if(e.code==="ENOENT"){let n=await ft();return n||null}throw console.error("[error] Failed to load tokens:",e),e}}function ut(t){return Date.now()<t.expires_at}async function ft(){let t=b();try{let n=(await _.readdir(t)).filter(r=>r.startsWith("tokens-")&&r.endsWith(".json"));if(n.length>0){let r=O.join(t,n[0]),o=await _.readFile(r,"utf8"),a=JSON.parse(o),i=De.safeParse(a);if(!i.success)return console.error("[error] Invalid legacy token file:",i.error.message),null;let s=i.data;return await X(s),await Promise.all(n.map(p=>_.unlink(O.join(t,p)).catch(m=>{console.error(`[warn] Failed to clean up legacy token file ${p}:`,m)}))),console.error("[info] Migrated legacy company-specific tokens to user-based tokens"),s}}catch(e){console.error("[warn] Error during legacy token migration attempt:",e)}return null}async function mt(t){let e=d(),n=await fetch(e.oauth.tokenEndpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"refresh_token",refresh_token:t,client_id:e.freee.clientId,client_secret:e.freee.clientSecret})});if(!n.ok){let a=await w(n);throw new Error(`Token refresh failed: ${n.status} ${JSON.stringify(a)}`)}let r=await n.json(),o={access_token:r.access_token,refresh_token:r.refresh_token||t,expires_at:Date.now()+r.expires_in*1e3,token_type:r.token_type||"Bearer",scope:r.scope||e.oauth.scope};return await X(o),o}async function Oe(){let t=me();try{await _.unlink(t),console.error("[info] Tokens cleared successfully")}catch(e){if(e.code==="ENOENT"){console.error("[info] No tokens to clear (file does not exist)");return}throw console.error("[error] Failed to clear tokens:",e),e}await dt()}async function dt(){let t=b();try{let n=(await _.readdir(t)).filter(r=>r.startsWith("tokens-")&&r.endsWith(".json"));await Promise.all(n.map(r=>_.unlink(O.join(t,r)).catch(o=>{console.error(`[warn] Failed to clear legacy token file ${r}:`,o)}))),n.length>0&&console.error("[info] Cleared legacy company-specific token files")}catch(e){console.error("[warn] Error during legacy token cleanup:",e)}}async function je(){let t=await de();if(!t)return null;if(ut(t))return t.access_token;try{return(await mt(t.refresh_token)).access_token}catch(e){return console.error("[warn] Failed to refresh token:",e),null}}function Le(){let t=Ue.randomBytes(32).toString("base64url"),e=Ue.createHash("sha256").update(t).digest("base64url");return{codeVerifier:t,codeChallenge:e}}function Q(t,e,n){let r=d(),o=new URLSearchParams({response_type:"code",client_id:r.freee.clientId,redirect_uri:n,scope:r.oauth.scope,state:e,code_challenge:t,code_challenge_method:"S256"});return`${r.oauth.authorizationEndpoint}?${o.toString()}`}async function Y(t,e,n){let r=d(),o=await fetch(r.oauth.tokenEndpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:r.freee.clientId,client_secret:r.freee.clientSecret,code:t,redirect_uri:n,code_verifier:e})});if(!o.ok){let s=await w(o);throw new Error(`Token exchange failed: ${o.status} ${JSON.stringify(s)}`)}let a=await o.json(),i={access_token:a.access_token,refresh_token:a.refresh_token,expires_at:Date.now()+a.expires_in*1e3,token_type:a.token_type||"Bearer",scope:a.scope||r.oauth.scope};return await X(i),i}var ge=class{pendingAuthentications=new Map;cliAuthHandlers=new Map;registerAuthentication(e,n){console.error(`Registering authentication request with state: ${e.substring(0,10)}...`),console.error(`Code verifier: ${n.substring(0,10)}...`);let r=setTimeout(()=>{this.pendingAuthentications.delete(e),console.error(`Authentication timeout for state: ${e.substring(0,10)}...`)},d().auth.timeoutMs);this.pendingAuthentications.set(e,{codeVerifier:n,resolve:o=>{console.error("Authentication completed successfully!")},reject:o=>{console.error("Authentication failed:",o)},timeout:r}),console.error(`Registration complete. Total pending: ${this.pendingAuthentications.size}`)}getPendingAuthentication(e){return this.pendingAuthentications.get(e)}removePendingAuthentication(e){let n=this.pendingAuthentications.get(e);n&&(clearTimeout(n.timeout),this.pendingAuthentications.delete(e))}clearAllPending(){for(let[e,n]of this.pendingAuthentications)clearTimeout(n.timeout),n.reject(new Error("Server shutdown"));this.pendingAuthentications.clear()}get pendingCount(){return this.pendingAuthentications.size}registerCliAuthHandler(e,n){this.cliAuthHandlers.set(e,n)}getCliAuthHandler(e){return this.cliAuthHandlers.get(e)}removeCliAuthHandler(e){this.cliAuthHandlers.delete(e)}},he=class{server=null;port=null;authManager;constructor(e){this.authManager=e}getRedirectUri(){if(this.port===null)throw new Error("Callback server not started. Call start() first.");return`http://127.0.0.1:${this.port}/callback`}getPort(){return this.port}isRunning(){return this.server!==null}async start(){if(this.server)return;let e=d().oauth.callbackPort,n=await this.findAvailablePort(e);return this.port=n,n!==e&&console.error(`Warning: Port ${e} is in use. Using fallback port ${n} for OAuth callback server.`),new Promise((r,o)=>{this.server=gt.createServer((a,i)=>{console.error(`Callback request: ${a.method} ${a.url}`);let s=new ht(a.url,`http://127.0.0.1:${n}`);s.pathname==="/callback"?this.handleCallback(s,i):s.pathname==="/"?(i.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),i.end("<h1>freee MCP OAuth Server</h1><p>\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u30B5\u30FC\u30D0\u30FC\u304C\u7A3C\u50CD\u4E2D\u3067\u3059\u3002</p>")):(i.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),i.end("<h1>404 Not Found</h1><p>\u3053\u306E\u30D1\u30B9\u306F\u5B58\u5728\u3057\u307E\u305B\u3093\u3002</p>"))}),this.server.on("error",a=>{console.error("Callback server error:",a),o(a)}),this.server.listen(n,"127.0.0.1",()=>{console.error(`OAuth callback server listening on http://127.0.0.1:${n}`),r()})})}stop(){this.server&&(this.authManager.clearAllPending(),this.server.close(()=>{console.error("OAuth callback server stopped")}),this.server=null,this.port=null)}async checkPortAvailable(e){return new Promise(n=>{let r=yt.createServer();r.listen(e,"127.0.0.1",()=>{r.close(()=>{n(!0)})}),r.on("error",()=>{n(!1)})})}async findAvailablePort(e,n=50){for(let r=e;r<e+n;r++)if(await this.checkPortAvailable(r))return r;throw new Error(`No available port found after checking ${n} ports starting from ${e}`)}handleCallback(e,n){let r=e.searchParams.get("code"),o=e.searchParams.get("state"),a=e.searchParams.get("error"),i=e.searchParams.get("error_description");console.error(`Callback received - URL: ${e.toString()}`),console.error("Callback parameters:",{code:r?`${r.substring(0,10)}...`:null,state:o?`${o.substring(0,10)}...`:null,error:a,errorDescription:i}),console.error(`Pending authentications count: ${this.authManager.pendingCount}`);let s=o?this.authManager.getCliAuthHandler(o):void 0;if(a){let m=i||a;if(console.error(`OAuth error: ${a} - ${i}`),n.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),n.end(`<h1>\u8A8D\u8A3C\u30A8\u30E9\u30FC</h1><p>\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${m}</p>`),s)s.reject(new Error(`OAuth error: ${a} - ${i}`));else if(o){let v=this.authManager.getPendingAuthentication(o);v&&(clearTimeout(v.timeout),v.reject(new Error(`OAuth error: ${a} - ${i}`)),this.authManager.removePendingAuthentication(o))}return}if(!r||!o){console.error("Missing code or state"),n.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),n.end("<h1>\u8A8D\u8A3C\u30A8\u30E9\u30FC</h1><p>\u8A8D\u8A3C\u30B3\u30FC\u30C9\u307E\u305F\u306F\u72B6\u614B\u30D1\u30E9\u30E1\u30FC\u30BF\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002</p>");return}if(s){console.error("Valid CLI callback received"),n.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),n.end("<h1>\u8A8D\u8A3C\u5B8C\u4E86</h1><p>\u8A8D\u8A3C\u304C\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002\u3053\u306E\u30DA\u30FC\u30B8\u3092\u9589\u3058\u3066\u30BF\u30FC\u30DF\u30CA\u30EB\u306B\u623B\u3063\u3066\u304F\u3060\u3055\u3044\u3002</p>"),s.resolve(r);return}let p=this.authManager.getPendingAuthentication(o);if(!p){console.error(`Unknown state: ${o}`),n.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),n.end("<h1>\u8A8D\u8A3C\u30A8\u30E9\u30FC</h1><p>\u4E0D\u660E\u306A\u8A8D\u8A3C\u72B6\u614B\u3067\u3059\u3002\u8A8D\u8A3C\u3092\u518D\u958B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>");return}console.error("Valid callback received, exchanging code for tokens..."),n.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),n.end("<h1>\u8A8D\u8A3C\u5B8C\u4E86</h1><p>\u8A8D\u8A3C\u304C\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002\u3053\u306E\u30DA\u30FC\u30B8\u3092\u9589\u3058\u3066\u304F\u3060\u3055\u3044\u3002</p>"),this.authManager.removePendingAuthentication(o),Y(r,p.codeVerifier,this.getRedirectUri()).then(m=>{console.error("Token exchange successful!"),p.resolve(m)}).catch(m=>{console.error("Token exchange failed:",m),p.reject(m)})}},ye=new ge,Ce=new he(ye);function j(){return Ce.getRedirectUri()}async function Z(){return Ce.start()}function Ne(t,e){ye.registerAuthentication(t,e)}function U(){Ce.stop()}function Be(){return ye}k();import Et from"crypto";import{z as E}from"zod";k();I();import Ct from"fs/promises";import wt from"path";function _t(t){return["application/pdf","application/octet-stream","image/"].some(n=>t.includes(n))}function kt(t){let e={"application/pdf":".pdf","image/png":".png","image/jpeg":".jpg","image/gif":".gif","image/webp":".webp","text/csv":".csv"};for(let[n,r]of Object.entries(e))if(t.includes(n))return r;return t.includes("image/"),".bin"}async function L(t,e,n,r,o){let a=o||d().freee.apiUrl,i=await A(),s=await je();if(!s)throw new Error(`\u8A8D\u8A3C\u304C\u5FC5\u8981\u3067\u3059\u3002freee_authenticate \u30C4\u30FC\u30EB\u3092\u4F7F\u7528\u3057\u3066\u8A8D\u8A3C\u3092\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002
|
|
5
|
+
\u73FE\u5728\u306E\u4E8B\u696D\u6240ID: ${i}
|
|
6
|
+
\u307E\u305F\u306F\u3001FREEE_CLIENT_ID\u74B0\u5883\u5909\u6570\u304C\u6B63\u3057\u304F\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u308B\u304B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);let p=a.endsWith("/")?a:a+"/",m=e.startsWith("/")?e.slice(1):e,v=new URL(m,p);n&&Object.entries(n).forEach(([g,y])=>{y!==void 0&&v.searchParams.append(g,String(y))});let oe=n?.company_id;if(oe!==void 0&&String(oe)!==String(i))throw new Error(`company_id \u306E\u4E0D\u6574\u5408: \u30EA\u30AF\u30A8\u30B9\u30C8\u306E company_id (${oe}) \u3068\u73FE\u5728\u306E\u4E8B\u696D\u6240 (${i}) \u304C\u7570\u306A\u308A\u307E\u3059\u3002
|
|
7
|
+
freee_set_company \u3067\u4E8B\u696D\u6240\u3092\u5207\u308A\u66FF\u3048\u308B\u304B\u3001\u30EA\u30AF\u30A8\u30B9\u30C8\u306E company_id \u3092\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);let ie=r?.company_id;if(ie!==void 0&&String(ie)!==String(i))throw new Error(`company_id \u306E\u4E0D\u6574\u5408: \u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3\u306E company_id (${ie}) \u3068\u73FE\u5728\u306E\u4E8B\u696D\u6240 (${i}) \u304C\u7570\u306A\u308A\u307E\u3059\u3002
|
|
8
|
+
freee_set_company \u3067\u4E8B\u696D\u6240\u3092\u5207\u308A\u66FF\u3048\u308B\u304B\u3001\u30EA\u30AF\u30A8\u30B9\u30C8\u306E company_id \u3092\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);let h=await fetch(v.toString(),{method:t,headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:r?JSON.stringify(typeof r=="string"?JSON.parse(r):r):void 0});if(h.status===401||h.status===403){let g=await w(h);throw new Error(`\u8A8D\u8A3C\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002freee_authenticate \u30C4\u30FC\u30EB\u3092\u4F7F\u7528\u3057\u3066\u518D\u8A8D\u8A3C\u3092\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002
|
|
9
|
+
\u73FE\u5728\u306E\u4E8B\u696D\u6240ID: ${i}
|
|
10
|
+
\u30A8\u30E9\u30FC\u8A73\u7D30: ${h.status} ${JSON.stringify(g)}
|
|
9
11
|
|
|
10
12
|
\u78BA\u8A8D\u4E8B\u9805:
|
|
11
13
|
1. FREEE_CLIENT_ID\u74B0\u5883\u5909\u6570\u304C\u6B63\u3057\u304F\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u308B\u304B
|
|
12
14
|
2. freee\u5074\u3067\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u8A2D\u5B9A\u304C\u6B63\u3057\u3044\u304B\uFF08\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8URI\u7B49\uFF09
|
|
13
15
|
3. \u30C8\u30FC\u30AF\u30F3\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u306A\u3044\u304B
|
|
14
|
-
4. \u4E8B\u696D\u6240ID\u304C\u6B63\u3057\u3044\u304B\uFF08freee_get_current_company \u3067\u78BA\u8A8D\uFF09`)}if(!
|
|
16
|
+
4. \u4E8B\u696D\u6240ID\u304C\u6B63\u3057\u3044\u304B\uFF08freee_get_current_company \u3067\u78BA\u8A8D\uFF09`)}if(!h.ok){let g=await w(h),y=`API request failed: ${h.status}`;if(g&&g.errors&&Array.isArray(g.errors)){let z=[];for(let $ of g.errors)$.messages&&Array.isArray($.messages)&&z.push(...$.messages);z.length>0&&(y+=`
|
|
15
17
|
|
|
16
18
|
\u30A8\u30E9\u30FC\u8A73\u7D30:
|
|
17
|
-
${
|
|
18
|
-
`)}`,
|
|
19
|
+
${z.join(`
|
|
20
|
+
`)}`,h.status===400&&(y+=`
|
|
19
21
|
|
|
20
|
-
\u30D2\u30F3\u30C8: \u4E0D\u6B63\u306A\u30EA\u30AF\u30A8\u30B9\u30C8\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002`,
|
|
21
|
-
\u65E2\u5B58\u306E\u30C7\u30FC\u30BF\u3092\u53D6\u5F97\u3057\u3066\u6B63\u3057\u3044\u69CB\u9020\u3092\u78BA\u8A8D\u3059\u308B\u3053\u3068\u3092\u304A\u52E7\u3081\u3057\u307E\u3059\u3002`,
|
|
22
|
-
\u4F8B: get_items, get_partners, get_account_items \u306A\u3069\u3067\u65E2\u5B58\u30C7\u30FC\u30BF\u306E\u69CB\u9020\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`))}throw
|
|
22
|
+
\u30D2\u30F3\u30C8: \u4E0D\u6B63\u306A\u30EA\u30AF\u30A8\u30B9\u30C8\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002`,y+=`
|
|
23
|
+
\u65E2\u5B58\u306E\u30C7\u30FC\u30BF\u3092\u53D6\u5F97\u3057\u3066\u6B63\u3057\u3044\u69CB\u9020\u3092\u78BA\u8A8D\u3059\u308B\u3053\u3068\u3092\u304A\u52E7\u3081\u3057\u307E\u3059\u3002`,y+=`
|
|
24
|
+
\u4F8B: get_items, get_partners, get_account_items \u306A\u3069\u3067\u65E2\u5B58\u30C7\u30FC\u30BF\u306E\u69CB\u9020\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`))}throw g?.errors||(y+=`
|
|
23
25
|
|
|
24
|
-
\u8A73\u7D30: ${JSON.stringify(
|
|
26
|
+
\u8A73\u7D30: ${JSON.stringify(g)}`),new Error(y)}let ae=h.headers.get("content-type")||"";if(_t(ae)){let g=await ue(),y=kt(ae),$=`freee_download_${Date.now()}${y}`,ve=wt.join(g,$),be=await h.arrayBuffer();return await Ct.writeFile(ve,Buffer.from(be)),{type:"binary",filePath:ve,mimeType:ae,size:be.byteLength}}return h.json()}I();function He(t){t.tool("freee_current_user","\u73FE\u5728\u306E\u30E6\u30FC\u30B6\u30FC\u60C5\u5831\u3092\u53D6\u5F97\u3002\u8A73\u7D30\u30AC\u30A4\u30C9\u306Ffreee-mcp skill\u3092\u53C2\u7167\u3002",{},async()=>{try{let e=await A(),n=await D(e);if(!e)return l("\u4F1A\u793EID\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002freee_set_current_company \u3067\u8A2D\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");let r=await L("GET","/api/1/users/me");return l(`\u73FE\u5728\u306E\u30E6\u30FC\u30B6\u30FC\u60C5\u5831:
|
|
25
27
|
\u4F1A\u793EID: ${e}
|
|
26
28
|
\u4F1A\u793E\u540D: ${n?.name||"Unknown"}
|
|
27
29
|
\u30E6\u30FC\u30B6\u30FC\u8A73\u7D30:
|
|
28
|
-
${JSON.stringify(r,null,2)}`}
|
|
30
|
+
${JSON.stringify(r,null,2)}`)}catch(e){return l(`\u30E6\u30FC\u30B6\u30FC\u60C5\u5831\u306E\u53D6\u5F97\u306B\u5931\u6557: ${C(e)}`)}}),t.tool("freee_authenticate","OAuth\u8A8D\u8A3C\u3092\u958B\u59CB\u3002\u521D\u56DE\u306E\u307F\u5FC5\u8981\u3002",{},async()=>{try{if(!d().freee.clientId)return l(`FREEE_CLIENT_ID\u74B0\u5883\u5909\u6570\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002
|
|
29
31
|
OAuth\u8A8D\u8A3C\u3092\u884C\u3046\u306B\u306F\u3001freee developers\u3067\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3092\u4F5C\u6210\u3057\u3001
|
|
30
|
-
\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8ID\u3092\u74B0\u5883\u5909\u6570\u306B\u8A2D\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
|
|
32
|
+
\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8ID\u3092\u74B0\u5883\u5909\u6570\u306B\u8A2D\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);if(!d().freee.clientSecret)return l(`FREEE_CLIENT_SECRET\u74B0\u5883\u5909\u6570\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002
|
|
31
33
|
OAuth\u8A8D\u8A3C\u3092\u884C\u3046\u306B\u306F\u3001freee developers\u3067\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3092\u4F5C\u6210\u3057\u3001
|
|
32
|
-
\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u30B7\u30FC\u30AF\u30EC\u30C3\u30C8\u3092\u74B0\u5883\u5909\u6570\u306B\u8A2D\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
|
|
34
|
+
\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u30B7\u30FC\u30AF\u30EC\u30C3\u30C8\u3092\u74B0\u5883\u5909\u6570\u306B\u8A2D\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);let{codeVerifier:e,codeChallenge:n}=Le(),r=Et.randomBytes(16).toString("hex"),o=Q(n,r,j());return Ne(r,e),console.error(`Authentication URL: ${o}`),l(`\u8A8D\u8A3CURL: ${o}
|
|
33
35
|
|
|
34
|
-
\u30D6\u30E9\u30A6\u30B6\u3067\u958B\u3044\u3066\u8A8D\u8A3C\u3057\u3066\u304F\u3060\u3055\u3044\u30025\u5206\u3067\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3059\u3002`}
|
|
36
|
+
\u30D6\u30E9\u30A6\u30B6\u3067\u958B\u3044\u3066\u8A8D\u8A3C\u3057\u3066\u304F\u3060\u3055\u3044\u30025\u5206\u3067\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3059\u3002`)}catch(e){return l(`\u8A8D\u8A3C\u958B\u59CB\u306B\u5931\u6557: ${C(e)}`)}}),t.tool("freee_auth_status","\u8A8D\u8A3C\u72B6\u614B\u3092\u78BA\u8A8D\u3002",{},async()=>{try{let e=await de();if(!e)return l("\u672A\u8A8D\u8A3C\u3002freee_authenticate \u3067\u8A8D\u8A3C\u3057\u3066\u304F\u3060\u3055\u3044\u3002");let n=Date.now()<e.expires_at,r=new Date(e.expires_at).toLocaleString();return l(`\u8A8D\u8A3C\u72B6\u614B: ${n?"\u6709\u52B9":"\u671F\u9650\u5207\u308C"}
|
|
35
37
|
\u6709\u52B9\u671F\u9650: ${r}`+(n?"":`
|
|
36
|
-
\u6B21\u56DEAPI\u4F7F\u7528\u6642\u306B\u81EA\u52D5\u66F4\u65B0\u3055\u308C\u307E\u3059\u3002`)}
|
|
37
|
-
|
|
38
|
-
`)}
|
|
38
|
+
\u6B21\u56DEAPI\u4F7F\u7528\u6642\u306B\u81EA\u52D5\u66F4\u65B0\u3055\u308C\u307E\u3059\u3002`))}catch(e){return l(`\u8A8D\u8A3C\u72B6\u614B\u306E\u78BA\u8A8D\u306B\u5931\u6557: ${C(e)}`)}}),t.tool("freee_clear_auth","\u8A8D\u8A3C\u60C5\u5831\u3092\u30AF\u30EA\u30A2\u3002",{},async()=>{try{return await Oe(),l("\u8A8D\u8A3C\u60C5\u5831\u3092\u30AF\u30EA\u30A2\u3057\u307E\u3057\u305F\u3002\u518D\u8A8D\u8A3C\u3059\u308B\u306B\u306F freee_authenticate \u3092\u4F7F\u7528\u3002")}catch(e){return l(`\u8A8D\u8A3C\u60C5\u5831\u306E\u30AF\u30EA\u30A2\u306B\u5931\u6557: ${C(e)}`)}}),t.tool("freee_set_current_company","\u4E8B\u696D\u6240\u3092\u8A2D\u5B9A\u30FB\u5207\u308A\u66FF\u3048\u3002",{company_id:E.string().describe("\u4E8B\u696D\u6240ID"),name:E.string().optional().describe("\u4E8B\u696D\u6240\u540D"),description:E.string().optional().describe("\u8AAC\u660E")},async e=>{try{let{company_id:n,name:r,description:o}=e;await pe(n,r,o);let a=await D(n);return l(`\u4E8B\u696D\u6240\u3092\u8A2D\u5B9A: ${a?.name||n}`)}catch(n){return l(`\u4E8B\u696D\u6240\u306E\u8A2D\u5B9A\u306B\u5931\u6557: ${C(n)}`)}}),t.tool("freee_get_current_company","\u73FE\u5728\u306E\u4E8B\u696D\u6240\u60C5\u5831\u3092\u8868\u793A\u3002",{},async()=>{try{let e=await A(),n=await D(e);return n?l(`\u4E8B\u696D\u6240: ${n.name} (ID: ${n.id})`):l(`\u4E8B\u696D\u6240ID: ${e} (\u8A73\u7D30\u60C5\u5831\u306A\u3057)`)}catch(e){return l(`\u4E8B\u696D\u6240\u60C5\u5831\u306E\u53D6\u5F97\u306B\u5931\u6557: ${C(e)}`)}}),t.tool("freee_list_companies","\u4E8B\u696D\u6240\u4E00\u89A7\u3092\u8868\u793A\u3002",{},async()=>{try{let e=E.object({companies:E.array(E.object({id:E.number(),name:E.string()})).optional()}),n=await L("GET","/api/1/companies"),r=e.safeParse(n);if(!r.success)return{content:[{type:"text",text:`API\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${r.error.message}`}]};let o=r.data,a=await A();if(!o?.companies?.length)return l("\u4E8B\u696D\u6240\u60C5\u5831\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002");let i=o.companies.map(s=>{let p=s.id===parseInt(a)?" *":"";return`${s.name} (${s.id})${p}`}).join(`
|
|
39
|
+
`);return l(`\u4E8B\u696D\u6240\u4E00\u89A7:
|
|
40
|
+
${i}`)}catch(e){return l(`\u4E8B\u696D\u6240\u4E00\u89A7\u306E\u53D6\u5F97\u306B\u5931\u6557: ${C(e)}`)}})}import{z as c}from"zod";import ze from"node:fs";import B from"node:path";import{fileURLToPath as vt}from"node:url";import{z as f}from"zod";var Pt=f.object({name:f.string(),in:f.enum(["path","query"]),required:f.boolean().optional(),description:f.string().optional(),type:f.string()}),N=f.object({summary:f.string().optional(),description:f.string().optional(),parameters:f.array(Pt).optional(),hasJsonBody:f.boolean().optional()}),At=f.object({get:N.optional(),post:N.optional(),put:N.optional(),delete:N.optional(),patch:N.optional()}),Je=f.object({paths:f.record(f.string(),At)});var we=B.dirname(vt(import.meta.url));function bt(){let t=[B.resolve(we,"./openapi/minimal"),B.resolve(we,"../dist/openapi/minimal"),B.resolve(we,"../../openapi/minimal")];for(let e of t)if(ze.existsSync(e))return e;throw new Error(`Could not find minimal schema directory. Searched paths:
|
|
39
41
|
${t.join(`
|
|
40
|
-
`)}`)}var
|
|
42
|
+
`)}`)}var Tt=bt();function It(t){let e=B.join(Tt,t),n=ze.readFileSync(e,"utf-8"),r=JSON.parse(n),o=Je.safeParse(r);if(!o.success)throw new Error(`Invalid schema file ${t}: ${o.error.message}`);return o.data}var ee={accounting:{schemaFile:"accounting.json",baseUrl:"https://api.freee.co.jp",prefix:"accounting",name:"freee\u4F1A\u8A08 API"},hr:{schemaFile:"hr.json",baseUrl:"https://api.freee.co.jp/hr",prefix:"hr",name:"freee\u4EBA\u4E8B\u52B4\u52D9 API"},invoice:{schemaFile:"invoice.json",baseUrl:"https://api.freee.co.jp/iv",prefix:"invoice",name:"freee\u8ACB\u6C42\u66F8 API"},pm:{schemaFile:"pm.json",baseUrl:"https://api.freee.co.jp/pm",prefix:"pm",name:"freee\u5DE5\u6570\u7BA1\u7406 API"},sm:{schemaFile:"sm.json",baseUrl:"https://api.freee.co.jp/sm",prefix:"sm",name:"freee\u8CA9\u58F2 API"}},_e={};function Ve(t){if(!_e[t]){let e=ee[t];_e[t]={schema:It(e.schemaFile),baseUrl:e.baseUrl,prefix:e.prefix,name:e.name}}return _e[t]}var ke=new Proxy({},{get(t,e){if(e in ee)return Ve(e)},ownKeys(){return Object.keys(ee)},getOwnPropertyDescriptor(t,e){if(e in ee)return{enumerable:!0,configurable:!0,value:Ve(e)}}});function qe(t,e,n,r){let o=r.schema.paths;if(e in o){let a=o[e];if(t in a)return{isValid:!0,message:"Valid path and method",operation:a[t],actualPath:e,apiType:n,baseUrl:r.baseUrl}}for(let a of Object.keys(o)){let i=a.replace(/\{[^}]+\}/g,"[^/]+");if(new RegExp(`^${i}$`).test(e)){let p=o[a];if(t in p)return{isValid:!0,message:"Valid path and method",operation:p[t],actualPath:e,apiType:n,baseUrl:r.baseUrl}}}return null}function Ge(t,e,n){let r=t.toLowerCase();if(n!==void 0){let o=ke[n],a=qe(r,e,n,o);return a||{isValid:!1,message:`Path '${e}' not found in ${o.name} schema. Please check the path format or use freee_api_list_paths to see available endpoints.`}}for(let[o,a]of Object.entries(ke)){let i=qe(r,e,o,a);if(i)return i}return{isValid:!1,message:`Path '${e}' not found in any freee API schema. Please check the path format or use freee_api_list_paths to see available endpoints.`}}function Ke(){let t=[];for(let[,e]of Object.entries(ke)){let n=e.schema.paths,r=[];Object.entries(n).forEach(([o,a])=>{let i=Object.keys(a).filter(s=>["get","post","put","delete","patch"].includes(s)).map(s=>s.toUpperCase());i.length>0&&r.push(` ${i.join("|")} ${o}`)}),r.length>0&&t.push(`
|
|
41
43
|
## ${e.name} (${e.baseUrl})
|
|
42
44
|
${r.sort().join(`
|
|
43
45
|
`)}`)}return t.join(`
|
|
44
|
-
`)}function
|
|
46
|
+
`)}function St(t){return typeof t=="object"&&t!==null&&"type"in t&&t.type==="binary"}function xt(t){let e=(t.size/1024).toFixed(2);return`\u30D5\u30A1\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3057\u307E\u3057\u305F
|
|
45
47
|
|
|
46
48
|
\u4FDD\u5B58\u5834\u6240: ${t.filePath}
|
|
47
49
|
MIME\u30BF\u30A4\u30D7: ${t.mimeType}
|
|
48
|
-
\u30B5\u30A4\u30BA: ${e} KB`}var
|
|
50
|
+
\u30B5\u30A4\u30BA: ${e} KB`}var H="service: accounting/hr/invoice/pm/sm",J=c.enum(["accounting","hr","invoice","pm","sm"]).describe("\u5BFE\u8C61\u306Efreee\u30B5\u30FC\u30D3\u30B9");function V(t){return async e=>{try{let{service:n,path:r,query:o,body:a}=e,i=Ge(t,r,n);if(!i.isValid)return l(`\u30D1\u30B9\u691C\u8A3C\u30A8\u30E9\u30FC: ${i.message}
|
|
49
51
|
|
|
50
|
-
\u5229\u7528\u53EF\u80FD\u306A\u30D1\u30B9\u3092\u78BA\u8A8D\u3059\u308B\u306B\u306F freee_api_list_paths \u30C4\u30FC\u30EB\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
|
|
52
|
+
\u5229\u7528\u53EF\u80FD\u306A\u30D1\u30B9\u3092\u78BA\u8A8D\u3059\u308B\u306B\u306F freee_api_list_paths \u30C4\u30FC\u30EB\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);let s=await L(t,i.actualPath,o,a,i.baseUrl);return St(s)?l(xt(s)):l(JSON.stringify(s,null,2))}catch(n){return l(`API\u30EA\u30AF\u30A8\u30B9\u30C8\u30A8\u30E9\u30FC: ${C(n)}`)}}}function We(t){t.tool("freee_api_get",`freee API GET\u3002${H}`,{service:J,path:c.string().describe("API\u30D1\u30B9 (\u4F8B: /api/1/deals, /invoices)"),query:c.record(c.string(),c.unknown()).optional().describe("\u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF (\u30AA\u30D7\u30B7\u30E7\u30F3)")},V("GET")),t.tool("freee_api_post",`freee API POST\u3002${H}`,{service:J,path:c.string().describe("API\u30D1\u30B9 (\u4F8B: /api/1/deals, /invoices)"),body:c.record(c.string(),c.unknown()).describe("\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3"),query:c.record(c.string(),c.unknown()).optional().describe("\u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF (\u30AA\u30D7\u30B7\u30E7\u30F3)")},V("POST")),t.tool("freee_api_put",`freee API PUT\u3002${H}`,{service:J,path:c.string().describe("API\u30D1\u30B9 (\u4F8B: /api/1/deals/123, /invoices/123)"),body:c.record(c.string(),c.unknown()).describe("\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3"),query:c.record(c.string(),c.unknown()).optional().describe("\u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF (\u30AA\u30D7\u30B7\u30E7\u30F3)")},V("PUT")),t.tool("freee_api_delete",`freee API DELETE\u3002${H}`,{service:J,path:c.string().describe("API\u30D1\u30B9 (\u4F8B: /api/1/deals/123)"),query:c.record(c.string(),c.unknown()).optional().describe("\u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF (\u30AA\u30D7\u30B7\u30E7\u30F3)")},V("DELETE")),t.tool("freee_api_patch",`freee API PATCH\u3002${H}`,{service:J,path:c.string().describe("API\u30D1\u30B9 (\u4F8B: /api/1/deals/123)"),body:c.record(c.string(),c.unknown()).describe("\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3"),query:c.record(c.string(),c.unknown()).optional().describe("\u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF (\u30AA\u30D7\u30B7\u30E7\u30F3)")},V("PATCH")),t.tool("freee_api_list_paths","freee API \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u4E00\u89A7\u3002\u8A73\u7D30\u306A\u30AC\u30A4\u30C9\u306Ffreee-mcp skill\u3092\u53C2\u7167\u3002",{},async()=>{let e=Ke();return l(`# freee API \u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u4E00\u89A7${e}
|
|
51
53
|
|
|
52
54
|
\u4F7F\u7528\u4F8B:
|
|
53
55
|
freee_api_get { "service": "accounting", "path": "/api/1/deals", "query": { "limit": 10 } }
|
|
54
56
|
freee_api_get { "service": "invoice", "path": "/invoices" }
|
|
55
|
-
freee_api_post { "service": "accounting", "path": "/api/1/deals", "body": { "issue_date": "2024-01-01", ... } }`}
|
|
57
|
+
freee_api_post { "service": "accounting", "path": "/api/1/deals", "body": { "issue_date": "2024-01-01", ... } }`)})}async function Xe(){let t=await fe(),e=new Rt({name:t.server.name,version:t.server.version});He(e),We(e);try{await Z(),console.error(`OAuth callback server started on http://127.0.0.1:${t.oauth.callbackPort}`)}catch(r){console.error("Failed to start callback server:",r),console.error("OAuth authentication will fall back to manual mode")}let n=new $t;await e.connect(n),console.error("Freee MCP Server running on stdio"),process.on("SIGINT",()=>{U(),process.exit(0)}),process.on("SIGTERM",()=>{U(),process.exit(0)})}import re from"prompts";import Ae from"node:crypto";import Ft from"open";I();import te from"node:fs/promises";import R from"node:path";import q from"node:os";var ne="freee-mcp",Mt={command:"npx",args:["@him0/freee-mcp"]};function Ee(t){let e=q.platform();if(t==="claude-code")return R.join(q.homedir(),".claude.json");if(e==="darwin")return R.join(q.homedir(),"Library","Application Support","Claude","claude_desktop_config.json");if(e==="win32"){let n=process.env.APPDATA||R.join(q.homedir(),"AppData","Roaming");return R.join(n,"Claude","claude_desktop_config.json")}else return R.join(q.homedir(),".config","Claude","claude_desktop_config.json")}function Qe(t){return t==="claude-code"?"Claude Code":"Claude Desktop"}async function Pe(t){try{let e=await te.readFile(t,"utf-8");return JSON.parse(e)}catch{return null}}async function Ye(t,e){let n=R.dirname(t);await te.mkdir(n,{recursive:!0}),await te.writeFile(t,JSON.stringify(e,null,2)+`
|
|
58
|
+
`,"utf-8")}async function Ze(t){let e=Ee(t);try{await te.access(e);let n=await Pe(e);return{path:e,exists:!0,hasFreeeConfig:n?.mcpServers?.[ne]!==void 0}}catch{return{path:e,exists:!1,hasFreeeConfig:!1}}}async function et(t){let e=Ee(t),n=await Pe(e);n||(n={}),n.mcpServers||(n.mcpServers={}),n.mcpServers[ne]={...Mt},await Ye(e,n)}async function tt(t){let e=Ee(t),n=await Pe(e);n?.mcpServers?.[ne]&&(delete n.mcpServers[ne],Object.keys(n.mcpServers).length===0&&delete n.mcpServers,await Ye(e,n))}F();async function Dt(t){let e=await fetch(`${W}/api/1/companies`,{headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"}});if(!e.ok){let r=await w(e);throw new Error(`\u4E8B\u696D\u6240\u4E00\u89A7\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${e.status} ${JSON.stringify(r)}`)}return(await e.json()).companies||[]}async function Ot(){let t=await Promise.resolve().then(()=>(I(),Me)).then(i=>i.loadFullConfig()),e=!!(t.clientId&&t.clientSecret);e&&(console.log("\u65E2\u5B58\u306E\u8A2D\u5B9A\u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F\u3002"),console.log(` \u5909\u66F4\u3057\u306A\u3044\u9805\u76EE\u306F\u305D\u306E\u307E\u307E Enter \u3092\u62BC\u3057\u3066\u304F\u3060\u3055\u3044\u3002
|
|
56
59
|
`)),console.log(`\u30B9\u30C6\u30C3\u30D7 1/3: OAuth\u8A8D\u8A3C\u60C5\u5831\u306E\u5165\u529B
|
|
57
|
-
`);let n=await
|
|
60
|
+
`);let n=await re([{type:"text",name:"clientId",message:"FREEE_CLIENT_ID:",initial:t.clientId||void 0,validate:i=>i.trim()?!0:"CLIENT_ID \u306F\u5FC5\u9808\u3067\u3059"},{type:"password",name:"clientSecret",message:e?"FREEE_CLIENT_SECRET (\u5909\u66F4\u3057\u306A\u3044\u5834\u5408\u306F\u7A7A\u6B04):":"FREEE_CLIENT_SECRET:",validate:i=>e&&!i.trim()||i.trim()?!0:"CLIENT_SECRET \u306F\u5FC5\u9808\u3067\u3059"},{type:"text",name:"callbackPort",message:"FREEE_CALLBACK_PORT:",initial:String(t.callbackPort||M)}]);if(!n.clientId)throw new Error("\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u304C\u30AD\u30E3\u30F3\u30BB\u30EB\u3055\u308C\u307E\u3057\u305F\u3002");let r=n.clientId.trim(),o=n.clientSecret.trim()||t.clientSecret,a=parseInt(n.callbackPort.trim(),10);if(!o)throw new Error("CLIENT_SECRET \u306F\u5FC5\u9808\u3067\u3059\u3002");return process.env.FREEE_CLIENT_ID=r,process.env.FREEE_CLIENT_SECRET=o,process.env.FREEE_CALLBACK_PORT=String(a),console.log(`
|
|
58
61
|
\u8A8D\u8A3C\u60C5\u5831\u3092\u53D7\u3051\u53D6\u308A\u307E\u3057\u305F\u3002
|
|
59
|
-
`),{clientId:r,clientSecret:o,callbackPort:
|
|
60
|
-
`),console.log("\u30D6\u30E9\u30A6\u30B6\u3067\u8A8D\u8A3C\u30DA\u30FC\u30B8\u3092\u958B\u304D\u307E\u3059..."),await Promise.resolve().then(()=>(
|
|
62
|
+
`),{clientId:r,clientSecret:o,callbackPort:a}}async function jt(){console.log(`\u30B9\u30C6\u30C3\u30D7 2/3: OAuth\u8A8D\u8A3C
|
|
63
|
+
`),console.log("\u30D6\u30E9\u30A6\u30B6\u3067\u8A8D\u8A3C\u30DA\u30FC\u30B8\u3092\u958B\u304D\u307E\u3059..."),await Promise.resolve().then(()=>(k(),Fe)).then(i=>i.loadConfig()),await Z();let t=Ae.randomBytes(32).toString("base64url"),e=Ae.createHash("sha256").update(t).digest("base64url"),n=Ae.randomBytes(16).toString("base64url"),r=Q(e,n,j());console.log(`
|
|
61
64
|
\u8A8D\u8A3CURL: ${r}
|
|
62
|
-
`),await
|
|
63
|
-
`);let o=
|
|
64
|
-
`),{accessToken:s.access_token,refreshToken:s.refresh_token}}finally{o.removeCliAuthHandler(n)}}async function
|
|
65
|
-
`),console.log("\u4E8B\u696D\u6240\u4E00\u89A7\u3092\u53D6\u5F97\u4E2D...");let e=await
|
|
65
|
+
`),await Ft(r),console.log("\u30D6\u30E9\u30A6\u30B6\u3067\u8A8D\u8A3C\u3092\u5B8C\u4E86\u3057\u3066\u304F\u3060\u3055\u3044..."),console.log(`\u8A8D\u8A3C\u304C\u5B8C\u4E86\u3059\u308B\u3068\u81EA\u52D5\u7684\u306B\u6B21\u306E\u30B9\u30C6\u30C3\u30D7\u306B\u9032\u307F\u307E\u3059\u3002
|
|
66
|
+
`);let o=Be(),a=new Promise((i,s)=>{let p=setTimeout(()=>{s(new Error("\u8A8D\u8A3C\u304C\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F\uFF085\u5206\uFF09"))},G);o.registerCliAuthHandler(n,{resolve:m=>{clearTimeout(p),i(m)},reject:m=>{clearTimeout(p),s(m)},codeVerifier:t})});try{let i=await a;console.log("\u8A8D\u8A3C\u30B3\u30FC\u30C9\u3092\u53D7\u3051\u53D6\u308A\u307E\u3057\u305F\u3002"),console.log("\u30C8\u30FC\u30AF\u30F3\u3092\u53D6\u5F97\u4E2D...");let s=await Y(i,t,j());return console.log(`\u30C8\u30FC\u30AF\u30F3\u3092\u53D6\u5F97\u3057\u307E\u3057\u305F\u3002
|
|
67
|
+
`),{accessToken:s.access_token,refreshToken:s.refresh_token}}finally{o.removeCliAuthHandler(n)}}async function Ut(t){console.log(`\u30B9\u30C6\u30C3\u30D7 3/3: \u30C7\u30D5\u30A9\u30EB\u30C8\u4E8B\u696D\u6240\u306E\u9078\u629E
|
|
68
|
+
`),console.log("\u4E8B\u696D\u6240\u4E00\u89A7\u3092\u53D6\u5F97\u4E2D...");let e=await Dt(t);if(e.length===0)throw new Error("\u5229\u7528\u53EF\u80FD\u306A\u4E8B\u696D\u6240\u304C\u3042\u308A\u307E\u305B\u3093\u3002");let n=await re({type:"select",name:"companyId",message:"\u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u4E8B\u696D\u6240\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044:",choices:e.map(o=>({title:`${o.display_name||o.name} (ID: ${o.id}) - ${o.role}`,value:o.id}))});if(!n.companyId)throw new Error("\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u304C\u30AD\u30E3\u30F3\u30BB\u30EB\u3055\u308C\u307E\u3057\u305F\u3002");let r=e.find(o=>o.id===n.companyId);if(!r)throw new Error(`\u9078\u629E\u3057\u305F\u4E8B\u696D\u6240\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ID ${n.companyId}`);return console.log(`
|
|
66
69
|
${r.display_name||r.name} \u3092\u9078\u629E\u3057\u307E\u3057\u305F\u3002
|
|
67
|
-
`),{selected:{id:r.id,name:r.name,displayName:r.display_name||r.name,role:r.role},all:e}}async function
|
|
68
|
-
`),console.log(
|
|
69
|
-
`),console.log(
|
|
70
|
-
`)
|
|
71
|
-
`),console.log(
|
|
72
|
-
|
|
73
|
-
`),console.log("\u8A8D\u8A3C\u60C5\u5831\u306F ~/.config/freee-mcp/config.json \u306B\u4FDD\u5B58\u3055\u308C\u307E\u3057\u305F\u3002"),console.log("\u30C8\u30FC\u30AF\u30F3\u306F ~/.config/freee-mcp/tokens.json \u306B\u4FDD\u5B58\u3055\u308C\u307E\u3057\u305F\u3002"),console.log(`Claude desktop\u3092\u518D\u8D77\u52D5\u3059\u308B\u3068\u3001freee-mcp\u304C\u5229\u7528\u53EF\u80FD\u306B\u306A\u308A\u307E\u3059\u3002
|
|
74
|
-
`)}async function De(){console.log(`
|
|
70
|
+
`),{selected:{id:r.id,name:r.name,displayName:r.display_name||r.name,role:r.role},all:e}}async function Lt(t,e,n){let r={clientId:t.clientId,clientSecret:t.clientSecret,callbackPort:t.callbackPort,defaultCompanyId:String(e.id),currentCompanyId:String(e.id),companies:{}};n.forEach(o=>{r.companies[String(o.id)]={id:String(o.id),name:o.display_name||o.name,description:`Role: ${o.role}`,addedAt:Date.now(),lastUsed:o.id===e.id?Date.now():void 0}}),await T(r),console.log(`\u8A2D\u5B9A\u60C5\u5831\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002
|
|
71
|
+
`),console.log("\u8A8D\u8A3C\u60C5\u5831\u306F ~/.config/freee-mcp/config.json \u306B\u4FDD\u5B58\u3055\u308C\u307E\u3057\u305F\u3002"),console.log(`\u30C8\u30FC\u30AF\u30F3\u306F ~/.config/freee-mcp/tokens.json \u306B\u4FDD\u5B58\u3055\u308C\u307E\u3057\u305F\u3002
|
|
72
|
+
`)}async function nt(t){let e=Qe(t),n=await Ze(t);if(n.hasFreeeConfig){let{action:r}=await re({type:"select",name:"action",message:`${e} \u306B freee-mcp \u304C\u8A2D\u5B9A\u6E08\u307F\u3067\u3059\u3002\u3069\u3046\u3057\u307E\u3059\u304B?`,choices:[{title:"\u305D\u306E\u307E\u307E (\u5909\u66F4\u306A\u3057)",value:"keep"},{title:"\u524A\u9664\u3059\u308B",value:"remove"}],initial:0});r==="remove"?(await tt(t),console.log(` \u2713 ${e} \u304B\u3089 freee-mcp \u3092\u524A\u9664\u3057\u307E\u3057\u305F\u3002`)):console.log(` - ${e} \u306E\u8A2D\u5B9A\u306F\u5909\u66F4\u3057\u307E\u305B\u3093\u3002`)}else{let{shouldAdd:r}=await re({type:"confirm",name:"shouldAdd",message:`${e} \u306B freee-mcp \u3092\u8FFD\u52A0\u3057\u307E\u3059\u304B?`,initial:!0});r?(await et(t),console.log(` \u2713 ${e} \u306B freee-mcp \u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F\u3002`),console.log(` \u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB: ${n.path}`)):console.log(` - ${e} \u3078\u306E\u8FFD\u52A0\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F\u3002`)}}async function Nt(){console.log(`=== MCP\u8A2D\u5B9A ===
|
|
73
|
+
`),console.log(`Claude Code / Claude Desktop \u306B freee-mcp \u3092\u8A2D\u5B9A\u3067\u304D\u307E\u3059\u3002
|
|
74
|
+
`),await nt("claude-code"),console.log(""),await nt("claude-desktop"),console.log(""),console.log("\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5B8C\u4E86!"),console.log(`\u5909\u66F4\u3092\u53CD\u6620\u3059\u308B\u306B\u306F\u3001Claude Code / Claude Desktop \u3092\u518D\u8D77\u52D5\u3057\u3066\u304F\u3060\u3055\u3044\u3002
|
|
75
|
+
`)}async function rt(){console.log(`
|
|
75
76
|
=== freee-mcp Configuration Setup ===
|
|
76
77
|
`),console.log("\u3053\u306E\u30A6\u30A3\u30B6\u30FC\u30C9\u3067\u306F\u3001freee-mcp\u306E\u8A2D\u5B9A\u3068\u8A8D\u8A3C\u3092\u5BFE\u8A71\u5F0F\u3067\u884C\u3044\u307E\u3059\u3002"),console.log(`freee OAuth\u8A8D\u8A3C\u60C5\u5831\u304C\u5FC5\u8981\u3067\u3059\u3002
|
|
77
|
-
`);try{let t=await
|
|
78
|
+
`);try{let t=await Ot(),e=await jt(),{selected:n,all:r}=await Ut(e.accessToken);await Lt(t,n,r),await Nt()}catch(t){t instanceof Error?console.error(`
|
|
78
79
|
Error: ${t.message}`):console.error(`
|
|
79
|
-
\u8A2D\u5B9A\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F:`,t),process.exit(1)}finally{
|
|
80
|
+
\u8A2D\u5B9A\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F:`,t),process.exit(1)}finally{U()}}var Bt=async()=>{let e=process.argv.slice(2)[0];if(e==="configure"){await rt();return}e&&e!=="client"&&(console.error(`Unknown subcommand: ${e}`),console.error("Usage: freee-mcp [configure]"),console.error(" configure - Interactive configuration setup"),process.exit(1)),console.error("Starting freee MCP server"),await Xe()};Bt().catch(t=>{console.error("Fatal error:",t),process.exit(1)});
|
package/dist/auth/server.d.ts
CHANGED
|
@@ -26,29 +26,9 @@ export declare class AuthenticationManager {
|
|
|
26
26
|
getCliAuthHandler(state: string): CliAuthHandler | undefined;
|
|
27
27
|
removeCliAuthHandler(state: string): void;
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
30
|
-
* CallbackServer - manages the OAuth callback HTTP server
|
|
31
|
-
* Encapsulates server state that was previously global
|
|
32
|
-
*/
|
|
33
|
-
export declare class CallbackServer {
|
|
34
|
-
private server;
|
|
35
|
-
private port;
|
|
36
|
-
private authManager;
|
|
37
|
-
constructor(authManager: AuthenticationManager);
|
|
38
|
-
getRedirectUri(): string;
|
|
39
|
-
getPort(): number | null;
|
|
40
|
-
isRunning(): boolean;
|
|
41
|
-
start(): Promise<void>;
|
|
42
|
-
stop(): void;
|
|
43
|
-
private checkPortAvailable;
|
|
44
|
-
private findAvailablePort;
|
|
45
|
-
private handleCallback;
|
|
46
|
-
}
|
|
47
29
|
export declare function getActualRedirectUri(): string;
|
|
48
|
-
export declare function getActualCallbackPort(): number | null;
|
|
49
30
|
export declare function startCallbackServer(): Promise<void>;
|
|
50
31
|
export declare function registerAuthenticationRequest(state: string, codeVerifier: string): void;
|
|
51
32
|
export declare function stopCallbackServer(): void;
|
|
52
33
|
export declare function getDefaultAuthManager(): AuthenticationManager;
|
|
53
|
-
export declare function getDefaultCallbackServer(): CallbackServer;
|
|
54
34
|
export {};
|
package/dist/auth/tokens.d.ts
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const TokenDataSchema: z.ZodObject<{
|
|
3
|
+
access_token: z.ZodString;
|
|
4
|
+
refresh_token: z.ZodString;
|
|
5
|
+
expires_at: z.ZodNumber;
|
|
6
|
+
token_type: z.ZodString;
|
|
7
|
+
scope: z.ZodString;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
access_token: string;
|
|
10
|
+
refresh_token: string;
|
|
11
|
+
expires_at: number;
|
|
12
|
+
token_type: string;
|
|
13
|
+
scope: string;
|
|
14
|
+
}, {
|
|
15
|
+
access_token: string;
|
|
16
|
+
refresh_token: string;
|
|
17
|
+
expires_at: number;
|
|
18
|
+
token_type: string;
|
|
19
|
+
scope: string;
|
|
20
|
+
}>;
|
|
1
21
|
export interface TokenData {
|
|
2
22
|
access_token: string;
|
|
3
23
|
refresh_token: string;
|