FastMCP 4.2のスキル配信を4.1.8と比べて検証|新しいSkillsDirectoryProviderの使い方と、4.3で削除予定の旧APIの置き換え

FastMCP 4.2.0で、MCPサーバーからスキルを配る仕組みが新しくなった

PythonでMCPサーバーを作るフレームワークFastMCPの4.2.0が、2026年11月14日(日本時間)に公開されました(4.2.0のリリースノート)。MCP(Model Context Protocol)は、AIアプリがツールやデータにつなぐための共通の通信規格です。4.2.0では、MCPサーバーから「スキル」を配る仕組みが作り直されました。

スキルは、AIエージェントに読ませる作業手順を、SKILL.mdというファイルと付属のファイルにまとめたフォルダーです。SKILL.mdの先頭には、スキルの名前(name)と、どんなときに使うかの説明(description)を書きます。4.2.0の新しいSkillsDirectoryProvider(スキルのフォルダーを公開する部品)を使うサーバーは、MCPの拡張仕様「Skills extension」(SEP-2640)に沿ってスキルの一覧を返し、クライアントは受け取ったファイルを1つずつハッシュ(中身が1バイトでも違うと変わる値)で照合できるようになりました。4.1のスキル用のクラスと関数は非推奨になり、リリースノートと警告文によると4.3で削除される予定です。

FastMCPのドキュメントは、このSkills extensionへの対応を実験的(experimental)としていて、リリースノートも、対応するクライアントはまだそろっていないと書いています。そのためFastMCPは、自前のクライアントでスキルを受け取り、AIエージェントが読むフォルダーへ書き出す関数も用意しました。

この記事では、4.1.8と4.2.0をPyPIから手元に入れ、同じスキルのフォルダーで動きを比べました。新しい書き方、規則に合わないフォルダーの扱い、受け取る側の照合と書き出し、4.1の書き方を置き換える手順、版の違うサーバーとクライアントの組み合わせを順に説明します。4.1でexperimental_skills=Trueを付けていたサーバーは、4.2.0へ上げた時点で動きが変わるので、別に取り上げます。

要点

4.2.0のスキル配信は、起動時の検査と受け取り時の照合が加わった

4.1.8と4.2.0に同じスキルのフォルダーを渡して比べた結果です。

新しい書き方

fastmcp.skills.providerのSkillsDirectoryProvider

深い階層のスキルも見つけ、ファイルごとの大きさとハッシュを付けて一覧を返します。

起動時の検査

新しいproviderは、規則に合わないフォルダーがあるとサーバーを起動させない

エラーにはフォルダーごとの直し方が出ます。4.1.8の旧いproviderは、同じフォルダーを公開したまま起動しました。

受け取る側

read_skillが照合し、to_directoryで書き出す

ハッシュと合わないファイルを返すサーバーでは、ValueErrorで止まりました。

4.1の書き方

既定の書き方は4.2でも動き、警告が置き換え先を示す

experimental_skills=Trueを付けたコードは、上げた時点で新しいproviderに変わります。4.2のクライアントは、4.1.8のサーバーからもスキルを受け取れました。

新しいSkillsDirectoryProviderは、フォルダーのスキルをハッシュ付きで公開する

4.2.0では、スキルのフォルダーを公開する部品がfastmcp.skills.providerのSkillsDirectoryProviderに変わりました。FastMCPでは、サーバーに機能を足すこうした部品を「provider」と呼びます。検証に使ったフォルダーは次の形です。release-notesは付属のテンプレートを1つ持ち、deploy-checkはopsの下に1階層深く置いています。

skills/
├── release-notes/
│   ├── SKILL.md
│   └── templates/summary.md
└── ops/
    └── deploy-check/
        └── SKILL.md

release-notes/SKILL.mdの中身です。先頭の---で囲んだ部分に、nameとdescriptionを書きます。

---
name: release-notes
description: Use when writing release notes from a list of merged pull requests. Groups changes and fills the summary template.
---

# Release notes

1. Group the merged pull requests into Features, Fixes and Breaking changes.
2. Fill `templates/summary.md` with one line per change.

サーバーのコードでは、FastMCPにSkillsDirectoryProviderを1つ足します。引数には、スキルのフォルダーを並べた親のフォルダー(ルート)を渡します。

from pathlib import Path

from fastmcp import FastMCP
from fastmcp.skills.provider import SkillsDirectoryProvider

mcp = FastMCP("team-skills")
mcp.add_provider(SkillsDirectoryProvider(Path(__file__).parent / "skills"))

if __name__ == "__main__":
    mcp.run()

4.2.0のfastmcp listには、サーバーが公開するスキルを表示する--skillsが加わりました。仮想環境を有効にした状態(fastmcpコマンドがPATHで見つかる状態)で実行すると、2つのスキルと各ファイルの大きさが並びます(ログの行は省いています)。仮想環境を有効にせず、.venv/bin/fastmcp list ...のようにパスを指定して実行すると、サーバーを起動する段階でfastmcpコマンドが見つからず、Error: Client failed to connect: [Errno 2] No such file or directory: 'fastmcp'で止まりました。4.1.8のfastmcp listはUnknown option: --skills.を返します。

$ fastmcp list server.py --skills
No tools found.
Skills (2)
  skill://ops/deploy-check/SKILL.md
    Use before deploying to staging. Lists the checks to run and the commands
for each.
    skill://ops/deploy-check/SKILL.md (196 bytes)
  skill://release-notes/SKILL.md
    Use when writing release notes from a list of merged pull requests. Groups
changes and fills the summary template.
    skill://release-notes/SKILL.md (308 bytes)
    skill://release-notes/templates/summary.md (53 bytes)

スキルがサーバーからエージェントの手元へ届くまでの流れを、手元で確かめた順に並べます。

4.2.0でスキルが届くまでの4段階

1. フォルダーを読む(サーバー)

SKILL.mdのあるフォルダーごとに1つのスキルになります。URIはルートからのパスと同じで、deploy-checkはskill://ops/deploy-check/SKILL.mdです。

2. 一覧を返す(サーバー)

Skills extensionのskills/listの要求に、名前、説明、ファイルごとの大きさとSHA-256のハッシュを返します。各ファイルと、ファイルの一覧(manifest)はskill://のリソースとしても読めます。

3. 照合して受け取る(クライアントのread_skill)

manifestにあるファイルをすべて読み、大きさとハッシュがmanifestに書かれた値と同じかを確かめてから、Skillとして返します。

4. 書き出す(Skillのto_directory)

照合済みのスキルを、AIエージェントがスキルを読むフォルダーへ、元と同じファイル構成で書き出します。

4.2.0のSkillsDirectoryProviderは、フォルダーをサーバーの起動時に1回だけ読みます。起動後にSKILL.mdを書き換えても、サーバーは起動時の中身を返し続けました。reload=Trueを付けると要求のたびに読み直し、書き換えた中身が次の読み出しから返りました。reload=TrueのままSKILL.mdのnameとdescriptionを消すと、ログにERROR Keeping the last valid skillsと出て、直前の正しい中身を返し続けます。

新しいproviderは、規則に合わないスキルがあると起動を止める

新しいSkillsDirectoryProviderは、SKILL.mdがAgent Skillsの仕様に合っているかを、サーバーを作る時点で確かめます。nameは小文字の英字・数字・ハイフンの64文字以内で、先頭と末尾のハイフンやハイフンの連続は使えず、フォルダー名と同じにします。descriptionは1〜1024文字で書きます。名前が.で始まるフォルダーは読み飛ばします。

大文字と下線を使ったRelease_Notesと、descriptionのないno-descriptionの2つのフォルダーを用意し、4.1.8の旧いprovider(experimental_skillsなし)と4.2.0の新しいproviderに渡しました。

4.1.8(旧いSkillsDirectoryProvider、experimental_skillsなし)

2つとも、警告なしで公開した

リソースにskill://Release_Notes/SKILL.mdとskill://no-description/SKILL.mdが並びました。

4.2.0(新しいSkillsDirectoryProvider)

ValueErrorで止まり、サーバーを作れない

2つのフォルダーそれぞれに、理由と直し方が1行ずつ出ました。

4.2.0のエラーです(フォルダーのパスは<skills>と縮めています)。Fix:のあとに、フォルダーの名前の直し方と、SKILL.mdの先頭に書く内容が出ます。

ValueError: 2 skill folder(s) are not valid Agent Skills (https://agentskills.io/specification):
- <skills>/Release_Notes: name must be 1-64 lowercase letters, digits, or hyphens; it cannot start or end with a hyphen or contain consecutive hyphens. Fix: rename the folder to 'release-notes' and start SKILL.md with '---\nname: release-notes\ndescription: <what it does and when to use it>\n---'
- <skills>/no-description: description must contain 1-1024 characters. Fix: start SKILL.md with '---\nname: no-description\ndescription: <what it does and when to use it>\n---'

4.2.0の環境で旧いproviderに同じフォルダーを渡すと、2つを公開したまま、非推奨の警告の最後にFix these folders first:と、上と同じフォルダーと直し方を並べました。規則に合うフォルダーだけなら、警告の最後はEvery skill folder found already passes.です。4.2へ上げたら、新しいproviderに書き換える前にこの警告を一度読むと、起動しなくなるフォルダーを先に直せます。

4.1でSkillsDirectoryProvider(root, experimental_skills=True)と書き、SkillsExtensionを登録していたサーバーは、上げた時点で動きが変わります。4.2.0では、この呼び出しが新しいproviderを返すからです。4.1.8では、同じコードに規則に合わない2つのフォルダーを渡してもサーバーは起動し、ログにWARNING Omitting invalid skillと出して、Skills extensionの一覧から2つを外しました。4.2.0では、コードを変えなくても同じValueErrorで止まり、サーバーは起動しません。

どちらの書き方でも、4.2の環境で次の1行を実行すると、本番のサーバーを動かす前にフォルダーを検査できます。"skills"は、サーバーに渡しているルートのパスに置き換えます。規則に合うフォルダーなら、見つかったスキルのパスの一覧を表示して終了コード0で終わり、合わないフォルダーがあれば上と同じValueErrorと直し方を出して終了コード1で終わりました。

$ python -c 'import asyncio; from fastmcp.skills.provider import SkillsDirectoryProvider; print(sorted(asyncio.run(SkillsDirectoryProvider("skills").load_skills())))'
['ops/deploy-check', 'release-notes']

ルートのパスを書き間違えた場合と、スキルのない空のフォルダーを渡した場合は、どちらもエラーにならず[]と表示して終了コード0で終わりました。一覧に、配っているスキルがすべて並んでいるかを確かめます。

read_skillで照合し、to_directoryでフォルダーへ書き出す

受け取る側では、クライアントのread_skillにスキルのSKILL.mdのURIを渡します。返ってくるSkillのto_directoryで、スキルをフォルダーへ書き出せます。サーバーのスキルを全部、手元のフォルダーへ写すスクリプトです。サーバーはstdio(標準入出力)でつなぎ、引数でサーバーを動かすPythonとスクリプト、書き出し先を渡します。

import asyncio
import sys
from pathlib import Path

from fastmcp import Client
from fastmcp.client.transports import StdioTransport


async def main(server_python: str, server_script: str, target: Path) -> None:
    transport = StdioTransport(command=server_python, args=[server_script])
    async with Client(transport) as client:
        for resource in await client.list_skill_resources():
            skill = await client.read_skill(str(resource.uri))
            path = skill.to_directory(target / skill.name, overwrite=True)
            print(f"{resource.uri} -> {path}")


asyncio.run(main(sys.argv[1], sys.argv[2], Path(sys.argv[3])))

4.2.0の新しいサーバーへ向けて実行すると、2つのスキルを書き出しました。deploy-checkはサーバー上ではopsの下にありますが、書き出し先ではnameと同じ名前のフォルダーになります。

skill://ops/deploy-check/SKILL.md -> out/.agents/skills/deploy-check
skill://release-notes/SKILL.md -> out/.agents/skills/release-notes

list_skill_resourcesは、skill://のリソースからSKILL.mdを探す関数で、Skills extensionに対応していないサーバーにも使えます。Skills extensionで一覧を受け取るlist_skillsは、クライアントを作るときにextensions=[SkillsClientExtension()]を渡します。SkillsClientExtensionはfrom fastmcp.skills import SkillsClientExtensionで読み込みます。渡さないとRuntimeError: This client did not opt into the Skills extension; pass SkillsClientExtension() in Client(extensions=[...])で止まりました。

照合と書き出しで止まる場面を、3つ試しました。

ハッシュと合わないファイル

read_skillがValueErrorで止まる

テンプレートの1語を同じ長さで書き換え、manifestのハッシュとは違う中身を返すサーバーでは、Skill file 'templates/summary.md' does not match its manifest digest。同じファイルをread_resourceで直接読むと、書き換えた中身がそのまま返りました。

書き出し先のフォルダー名

スキルのnameと同じ名前にする

notesへ書き出そうとすると、Skill folder 'notes' must match the skill's name 'release-notes'のValueErrorになりました。

2回目の書き出し

overwrite=Trueはフォルダーを丸ごと置き換える

指定なしではFileExistsError。overwrite=Trueでは、書き出したあとに手で足したlocal-note.txtも消えました。4.1のdownload_skill(..., overwrite=True)は、同じファイルを残しました。

ハッシュの照合はread_skillの中で行われるので、スキルを受け取るコードはread_resourceで1ファイルずつ読むより、read_skillでまとめて受け取ります。overwrite=Trueは手元の編集も消すため、手で書き足すスキルは、サーバーから写すフォルダーとは別の場所に置きます。

ハッシュが一致して分かるのは、サーバーが返した一覧とファイルの中身が食い違っていないことです。一覧もファイルも同じサーバーが返すため、SEP-2640は、ハッシュの一致を安全の保証として扱わないよう定めています。エージェントのフォルダーへ書き出すスキルは、信頼できるサーバーから受け取り、中身を一度読んでから使います。

4.1の既定の書き方は4.2でも動き、警告が置き換え先を示す

experimental_skillsを付けない4.1の書き方で作ったサーバーとクライアントの関数は、4.2.0でもそのまま動きました。旧いproviderを作ったときと旧い関数を呼んだときにFastMCPDeprecationWarningが出て、置き換え先のコードを示します。4.1.8では、同じコードで警告は出ません。手元の4.2.0で警告が示した置き換え先を、表にまとめます。

表の見方:左が4.1の書き方、右が4.2.0の警告が示した置き換え先です。左の書き方は、4.3で削除される予定です。

4.1の書き方 4.2での置き換え先
from fastmcp.server.providers.skills import SkillsDirectoryProvider from fastmcp.skills.provider import SkillsDirectoryProvider
SkillProvider(path)(1つのスキルのフォルダーを公開) SkillCatalogProvider([Skill.from_directory(path)])
SkillsDirectoryProvider(root, experimental_skills=True) SkillsDirectoryProvider(root)。4.2.0では、この呼び出しがすでに新しいproviderを返すので、規則に合わないフォルダーがあるとここでValueErrorになります。SkillsExtensionの登録も要らなくなりました
fastmcp.utilities.skills.list_skills(client) await client.list_skill_resources()、Skills extensionに対応したサーバーならawait client.list_skills()
fastmcp.utilities.skills.download_skill(...) (await client.read_skill('skill://<name>/SKILL.md')).to_directory(target_dir / '<name>')
fastmcp.utilities.skills.sync_skills(client, target_dir) list_skill_resources()の各URIをread_skillで受け取り、to_directoryで書き出す(この記事の同期スクリプトの形)
Skill.model_validate(...)(4.1の通信用の形) MCPSkill.model_validate(...)(from fastmcp.skills import MCPSkill)

4.2では、fastmcp.skills.Skillはスキルを作るためのクラスになり、4.1で通信用の型だったものはMCPSkillに名前が変わりました。型注釈やisinstanceでSkillを通信用の型として使っているコードは、4.2ではまだ通りますが、4.3までにMCPSkillへ書き換えます。ClaudeSkillsProviderやCodexSkillsProviderのように、エージェントごとのフォルダーを決め打ちしたproviderも、そのフォルダーを新しいSkillsDirectoryProviderに渡す形へ置き換えます(FastMCPのドキュメントの移行表)。

experimental_skillsを付けない4.1の旧いproviderは、ルートのすぐ下のフォルダーだけをスキルとして公開します。opsの下のdeploy-checkは、この書き方では4.1.8でも4.2.0でも出ませんでした。新しいproviderは深い階層のSKILL.mdも探すので、書き換えた直後からdeploy-checkも公開されます。4.1でexperimental_skills=Trueを付けていたサーバーは、4.1.8の時点で深い階層のスキルも公開していました。

4.2のクライアントは、4.1のサーバーからもスキルを受け取れる

サーバーとクライアントを別々に上げる場合に備えて、版の違う組み合わせをstdioでつないで試しました。

結論:4.2のクライアントでlist_skill_resourcesとread_skillを使えば、サーバーが4.1.8でも4.2.0でもスキルを受け取れました。

クライアント4.2.0 → サーバー4.1.8(旧いprovider、experimental_skillsなし)

一覧を受け取る関数

list_skillsはRuntimeError: The server does not advertise the Skills extension。list_skill_resourcesはrelease-notesの1件

スキルを受け取る関数

read_skillで2ファイルを受け取れた

クライアント4.2.0 → サーバー4.1.8(experimental_skills=TrueとSkillsExtension)

一覧を受け取る関数

list_skills、list_skill_resourcesとも2件(deploy-checkを含む)

スキルを受け取る関数

read_skillで2つとも受け取れた

クライアント4.2.0 → サーバー4.2.0(新しいprovider)

一覧を受け取る関数

list_skills、list_skill_resourcesとも2件

スキルを受け取る関数

read_skillで受け取れた

4.1.8の関数 → サーバー4.2.0(新しいprovider)

一覧を受け取る関数

list_skillsは2件。名前の欄に、deploy-checkだけops/deploy-checkとパスが入る

スキルを受け取る関数

download_skill(client, "deploy-check", ...)はResource not found: 'skill://deploy-check/_manifest'。"ops/deploy-check"と渡すと受け取れた

experimental_skillsを付けずに作った4.1.8のサーバーはSkills extensionを広告しないため、list_skillsはRuntimeErrorで止まります。read_skillは、FastMCPのサーバーがファイルの一覧として公開する_manifestのリソースを使って照合するので、このサーバーからもハッシュを確かめて受け取れました。4.1.8の関数のまま4.2.0のサーバーにつなぐ場合は、深い階層のスキルをlist_skillsが返したパスのままdownload_skillへ渡します。この方法では、書き出し先もops/deploy-checkのように深い階層のフォルダーになります。

4.2へ上げる前に確かめる4つのこと

ここまでの結果から、4.1でスキルを配っているサーバーを4.2へ上げるときの確認を、順に並べます。

上げる前の確認

1. Pythonが3.11以上か

PyPIの登録情報では、4.2.0は>=3.11、4.1.8は>=3.10です。3.10で動かしている環境は、Pythonから上げます。

2. 4.2の環境で、新しいproviderにフォルダーを検査させる

規則に合わないフォルダーの節で示した1行で、新しいproviderにルートのフォルダーを検査させ、表示された一覧に配っているスキルがすべて並ぶかを確かめます。ValueErrorが出たら、Fix:のとおりにフォルダー名とSKILL.mdのname・descriptionを直します。experimental_skills=Trueを付けた4.1のサーバーは、上げた時点で新しいproviderに変わるので、本番で上げる前にこの検査を通します。

3. ルートの深い階層に、配る予定のないSKILL.mdがないか

新しいproviderは深い階層のスキルも公開します。配る予定のないスキルがルートの下にあれば、書き換える前に別のフォルダーへ移します。

4. 受け取る側のコードを、read_skillとto_directoryへ書き換えたか

fastmcp.utilities.skillsのdownload_skillとsync_skillsを、list_skill_resources・read_skill・to_directoryの組み合わせへ書き換えます。この形なら、4.1.8のサーバーからも受け取れました。

4.2.0のスキル配信で増えたのは、配る側の起動時の検査と、受け取る側のハッシュの照合です。チームのスキルを1台のMCPサーバーにまとめておけば、同期スクリプトで各自の手元へ同じ中身を写せ、受け取る側ではmanifestのハッシュと合わないファイルを書き出す前に止められます。4.1の書き方は4.3で使えなくなる予定なので、4.2へ上げたら警告を手がかりに書き換えておきます。

参考資料

検証環境

  • macOS 27.0.1(Apple Silicon)、Python 3.13.14(uv 0.11.32の仮想環境)
  • FastMCP 4.1.8と4.2.0(PyPI、どちらもmcp 2.3.0)
  • 検証日: 2026年11月14日

この記事は、AIが調査と下書きを担当し、上記の環境で実際に動かして結果を確かめ、下書きとは別の担当が公開前に本文と出典を確認しています。制作と訂正の方針は編集・訂正方針で公開しています。