こんにちは
株式会社BTMでエンジニアをしている島谷です。
現在、他社様と副業契約を結んでおり、毎月月初に請求書を作成して送付しています。
その作業が毎回手間に感じられ、「もっとスマートに作成できないか」と考えるようになりました。
そこで、話題の Model Context Protocol(以下、MCP)を使って、請求書作成から送付までの自動化に試みました。
https://modelcontextprotocol.io/introduction
これまで、月初になると手作業で次のような手順を踏んでいました。
流れ自体は単純ですが、シートへの入力ミスを防ぐために何度も確認していることから、10〜15分ほど取られていました。
そこで、「MCPサーバーを使って各サービスを連携させれば、AIチャットに『先月分の請求書を作って送って』とひと言指示するだけで、一連の処理を完結できるのではないか」と考えました。
結果として、請求書作成から送付までを自動化できたので、成果物と自動化までの過程を紹介します。
わかりにくいですが、プロンプトを投げた後、以下の処理を自動で行ってます。
請求書の内容は全てサンプルとなっています。
デモ用のためメール送信は行わず、宛先を空欄にし下書き保存しています。
toggle-toolsのMCPサーバーを使って以下のことをしてほしい。・togglApiTokenはmcp.jsonに記載している値を対象とする・2025年4月の合計作業時間を取得してGoogle SheetsのMCPサーバーを使って以下のことをしてほしい。・spreadsheetIdはmcp.jsonに記載している値を対象とする・「template」シートを複製してGAS ToolboxのMCPサーバーを使って以下のことをしてほしい。・spreadsheetIdやrenameSheetBaseUrlはmcp.jsonに記載している値を対象とする・上記で複製したシートの名前を「25.05」に変更して再度、Google SheetsのMCPサーバーを使って以下のことをしてほしい。・「25.05」シートのシートIDを取得して・上記のシートIDを使用して、K10セルに合計作業時間をhh:mm:ss形式でセットして・上記のシートIDを使用して、K17セルに「'2025年4月」をセットして・上記のシートIDを使用して、K18セルに「2025年5月1日」をセットして・上記のシートIDを使用して、K19セルに「2025年5月31日」をセットしてGAS ToolboxのMCPサーバーを使って以下のことをしてほしい。・createSheetPdfBaseUrlはmcp.jsonに記載している値を対象とする・上記で複製したシートをPDFにしてほしい。ファイル名は「請求書(25.04)」としてGmailのMCPサーバーを用いて、以下のことをしてほしい。・宛先は空欄とする・上記のダウンロードリンクを添付ファイルとしてメールを下書き保存で作成して・添付ファイル名は「請求書(25.04)」として
アーキテクチャは以下の通りです。
作成したソースコードは以下の通りです。
https://github.com/kazuya-shimatani/toggle-mcp-with-vercel-functions
https://github.com/kazuya-shimatani/gas-toolbox-with-vercel-functions
まず、自動化する上で関わってくる、MCPホストとMCPクライアント、MCPサーバーの役割について簡単に説明しておきます。
名称 | 説明 |
---|---|
MCPホスト | “LLMを動かすアプリ本体” で、ユーザーからの入力を受け取り、内部でMCPクライアントを起動します。 |
MCPクライアント | ホストとサーバーの間に立つプロキシで、ホスト側からのリクエストをサーバーへ送り、返ってきたレスポンスをホストに中継する役目を担います。 |
MCPサーバー | クライアントに対して特定のデータソースやツールへのアクセスを提供します。 |
今回、CursorをMCPホストとして使用してみます。
前月分の作業時間を取得できるようにするため、Toggl TrackのMCPサーバーが公開されているか調査しました。
調査の結果、Pipedreamにありました。
Pipedreamは、 2,500を超える統合アプリすべてに専用のMCP(モデルコンテキストプロトコル)サーバーを提供しています。これらのサーバーにより、ClaudeのようなAIアシスタントは、標準化された通信プロトコルを介して数千ものAPIに安全にアクセスし、対話することができます。そして、お客様のアカウントに接続することで、現実世界のタスクを実行できます。
MCPサーバーの公開URLとOAuthトークンをコピーして貼り付けるだけでホストと接続できるため、導入がとても手軽です。そこで今回の自動化においては、PipedreamのMCPサーバーを採用することにしました。
Toggl TrackのMCPサーバーの仕様は以下の通りです。
Available toolsに「Get Time Entries」アクションがあるので、こちらが使えそうです。
Toggl Trackが提供している「Get Time Entries」は 指定した期間に記録された作業時間を一覧で取得するエンドポイントです。
たとえば、クエリパラメータで「2025年4月1日 〜 4月30日」を指定すると、その期間に記録されたすべての作業時間を取得できます。
https://engineering.toggl.com/docs/api/time_entries/
しかし、この記事を執筆している時点では、下記のGitHub Issuesにもあるように、MCPサーバー側の「Get Time Entries」アクションはリクエストパラメータ(開始日・終了日など)を受け付けません。そのため「2025年4月1日 〜 4月30日」といった期間を指定しても絞り込めず、期間を限定した作業時間を取得できないことがわかりました。
https://github.com/PipedreamHQ/pipedream/issues/16539
!2025年5月16日時点で上記IssueはClose済です。
そこで、上記を解決するMCPサーバーを一旦自作することにしました。
MCPサーバーを作成するのに色々調査していたら、以下リポジトリを見つけたので、これを使って自作してみます。
https://github.com/vercel-labs/mcp-on-vercel
server.tsを以下のように編集します。
import{ z}from"zod";import{ initializeMcpApiHandler}from"../lib/mcp-api-handler";import{ TogglTrackClient}from"../lib/toggle-track";const handler=initializeMcpApiHandler((server)=>{ server.tool("getTimeEntries",{// ツールとして、「getTimeEntries」を定義します。 togglApiToken: z.string(),// Toggl TrackのAPIトークン startDate: z.string(),// 開始日 endDate: z.string() // 終了日},async({ togglApiToken, startDate, endDate})=>{if(!togglApiToken){thrownewError("Unauthorized: Invalid Toggle API key");}const togglTrackClient=newTogglTrackClient(togglApiToken);// 開始日~終了日の期間に記録されたすべての作業時間を取得const data=await togglTrackClient.getTotalMonthlyDuration(startDate, endDate);return{ content:[{ type:"text", text:`Result:${JSON.stringify(data)}`}],};});},{ capabilities:{ tools:{// ツールの説明 getTimeEntries:{ description:"Get span time entries",},},},});exportdefault handler;
以下のファイルを新規作成し、Toggl Trackの「Get Time Entries」を呼び出すように実装します。
import axiosfrom'axios';constTOGGL_TRACK_API_BASE_URL='https://api.track.toggl.com/api/v9';// 省略 //exportclassTogglTrackClient{private apiToken:string;constructor(apiToken:string){this.apiToken= apiToken;}privategetAuthHeader(){return{'Content-Type':"application/json", Authorization:`Basic${Buffer.from(`${this.apiToken}:api_token`).toString('base64')}`,};}asyncgetTotalMonthlyDuration(inputStartDate?:string, inputEndDate?:string){const now=newDate();const start= inputStartDate? inputStartDate:`${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-01`;const end= inputEndDate? inputEndDate:`${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${newDate(now.getFullYear(), now.getMonth()+1,0).getDate()}`;const{ startUTC}=toJSTDayRangeUTC(start);const{ endUTC: endOfEndUTC}=toJSTDayRangeUTC(end);console.log(`startDate:${startUTC}`);console.log(`endDate:${endOfEndUTC}`);// Toggl Trackの「Get Time Entries」を呼び出しconst response=await axios.get(`${TOGGL_TRACK_API_BASE_URL}/me/time_entries`,{ headers:this.getAuthHeader(), params:{ start_date: startUTC, end_date: endOfEndUTC,},});// 作業時間の合計(秒)を算出して、hh:mm:ss形式に変換するconst totalSeconds=this.getTotalDurationSeconds(response.data);const formattedTime=this.formatSecondsToHHMMSS(totalSeconds);return formattedTime;}// 省略 //}
Vercelへログインし、デプロイ対象となるリポジトリを選択し、「Import」をクリックします。
デプロイが完了すると、以下の通りになります。
Cursor > 基本設定 > Cursor Settingsをクリックします。
「+ Add new global MCP server」をクリックします。
mcp.jsonが作成されるので、以下のように記述します。
{"mcpServers":{"toggle-tools":{"url":"https://toggle-mcp-with-vercel-functions.vercel.app/sse","togglApiToken":"{{ 自身のtogglのAPIトークン }}"}},"default":"toggle-tools"}
Cursor SettingsのMCP Serversに表示されればOKです。
実際に、Toggl Trackから合計作業時間を取得できるか試してみます。
無事に取得できました。
次に、Googleスプレッドシートに対して以下の処理を自動化します。
まず、Toggl Trackの時と同様に、PipedreamにGoogleスプレッドシートのMCPサーバーが存在するか検索したところ、ありました。
Available toolsに「Copy Worksheet」アクションと「Update Cell」アクションがあるので、シートの複製やセルへの入力は、これらが使えそうです。
PDFとしてエクスポートできるアクションはないため、PDF出力機能を自前で用意し、MCPサーバーから呼び出せるように設定する必要があります。
さらに、「Copy Worksheet」アクションはシートをそのまま複製する機能で、複製後のシート名を変更できません。
たとえば「template」シートをコピーすると、自動的に「template のコピー」という名前で複製されます。
つまり、複製後のシート名を変えたい場合は、シート名を変更する機能を同様に用意しておき、こちらもMCPサーバー経由で呼び出せるようにしておく必要があります。
今回はGoogleスプレッドシートを使っているため、これら二つの機能(PDF出力とシート名の変更)の実装には、親和性の高いGoogle Apps Script(GAS)を採用しました。
「Sign In」ボタンをクリックし、Pipedreamにログインした後、「Connect account」ボタンをクリックします。
クリック後、以下のような画面になります。
Cursorのmcp.jsonに「MCP server config」の内容を追記します。
{"mcpServers":{"toggle-tools":{"url":"https://toggle-mcp-with-vercel-functions.vercel.app/sse","togglApiToken":"{{ 自身のToggl TrackのAPIトークン }}"},"Google Sheets":{"url":"https://mcp.pipedream.net/{{ 機密トークン }}/google_sheets","spreadsheetId":"{{ 対象のスプレッドシートのID }}"}},"default":"toggle-tools"}
spreadsheetIdを定義している理由は、どのスプレッドシートに対して操作するかを指定するためです。
Cursor SettingsのMCP Serversに「Google Sheets」が表示されればOKです。
Toggl Trackの時と同様に、これを使って自作します。
https://github.com/vercel-labs/mcp-on-vercel
シート名の変更やPDF出力といったGASの処理が中心となるため、これらをひとまとめに扱えるMCPサーバー「GAS Toolbox」として作成していきます。
server.tsを以下のように編集します。
import{ z}from"zod";import{ initializeMcpApiHandler}from"../lib/mcp-api-handler";const{ renameSheetViaGas}=require("../lib/rename-sheet");const handler=initializeMcpApiHandler((server)=>{ server.tool(// ツールとして、「renameSheet」を定義します。"renameSheet",{ renameSheetBaseUrl: z.string(),// GASのWeb Apps URL spreadsheetId: z.string(),// 対象のスプレッドシートID oldSheetName: z.string(),// 変更前のシート名 newSheetName: z.string(),// 変更後のシート名},async({ renameSheetBaseUrl, spreadsheetId, oldSheetName, newSheetName})=>{try{// シート名を変更するGASを呼び出しますconst response=awaitrenameSheetViaGas({ renameSheetBaseUrl, spreadsheetId, oldSheetName, newSheetName});return{ content:[{ type:"text", text:`シート名を変更しました:${oldSheetName} ->${newSheetName}`},],};}catch(e:any){return{ content:[{ type:"text", text:`Error:${e.message}`},],};}});},{ capabilities:{ tools:{ renameSheet:{ description:"Rename a Google Sheet",}},},});exportdefault handler;
以下のファイルを新規作成し、ツール名を変更するGASを呼び出すように実装します。
const renameSheetFetch=require('node-fetch');/** * GAS Web APIのdoPostでシート名を変更 */asyncfunctionrenameSheetViaGas({ renameSheetBaseUrl, spreadsheetId, oldSheetName, newSheetName}):Promise<string>{const headers={'Content-Type':'application/json'};const body=JSON.stringify({ spreadsheetId, oldSheetName, newSheetName});const res=awaitrenameSheetFetch(renameSheetBaseUrl,{ method:'POST', headers, body,});if(!res.ok)thrownewError(`GAS API error:${res.statusText}`);return res.text();}module.exports={ renameSheetViaGas};
server.tsを以下のように編集します。
import{ z}from"zod";import{ initializeMcpApiHandler}from"../lib/mcp-api-handler";const{ fetchPdfFromGas}=require("../lib/sheet-to-pdf");const handler=initializeMcpApiHandler((server)=>{ server.tool("createSheetPdf",// ツールとして、「createSheetPdf」を定義します。{ createSheetPdfBaseUrl: z.string(),// GASのWeb Apps URL spreadsheetId: z.string(),// 対象のスプレッドシートID sheetName: z.string(),// PDF出力対象のシート名 downloadFileName: z.string(),// PDFファイル名},async({ createSheetPdfBaseUrl, spreadsheetId, sheetName, downloadFileName})=>{try{// PDF出力するGASを呼び出しますconst downloadUrl=awaitfetchPdfFromGas({ createSheetPdfBaseUrl, spreadsheetId, sheetName, downloadFileName});return{ content:[{ type:"text", text:`ダウンロードリンク:${downloadUrl}`},],};}catch(e:any){return{ content:[{ type:"text", text:`Error:${e.message}`},],};}});},{ capabilities:{ tools:{ createSheetPdf:{ description:"Create a PDF from a Google Sheet",}},},});exportdefault handler;
以下のファイルを新規作成し、PDF出力するGASを呼び出すように実装します。
const nodeFetch=require('node-fetch');/** * GAS Web APIのdoGetでPDFを生成およびダウンロードリンクを取得 */asyncfunctionfetchPdfFromGas({ createSheetPdfBaseUrl, spreadsheetId, sheetName, downloadFileName}){const url=`${createSheetPdfBaseUrl}?spreadsheetId=${encodeURIComponent(spreadsheetId)}&sheetName=${encodeURIComponent(sheetName)}&downloadFileName=${encodeURIComponent(downloadFileName)}`;const headers={};const res=awaitnodeFetch(url,{ headers});if(!res.ok)thrownewError(`GAS API error:${res.statusText}`);return res.text();}module.exports={ fetchPdfFromGas};
本記事ではGASそのものの作成手順、デプロイ設定、スコープの付与方法などの詳細説明は割愛します。
ここで必要なのは、次の2本のエンドポイントだけです。
いずれも「ウェブアプリ」として公開し、MCPサーバー側でHTTPS URLを登録しておけば、本記事のフローは動きます。
GAS の基本的な説明は公式ドキュメントにあるとおりですので、そちらを参照してください。
https://developers.google.com/apps-script?hl=ja
同様に、Vercelへログインし、デプロイ対象となるリポジトリを選択し、「Import」をクリックします。
デプロイが完了すると、以下の通りになります。
mcp.jsonへ以下のように記述します。
{"mcpServers":{"toggle-tools":{"url":"https://toggle-mcp-with-vercel-functions.vercel.app/sse","togglApiToken":"{{ 自身のToggl TrackのAPIトークン }}"},"Google Sheets":{"url":"https://mcp.pipedream.net/{{ 機密トークン }}/google_sheets","spreadsheetId":"{{ 対象のスプレッドシートのID }}"},"GAS Toolbox":{"url":"https://gas-toolbox-with-vercel-functions.vercel.app/sse","spreadsheetId":"{{ 対象のスプレッドシートのID }}","createSheetPdfBaseUrl":"{{ PDF出力用GASのWeb Apps URL }}","renameSheetBaseUrl":"{{ シート名変更用GASのWeb Apps URL }}"}},"default":"toggle-tools"}
Cursor SettingsのMCP Serversに「GAS Toolbox」が表示されればOKです。
実際に、Googleスプレッドシートに対して操作できるか試してみます。
まずは、シートの複製を行ってみます。
スプレッドシートを確認すると、複製されていることが確認できました。
次に、シート名の変更を行なってみます。
スプレッドシートを確認すると、シート名が変更されていることが確認できました。
次に、指定したセルに対して作業時間や日付等の入力を行なってみます。
スプレッドシートを確認すると、指定したセルに作業時間や日付等が入力されていることが確認できました。
次に、PDF出力を行なってみます。
出力したPDFはGoogle Driveに保存されていることが確認できました。
PDFも問題なく開くことができます。
最後に、出力したPDFをGmailで担当者へ自動送信できるようにします。
PipedreamにGmailのMCPサーバーが存在するので、こちらを使用します。
メールの下書き保存や送信は以下のアクションになります。
「Connect account」ボタンをクリックした後に表示される、「MCP server config」の内容をCursorのmcp.jsonに追記します。
{"mcpServers":{"toggle-tools":{"url":"https://toggle-mcp-with-vercel-functions.vercel.app/sse","togglApiToken":"{{ 自身のToggl TrackのAPIトークン }}"},"Google Sheets":{"url":"https://mcp.pipedream.net/{{ 機密トークン }}/google_sheets","spreadsheetId":"{{ 対象のスプレッドシートのID }}"},"GAS Toolbox":{"url":"https://gas-toolbox-with-vercel-functions.vercel.app/sse","spreadsheetId":"{{ 対象のスプレッドシートのID }}","createSheetPdfBaseUrl":"{{ PDF出力用GASのWeb Apps URL }}","renameSheetBaseUrl":"{{ シート名変更用GASのWeb Apps URL }}"},"Gmail":{"url":"https://mcp.pipedream.net/{{ 機密トークン }}/gmail"}},"default":"toggle-tools"}
Cursor SettingsのMCP Serversに「Gmail」が表示されればOKです。
実際に、メール送信(下書き保存)できるか確認してみます。
無事に下書き保存されました。
添付ファイル付きであることも確認できました。
各サービスとの連携設定が済んだら、これまで使ったプロンプトをひとつにまとめて送るだけで、請求書作成から送付までを一括で実行できます。
ただ、Cursorの設定がデフォルトのままだと、Agentはツールを実行するたびユーザに確認を求めてきます。
Cursor Settings → Features で 「Enable auto-run mode」 にチェックを入れておくと、
Agentでのコマンド実行に対して確認を行わず実行するようになります。
MCPサーバーを活用し、請求書の作成から送付までを自動化する過程を紹介しました。
公開されている MCPサーバーを組み合わせるだけで、「データを取得して別サービスへ渡す」といった連携フローが簡単に構築できます。
今回、シート名の変更やPDF生成など、まだ用意されていない機能は自前で実装しましたが、もしこれらも公式サーバーに追加されれば、完全ノーコードで済むでしょう。
とりわけ魅力なのは導入障壁の低さです。
公開サーバーのセットアップ手順に沿って URLとトークンを登録するだけで連携が完了し、あとは「こうしたい」を自然言語で指示すれば、LLMが適切なツールを選んで実行してくれます。
MCPはまだ新しい仕組みですが、今後の発展を大いに期待しています。
以上です、最後までご覧いただきありがとうございました。
!株式会社BTMではエンジニアの採用をしております。
ご興味がある方はぜひコチラをご覧ください。