☃️

CursorとSnowflake MCP Serverでデータ基盤の健康診断を実践してみる

に公開

この記事はSnowflake Advent Calendar 2025の18日目の投稿です。

突然ですが、皆さん今年の健康診断はお済みでしょうか?
その前にお前はどうなんだ、というツッコミが来そうなので答えると...
自分は5月に受けようと申請していたところ...忙しいを理由に2度ほど延期しまして、10月にようやく受診しました😅(結果は特に異常なしでよかったです😇)

健康診断でイメージするのは、重要だとわかってはいても、日々割り込んでくる緊急な事象に追われて後回しになりガチというところかと...

そんな健康診断でも、今回はデータ基盤の健康診断(調査系タスク)についての話です。
この年末の時期、セキュリティ部門や内部監査部門から期末の報告を求められて急いで調査するなんてことになってはいないでしょうか💦(目標設定や振り返り、Adventカレンダーも書かないとなと思いつつ、涙目になりながら)...自分は例年そんな感じでした...

この記事は、そんな重要だけど優先度が低くなりがちなデータ基盤の健康診断について、
AIエディタのCursorとSnowflake MCP Serverを使ってサッとやってしまおうと実践してみた話です😃

まず、CursorへSnowflake MCP Serverを設定する

※この2025/12/18現在の設定方法です。
※詳細な設定の説明は、この実践のキッカケになった菅野さんの解説が詳しいです。初めて設定される方はそちらをご参照ください。ここでは設定のポイントのみを取り上げます。

  1. uvxをインストールする
  2. PATを生成する(Snowflake)
  3. サービス設定ファイルを作成する
  • mcp_snowflake.yml
# その他のサービス設定 (Semantic View、オブジェクト管理など)
other_services:
  semantic_manager: True  # Semantic Viewの発見・クエリを有効化
  object_manager: True    # オブジェクト管理機能を有効化
  query_manager: True    # SQLクエリ実行機能を有効化

# SQL 実行権限の設定
sql_statement_permissions:
  # - All: True           # 全てのSQL文を許可する場合はこの行のコメントを解除する。
  - Alter: False          # ALTER文を許可するかどうか
  - Command: False        # COMMAND文を許可するかどうか
  - Comment: False        # COMMENT文を許可するかどうか
  - Commit: False         # COMMIT文を許可するかどうか
  - Create: False         # CREATE文を許可するかどうか
  - Delete: False         # DELETE文を許可するかどうか
  - Describe: True        # DESCRIBE文を許可するかどうか
  - Drop: False           # DROP文を許可するかどうか
  - Insert: False         # INSERT文を許可するかどうか
  - Merge: False          # MERGE文を許可するかどうか
  - Rollback: False       # ROLLBACK文を許可するかどうか
  - Select: True          # SELECT文を許可するかどうか
  - Transaction: False    # TRANSACTION文を許可するかどうか
  - TruncateTable: False  # TRUNCATE TABLE文を許可するかどうか
  - Unknown: False        # 定義されていないSQL文を許可するかどうか
  - Update: False         # UPDATE文を許可するかどうか
  - Use: True             # USE文を許可するかどうか

参照元の記事の記述からquery_executer -> query_managerへ修正:mcpの最新の実装に合わせて機能させるため

  • mcp.json
{
  "mcpServers": {
    "mcp-server-snowflake": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/Snowflake-Labs/mcp",
        "mcp-server-snowflake",
        "--service-config-file",
        "[mcp_snowflake.yml(上記)へのパス]",
        "--account",
        "[SnowflakeAccountのid]",
        "--user",
        "[ユーザーid]",
        "--password",
        "[PATで生成されたトークン]"
      ]
    }
  }
}

※[]内は接続先のSnowflakeに対応した値へ

  1. CursorでMCPを設定する
    ※自分は当初、ここが上手くいかずに苦戦しました。原因はSnowflake接続でPAT認証時のネットワークポリシー設定がないことによるエラーだったため、一時的にBypassを設定することで解決しました。

成功すると↓のキャプチャのようになります。
mcp-server-snowflake

データ基盤の健康診断を実践する

ここからはCursorのAIチャットを使って実行します。
※モデルは最近のお気に入りのgemini-3-proを使います
※ここでは本番データがない開発環境へ接続しています。プロジェクトの本番データを取り込む際は、LLMモデルがセキュリティポリシーに準拠しているか確認しましょう※

○Snowflakeデータ基盤への不正アクセスを調査する

健康診断といえば、まずは不正アクセスの脅威がないか気になるところです。
そこで、下記のプロンプトからCursorのチャットへ投げました。

結果、そのままスムーズにデータの要約までサッと作ってくれました。
(詳しくは調べられてないですが、Snowflakeの構造まで理解した上でクエリを作ってくれている様子)

プロンプト

mcp-server-snowflakeを使って以下の分析を実行してください。
直近1年間でSnowflakeへのアクセスに対して不正が疑われるものがなかったか検証してください

生成されたクエリ

いずれも一発で正しいクエリが作成されて結果も正しく返って来ています。

厳しく見ると、account_usageに入っているデータは最大で1年分で、期間を絞るwhere句はいらないハズですが、細かいところなので問題なしと😇
cf. https://docs.snowflake.com/ja/sql-reference/account-usage/login_history

  1. エラー内容とユーザーごとの失敗回数集計(全件)
    どのようなエラーが誰にどれくらい発生しているかを全体把握するために使用
SELECT
    ERROR_MESSAGE,
    USER_NAME,
    COUNT(*) as FAIL_COUNT,
    MIN(EVENT_TIMESTAMP) as FIRST_FAIL,
    MAX(EVENT_TIMESTAMP) as LAST_FAIL
FROM SNOWFLAKE.ACCOUNT_USAGE.LOGIN_HISTORY
WHERE EVENT_TIMESTAMP >= DATEADD('year', -1, CURRENT_DATE())
  AND IS_SUCCESS = 'NO'
GROUP BY 1, 2
ORDER BY FAIL_COUNT DESC;
  1. パスワード間違いの特定抽出
    不正アクセス(総当たり攻撃など)の兆候となる「パスワード間違い」のみを抽出
SELECT
    CLIENT_IP,
    USER_NAME,
    COUNT(*) as FAIL_COUNT,
    MIN(EVENT_TIMESTAMP) as FIRST_ATTEMPT,
    MAX(EVENT_TIMESTAMP) as LAST_ATTEMPT
FROM SNOWFLAKE.ACCOUNT_USAGE.LOGIN_HISTORY
WHERE EVENT_TIMESTAMP >= DATEADD('year', -1, CURRENT_DATE())
  AND ERROR_MESSAGE = 'INCORRECT_USERNAME_PASSWORD'
GROUP BY 1, 2
ORDER BY FAIL_COUNT DESC;
  1. ネットワークポリシーブロックの特定抽出
    IP制限などでブロックされたアクセスを抽出
SELECT
    CLIENT_IP,
    USER_NAME,
    COUNT(*) as BLOCK_COUNT,
    MIN(EVENT_TIMESTAMP) as FIRST_BLOCK,
    MAX(EVENT_TIMESTAMP) as LAST_BLOCK
FROM SNOWFLAKE.ACCOUNT_USAGE.LOGIN_HISTORY
WHERE EVENT_TIMESTAMP >= DATEADD('year', -1, CURRENT_DATE())
  AND ERROR_MESSAGE LIKE 'INCOMING_REQUEST_BLOCKED%'
GROUP BY 1, 2
ORDER BY BLOCK_COUNT DESC;

データの要約

結論として、外部からの悪意ある不正アクセス(パスワード総当たり攻撃など)の明確な痕跡は見当たりませんでしたが、運用設定の不備やネットワークポリシーによる意図しないブロックと思われる事象がいくつか確認されました。
以下に詳細を報告します。
1. ネットワークポリシーによるブロック(要確認)
特定のIPアドレスからのアクセスがネットワークポリシーによってブロックされています。これらは社内システムや連携ツールからのアクセスの可能性が高いですが、設定漏れの可能性があります。
2. JWT認証エラーの多発(設定不備の可能性)
プログラムやツールからの接続(Pythonドライバ等)において、トークン設定に関連するエラーが多数発生しています。これらは不正アクセスではなく、クライアント側の設定ミスやトークン期限切れと考えられます。
3. その他
パスワード間違い(INCORRECT_USERNAME_PASSWORD)はごく僅かで、攻撃的な傾向は見られません。
MFA(多要素認証)が必要な環境での未設定アクセスがx件ありました。

このように、全体像をサッと把握することができます。
気になるポイントが出てきたら、追加のプロンプトで深掘りを指示すれば詳細なところまで問題の有無を確認することができます。(詳細の調査までの公開はさすがに控えます)

※実験的に実施した開発環境へのアクセス分析にて、詳細は割愛しています。

○年間の利用状況を振り返る

これもやりたかったけど、なかなか着手できていなかったレポートです。
人手で対応しようとすると、
何を見るのかから始まり、データはどこにあるのか、どうクエリを書くのかと、初手で結構な手数が想定され、
後回しになってしまうタスクでした。

プロンプト

mcp-server-snowflakeを使って以下の分析を実行してください。
我々のプロジェクトはSnowflakeでデータ基盤を運用しています。
年間の振り返りとして、総合的に利用状況の推移を見るレポートを作りたいです。
このような目的に合うクエリと、その結果の要約を作ってください

生成されたクエリ

(同じく、1年だと期間を絞るwhere句はいらない想定ですが間違いではないので😇)

  1. 月次クレジット消費量(コスト)の遷移
SELECT
    TO_CHAR(START_TIME, 'YYYY-MM') AS month,
    SUM(CREDITS_USED) AS total_credits
FROM
    SNOWFLAKE.ACCOUNT_USAGE.WAREHOUSE_METERING_HISTORY
WHERE
    START_TIME >= DATEADD('year', -1, CURRENT_DATE())
GROUP BY
    1
ORDER BY
    1;
  1. 月次クエリ実行数と平均実行時間の遷移(パフォーマンス)
SELECT
    TO_CHAR(START_TIME, 'YYYY-MM') AS month,
    COUNT(*) AS query_count,
    AVG(TOTAL_ELAPSED_TIME) / 1000 AS avg_execution_seconds
FROM
    SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
    START_TIME >= DATEADD('year', -1, CURRENT_DATE())
GROUP BY
    1
ORDER BY
    1;
  1. 月次アクティブユーザー数の遷移(利用規模)
SELECT
    TO_CHAR(START_TIME, 'YYYY-MM') AS month,
    COUNT(DISTINCT USER_NAME) AS active_users
FROM
    SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
    START_TIME >= DATEADD('year', -1, CURRENT_DATE())
GROUP BY
    1
ORDER BY
    1;
  1. 月次新規ユーザー登録数の推移
SELECT
    TO_CHAR(CREATED_ON, 'YYYY-MM') AS month,
    COUNT(*) AS new_users
FROM
    SNOWFLAKE.ACCOUNT_USAGE.USERS
WHERE
    CREATED_ON >= DATEADD('year', -1, CURRENT_DATE())
GROUP BY
    1
ORDER BY
    1;

データの要約

# 分析結果の要約
全体として、xに利用のピークがあり、直近にかけては利用が落ち着いている傾向が見られました。
# コスト(クレジット消費):
ピーク: xとxに利用の山がありました。
直近の傾向: xxx
# アクティビティ(クエリ実行数):
コストと同様に、xxxのクエリが実行されています。
# パフォーマンス:
平均クエリ実行時間はxxxで推移しており、大きなパフォーマンス劣化は見られません。
# ユーザー数:
月間アクティブユーザー数はxxxの間で安定して推移しています。xx月に最多のxx人を記録しました。
# 新規ユーザー登録数の推移:
年間で合計x名の新規ユーザーが追加されました。
最も追加が多かったのはxx月のxx名です。これは先ほどの利用状況レポートでxxxと重なっており、この時期に新しいプロジェクトやチームの参画があった可能性が考えられます。

この指示もプロンプトから追加の指示不要で一発で通ってくれました。
この全体レポートを基点として、さらに追加のプロンプトで詳細まで深掘ってみるようなこともできます。
(上記同様、気になるポイントは追加で深掘りましたが詳細は割愛します。)

実践してわかったこと(良かったこと/改善ポイント)

Snowflakeはこのように、調査しようと思えば見れるデータがSNOWFLAKEデータベースにありますが、
いかんせんその情報量の多さから、見たい情報をみるために、どうクエリを書けばよいのかをサッと実現し辛いところがありました。(そのために調査系タスクがズルズルと後回しに💦)

○良かったこと

  • 日頃の開発で慣れているCursorの延長で調査を実施できる。
  • 事前準備が簡単ですぐに使える。
  • 思ったよりtext-to-SQLが機能した、よっぽど難しいことをやろうとしない限り手直しも不要なのではないか。

□改善ポイント

  • PATを上手く運用できるようにしたい。
  • 健康診断(調査)以外の用途ではどれくらい機能するか未知数。

まとめ

今回実施してみたデータ基盤の健康診断という観点では、想像したより使えそうだということがわかりました。

今後はこれを応用して自動監視を自前で作ってみたり、まだ見えていないデータ利活用のユースケースの発見などに繋げられたら面白いかなと思います。

他にもこんなことができそうだと思いついたら、ぜひ色々やってみてください。

Snowflake Data Heroes

Discussion