🍣

vue storybook + mcp をやった

に公開

概要

storybook と mcpで storybook経由で接続する

参考にしました!

https://github.com/KOBATATU/mcp-vue-storybook/tree/main

vue3 + storybook

vue3とstorybookでつくっています.いい感じに環境は用意する.

mcpサーバ

storybookのindex.jsonをキーにcomponentをサーチ.その情報をもとに,component.vueやcomponent.stories.tsをaiに調べてもらう.カタログからコンポーネントを探してもらい自分の実装したい内容をプロンプトにいれて,aiにつくってもらうイメージ.

storybookのindex.jsonは以下

{
   "v":5,
   "entries":{
   "example-button--primary":{
         "type":"story",
         "id":"example-button--primary",
         "name":"Primary",
         "title":"Example/Button",
         "importPath":"./src/components/Button.stories.ts",
         "componentPath":"./src/components/Button.vue",
         "tags":[
            "dev",
            "test",
            "autodocs"
         ]
      },
}}

今回は簡単な例で:使い方を聞くことができるようにする

コードは以下.説明が必要そうなところだけ書く

const server = new McpServer({
  name: SERVER_NAME,
  version: SERVER_VERSION,
})
server.tool(
  'component-usage',
  'Explain how to use',
  {
    componentName: z.string(),
  },
  async ({ componentName }) => {
    let responseText = `Component Information for: ${componentName}\n(Based on data from index.json)\n\n`
    // サーチ
    const relevantEntry = findComponent(componentName)

componentの使い方の toolを作成. findComponentにてpromptで入ってきたcomponentをサーチする.プロンプト的には Buttonについての使い方や実際の使用例を教えてください.みたいな感じ. 今回は単一のcomponentを探すことにしているが,server.tool('component-list'などで書くことで一覧的な意味を含めることもできると思う.

以下でサーチする. "importPath":"./src/components/Button.stories.ts","componentPath":"./src/components/Button.vue"この辺を使う

function findComponent(componentName: string): EntryInfo | null {
  const lowerComponentName = componentName.toLowerCase()
  let bestMatch: EntryInfo | null = null

  for (const storyId in storiesData.entries) {
    const story = storiesData.entries[storyId]
    // 1. title がコンポーネント名に部分一致するか (例: "Example/Button" と "Button")
    const titleParts = story.title.split('/')
    const storyComponentName = titleParts[titleParts.length - 1]
    if (storyComponentName?.toLocaleLowerCase() === lowerComponentName) {
      if (!bestMatch || bestMatch.title !== story.title) {
        bestMatch = story
      }
    }
    // 2. importPath がコンポーネント名を含んでいるか
    if (!bestMatch && story.componentPath?.toLowerCase().includes(`/${lowerComponentName}.vue`)) {
      bestMatch = story
    }
    if (!bestMatch && story.name.toLowerCase() === lowerComponentName) {
      bestMatch = story
    }
  }

  if (!bestMatch) {
    console.warn(`No component: ${componentName}`)
  }
  return bestMatch
}

ここで,mcpサーバとstorybookのパス絶対位置は以下となっている

  • /Users/hogeuser/development/mcp/mcp-storybook/mcp/build/index.js
  • /Users/hogeuser/development/mcp/mcp-storybook/storybook-static/index.json

コンポーネントの場所が"componentPath":"./src/components/Button.vue",なので,絶対パスをいい感じに合わせてファイルの場所を教えてあげる必要がある.github上にある場合はいい感じに修正すれば良いです.レスポンスの文言とかはaiに生成させている.

    if (relevantEntry) {
      const indexJsonDir = path.dirname(storiesJsonPath)
      const baseDirectory = path.dirname(indexJsonDir)

      let absoluteComponentPath: string | undefined
      if (relevantEntry.componentPath) {
        try {
          // コンポーネントパスを解決
          absoluteComponentPath = path.resolve(baseDirectory, relevantEntry.componentPath)
          responseText += `コンポーネントファイルパス: ${absoluteComponentPath}\n`
        } catch (e) {
          responseText += `コンポーネントパスを解決できませんでした: ${relevantEntry.componentPath}\n`
        }
      }

      let absoluteImportPath: string | undefined
      if (relevantEntry.importPath) {
        try {
          // インポートパス (ストーリー/ドキュメントファイル) を解決
          absoluteImportPath = path.resolve(baseDirectory, relevantEntry.importPath)
          responseText += `ストーリー/ドキュメントファイルパス: ${absoluteImportPath}\n`
        } catch (e) {
          responseText += `ストーリー/ドキュメントパスを解決できませんでした: ${relevantEntry.importPath}\n`
        }
      } else {
        responseText += `ストーリー/ドキュメントファイルパス: index.json エントリーに指定されていません。\n`
      }
      responseText +=
        '\nAIへの指示:\n上記で提供された絶対パスにあるファイルの内容を読み取り、コンポーネントの実装と使用例およびどのようなコンポーネントであるかを説明してください。\n'
    }

これで準備が整う

cursol

cursolを使う

{
  "mcpServers": {
     "mcp-storybook": {
      "command": "node",
      "args": ["/Users/hogeuser/development/mcp/mcp-storybook/mcp/build/index.js","/Users/hogeuser/development/mcp/mcp-storybook/storybook-static/index.json"]
    }
  }
}

まとめ

他にも design-tokenなどを取得するresourceなどをつくっておけばデザイン実装もお任せでできる見込みはある

終わり

Discussion