FileMakerのDBをLLMで操作するMCPサーバーを作ってみた


## はじめに

MCPサーバーを使って、CursorなどのLLMクライアントからwebサービスやローカルデータを接続するというトレンドが急速に広がっています。毎日のようにMCP関連の話題が取り上げられている状況です。

FileMakerにおいても、「在庫が10個以下の商品を教えて」といった自然言語でデータベースを操作できるようになるはずですが、残念ながらClarisからは2025年4月上旬の時点でアナウンスはありません。

そこで、試験的に簡単なMCPサーバーを作ってみることにしました。自分自身もぼんやりとしか理解していないため、仕組みを勉強する良い機会になると考えています。

## MCPサーバーって何？

MCPサーバーは、Cursor, ClineなどのLLMクライアントと様々なサービスを繋ぐための仕組みです。例えば、NotionやGitHubなどのサービスでは、すでにMCPサーバーを通じて自然言語での操作が可能になっています。簡単に言うと、LLMモデルが理解できるように橋渡し（通訳）してくれる技術です。

## 現時点で実装できた機能
:::note warn
注意
あくまで"多分こうだろうな"と試行錯誤して実装したので、見当違いな実装も多分に含まれていると思います。機能しているから現状は良しとしています。（公開されてまだ半年の新しい技術のせいかAIもあまりうまく回答してくれなかったのです）
:::


1. FileMakerへのログイン
   - Data APIを使用してFileMakerに接続し、tokenを取得する

2. 簡単な検索機能(and検索のみ)
   - Chatの中の質問ワードから対象フィールドを類推し、それを元に必要なレコード情報を答えてくれる

サンプルとして以下のようなフィールドデータを持つ走行距離の記録データベースを対象にしてみました。

```
[
  {
    "name": "date",
    "type": "normal",
    "displayType": "editText",
    "result": "date",
    "global": false
  },
  {
    "name": "startCounter",
    "type": "normal",
    "displayType": "editText",
    "result": "number",
    "global": false
  },
  {
    "name": "endCounter",
    "type": "normal",
    "displayType": "editText",
    "result": "number",
    "global": false
  },
  {
    "name": "distance",
    "type": "calculation",
    "displayType": "editText",
    "result": "number",
    "global": false
  }
]

```

たとえば、以下のような操作が可能です：
- 「4月1日の走行データを見せて」
- 結果としてレコード情報が見やすいように整形されて表示される

### 実際の会話の様子

*プロンプト*
![ss1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171037/74cc4962-57d3-4fd8-b092-623d0630e340.png)

*AIからの回答*
![ss1_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171037/e130ef36-67f1-48c2-aa51-c847121b954c.png)
このように会話中でtoolを指示しなくても、toolを使い分けてくれています。

それぞれのtoolの実行結果を見ると、

*get_token*
![ss2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171037/1bbda331-58ef-430c-930f-1e0573b59ea4.png)
トークンを取得した後に次のtoolに引数として勝手にセットしてくれています

get_layout_metadataのresultにはちゃんとフィールドメタデータが取得できています。
![ss3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171037/00f2ad65-d35b-4bb5-9fde-535e4dc81274.png)

*find_records*
![ss4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171037/994e3b83-308b-43a1-afba-e7aa6051d54d.png)
検索ワードから対象となるフィールドを推論して（フィールドメタデータから推論している）、引数のfieldNameに"date"とセットしてくれています。これが重要で、フィールドメタデータがないと"日付","Date"など適当なfieldNameを与えてしまいます

*resultとしてdataAPIの結果が取得できています*
![ss5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171037/eb951a63-57ce-4a1f-81c3-e922c6368be7.png)



## 実装のポイント

[公式のチュートリアル](https://modelcontextprotocol.io/quickstart/server)は天気予報サービスのAPIを叩くMCPサーバーを例として提供しており、これを参考にしてFileMakerのdata APIを利用しています。

作成したコードは[GitHubに置いてます](https://github.com/calafate40/MCP-FileMaker/tree/main)

### 関数をtoolとして登録する際の重要なポイント：

- 一つ一つのtoolは単機能にしておくこと
- AI側が自動で勘案してtoolを選択するので、tool名は機能を明確に表現すること
- toolの第二引数にどんな機能のtoolかを詳しく記述しておくこと
- 引数のdescribeも明確に記述する
- tool間の連携（処理の順番）も列挙しておく

例えば、`tool.find-records`の場合：
1. `get-token`を実行してtokenを取得
2. `get-layout-metadata`に1で得たtokenを引数で受け取って、fieldDataを取得（このフィールド一覧が推論に大切）
3. ユーザーから与えられた検索文字列から、どのフィールドを使って検索するか推論し`find-records`の引数として渡す
4. 数値や日付の範囲指定の書き方は、"04-01-2025...04-30-2025",2...9のように検索文字列を組み立てて下さい（FileMaker独自の検索記法を伝えておく）

## 現状の課題

この実装を通じて、以下のような課題が見えてきました：

- LLMクライアントが、優先的にターゲットとなるMCPサーバーを探してくれないため、最初にMCPサーバーを指定する必要がある（system.roleに記述することで解決できる可能性あり）
- 意外とたくさんのケースを想定したdescriptionを記述しておかないとうまく回答できない
- toolはとにかく細かく分割する必要がある（一つのtoolに機能を盛り込まない）
- まだクライアントソフト側がこなれていないため、会話が噛み合わないことがある（これは早晩解決するでしょう）

## 今後の展望

現時点では、従来通りdata APIを使用した操作のほうが、確実で実装も簡単です。
ただし、MCPサーバーを組み合わせると、以下のような操作がプログラムを作成せずに自然言語で一つのプロンプトの中で実現可能になります：

1. 「今月の売り上げデータを地区別にまとめて、見栄えの良いグラフで表示して」と投げる
2. LLMクライアントがFileMakerから今月の売り上げデータを取り出し、その回答を今度は別の指定した分析サービスのMCPサーバーにそのまま渡し、解析結果をグラフにして一瞬で返してくる
3. 使う人によってはFileMakerクライアントソフト自体が不要 

世の中はもうそちらに向かっているため、数ヶ月以内にオフィシャルのMCPサーバーが用意されているだろうと予想されます。

## まとめ

今回の実験を通じて、このMCPを使えばFileMaker DBを自然言語で操作できるハードルは低く、独自にカスタマイズした設計も可能であることがわかりました。

将来的にはFileMakerの新しい使い方として期待できそうです。今のAI界隈の激流からすると、3ヶ月から半年以内に公式から汎用的なMCPサーバー（もしくは何らかの設計指針）が出てくるでしょう。もしそれが実現できていないとやばいです。