draw.io MCP Serverを使って構成図からIaCを出力するCode as Diagramをやってみよう

記事タイトルとURLをコピーする

さとうです。

先日、draw.ioのMCP Serverが公開されましたね。

blog.serverworks.co.jp

このMCP Serverを使うと構成図からIaCが楽に起こせそうだったので記事にしてみます。

Diagram as CodeならぬCode as Diagram*1をやってみました。

Diagram as Codeではなく、Code as Diagramがしたい

構成図といえばDiagram as Codeという考え方があります。

AWSが提唱しているコード(yaml)から構成図を作成するアプローチのことです。draw.ioのMCP Serverを使ってプロンプトから構成図を作る方法もこのアプローチに近いです。

blog.serverworks.co.jp

aws.amazon.com

ただ、個人的にはその逆のCode as Diagramができたらいいなと思っておりますがその実現例は調べてもあまり見つかりませんでした。逆はたくさん見つかるのですが…。

というのも、私はまず全体を俯瞰した絵を描いてから細部を作り込むトップダウン型の考え方をすることが多いタイプだからです。

図は人間が描いて、その図と整合性が取れるようにIaCを起こせないものかと。

最近公開されたdraw.ioのMCP Serverを使うと、この概念が実証ができるのではないか?と思ったので検証してみることにしました。

ボトムアップで構成図を作成するというアプローチ自体を否定するものではなく、思考スタイルの違いとしてまとめているものとご理解ください

コンセプト

構成図を描いたdraw.io(.drawio)ファイルをGitで管理して、まずは構成図からIaCが作成できるかを確認します。

その後構成図に変更を加えて、その差分からIaCの管理ができるか試してみます。

.drawioファイルの実体はXMLなので、コミットログから差分を検出してIaCに反映するというサイクルが回せるはずです。

検証してみる

検証環境

エージェントにはKiro CLIを使用し、.kiro/settings/mcp.jsonでnpxを使ってdraw.ioのMCP Serverの設定をします。モデルはclaude-opus-4.6を使用しています。

    "drawio": {
      "command": "npx",
      "args": ["-y", "@drawio/mcp"]
    }

IaCはAWS CDK(TypeScript)で実装します。

まずは初版からIaCを作成する

draw.ioで以下のような構成図を作成しました。Amazon API Gateway, AWS Lambda, Amazon Auroraを使用した典型的なサーバレスのAPI構成です。

タスク管理ツールだと仮定して/tasksというAPIを作成します。

作成した構成図をdiagram.drawioとして保存してgit initします。

code-as-diagram-test
└── diagram.drawio

Kiro CLIから以下のように指示をします。

diagram.drawioはdraw.ioの構成図です。
この構成図で表現された内容を、AWS CDK(TypeScript)で`cdk/`以下に実装してくれますか?
モックなので、構成図にない情報は類推した上でモック用のデータおよびコードも用意してデプロイできるようにしてください。
※drawioというMCP Serverを使って構造を読み取ってみてください。

レスポンスは以下の通り、MCP Serverのopen_drawio_xmlを使って構成図から内容は正しく理解できていますね!

> 構成図の内容を把握しました。以下の構成です:

- リージョン: ap-northeast-1
- VPC: 10.1.0.0/16、Private subnet 1a: 10.1.0.0/24
- API Client → API Gateway(API Key認証)→ Lambda(Python Latest)→ Aurora PostgreSQL(db.t4g.medium, Data API有効)
- Lambda → Secrets Manager(認証情報取得)
- APIエンドポイント: GET /tasks → タスク一覧を表示

では、CDKプロジェクトを作成します。
(...)

作成されたプロジェクトでcdk diffから作成されるリソースを確認してみます。モック用のコードも含めて必要なリソースは網羅されていそうです。

CDKの実装方針は特に指示していないので、Constructがベタ書きで抽象化はあまり意識されていないような書き方でした。

Parameters
[+] Parameter BootstrapVersion BootstrapVersion: {"Type":"AWS::SSM::Parameter::Value<String>","Default":"/cdk-bootstrap/hnb659fds/version","Description":"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"}

Resources
[+] AWS::EC2::VPC Vpc Vpc8378EB38
[+] AWS::EC2::Subnet Vpc/PrivateSubnet1/Subnet VpcPrivateSubnet1Subnet536B997A
[+] AWS::EC2::RouteTable Vpc/PrivateSubnet1/RouteTable VpcPrivateSubnet1RouteTableB2C5B500
[+] AWS::EC2::SubnetRouteTableAssociation Vpc/PrivateSubnet1/RouteTableAssociation VpcPrivateSubnet1RouteTableAssociation70C59FA6
[+] AWS::EC2::Subnet Vpc/PrivateSubnet2/Subnet VpcPrivateSubnet2Subnet3788AAA1
[+] AWS::EC2::RouteTable Vpc/PrivateSubnet2/RouteTable VpcPrivateSubnet2RouteTableA678073B
[+] AWS::EC2::SubnetRouteTableAssociation Vpc/PrivateSubnet2/RouteTableAssociation VpcPrivateSubnet2RouteTableAssociationA89CAD56
[+] Custom::VpcRestrictDefaultSG Vpc/RestrictDefaultSecurityGroupCustomResource VpcRestrictDefaultSecurityGroupCustomResourceC73DA2BE
[+] AWS::IAM::Role Custom::VpcRestrictDefaultSGCustomResourceProvider/Role CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0
[+] AWS::Lambda::Function Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E
[+] AWS::RDS::DBSubnetGroup AuroraCluster/Subnets AuroraClusterSubnetsF3E9E6AD
[+] AWS::EC2::SecurityGroup AuroraCluster/SecurityGroup AuroraClusterSecurityGroupD85BF9CB
[+] AWS::SecretsManager::Secret AuroraCluster/Secret AuroraClusterSecret8E4F2BC8
[+] AWS::SecretsManager::SecretTargetAttachment AuroraCluster/Secret/Attachment AuroraClusterSecretAttachmentDB8032DA
[+] AWS::RDS::DBCluster AuroraCluster AuroraCluster23D869C0
[+] AWS::RDS::DBInstance AuroraCluster/Writer AuroraClusterWriterAA1AB06C
[+] AWS::IAM::Role TasksFunction/ServiceRole TasksFunctionServiceRole7A67749F
[+] AWS::IAM::Policy TasksFunction/ServiceRole/DefaultPolicy TasksFunctionServiceRoleDefaultPolicy240AA715
[+] AWS::Lambda::Function TasksFunction TasksFunction1A043ECB
[+] AWS::ApiGateway::RestApi TasksApi TasksApi2C54C787
[+] AWS::ApiGateway::Deployment TasksApi/Deployment TasksApiDeploymentE29B782A7957c8b660fec38a5ec560e18de7b735
[+] AWS::ApiGateway::Stage TasksApi/DeploymentStage.prod TasksApiDeploymentStageprod14B6EF0B
[+] AWS::ApiGateway::Resource TasksApi/Default/tasks TasksApitasks02B6AB70
[+] AWS::Lambda::Permission TasksApi/Default/tasks/GET/ApiPermission.CdkStackTasksApiBC3C167E.GET..tasks TasksApitasksGETApiPermissionCdkStackTasksApiBC3C167EGETtasks187C47ED
[+] AWS::Lambda::Permission TasksApi/Default/tasks/GET/ApiPermission.Test.CdkStackTasksApiBC3C167E.GET..tasks TasksApitasksGETApiPermissionTestCdkStackTasksApiBC3C167EGETtasksC9CF8385
[+] AWS::ApiGateway::Method TasksApi/Default/tasks/GET TasksApitasksGET8AD6C021
[+] AWS::ApiGateway::UsagePlan TasksApi/UsagePlan TasksApiUsagePlanAC6E5A7C
[+] AWS::ApiGateway::UsagePlanKey TasksApi/UsagePlan/UsagePlanKeyResource:CdkStackTasksApiApiKey8627A809 TasksApiUsagePlanUsagePlanKeyResourceCdkStackTasksApiApiKey8627A809E5DA8069
[+] AWS::ApiGateway::ApiKey TasksApi/ApiKey TasksApiApiKey11A5E841
[+] AWS::IAM::Role InitDbFunction/ServiceRole InitDbFunctionServiceRole05110DC9
[+] AWS::IAM::Policy InitDbFunction/ServiceRole/DefaultPolicy InitDbFunctionServiceRoleDefaultPolicy54BD6E7E
[+] AWS::Lambda::Function InitDbFunction InitDbFunction67777D6B
[+] Custom::AWS InitDb/Resource InitDb2DAABCD0
[+] AWS::IAM::Policy InitDb/CustomResourcePolicy InitDbCustomResourcePolicy4059EAA2
[+] AWS::IAM::Role AWS679f53fac002430cb0da5b7982bd2287/ServiceRole AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2
[+] AWS::Lambda::Function AWS679f53fac002430cb0da5b7982bd2287 AWS679f53fac002430cb0da5b7982bd22872D164C4C

Outputs
[+] Output TasksApi/Endpoint TasksApiEndpointD54269F3: {"Value":{"Fn::Join":["",["https://",{"Ref":"TasksApi2C54C787"},".execute-api.ap-northeast-1.",{"Ref":"AWS::URLSuffix"},"/",{"Ref":"TasksApiDeploymentStageprod14B6EF0B"},"/"]]}}
[+] Output ApiUrl ApiUrl: {"Value":{"Fn::Join":["",["https://",{"Ref":"TasksApi2C54C787"},".execute-api.ap-northeast-1.",{"Ref":"AWS::URLSuffix"},"/",{"Ref":"TasksApiDeploymentStageprod14B6EF0B"},"/"]]}}
[+] Output ApiKeyId ApiKeyId: {"Value":{"Ref":"TasksApiApiKey11A5E841"}}

initスクリプトでAmazon Auroraにテーブルが作成されるようになっています。

import json
import os
import time
import boto3

def handler(event, context):
    client = boto3.client("rds-data")
    secret_arn = os.environ["SECRET_ARN"]
    cluster_arn = os.environ["CLUSTER_ARN"]
    database = os.environ["DATABASE_NAME"]
    params = {"secretArn": secret_arn, "resourceArn": cluster_arn, "database": database}

    # Retry: Aurora instance may still be starting
    for attempt in range(10):
        try:
            client.execute_statement(sql="SELECT 1", **params)
            break
        except Exception:
            if attempt == 9:
                raise
            time.sleep(15)

    client.execute_statement(sql="CREATE TABLE IF NOT EXISTS tasks (id SERIAL PRIMARY KEY, title VARCHAR(255) NOT NULL, status VARCHAR(50) NOT NULL)", **params)
    client.execute_statement(sql="INSERT INTO tasks (title, status) SELECT '買い物リスト作成', '未着手' WHERE NOT EXISTS (SELECT 1 FROM tasks LIMIT 1)", **params)
    client.execute_statement(sql="INSERT INTO tasks (title, status) SELECT 'レポート提出', '進行中' WHERE (SELECT COUNT(*) FROM tasks) < 2", **params)
    client.execute_statement(sql="INSERT INTO tasks (title, status) SELECT 'ミーティング準備', '完了' WHERE (SELECT COUNT(*) FROM tasks) < 3", **params)

    return {"StatusCode": 200}

IaCをデプロイしてみる

cdk deployで実際に問題なくデプロイできることも確認できました。

API KeyとURLをOutputから取得できるようになっていたので実際にAPIを叩いてみます。

$ aws apigateway get-api-key --api-key <ApiKeyId> --include-value --query 'value' --output text
$ curl -H "x-api-key: <取得したAPIキー>" <ApiUrl>tasks | python3 -m json.tool --no-ensure-ascii

想定通りレスポンスも返ってきたのでモックとしては完璧ですね。

※一回エラーになってしまったのですが、依存関係の実装不備でAuroraの作成が終わる前にinitスクリプト(CREATE TABLE)が実行されてテーブルが作成できていなかったことが原因でした。initスクリプトの手動実行で対処しつつ、修正の指示は入れています。

{
    "tasks": [
        {
            "id": 1,
            "title": "買い物リスト作成",
            "status": "未着手"
        },
        {
            "id": 2,
            "title": "レポート提出",
            "status": "進行中"
        },
        {
            "id": 3,
            "title": "ミーティング準備",
            "status": "完了"
        }
    ]
}

ここまで確認したあと、git commitでコードをコミットしておきます。

構成図を変更してIaCに反映してみる

構成図の経路上にAWS WAFを追加してIPアドレス制限を行うように図を修正しました。

この修正をcommitして、差分をCDKに反映できるか確認してみましょう。

Kiro CLIから以下のように指示します。

diagram.drawioはdraw.ioの構成図です。
cdk/は構成図をAWS CDK(TypeScript)で実装したプロジェクトディレクトリです。
この構成図に、commit <commit id>で変更を加えました。
この構成図で変更された内容を確認し、CDKの実装に反映してくれますか?
デプロイは私がやりますので不要です。
※drawioというMCP Serverを使って構造を読み取ってみてください。

レスポンスは以下のような感じで、コミットメッセージだけではなく構成図の差分を読み取って反映されていることがわかります。

> コミットメッセージから、API GatewayにAWS WAFが追加されたことがわかります。構成図の詳細を確認します。
(...中略...)
> 構成図の変更内容を確認しました。以下の変更が加えられています:

1. AWS WAFの追加: API Gatewayの前段にAWS WAFが配置されました
2. IPアドレス制限: XX.XX.XX.XX/32からのアクセスのみ許可

修正が完了しました。実装意図は正しく理解できていそうです。

> 構成図の変更内容をCDKに反映しました。

変更内容:

1. AWS WAFの追加: API Gatewayの前段にAWS WAF (Web ACL)を配置
2. IPアドレス制限: XX.XX.XX.XX/32からのアクセスのみ許可するルールを設定
3. デフォルトアクション: 許可されていないIPアドレスからのアクセスはブロック

実装のポイント:
- CfnIPSetでIPアドレスセット(XX.XX.XX.XX/32)を定義
- CfnWebACLでWeb ACLを作成し、デフォルトでブロック、特定IPのみ許可
- CfnWebACLAssociationでAPI GatewayのステージとWeb ACLを関連付け
- CloudWatch メトリクスとサンプリングを有効化

念のため、cdk diffで差分を確認してみますが必要な修正のみできていそうです。

Resources
[+] AWS::WAFv2::IPSet IpSet IpSet
[+] AWS::WAFv2::WebACL WebAcl WebAcl
[+] AWS::WAFv2::WebACLAssociation WebAclAssociation WebAclAssociation

デプロイ後に改めてAPIを叩きます。許可されたIPアドレス以外は403 Forbidedenになるので完璧です!

許可されたIPアドレス

{
    "tasks": [
        {
            "id": 1,
            "title": "買い物リスト作成",
            "status": "未着手"
        },
        {
            "id": 2,
            "title": "レポート提出",
            "status": "進行中"
        },
        {
            "id": 3,
            "title": "ミーティング準備",
            "status": "完了"
        }
    ]
}

許可されていないIPアドレス

{
    "message": "Forbidden"
}

わかったこと

400KBの制限がある

draw.io MCP Serverのopen_drawio_xmlで読み取れるファイルは400,000B(約400KB)までの制限があるようです。

This tool only supports reading 400000 bytes at a
time. You tried to read 1605885 bytes. Try executing with fewer lines specified.

XMLなのでMCP Serverを使わずとも読み取ることはできますが、トークンの浪費には注意しましょう。

ちなみに先ほどのAWS WAFを加えた構成図で11KBほどでした。

画像の埋め込みは解析できない

draw.ioの外部から取得してきた画像はバイナリとして扱われるので正しく内容を読み取ることができませんでした。

400KBの制限も容易に超えてしまうので画像の埋め込みはしない方がよいでしょう。

構成図で表現されていない内容は揺らぐ

構成図でIaCの要素を網羅的に表現することは現実的ではありません。

細部はAIの推論任せかプロンプトで補うことになるので、本番環境の構成管理といった用途に使うことは難しいです。

ユースケース

  • ハンズオンのデモ環境の構築
  • プリセールス活動におけるモック環境の作成

など、厳密な精度を必要としないが手を動かして確認できる環境を作りたいといったユースケースにおいて最適な方法だと思いました。積極的に活用していきたいですね。

まとめ

Diagram as CodeならぬCode as Diagramを試してみました。

IaCは総じて宣言型の言語なので構成図からコードを起こす方法とは相性が良いようです。

トップダウン型で絵を描くことから始めたい方には便利な方法なので、皆さんもぜひ試してみてください。

*1:本記事の趣旨がわかりやすくなるよう便宜上用いている造語です。一般的な用語ではないのでご注意ください。

佐藤 航太郎(執筆記事の一覧)

クロスインダストリー第1本部 クラウドモダナイズ課
最近はデータエンジニアのようなことをしています。