diff --git a/.github/requirements/mcp-minimum.txt b/.github/requirements/mcp-minimum.txt new file mode 100644 index 00000000..c396e3c1 --- /dev/null +++ b/.github/requirements/mcp-minimum.txt @@ -0,0 +1,2 @@ +# Exact supported SDK floor; transitive packages resolve normally and are audited. +mcp==1.30.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 624cd7f6..f006d9b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,19 +162,86 @@ jobs: test ! -L "$HOME/.local/bin/session-peer" mcp: - name: optional MCP (${{ matrix.os }}, python ${{ matrix.python }}) + name: optional MCP (${{ matrix.os }}, python ${{ matrix.python }}, ${{ matrix.profile }}) runs-on: ${{ matrix.os }} + timeout-minutes: 20 strategy: + fail-fast: false matrix: os: [ubuntu-latest, macos-latest] python: ['3.10', '3.13'] + profile: [minimum, latest] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python }} - - run: python -m pip install '.[mcp]' --requirement .github/requirements/build.txt - - run: python -m unittest discover -s tests/mcp_integration -t . -v + - name: Install separate build and audit tooling + run: python -m pip install --requirement .github/requirements/build.txt --requirement .github/requirements/audit.txt + - name: Install MCP runtime profile + id: mcp_runtime + shell: bash + env: + MCP_PROFILE: ${{ matrix.profile }} + run: | + mkdir -p mcp-reports + python -m venv "$RUNNER_TEMP/session-peer-mcp-runtime" + runtime="$RUNNER_TEMP/session-peer-mcp-runtime/bin/python" + "$runtime" -m pip install --upgrade pip setuptools + if test "$MCP_PROFILE" = minimum; then + "$runtime" -m pip install '.[mcp]' --constraint .github/requirements/mcp-minimum.txt --report mcp-reports/install.json + else + test "$MCP_PROFILE" = latest + "$runtime" -m pip install --upgrade '.[mcp]' --report mcp-reports/install.json + fi + "$runtime" -m pip check + - name: Record and verify resolved MCP profile + id: mcp_profile + shell: bash + env: + MCP_PROFILE: ${{ matrix.profile }} + run: | + runtime="$RUNNER_TEMP/session-peer-mcp-runtime/bin/python" + "$runtime" -m pip list --format=json > mcp-reports/runtime.json + "$runtime" -m pip freeze --all > mcp-reports/runtime.txt + "$runtime" - <<'PY' | tee mcp-reports/profile.json + import importlib.metadata as metadata + import json, os, re, sys + from pathlib import Path + import mcp + pins = [line for line in Path('.github/requirements/mcp-minimum.txt').read_text().splitlines() if line and not line.startswith('#')] + if len(pins) != 1 or not re.fullmatch(r'mcp==[0-9]+\.[0-9]+\.[0-9]+', pins[0]): + raise SystemExit('Invalid MCP minimum constraint') + minimum = pins[0].removeprefix('mcp==') + version = metadata.version('mcp') + profile = os.environ['MCP_PROFILE'] + if profile not in ('minimum', 'latest') or not (version == minimum if profile == 'minimum' else version.startswith('1.')): + raise SystemExit('MCP profile version mismatch') + print(json.dumps({'profile': profile, 'mcp': version, 'python': sys.version, 'platform': sys.platform})) + PY + - name: MCP stdio, policy, timeout and owned-child cleanup + shell: bash + run: '"$RUNNER_TEMP/session-peer-mcp-runtime/bin/python" -m unittest discover -s tests/mcp_integration -t . -v' + - name: Audit resolved MCP runtime graph + if: ${{ !cancelled() && steps.mcp_runtime.outcome == 'success' && steps.mcp_profile.outcome == 'success' }} + shell: bash + run: | + audit_path=$("$RUNNER_TEMP/session-peer-mcp-runtime/bin/python" -I -c 'import site; print(site.getsitepackages()[0])') + set +e + python -m pip_audit --path "$audit_path" --format=json --output mcp-reports/audit.json 2> mcp-reports/audit.log + audit_status=$? + set -e + echo "$audit_status" > mcp-reports/audit-exit-status.txt + cat mcp-reports/audit.log >&2 + exit "$audit_status" + - name: Retain MCP dependency and audit reports + if: ${{ always() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: mcp-${{ matrix.os }}-${{ matrix.python }}-${{ matrix.profile }} + path: mcp-reports/ + if-no-files-found: error + retention-days: 30 - run: python -m build - name: Verify optional module in distributions run: | diff --git a/docs/i18n-manifest.json b/docs/i18n-manifest.json index b5fddb1b..bde53e3d 100644 --- a/docs/i18n-manifest.json +++ b/docs/i18n-manifest.json @@ -21,7 +21,7 @@ "docs/diagnostics.md": "0949e53612eeab2cd7c7bbda498e14d71e9cb1b1189aa7722030cb8702cd8481", "docs/download-metrics.md": "a537300d3fe2b60273b6f4bd84e0ae0944364fda3881d7fb7e32ce665dc2c724", "docs/handoff-runtime.md": "a236e48392c91eca0f7c3734a6008a991955ae66d16fd71f97691aac5bf0091f", - "docs/mcp.md": "a64010cdd63f2cde6a0052450bed2d05faa0f19c93c76143cb0d585d36f8ff62", + "docs/mcp.md": "48c761666abc09540fdbdb39b6974b0002e5cc89dda774ac6c24376190fa2c7b", "docs/multi-home-list.md": "f8d8e3adf727e4842ea7c4d45e8f6523b11d0513bf2509d4d1204645875e6489", "docs/paired-devices.md": "03619867241a2db20d0b2281f17fdef2020ba159b7d565a0f5bc32de3152228d", "docs/relay-auth.md": "e6da5291ce6a9da31e79e66ceeff7e9fef7e95046359acbf04d708c96ffab62f", diff --git a/docs/ja/mcp.md b/docs/ja/mcp.md index a167174b..08f2cca6 100644 --- a/docs/ja/mcp.md +++ b/docs/ja/mcp.md @@ -2,6 +2,8 @@ Python 3.10 以降でインストールします: `pipx install 'session-peer[mcp]'`(または隔離された venv にエクストラをインストール)。依存関係のない Python 3.9 CLI および `install.sh` は引き続きサポートされます。シェルインストーラーは MCP の依存関係をインストールしません。Python 3.9 では、MCP エントリポイントが Python 3.10+ が必要である旨を報告します。 +任意のエクストラはMCP SDK `mcp>=1.30,<2`を必要とします。上流の1.xセキュリティ修正を含む版まで依存関係の下限を引き上げるもので、stdio専用のこのサーバーで悪用可能な欠陥を確認したという意味ではありません。HTTP転送、OAuthサーバー経路、MCPクライアント経路は有効にしません。既存環境では明示的なパッケージ更新が必要で、宣言の変更だけではインストール済みSDKは更新されません。 + `session-peer-mcp --config /absolute/path/policy.json` を起動します。ポリシーがない場合、サーバーは起動環境の Codex ホームを使用して、ローカルの一覧取得のみを許可します。ポリシーを指定するとデフォルトが置き換えられます。例: ```json @@ -42,12 +44,14 @@ MCP ツールのアノテーションは、一覧取得を読み取り専用、 MCP エクストラの有無の両方で `python -m unittest discover -s tests -v` を実行します。オプションのテストでは実際の stdio MCP サーバーを起動し、モデルの認証情報なしで初期化、スキーマ、ポリシー拒否、および CLI の部分的な失敗を検証します。wheel と sdist の両方のインストールをテストしてください。実環境チェックでは Codex バージョン、OS、およびホストの承認設定を記録する必要があります。UDS アクセスは環境依存であり、承認のバイパスを約束するものではありません。メッセージには専用のテストセッションを使用してください。 -公式リファレンス: [Codex MCP](https://developers.openai.com/codex/extend/mcp), [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk)。 +CIではmacOS/LinuxのPython 3.10/3.13で、SDK下限の`mcp==1.30.0`と最新の互換1.xを別々に試験します。両プロファイルでstdio・ポリシー・タイムアウト・キャンセル・所有する子プロセスの終了処理を試験し、解決されたランタイム依存関係を監査して、版とレポートを保存します。推移的依存関係の版は通常の依存関係解決に従って選択されます。すべての推移的パッケージの最小版を試験するものではありません。版を固定したビルド・監査用要件はランタイム環境の外にインストールし、監査対象には初期構成用の`pip`/`setuptools`パッケージも含めます。既存の最新版解決監査とリリース監査も引き続き必須です。 + +公式リファレンス: [Codex MCP](https://developers.openai.com/codex/extend/mcp), [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk)。 [SDK 1.30.0](https://pypi.org/pypi/mcp/1.30.0/json), [GHSA-84m7-p3x7-pcfv](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-84m7-p3x7-pcfv), [GHSA-fmmv-w9g8-j3gc](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-fmmv-w9g8-j3gc). ### 非対話型クライアントの承認 Codex `exec` は、サーバーポリシーが許可している場合でも「requires approval, but approval policy is never」として send ツールを拒否することがあります。`approval_mode="auto"` は無条件の承認ではありません。非対話型統合を明示的に承認するオペレーターは、制限された宛先ポリシーと併せて、その特定の MCP サーバー/ツールに対して `mcp_servers.session_peer.tools.send_message.approval_mode="approve"` を設定できます。プラグインがこの設定を有効にすることはありません。対話型クライアントは、代わりに通常の承認プロンプトを使用できます。テストクライアントがこの承認設定を無関係なサーバーやツールにコピーしてはなりません。 -macOS/LinuxではMCP CLI呼び出しごとに新しいプロセスグループを所有します。完了・timeout・取消時はleaderの回収前にTERM→KILLでTERMを無視する子も整理し、取消は再伝播します。別sessionへ離脱した子やSSH先のプロセスは対象外です。Windowsは直接の子の終了のみで、ツリー終了は保証しません。ローカル終了で遠隔送信は取り消せません。timeoutは `outcome_unknown` のままで自動再送は禁止です。 +macOS/LinuxではMCP CLI呼び出しごとに新しいプロセスグループを所有します。完了・timeout・取消時はleaderの回収前に制限時間付きのTERM→KILLでTERMを無視する子も整理し、キャンセルは引き続き伝播します。別sessionへ離脱した子やSSH先のプロセスは対象外です。Windowsは直接の子の終了のみで、ツリー終了は保証しません。ローカル終了で遠隔送信は取り消せません。timeoutは `outcome_unknown` のままで自動再送は禁止です。 呼び出し元セッションが不明であることを示す From ラベルを含め、MCP の本文は宛先 CLI の受信側の枠で引用されます。本文のヘッダーや埋め込まれた Reply-To は権限を与えません。第三者への宛先はセッション所有者に確認してください。共有 MCP プロセスは、認証済み呼び出し元セッションや自動返信経路を引き続き提供できません。最終的な枠もネイティブメッセージの上限に含まれます。 diff --git a/docs/ko/mcp.md b/docs/ko/mcp.md index 5d9b4b69..72c28489 100644 --- a/docs/ko/mcp.md +++ b/docs/ko/mcp.md @@ -2,6 +2,8 @@ Python 3.10 이상에서 설치하십시오: `pipx install 'session-peer[mcp]'`(또는 격리된 venv에 extra 설치). 의존성이 없는 Python 3.9 CLI 및 `install.sh`는 계속 지원됩니다. 셸 설치기는 MCP 의존성을 설치하지 않습니다. Python 3.9에서 MCP 엔트리 포인트는 Python 3.10+이 필요하다고 보고합니다. +선택형 extra는 MCP SDK `mcp>=1.30,<2`를 요구합니다. 업스트림 1.x 보안 수정이 포함된 버전으로 의존성 하한을 올리는 것으로, stdio만 사용하는 이 서버에서 악용 가능한 결함이 확인됐다는 뜻은 아닙니다. 이번 변경으로 HTTP 전송, OAuth 서버 경로, MCP 클라이언트 경로가 활성화되지는 않습니다. 기존 환경에는 명시적인 패키지 업그레이드가 필요하며, 선언 변경만으로 설치된 SDK가 업데이트되지는 않습니다. + `session-peer-mcp --config /absolute/path/policy.json`으로 시작합니다. 정책이 없으면 서버는 실행 환경의 Codex 홈을 사용하여 로컬 목록 조회만 허용합니다. 제공된 정책은 기본값을 대체합니다. 예시: ```json @@ -42,12 +44,14 @@ MCP 도구 어노테이션은 목록 조회를 읽기 전용으로, 전송을 MCP extra가 있는 상태와 없는 상태에서 각각 `python -m unittest discover -s tests -v`를 실행하십시오. 선택형 테스트는 실제 stdio MCP 서버를 시작하고 모델 자격 증명 없이 초기화, 스키마, 정책 거부 및 CLI 부분 실패를 실행합니다. wheel 및 sdist 설치를 모두 테스트하십시오. 실시간 확인 시에는 Codex 버전, OS 및 호스트 승인 설정을 기록해야 합니다. UDS 접근은 환경에 따라 다르며 승인 우회를 약속하는 것이 아닙니다. 메시지에는 전용 테스트 세션을 사용하십시오. -공식 참조: [Codex MCP](https://developers.openai.com/codex/extend/mcp), [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk). +CI는 macOS/Linux의 Python 3.10/3.13에서 SDK 하한 `mcp==1.30.0`과 최신 호환 1.x를 별도로 시험합니다. 두 프로필 모두 stdio·정책·시간 초과·취소·소유 자식 프로세스 정리 시험을 실행하고, 해결된 런타임 의존성 그래프를 감사하며 버전과 보고서를 보존합니다. 전이 의존성 버전은 패키지 관리자가 일반적인 방식으로 결정합니다. 모든 전이 패키지의 최솟값을 시험하는 것은 아닙니다. 버전을 고정한 빌드·감사 요구 패키지는 런타임 환경 밖에 설치하며, 감사 대상 환경에는 초기 구성용 `pip`/`setuptools` 패키지도 포함됩니다. 기존 최신 버전 해결 감사와 릴리스 감사도 계속 필수입니다. + +공식 참조: [Codex MCP](https://developers.openai.com/codex/extend/mcp), [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk). [SDK 1.30.0](https://pypi.org/pypi/mcp/1.30.0/json), [GHSA-84m7-p3x7-pcfv](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-84m7-p3x7-pcfv), [GHSA-fmmv-w9g8-j3gc](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-fmmv-w9g8-j3gc). ### 비대화형 클라이언트 승인 서버 정책이 허용하더라도 Codex `exec`는 전송 도구를 “requires approval, but approval policy is never”로 거부할 수 있습니다. `approval_mode="auto"`는 무조건적인 승인이 아닙니다. 비대화형 연동을 명시적으로 승인하는 운영자는 제한된 목적지 정책과 함께 해당 특정 MCP 서버/도구에 대해 `mcp_servers.session_peer.tools.send_message.approval_mode="approve"`를 구성할 수 있습니다. 플러그인은 이 설정을 활성화하지 않습니다. 대화형 클라이언트는 대신 일반적인 승인 프롬프트를 사용할 수 있습니다. 테스트 클라이언트는 이 승인을 관련 없는 서버나 도구에 복사해서는 안 됩니다. -macOS/Linux에서 MCP CLI 호출은 새 프로세스 그룹을 소유합니다. 완료·시간 초과·취소 시 리더를 회수하기 전에 TERM→KILL로 해당 그룹을 정리하며 TERM을 무시하는 자식도 포함합니다. 취소는 그대로 전파됩니다. 별도 세션으로 이탈한 자식과 원격 SSH 프로세스는 범위 밖입니다. Windows는 직접 자식 종료만 유지하며 프로세스 트리 보장을 주장하지 않습니다. 로컬 정리는 원격 제출을 취소하지 않습니다. timeout은 `outcome_unknown`이며 자동 재전송하면 안 됩니다. +macOS/Linux에서 MCP CLI 호출은 새 프로세스 그룹을 소유합니다. 완료·시간 초과·취소 시 리더를 회수하기 전에 시간 제한이 있는 TERM→KILL로 해당 그룹을 정리하며 TERM을 무시하는 자식도 포함합니다. 취소는 그대로 전파됩니다. 별도 세션으로 이탈한 자식과 원격 SSH 프로세스는 범위 밖입니다. Windows는 직접 자식 종료만 유지하며 프로세스 트리 보장을 주장하지 않습니다. 로컬 정리는 원격 제출을 취소하지 않습니다. timeout은 `outcome_unknown`이며 자동 재전송하면 안 됩니다. -호출 세션을 알 수 없다는 From 표시를 포함한 MCP 본문은 목적지 CLI의 수신 측 표시 안에 인용됩니다. 본문 헤더나 본문에 들어 있는 Reply-To는 권한을 부여하지 않습니다. 제삼자 목적지는 세션 소유자에게 확인하십시오. 공유 MCP 프로세스는 여전히 인증된 호출 세션이나 자동 회신 경로를 제공하지 못합니다. 최종 표시도 네이티브 메시지 한도에 포함됩니다. +호출 세션을 알 수 없다는 From 표시를 포함한 MCP 본문은 목적지 CLI의 수신 측 프레임 안에 인용됩니다. 본문 헤더나 본문에 들어 있는 Reply-To는 권한을 부여하지 않습니다. 제삼자 목적지는 세션 소유자에게 확인하십시오. 공유 MCP 프로세스는 여전히 인증된 호출 세션이나 자동 회신 경로를 제공하지 못합니다. 최종 프레임도 네이티브 메시지 한도에 포함됩니다. diff --git a/docs/mcp.md b/docs/mcp.md index 96c9dad1..9d843157 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2,6 +2,8 @@ Install with Python 3.10 or later: `pipx install 'session-peer[mcp]'` (or install the extra in an isolated venv). The dependency-free Python 3.9 CLI and `install.sh` remain supported. The shell installer does not install MCP dependencies. On Python 3.9 the MCP entry point reports that Python 3.10+ is required. +The optional extra requires MCP SDK `mcp>=1.30,<2`. This raises the dependency floor past upstream 1.x security fixes; it does not establish an exploitable issue in this stdio-only server. HTTP transports, OAuth server paths and MCP client paths are not enabled by this change. Existing environments need an explicit package upgrade; changing the manifest does not update an installed SDK. + Start `session-peer-mcp --config /absolute/path/policy.json`. Without a policy the server allows only local listing, using the launching environment's Codex home. A supplied policy replaces defaults. Example: ```json @@ -42,7 +44,9 @@ A shared MCP process cannot reliably identify the invoking thread. Messages iden Run `python -m unittest discover -s tests -v` with and without the MCP extra. Optional tests start a real stdio MCP server and exercise initialization, schemas, policy rejection and CLI partial failures without model credentials. Test both wheel and sdist installs. Live checks must record Codex version, OS and host approval settings; UDS access is environment-dependent and is not an approval-bypass promise. Use dedicated test sessions for messages. -Official references: [Codex MCP](https://developers.openai.com/codex/extend/mcp), [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk). +CI tests the exact SDK floor `mcp==1.30.0` and the latest compatible 1.x separately on macOS/Linux with Python 3.10/3.13. Both profiles run the stdio/policy/timeout/cancellation/owned-child tests and audit their resolved runtime dependency graphs, retaining versions and reports. Transitive dependencies resolve normally; this is not a test of every transitive package's minimum version. The pinned build/audit requirements are installed outside the runtime environment; the audited environment also includes its `pip`/`setuptools` bootstrap packages. Existing latest-resolution and release audits remain required. + +Official references: [Codex MCP](https://developers.openai.com/codex/extend/mcp), [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk). [SDK 1.30.0](https://pypi.org/pypi/mcp/1.30.0/json), [GHSA-84m7-p3x7-pcfv](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-84m7-p3x7-pcfv), [GHSA-fmmv-w9g8-j3gc](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-fmmv-w9g8-j3gc). ### Non-interactive client approvals diff --git a/docs/zh-CN/mcp.md b/docs/zh-CN/mcp.md index 5f53b3f8..93d2e40f 100644 --- a/docs/zh-CN/mcp.md +++ b/docs/zh-CN/mcp.md @@ -2,6 +2,8 @@ 在 Python 3.10 或更高版本下安装:`pipx install 'session-peer[mcp]'`(或在隔离的 venv 中安装 extra)。仍支持无依赖的 Python 3.9 CLI 和 `install.sh`。shell 安装程序不安装 MCP 依赖项。在 Python 3.9 上,MCP 入口点会报告需要 Python 3.10+。 +可选extra要求MCP SDK `mcp>=1.30,<2`。这是将依赖版本下限提高到包含上游1.x安全修复的版本,并不表示已在这个仅使用stdio的服务器中确认可利用漏洞。本次变更不启用HTTP传输、OAuth服务器路径或MCP客户端路径。现有环境需要显式升级软件包;仅修改声明不会更新已安装的SDK。 + 启动 `session-peer-mcp --config /absolute/path/policy.json`。在没有策略的情况下,服务器仅允许本地列出,使用启动环境的 Codex 主目录。提供的策略将替换默认值。例如: ```json @@ -42,12 +44,14 @@ MCP 工具注解将列出标记为只读,将发送标记为非幂等。根据 在有和没有 MCP extra 的情况下运行 `python -m unittest discover -s tests -v`。可选测试会启动真实的 stdio MCP 服务器,并在没有模型凭据的情况下执行初始化、模式、策略拒绝和 CLI 部分失败测试。同时测试 wheel 和 sdist 安装。实时检查必须记录 Codex 版本、操作系统和宿主批准设置;UDS 访问取决于环境,不是批准绕过承诺。消息请使用专用测试会话。 -官方参考:[Codex MCP](https://developers.openai.com/codex/extend/mcp)、[Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk)。 +CI在macOS/Linux的Python 3.10/3.13上分别测试SDK下限`mcp==1.30.0`及最新兼容1.x。两个配置均运行stdio、策略、超时、取消及所属子进程清理测试,并审计解析得到的运行时依赖图,保留版本和报告。传递依赖按常规方式解析;这不是对所有传递依赖包的最低版本进行测试。固定版本的构建与审计依赖安装在运行时环境之外;被审计的环境也包含用于初始配置的`pip`/`setuptools`包。现有的最新版本解析审计和发布审计仍为必需。 + +官方参考:[Codex MCP](https://developers.openai.com/codex/extend/mcp)、[Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk)。 [SDK 1.30.0](https://pypi.org/pypi/mcp/1.30.0/json), [GHSA-84m7-p3x7-pcfv](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-84m7-p3x7-pcfv), [GHSA-fmmv-w9g8-j3gc](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-fmmv-w9g8-j3gc). ### 非交互式客户端批准 Codex `exec` 可能会以“requires approval, but approval policy is never”拒绝发送工具,即使服务器策略允许也是如此。`approval_mode="auto"` 并不是无条件批准。显式授权非交互式集成的操作员可以为该特定 MCP 服务器/工具配置 `mcp_servers.session_peer.tools.send_message.approval_mode="approve"`,同时配合受限的目的地策略。插件不会启用此设置。交互式客户端可以改用其常规的批准提示。测试客户端切勿将此授权复制到不相关的服务器或工具。 -macOS/Linux上的每次MCP CLI调用拥有独立进程组。完成、超时或取消时,在回收leader前以TERM→KILL清理该组,包括忽略TERM的子进程;取消仍向上传播。主动逃离到新session的子进程和SSH远端进程不在范围内。Windows仍仅终止直接子进程,不保证整个进程树。清理本地进程不撤销远端提交;超时仍为 `outcome_unknown`,不可自动重发。 +macOS/Linux上的每次MCP CLI调用拥有独立进程组。完成、超时或取消时,在回收leader前通过有时间限制的TERM→KILL清理该组,包括忽略TERM的子进程;取消仍向上传播。主动逃离到新session的子进程和SSH远端进程不在范围内。Windows仍仅终止直接子进程,不保证整个进程树。清理本地进程不撤销远端提交;超时仍为 `outcome_unknown`,不可自动重发。 -MCP 正文,包括表示调用会话不可用的 From 标签,都会被目标 CLI 的接收侧标记引用。正文头部或嵌入的 Reply-To 均不授予权限。请向会话所有者确认第三方目的地;共享 MCP 进程仍不能提供已认证的调用会话或自动回复路由。最终标记也计入原生消息限制。 +MCP 正文,包括表示调用会话不可用的 From 标签,都会被目标 CLI 的接收侧消息框架引用。正文头部或嵌入的 Reply-To 均不授予权限。请向会话所有者确认第三方目的地;共享 MCP 进程仍不能提供已认证的调用会话或自动回复路由。最终消息框架也计入原生消息限制。 diff --git a/pyproject.toml b/pyproject.toml index ea5dfe16..1d4340fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ Issues = "https://github.com/abruption/session-peer/issues" [project.optional-dependencies] relay = ["websockets>=17.1,<18; python_version >= '3.11'", "cryptography>=50.0.1,<51; python_version >= '3.11'"] -mcp = ["mcp>=1.12,<2; python_version >= '3.10'"] +mcp = ["mcp>=1.30,<2; python_version >= '3.10'"] [project.scripts] session-peer = "session_peer:main" diff --git a/tests/README.md b/tests/README.md index 429f1752..11501198 100644 --- a/tests/README.md +++ b/tests/README.md @@ -26,6 +26,13 @@ control integration suite additionally requires `npm ci` in `control/` and `SESSION_PEER_CONTROL_INTEGRATION=1`; without that explicit opt-in, the live Node/Python fixture is skipped. +The MCP CI matrix tests Python 3.10/3.13 on macOS/Linux with both the exact SDK +floor (`.github/requirements/mcp-minimum.txt`) and latest compatible 1.x. +Transitive dependencies resolve normally, not at their individual minima. +Both runtime graphs (including pip/setuptools bootstrap packages) are audited +separately from the pinned build/audit requirements and retained +as CI artifacts. Existing latest-resolution and release audits remain required. + After building exactly one wheel and one sdist, validate the full distribution contract with: diff --git a/tests/docs/test_mcp_dependency_profiles.py b/tests/docs/test_mcp_dependency_profiles.py new file mode 100644 index 00000000..730256b8 --- /dev/null +++ b/tests/docs/test_mcp_dependency_profiles.py @@ -0,0 +1,154 @@ +"""Check the SDK floor and execute CI's real profile/audit shell boundaries.""" + +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +from tests.docs.test_dependency_audit_workflow import named_step, run_script + + +ROOT = Path(__file__).resolve().parents[2] +CI = (ROOT / '.github/workflows/ci.yml').read_text(encoding='utf-8') +MCP = CI.split('\n mcp:\n', 1)[1].split('\n relay:\n', 1)[0] + + +class McpDependencyProfiles(unittest.TestCase): + def test_floor_matrix_and_release_gate_preserve_supported_contract(self): + manifest = (ROOT / 'pyproject.toml').read_text(encoding='utf-8') + self.assertIn('requires-python = ">=3.9"', manifest) + self.assertIn('mcp>=1.30,<2; python_version >= \'3.10\'', manifest) + constraint = (ROOT / '.github/requirements/mcp-minimum.txt').read_text() + self.assertEqual(['mcp==1.30.0'], [x for x in constraint.splitlines() if x and not x.startswith('#')]) + for text in ("os: [ubuntu-latest, macos-latest]", "python: ['3.10', '3.13']", + 'profile: [minimum, latest]', 'fail-fast: false'): + self.assertIn(text, MCP) + install = run_script(MCP, 'Install MCP runtime profile') + self.assertIn('--constraint .github/requirements/mcp-minimum.txt', install) + self.assertIn('--upgrade \'.[mcp]\'', install) + self.assertNotIn('requirements/audit.txt', install) + self.assertNotIn('requirements/build.txt', install) + self.assertIn('tests/mcp_integration', named_step(MCP, 'MCP stdio, policy, timeout and owned-child cleanup')) + upload = named_step(MCP, 'Retain MCP dependency and audit reports') + self.assertIn('if: ${{ always() }}', upload) + self.assertIn('matrix.profile', upload) + self.assertIn('retention-days: 30', upload) + self.assertNotIn('continue-on-error', MCP) + gate = CI.split('\n release-gate:\n', 1)[1] + self.assertRegex(gate, r'needs: \[[^\]]*\bmcp\b') + self.assertIn('needs.mcp.result', gate) + # Keep existing latest full-extra audits, including release-time audits. + for workflow in ('ci.yml', 'dependency-audit.yml', 'prepare-release.yml', 'publish.yml'): + self.assertIn("install '.[relay,mcp]'", (ROOT / '.github/workflows' / workflow).read_text()) + + @unittest.skipIf(os.name == 'nt', 'CI profile shell runs on POSIX') + def test_install_branches_pin_only_minimum_and_propagate_failure(self): + for profile in ('minimum', 'latest'): + for failure in (0, 17): + with self.subTest(profile=profile, failure=failure), tempfile.TemporaryDirectory(prefix='codex-mcp-install-') as folder: + root = Path(folder) + runtime = root / 'session-peer-mcp-runtime/bin' + runtime.mkdir(parents=True) + recorder = root / 'record.py' + recorder.write_text('import json, os, sys\n' + 'with open(os.environ["CALLS"], "a") as f: f.write(json.dumps(sys.argv[1:]) + "\\n")\n' + 'sys.exit(int(os.environ["INSTALL_FAILURE"]) if ".[mcp]" in sys.argv else 0)\n') + (runtime / 'python').write_text('#!/bin/sh\nexec "$FIXTURE_PYTHON" "$FIXTURE_RECORDER" "$@"\n') + (runtime / 'python').chmod(0o700) + result = subprocess.run( + ['bash', '--noprofile', '--norc', '-e', '-o', 'pipefail', '-c', + 'python() { test "$1" = -m && test "$2" = venv; }\n' + + run_script(MCP, 'Install MCP runtime profile')], + cwd=root, env=dict(os.environ, RUNNER_TEMP=str(root), MCP_PROFILE=profile, + FIXTURE_PYTHON=sys.executable, FIXTURE_RECORDER=str(recorder), + CALLS=str(root / 'calls.jsonl'), INSTALL_FAILURE=str(failure)), + capture_output=True, text=True) + self.assertEqual(failure, result.returncode) + calls = [json.loads(x) for x in (root / 'calls.jsonl').read_text().splitlines()] + install = next(x for x in calls if '.[mcp]' in x) + self.assertEqual(profile == 'minimum', '--constraint' in install) + if profile == 'minimum': + self.assertEqual('.github/requirements/mcp-minimum.txt', install[install.index('--constraint') + 1]) + else: + self.assertIn('--upgrade', install) + self.assertEqual(failure == 0, ['-m', 'pip', 'check'] in calls) + + @unittest.skipIf(os.name == 'nt', 'CI profile shell runs on POSIX') + def test_record_step_rejects_wrong_minimum_missing_sdk_and_wrong_major(self): + # Run the workflow's actual shell/code with only fixture distribution + # metadata, not an installed SDK or a package/advisory network request. + cases = [('minimum', '1.30.0', True), ('latest', '1.31.0', True), + ('minimum', '1.31.0', False), ('latest', '2.0.0', False), + ('minimum', None, False)] + for profile, version, success in cases: + with self.subTest(profile=profile, version=version), tempfile.TemporaryDirectory(prefix='codex-mcp-profile-') as folder: + root = Path(folder) + runtime = root / 'session-peer-mcp-runtime/bin' + runtime.mkdir(parents=True) + (root / 'mcp-reports').mkdir() + constraints = root / '.github/requirements' + constraints.mkdir(parents=True) + (constraints / 'mcp-minimum.txt').write_text((ROOT / '.github/requirements/mcp-minimum.txt').read_text()) + (root / 'mcp.py').write_text('# metadata fixture only\n') + if version: + info = root / ('mcp-' + version + '.dist-info') + info.mkdir() + (info / 'METADATA').write_text('Metadata-Version: 2.1\nName: mcp\nVersion: ' + version + '\n') + # -S prevents a real installed SDK in the test interpreter's + # site-packages from satisfying the missing-metadata control. + (runtime / 'python').write_text( + '#!/bin/sh\nif test "$1" = -m; then printf "[]\\n"; exit 0; fi\n' + 'exec "$FIXTURE_PYTHON" -S "$@"\n') + (runtime / 'python').chmod(0o700) + result = subprocess.run( + ['bash', '--noprofile', '--norc', '-e', '-o', 'pipefail', '-c', + run_script(MCP, 'Record and verify resolved MCP profile')], + cwd=root, env=dict(os.environ, RUNNER_TEMP=str(root), + PYTHONPATH=str(root), MCP_PROFILE=profile, + FIXTURE_PYTHON=sys.executable, PYTHONOPTIMIZE='1'), + capture_output=True, text=True) + self.assertEqual(success, result.returncode == 0, 'Profile acceptance mismatch') + if success: + record = json.loads((root / 'mcp-reports/profile.json').read_text()) + self.assertEqual((profile, version), (record['profile'], record['mcp'])) + + @unittest.skipIf(os.name == 'nt', 'CI audit shell runs on POSIX') + def test_audit_findings_and_service_errors_keep_exit_and_reports(self): + audit = named_step(MCP, 'Audit resolved MCP runtime graph') + self.assertIn("steps.mcp_runtime.outcome == 'success'", audit) + self.assertIn("steps.mcp_profile.outcome == 'success'", audit) + self.assertIn('!cancelled()', audit) + for status in (0, 1, 2): + with self.subTest(status=status), tempfile.TemporaryDirectory(prefix='codex-mcp-audit-') as folder: + root = Path(folder) + runtime = root / 'session-peer-mcp-runtime/bin' + runtime.mkdir(parents=True) + (runtime / 'python').symlink_to(sys.executable) + (root / 'mcp-reports').mkdir() + stub = '''python() { + test "$1" = -m && test "$2" = pip_audit || return 99 + while test "$#" -gt 0; do + if test "$1" = --output; then shift; printf '{}\\n' > "$1"; break; fi + shift + done + printf 'audit fixture diagnostic\\n' >&2 + return "$AUDIT_STATUS" + } + ''' + result = subprocess.run( + ['bash', '--noprofile', '--norc', '-e', '-o', 'pipefail', '-c', + stub + run_script(MCP, 'Audit resolved MCP runtime graph')], + cwd=root, env=dict(os.environ, RUNNER_TEMP=str(root), AUDIT_STATUS=str(status)), + capture_output=True, text=True) + self.assertEqual(status, result.returncode) + reports = root / 'mcp-reports' + self.assertEqual(str(status), (reports / 'audit-exit-status.txt').read_text().strip()) + self.assertEqual({}, json.loads((reports / 'audit.json').read_text())) + self.assertIn('audit fixture diagnostic', (reports / 'audit.log').read_text()) + + +if __name__ == '__main__': + unittest.main()