0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Strands harnessでMCPサーバをつなぐ (設定ファイルで済むこと、コードで書くべきこと)

0
Posted at

はじめに

Strands harnessの機能を色々と試したく、MCPサーバの接続を取り上げます。

Strands harnessでは、MCPサーバをmcp.jsonという設定ファイルでつなげます。一方で、その下にあるStrands Harness SDKにはMCPClientというクラスがあり、コードで細かく設定することもできます。

では、どこまでが設定ファイルで済んで、どこからコードを書く必要があるのか。公式ドキュメントを読みつつ、実際に手を動かして確かめてみました。

この記事でわかること

  • mcp.jsonだけでMCPサーバをつなぐ方法
  • 複数のMCPサーバをつないだときに、ツール名がどう扱われるか
  • MCPサーバのツールを絞り込む方法と、そのときの落とし穴
  • 設定ファイルでは書けず、コードで書く必要があるもの

先に結論を書いておくと、MCPサーバの接続はほとんどmcp.jsonで書けます。コードが必要になるのは「関数を渡したいとき」だけでした。

題材:ファイル整理を手伝うエージェント

1つのエージェントを少しずつ変えながら、以下のケースを1つずつ試します。

章 困りごと 解決策 書く場所
1 エージェントにフォルダを読ませたい filesystemサーバをつなぐ mcp.json
2 作業用と保管用のフォルダを別々につなぎたい ツール名の衝突とprefix mcp.json
3 保管用フォルダは書き換えさせたくない tool_filtersで絞り込む mcp.json
4 ファイルの削除は人の承認を挟みたい 承認用の関数を渡す コード

検証環境

項目 バージョン
Python 3.14
uv 0.13.3
strands-harness 0.3.1
strands-agents 1.61.1
mcp 2.1.1
Node.js 24以上
@modelcontextprotocol/server-filesystem 2.0.0
モデル Amazon Bedrock(bedrock/global.anthropic.claude-opus-5)

Strands harnessやmcpは更新が速く、本記事の内容はバージョンによって変わる可能性があります。特にmcpは2.x系で書き方が大きく変わっています(最後のコラムで紹介します)。

準備

必要なもの

  • uv:Pythonのバージョンとライブラリの管理に使います
  • Node.js(24以上):今回使うfilesystemサーバはNode.js製で、npxで起動します。2.0.0はpackage.jsonでNode.js 24以上を要求しています
  • AWSの認証情報:4章でAmazon Bedrockのモデルを呼び出すときに使います

プロジェクトの構成

file-agent/
├── .python-version     # uvが使うPythonのバージョン(3.14)
├── pyproject.toml      # 依存ライブラリ
├── uv.lock
├── config/             # 章ごとのmcp.json
│   ├── ch1.json
│   ├── ch2.json
│   ├── ch2_noprefix.json
│   └── ch3.json
├── work/               # 作業用フォルダ
│   ├── design_notes.md
│   ├── draft_old.md
│   └── minutes_2026-10-01.md
├── archive/            # 保管用フォルダ(昨年度の資料みたいな形を想定)
│   ├── contract_2025.md
│   └── report_2025.md
├── run.py              # 1〜3章で使う実行スクリプト
├── run_ch4.py          # 4章で使う実行スクリプト
└── trash_server.py     # 4章で使う自作MCPサーバ

pyproject.tomlは次のとおりです。

[project]
name = "file-agent"
version = "0.1.0"
requires-python = ">=3.14"
dependencies = [
    "strands-harness==0.3.1",
    "strands-agents==1.61.1",
    "mcp==2.1.1",
]

requires-pythonは「このプロジェクトは3.14以上で動く」という条件を書くもので、実際に使うバージョンは決めません。uvに使うバージョンを指定するには、.python-versionファイルを置きます。

uv python pin 3.14

セットアップ

uv sync

uv syncで、Python 3.14の取得、仮想環境(.venv)の作成、ライブラリのインストールまでまとめて行われます。以降のスクリプトはすべてuv runで実行します。

おさらい:create_harness()の使い方

Strands harnessでは、create_harness()を呼ぶだけでエージェントが手に入ります。

from strands_harness import create_harness

agent = create_harness()
agent("このフォルダにあるファイルを教えて")

create_harness()の戻り値は、Strands Harness SDKのAgentそのものです。公式ドキュメントでも、Strands harnessは独自のフレームワークではなく、SDKの部品を組み立てる薄いレイヤーだと説明されています。

Strands harness is a thin composition layer, not a framework of its own.

この「SDKの部品を組み立てている」という点が、4章で使ってみます。

1章:mcp.jsonでfilesystemサーバをつなぐ

まずは、エージェントにworkフォルダの中身を読ませてみます。

filesystemサーバとは

今回使う@modelcontextprotocol/server-filesystemは、MCPの開発元が公開しているファイル操作用のMCPサーバです。起動時にフォルダを指定すると、そのフォルダの中だけを読み書きできるツールをエージェントに提供します。

npx -y @modelcontextprotocol/server-filesystem ./work
#                                              ↑ 操作を許可するフォルダ

ファイルの読み込み、書き込み、一覧表示、検索、移動など14個のツールを持っています。なお、ファイルを削除するツールはありません(4章で関係してきます)。

mcp.jsonを書く

Strands harnessは、他のMCPクライアントと同じmcpServers形式の設定を受け付けます。

Strands harness connects them from a standard mcpServers config, the same shape other MCP clients use, and adds every discovered tool to the agent's tool list.

Claude DesktopやKiroなどでMCPサーバを設定したことがあれば、同じ書き方です。

{
  "mcpServers": {
    "work": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem@2.0.0", "./work"]
    }
  }
}

workがサーバの名前で、commandとargsがサーバを起動するコマンドです。バージョンを固定しないとnpxがその時点の最新版を取ってくるので、@2.0.0まで書いておきます。

実行スクリプト

1〜3章では、次のスクリプトを使います。

import sys
from strands_harness import create_harness

agent = create_harness(mcp_servers=sys.argv[1])   # mcp.jsonを指定してエージェントを作る
print(sorted(agent.tool_names))                    # 登録されたツールを表示

if len(sys.argv) > 2:
    agent(sys.argv[2])                             # 質問があれば実行する

MCPサーバをつなぐのに必要なのは、create_harness()にmcp_serversでmcp.jsonのパスを渡すことだけです。ファイルのパスの代わりに、同じ内容の辞書を直接渡すこともできます。

動かしてみる

uv run run.py config/ch1.json "workフォルダにどんなファイルがある?"
Secure MCP Filesystem Server running on stdio
['edit', 'programmatic_tool_caller', 'read', 'retrieve_context', 'search_memory', 'shell', 'strands_manage_background_task', 'subagent', 'todo_write', 'web_fetch', 'work_create_directory', 'work_directory_tree', 'work_edit_file', 'work_get_file_info', 'work_list_allowed_directories', 'work_list_directory', 'work_list_directory_with_sizes', 'work_move_file', 'work_read_file', 'work_read_media_file', 'work_read_multiple_files', 'work_read_text_file', 'work_search_files', 'work_write_file', 'write']

Tool #1: work_list_allowed_directories

Tool #2: work_directory_tree
I should include file sizes too, so let me put together a proper listing.

Tool #3: work_list_directory_with_sizes
workフォルダには Markdown ファイルが3つあります(サブフォルダはありません)。

| ファイル名 | サイズ |
|---|---|
| design_notes.md | 34 B |
| draft_old.md | 64 B |
| minutes_2026-10-01.md | 62 B |

合計 160 B と、どれもごく短いファイルです。中身も確認しますか?

1行目のSecure MCP Filesystem Server running on stdioは、filesystemサーバが起動したときに出すメッセージです。create_harness()を呼んだ時点で、mcp.jsonに書いたサーバが起動されていることが分かります。

エージェントは、work_list_allowed_directoriesで操作できるフォルダを確認し、work_directory_treeとwork_list_directory_with_sizesで中身を調べてから答えています。設定ファイルを1つ書いただけで、MCPサーバのツールを使えるようになりました。

ツール名に注目

ツール一覧をよく見ると、filesystemサーバのツールにはwork_が付いています。filesystemサーバ自体が持っているツール名はread_fileやlist_directoryですが、harnessがmcp.jsonに書いたサーバ名を頭に付けているためです。

また、edit・read・shell・writeなど、work_が付いていないツールも並んでいます。これはharnessに最初から入っている組み込みツールです。MCPサーバのツールは、組み込みツールに追加される形で登録されるようです。

このツール名の付け方について、次の2章で試していきます。

2章:保管用フォルダも追加する

次に、昨年度の資料という建て付けでファイルを置いているarchiveフォルダもつなぎます。

作業用のworkとは役割が違うので、別のサーバとして起動します。

mcp.jsonにサーバを追加する

同じfilesystemサーバを、フォルダだけ変えてもう1つ書きます。

{
  "mcpServers": {
    "work":    { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem@2.0.0", "./work"] },
    "archive": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem@2.0.0", "./archive"] }
  }
}

ここで気になるのがツール名です。2つとも同じfilesystemサーバなので、どちらもread_fileやwrite_fileといった同じ名前のツールを持っています。

動かしてみる

uv run run.py config/ch2.json
Secure MCP Filesystem Server running on stdio
Secure MCP Filesystem Server running on stdio
['archive_create_directory', 'archive_directory_tree', 'archive_edit_file', 'archive_get_file_info',
 'archive_list_allowed_directories', 'archive_list_directory', 'archive_list_directory_with_sizes',
 'archive_move_file', 'archive_read_file', 'archive_read_media_file', 'archive_read_multiple_files',
 'archive_read_text_file', 'archive_search_files', 'archive_write_file',
 ...
 'work_create_directory', 'work_directory_tree', 'work_edit_file', 'work_get_file_info',
 'work_list_allowed_directories', 'work_list_directory', 'work_list_directory_with_sizes',
 'work_move_file', 'work_read_file', 'work_read_media_file', 'work_read_multiple_files',
 'work_read_text_file', 'work_search_files', 'work_write_file', ...]

サーバが2つ起動し、work_read_fileとarchive_read_fileのように、サーバ名で区別された28個のツールが登録されました。1章で見たとおり、harnessがmcp.jsonのサーバ名をツール名の頭に付けてくれるので、同じサーバを2つつないでも名前がぶつかりません。

この動きは、create_harness()のdocstringにも書かれています。

Each server's tools are prefixed with its name by default (<server>_<tool>) so servers don't clash; set a server's "prefix" to override, or "prefix": "" to opt out.
(strands-harness 0.3.1 create_harness()のdocstring)

次のコマンドで、お使いのバージョンの説明を確認できます。

uv run python -c "from strands_harness import create_harness; help(create_harness)"

公式ドキュメントのConnect MCP serversには「名前が衝突しそうならprefixを設定する」とだけ書かれていて、サーバ名が自動で付くことには触れられていません(2026年10月時点)。

prefixを外すとどうなるか

試しに、"prefix": ""で自動のprefixを外してみます。

{
  "mcpServers": {
    "work":    { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem@2.0.0", "./work"],    "prefix": "" },
    "archive": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem@2.0.0", "./archive"], "prefix": "" }
  }
}
uv run run.py config/ch2_noprefix.json
ValueError: Failed to load tool <strands.tools.mcp.mcp_client.MCPClient object at 0x10c4046e0>: Tool name 'read_file' already exists. Cannot register tools with exact same name.

2つのサーバがどちらもread_fileを登録しようとして、エージェントの作成そのものが失敗しました。片方のツールが黙って上書きされるのではなく、起動時にエラーで止まるので、気づかないまま動いてしまう心配はありません。

好きな名前を付けることもできる

サーバ名とは別の名前を付けたい場合は、prefixに文字列を書きます。

"archive": { "command": "npx", "args": [...], "prefix": "old" }

この場合、ツール名はold_read_fileのようになります。

基本的にはサーバ名がそのまま使われるので、mcp.jsonでは「何のサーバか分かる名前」を付けておくのがおすすめです。モデルがツールを選ぶときや、ログを読むときの手がかりになります。

3章:保管用フォルダを読み取り専用にする

2章でarchiveをつなぎましたが、このままだとarchive_write_fileやarchive_edit_fileも使えてしまいます。昨年度の資料は参照するだけで、書き換えられては困ります。

そこで、archiveのサーバから使えるツールを読み取り系だけに絞ります。

tool_filtersを書く

ツールの絞り込みにはtool_filtersを使います。Strands Harness SDKのドキュメントではMCPClientの引数として紹介されていますが、mcp.jsonにもそのまま書けます。

{
  "mcpServers": {
    "work":    { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem@2.0.0", "./work"] },
    "archive": {
      "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem@2.0.0", "./archive"],
      "tool_filters": {
        "allowed": ["^read_", "^list_", "^directory_tree$", "^search_files$", "^get_file_info$"]
      }
    }
  }
}

allowedに書いたものに当てはまるツールだけが登録されます。逆に、外したいツールをrejectedに書くこともできます。両方書いた場合は、allowedで残したあとにrejectedで外す順番で適用されます。

Combined filters - applies allowed first, then rejected

動かしてみる

uv run run.py config/ch3.json
Secure MCP Filesystem Server running on stdio
Secure MCP Filesystem Server running on stdio
['archive_directory_tree', 'archive_get_file_info', 'archive_list_allowed_directories',
 'archive_list_directory', 'archive_list_directory_with_sizes', 'archive_read_file',
 'archive_read_media_file', 'archive_read_multiple_files', 'archive_read_text_file',
 'archive_search_files', ...]

2章では14個あったarchive_のツールが10個になりました。archive_write_file、archive_edit_file、archive_move_file、archive_create_directoryの4つが消えています。workのほうは何も書いていないので、14個のままです。

書くときの注意点

1. prefixを付ける前の名前で書く

tool_filtersは、サーバが持っている元のツール名と照らし合わせます。archive_read_fileではなくread_fileで書きます。

2. 文字列は正規表現として扱われる

mcp.jsonに書いた文字列は、正規表現として先頭から照らし合わされます。SDKのドキュメントにも、設定ファイルで書く場合は正規表現になると書かれています。

Declarative McpClient.loadServers() config accepts prefix and toolFilters too, where filter strings are regexes.

この一文はTypeScript版の説明ですが、AIに調査させて、Python版でも同じ動きになることをライブラリのコードで確認しました(strands-agents 1.61.1)。

そのため、"read_file"と書くとread_file_v2のようにread_fileで始まる名前にも当てはまります。名前を完全に一致させたいときは、"^read_file$"のように^と$で囲んでおくと安全です。

注意:組み込みツールは別に残っている

ここまでで、archiveのMCPツールからは書き込み系が消えました。ただし、1章で見たとおり、harnessにはedit・write・shellといった組み込みツールが最初から入っています。これらはMCPサーバとは関係なく動くので、tool_filtersだけではエージェントがarchiveのファイルを書き換える可能性が残ります。

確実に防ぎたい場合は、create_harness()のbuiltin_toolsで組み込みのファイル操作ツールも外します。

agent = create_harness(
    mcp_servers="./config/ch3.json",
    builtin_tools={"shell": False, "read": False, "write": False, "edit": False},
)

builtin_toolsに辞書を渡すと、デフォルトの組み込みツールからFalseにしたものだけが外れます。

A mapping edits the harness's default set: False removes a tool, True adds one
(strands-harness 0.3.1 create_harness()のdocstringより)

4章では、この設定を入れた状態でエージェントを動かします。

4章:削除だけは人の承認を挟む

最後に、不要になったファイルを削除できるようにします。ただし、削除は取り返しがつかないので、実行する前に人の承認を挟みたいところです。

承認付きの削除サーバを自作する

1章で触れたとおり、filesystemサーバには削除ツールがありません。そこで、削除用のMCPサーバを自作します。

Elicitationとは

MCPにはElicitation(「聞き出す」という意味)という仕組みがあります。ツールの実行途中で、サーバから人に質問できる仕組みです。

ふつうのツールは、エージェントから頼まれたら実行して結果を返すだけです。Elicitationを使うと、その途中に「人に確認する」ステップを挟めます。

エージェント ──「draft_old.md を削除して」──→ 削除サーバ
                                               │
人 ←───────「削除してよいですか?」────────────┘  ← ここがElicitation
 │
 └──────────「はい(承認者:mtmt_factory)」──────→ 削除サーバ ── 削除を実行

今回はこれを使って、「削除してよいか」を人に聞いてから実行するツールを作ります。

"""work フォルダのファイルを、人の承認を得てからゴミ箱(./trash)へ移す MCP サーバ"""
import shutil
from pathlib import Path
from typing import Annotated
from pydantic import BaseModel, Field
from mcp.server.mcpserver import MCPServer, Resolve, Elicit, ElicitationResult, CancelledElicitation

WORK = Path("./work").resolve()
TRASH = Path("./trash").resolve()

mcp = MCPServer("trash")

class Approval(BaseModel):
    approver: str = Field(description="承認者の名前")

def find_target(filename: str) -> Path | None:
    target = (WORK / filename).resolve()
    return target if WORK in target.parents and target.is_file() else None

def ask_approval(filename: str) -> Elicit[Approval] | CancelledElicitation:
    if find_target(filename) is None:
        return CancelledElicitation()  # 対象がなければ承認を聞かない
    return Elicit(f"work/{filename} をゴミ箱へ移動してよいですか?", Approval)

@mcp.tool(description="work フォルダのファイルをゴミ箱へ移動する。実行前に人の承認を求める。filename は work フォルダからの相対パス。")
async def delete_file(
    filename: str,
    approval: Annotated[ElicitationResult[Approval], Resolve(ask_approval)],
) -> str:
    target = find_target(filename)
    if target is None:
        return f"エラー: work フォルダ内に {filename} というファイルはありません。"
    if approval.action != "accept":
        return f"承認されなかったため、{filename} は削除していません({approval.action})。"
    TRASH.mkdir(exist_ok=True)
    shutil.move(str(target), TRASH / target.name)
    return f"{approval.data.approver} さんの承認により、{filename} をゴミ箱へ移動しました。"

mcp.run()

ポイントはdelete_fileの引数approvalです。Resolve(ask_approval)と書いておくと、ツールの本体が動く前にask_approvalが呼ばれ、そこで返したElicit(...)の内容がユーザーへの質問になります。ユーザーの答えがapprovalに入ってから、ツールの本体が動きます。

ここでは完全な削除はせずに、./trashフォルダへの移動にしています。

MCPの公式SDK(Python)は2.x系で書き方が大きく変わっています。StrandsのドキュメントにあるFastMCPやelicit(...)を使った書き方は、mcp 2.1.1ではそのままでは動きませんでした。詳しくは最後のコラムで紹介します。

なぜここだけコードが必要なのか

サーバが「削除してよいですか?」と聞いてきても、エージェント側にそれを人へ伝える処理がなければ、誰も答えられません。そこで、「質問が来たらこうしてね」という処理を関数で用意して、あらかじめ渡しておく必要があります。

この関数を渡す場所が、Strands Harness SDKのMCPClientにあるelicitation_callbackです。

Configure an elicitation callback on the client to respond to these requests

ところが、mcp.jsonはただの設定ファイルなので、文字列や数字は書けても、Pythonの関数は書けません。だから、このサーバだけはコードで接続します。

mcp.jsonで書いていた内容を、そのままPythonで書き直すだけです。違いは、最後に関数を1つ足すところだけです。

mcp.jsonだと以下となり、

"trash": { "command": "python", "args": ["trash_server.py"], "prefix": "trash" }

Pythonで書くとこうなります。

trash = MCPClient(
lambda: stdio_client(StdioServerParameters(command="python", args=["trash_server.py"])),
prefix="trash",
elicitation_callback=on_elicit,   # ← ここだけがmcp.jsonでは書けない
)

実行スクリプト

"""4章:削除だけは承認制にする。承認を聞く関数を渡したいので、trash サーバはコードで接続する"""
import os, sys
from mcp import stdio_client, StdioServerParameters
from mcp.types import ElicitResult
from strands.tools.mcp import MCPClient
from strands_harness import create_harness

# サーバから「承認して」と聞かれたときに呼ばれる関数
async def on_elicit(context, params):
    print(f"\n[承認依頼] {params.message}")
    if input("  承認しますか? (y/N): ").strip().lower() != "y":
        return ElicitResult(action="decline")
    name = input("  承認者の名前: ").strip() or "unknown"
    return ElicitResult(action="accept", content={"approver": name})

# trash サーバへの接続(mcp.json の "command"/"args"/"prefix" に相当+コールバック)
trash = MCPClient(
    lambda: stdio_client(StdioServerParameters(command=sys.executable, args=["trash_server.py"])),
    prefix="trash",
    application_name="trash",
    elicitation_callback=on_elicit,
)

agent = create_harness(
    model=os.environ.get("HARNESS_MODEL", "bedrock/global.anthropic.claude-opus-5"),
    mcp_servers="./config/ch3.json",   # work と archive は設定ファイルのまま
    tools=[trash],                     # trash だけコードで渡す
    builtin_tools={"shell": False, "read": False, "write": False, "edit": False},
)
print("tools:", sorted(agent.tool_names))
if os.environ.get("DRY_RUN"):
    sys.exit(0)

prompt = sys.argv[1] if len(sys.argv) > 1 else "work フォルダの中を確認して、不要になった下書きファイルを削除して。"
print(f"\n> {prompt}")
agent(prompt)
print()

ポイントはcreate_harness()の部分です。

  • workとarchiveは、3章のmcp.jsonをそのまま使う
  • trashだけは、コードで作ったMCPClientをtools=で渡す
  • 3章の最後で触れたとおり、組み込みのファイル操作ツールは外しておく

tools=は、組み込みツールに追加するツールを渡す引数です。0章で触れたとおりcreate_harness()はSDKの部品を組み立てているだけなので、SDKのMCPClientをそのまま渡せます。設定ファイルとコードは混ぜて使えるので、コードが必要なサーバだけコードで書けば済みます。

動かしてみる:承認した場合

uv run run_ch4.py
> work フォルダの中を確認して、不要になった下書きファイルを削除して。
I'll start by looking at what's in the work folder.
Tool #1: work_directory_tree

Tool #2: work_list_allowed_directories
I should check the files to figure out which drafts are outdated and no longer needed.

Three files. Let me read them to judge which are genuinely unneeded drafts.
Tool #3: work_read_multiple_files
draft_old.md is explicitly marked as unneeded while the other two files are active content, so I should delete only that one. The tool itself requires human approval before executing, and since the user explicitly asked to delete unneeded draft files, this request is covered.

`draft_old.md` is the only match: its title is「下書き(古い)」and its body states it's no longer needed. The other two are live content (設計メモ with VPC検討, and an議事録 with an open action item), so I'll leave them alone.
Tool #4: trash_delete_file

[承認依頼] work/draft_old.md をゴミ箱へ移動してよいですか?
  承認しますか? (y/N): y
  承認者の名前: mtmt_factory

Tool #5: work_list_directory
完了しました。

**削除したファイル(ゴミ箱へ移動)**
- `draft_old.md` — 見出しが「下書き(古い)」で、本文に「このファイルは不要になった」と明記されていたため

**残したファイル**
- `design_notes.md` — VPC構成の検討中の設計メモ(下書きではなく作業中の資料)
- `minutes_2026-10-01.md` — 議事録。「次回までに見積もりを作成」という未完了のアクションアイテムが残っています

削除後のフォルダ一覧を再確認し、上記2ファイルのみが残っていることを検証済みです。なお `draft_old.md` はゴミ箱へ移動されただけなので、必要であれば復元できます。

エージェントはworkフォルダの3ファイルを読み、本文に「不要になった」と書かれたdraft_old.mdだけを削除対象に選びました。trash_delete_fileを呼んだところで承認依頼が表示され、yと名前を入力すると、その名前がサーバまで届いて削除が実行されています。

動かしてみる:却下した場合

draft_old.mdをworkに戻してから、もう一度実行してnを入力します。

mv trash/draft_old.md work/
uv run run_ch4.py
> work フォルダの中を確認して、不要になった下書きファイルを削除して。
I'll start by looking at what's in the work folder.
Tool #1: work_list_allowed_directories

Tool #2: work_directory_tree
I should check the files to figure out which drafts are no longer needed.

Three files. Let me read them before deciding anything.
Tool #3: work_read_multiple_files
Only draft_old.md is explicitly marked as no longer needed, so that's the one I should move to trash. The other two files contain active content and shouldn't be touched. I'll use the trash tool, which already requires human approval before executing, so that serves as the appropriate safeguard here.

ファイルの中身を確認しました。結果は明確です。

| ファイル | 内容 | 判断 |
|---|---|---|
| `design_notes.md` | 「VPC構成の検討」 | 現役の設計メモ → 残す |
| `draft_old.md` | 「下書き(古い) / このファイルは不要になった」 | 本文に不要と明記 → 削除対象 |
| `minutes_2026-10-01.md` | 「次回までに見積もりを作成」 | 未完了タスクあり → 残す |

削除対象は `draft_old.md` の1件のみです。ゴミ箱への移動(復元可能)で実行します。
Tool #4: trash_delete_file

[承認依頼] work/draft_old.md をゴミ箱へ移動してよいですか?
  承認しますか? (y/N): n
The user declined the deletion, so draft_old.md stays in place. I shouldn't retry — I'll just report this outcome and ask what they'd like to do next.

承認されなかったため、削除は行っていません。**work フォルダの3ファイルはすべてそのまま残っています。**

(以下、今後の対応の選択肢を提示)

却下すると、サーバは削除せずに「承認されなかった」という結果を返します。エージェントはそれを受けて、同じ削除を再試行しませんでした。work_write_fileで中身を消すなど、別の手段で削除しようともしていません。

ただし、再試行しなかったのはモデルの判断です。仕組みとして保証されているのは「承認されなければtrash_delete_fileは何もしない」ところまでなので、削除の手段を承認付きのツールだけに絞っておくことが大事です。今回は3章で組み込みのファイル操作ツールを外しているので、work_のツールではworkフォルダの外へは移動できず、削除の手段はtrash_delete_fileだけになっています。

progress_callbackもコードで渡す

MCPClientには、elicitation_callbackのほかに、時間のかかるツールの進捗を受け取るprogress_callbackという引数もありました。

MCP servers can report incremental progress during long-running tool calls. Configure a progress_callback on the client to receive these updates

こちらも関数を渡すものなので、使いたい場合は同じようにMCPClientをコードで作ることになります。

コラム:mcp 2.xでは公式サンプルがそのまま動かない

Strandsのドキュメントにある自作MCPサーバのサンプルは、mcp 1.x系の書き方です。

mcp 2.1.1では、次の2点でつまずきました。

ドキュメントの書き方 mcp 2.xの書き方
サーバの作成 from mcp.server import FastMCP from mcp.server.mcpserver import MCPServer
承認を求める ツールの中でserver.get_context().elicit(...) 引数にAnnotated[ElicitationResult[...], Resolve(関数)]と書き、その関数からElicit(...)を返す

FastMCPはMCPServerに名前が変わっていて、ドキュメントどおりにimportするとImportErrorになります。

承認の求め方も変わっています。mcp 2.xでもツールの中でctx.elicit(...)を呼ぶ書き方は残っていますが、試すとno back-channel for server-initiated requestsというエラーになりました。

原因は、2026-07-28版のMCPの仕様で、質問のやり取りの仕方が変わったからでした。イメージで理解しましたが、以下のような変更であったみたいです。

  • これまで:サーバがツールの実行を途中で止めて、その場でクライアントに「質問があります」と送る
  • 2026-07-28版から:サーバは「答えが必要です」という返事をいったん返して終わる。クライアントが答えを付けて、もう一度ツールを呼び直す

ctx.elicit(...)は「これまで」のやり方なので、新しい仕様で接続するとエラーになります。Resolveを使う書き方にしておけば、どちらのやり方で送るかをライブラリが自動で選んでくれます。

まとめ

Strands harnessでMCPサーバをつないで、動かしてみました。

やりたいこと 書く場所
MCPサーバをつなぐ mcp.json
複数のサーバのツール名を区別する mcp.json(サーバ名が自動で付く)
使えるツールを絞る mcp.json(tool_filters)
承認を挟む(関数を渡す) コード(MCPClient)

MCPサーバの接続は、ほとんどmcp.jsonだけで済みました。コードが必要になるのは、elicitation_callbackのように関数を渡したいときだけです。そのときも、コードが必要なサーバだけをMCPClientで書いてtools=に渡せば、残りはmcp.jsonのまま使えます。

あわせて、MCPのツールを絞っても組み込みのwriteやeditは残る、という点には注意が必要です。書き換えさせたくないものがあるなら、builtin_toolsで組み込みツールも外しておくと良いと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?