From afa2c12baca1bb687ccb5bef4145ad6d7f47f233 Mon Sep 17 00:00:00 2001 From: Dan Dye Date: Fri, 28 Aug 2026 00:52:01 +0000 Subject: [PATCH 1/2] feat(packaging): sync publishing for google-secops-mcp and secops-mcp - Add server/secops-alias metapackage for secops-mcp official alias - Update publish_secops workflow to publish canonical and alias packages in lockstep - Add secops-mcp and google-secops-mcp script and entry point aliases - Add packaging unit tests to verify version alignment and entry point resolution - Add .env.example with local configuration templates --- .env.example | 43 ++++++++++++++ .github/workflows/publish-packages.yml | 14 ++++- .gitignore | 7 ++- server/secops-alias/README.md | 26 +++++++++ server/secops-alias/pyproject.toml | 37 +++++++++++++ server/secops/pyproject.toml | 3 + server/secops/setup.py | 2 + server/secops/tests/test_packaging.py | 77 ++++++++++++++++++++++++++ 8 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 .env.example create mode 100644 server/secops-alias/README.md create mode 100644 server/secops-alias/pyproject.toml create mode 100644 server/secops/tests/test_packaging.py diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..7c86a11d --- /dev/null +++ b/.env.example @@ -0,0 +1,43 @@ +# ============================================================================== +# Environment Configuration for MCP Security Servers & Publishing +# ============================================================================== + +# ------------------------------------------------------------------------------ +# PyPI / TestPyPI Publishing Configuration +# ------------------------------------------------------------------------------ +# When publishing via Twine, use '__token__' as username and your API token as password. +TWINE_USERNAME=__token__ +TWINE_PASSWORD=pypi-your-token-here + +# Target repository URL (default is TestPyPI for testing; comment out or change for PyPI) +# TestPyPI: https://test.pypi.org/legacy/ +# PyPI: https://upload.pypi.org/legacy/ +TWINE_REPOSITORY_URL=https://test.pypi.org/legacy/ + +# ------------------------------------------------------------------------------ +# Google Chronicle / SecOps MCP Server (`google-secops-mcp` / `secops-mcp`) +# ------------------------------------------------------------------------------ +CHRONICLE_PROJECT_ID=your-google-cloud-project-id +CHRONICLE_CUSTOMER_ID=your-chronicle-customer-id +CHRONICLE_REGION=us + +# Optional: Path to Google Cloud Service Account JSON key (falls back to ADC if unset) +# SECOPS_SA_PATH=/path/to/service-account-key.json + +# ------------------------------------------------------------------------------ +# Google Threat Intelligence MCP Server (`gti-mcp`) +# ------------------------------------------------------------------------------ +# VirusTotal / GTI API key +# VT_API_KEY=your-virustotal-api-key + +# ------------------------------------------------------------------------------ +# Google SecOps SOAR MCP Server (`secops-soar-mcp`) +# ------------------------------------------------------------------------------ +# SECOPS_SOAR_URL=https://your-soar-instance.siemplify.co +# SECOPS_SOAR_API_KEY=your-soar-api-key + +# ------------------------------------------------------------------------------ +# Google Security Command Center MCP Server (`scc-mcp`) +# ------------------------------------------------------------------------------ +# SCC_ORG_ID=your-organization-id +# SCC_PROJECT_ID=your-google-cloud-project-id diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index ca5a92ca..b8c21104 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -514,7 +514,7 @@ jobs: python -m pip install --upgrade pip pip install build twine - - name: Build and publish + - name: Build and publish canonical google-secops-mcp working-directory: ./server/secops env: TWINE_USERNAME: __token__ @@ -522,6 +522,18 @@ jobs: run: | python -m build python -m twine upload dist/* + + - name: Synchronize and publish alias secops-mcp + working-directory: ./server/secops-alias + env: + VERSION: ${{ needs.request_approval.outputs.secops_current_ver }} + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml + sed -i "s/\"google-secops-mcp==.*\"/\"google-secops-mcp==${VERSION}\"/" pyproject.toml + python -m build + python -m twine upload dist/* publish_soar: needs: request_approval diff --git a/.gitignore b/.gitignore index a614800b..35f34ec8 100644 --- a/.gitignore +++ b/.gitignore @@ -209,4 +209,9 @@ app_data.db .gemini/ # devcontainer -.devcontainer/ \ No newline at end of file +.devcontainer/ + +# Local scratch and working notes +notes.md +notes.MD +NOTES.md \ No newline at end of file diff --git a/server/secops-alias/README.md b/server/secops-alias/README.md new file mode 100644 index 00000000..b1a009c3 --- /dev/null +++ b/server/secops-alias/README.md @@ -0,0 +1,26 @@ +# Google SecOps MCP Server (`secops-mcp`) + +`secops-mcp` is an official alias metapackage for [`google-secops-mcp`](https://pypi.org/project/google-secops-mcp/). + +Installing this package installs `google-secops-mcp` and provides the exact same server, CLI entry points, and tooling. + +## Quick Start + +### Installation + +```bash +pip install secops-mcp +``` + +Or run directly with `uvx`: + +```bash +uvx secops-mcp +``` + +## Documentation & Source Code + +Please refer to the main repository and documentation: +- **Repository**: [https://github.com/google/mcp-security](https://github.com/google/mcp-security) +- **Documentation**: [https://github.com/google/mcp-security/tree/main/server/secops#readme](https://github.com/google/mcp-security/tree/main/server/secops#readme) +- **Canonical PyPI Package**: [https://pypi.org/project/google-secops-mcp/](https://pypi.org/project/google-secops-mcp/) diff --git a/server/secops-alias/pyproject.toml b/server/secops-alias/pyproject.toml new file mode 100644 index 00000000..49b362cd --- /dev/null +++ b/server/secops-alias/pyproject.toml @@ -0,0 +1,37 @@ +[project] +name = "secops-mcp" +version = "0.7.1" +description = "Google SecOps MCP server (official alias for google-secops-mcp)" +readme = "README.md" +requires-python = ">=3.11" +authors = [ + { name = "Google SecOps Team", email = "chronicle@google.com" } +] +keywords = ["google", "security", "chronicle", "secops", "mcp"] +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Topic :: Security", +] +dependencies = [ + "google-secops-mcp==0.7.1" +] + +[project.urls] +Homepage = "https://github.com/google/mcp-security" +Documentation = "https://github.com/google/mcp-security/tree/main/server/secops#readme" +Repository = "https://github.com/google/mcp-security" +Issues = "https://github.com/google/mcp-security/issues" + +[project.scripts] +secops_mcp = "secops_mcp.server:main" +secops-mcp = "secops_mcp.server:main" +google-secops-mcp = "secops_mcp.server:main" + +[project.entry-points.mcp] +secops_mcp = "secops_mcp.server:main" +secops-mcp = "secops_mcp.server:main" + +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" diff --git a/server/secops/pyproject.toml b/server/secops/pyproject.toml index 8fd37a66..5d62eafe 100644 --- a/server/secops/pyproject.toml +++ b/server/secops/pyproject.toml @@ -36,9 +36,12 @@ test = [ [project.scripts] secops_mcp = "secops_mcp.server:main" +secops-mcp = "secops_mcp.server:main" +google-secops-mcp = "secops_mcp.server:main" [project.entry-points.mcp] secops_mcp = "secops_mcp.server:main" +secops-mcp = "secops_mcp.server:main" [build-system] requires = ["setuptools>=61.0"] diff --git a/server/secops/setup.py b/server/secops/setup.py index be4348ef..daf8165b 100644 --- a/server/secops/setup.py +++ b/server/secops/setup.py @@ -32,6 +32,8 @@ entry_points={ "console_scripts": [ "secops-mcp=secops_mcp.server:main", + "secops_mcp=secops_mcp.server:main", + "google-secops-mcp=secops_mcp.server:main", ], }, ) diff --git a/server/secops/tests/test_packaging.py b/server/secops/tests/test_packaging.py new file mode 100644 index 00000000..0300f0e4 --- /dev/null +++ b/server/secops/tests/test_packaging.py @@ -0,0 +1,77 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib +from pathlib import Path +import tomllib +import pytest + +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent +CANONICAL_PYPROJECT = REPO_ROOT / "server" / "secops" / "pyproject.toml" +ALIAS_PYPROJECT = REPO_ROOT / "server" / "secops-alias" / "pyproject.toml" + + +def test_pyproject_files_exist(): + assert CANONICAL_PYPROJECT.is_file(), f"Missing {CANONICAL_PYPROJECT}" + assert ALIAS_PYPROJECT.is_file(), f"Missing {ALIAS_PYPROJECT}" + + +def test_versions_in_sync(): + with open(CANONICAL_PYPROJECT, "rb") as f: + canonical_data = tomllib.load(f) + with open(ALIAS_PYPROJECT, "rb") as f: + alias_data = tomllib.load(f) + + canonical_version = canonical_data["project"]["version"] + alias_version = alias_data["project"]["version"] + + assert canonical_version == alias_version, ( + f"Version mismatch: google-secops-mcp is {canonical_version} " + f"but secops-mcp alias is {alias_version}" + ) + + +def test_alias_depends_on_exact_canonical_version(): + with open(CANONICAL_PYPROJECT, "rb") as f: + canonical_data = tomllib.load(f) + with open(ALIAS_PYPROJECT, "rb") as f: + alias_data = tomllib.load(f) + + canonical_version = canonical_data["project"]["version"] + alias_deps = alias_data["project"]["dependencies"] + + expected_dep = f"google-secops-mcp=={canonical_version}" + assert expected_dep in alias_deps, ( + f"Expected alias dependencies to contain '{expected_dep}', " + f"found: {alias_deps}" + ) + + +def test_script_entry_points_valid(): + with open(CANONICAL_PYPROJECT, "rb") as f: + canonical_data = tomllib.load(f) + + scripts = canonical_data["project"].get("scripts", {}) + entry_points = canonical_data["project"].get("entry-points", {}).get("mcp", {}) + + # Ensure critical CLI aliases exist + assert "secops_mcp" in scripts + assert "secops-mcp" in scripts + assert "google-secops-mcp" in scripts + + for name, target in {**scripts, **entry_points}.items(): + module_name, func_name = target.split(":") + module = importlib.import_module(module_name) + func = getattr(module, func_name, None) + assert callable(func), f"Entry point {name} -> {target} is not callable" From f9267e0c525f44c4005f33ae4d167bd40266f292 Mon Sep 17 00:00:00 2001 From: Dan Dye Date: Fri, 28 Aug 2026 01:06:45 +0000 Subject: [PATCH 2/2] fix(ci): pin action commit SHAs and restrict permissions for zizmor --- .github/workflows/publish-packages.yml | 52 +++++++++++++++++++------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index b8c21104..f2f48036 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -9,10 +9,15 @@ on: description: 'Comma-separated list of package tags (e.g., gti-v1.0.0,secops-v2.1.0,secops-soar-v1.0.0,scc-v1.0.0)' required: true +permissions: + contents: read + jobs: detect_changes: runs-on: ubuntu-latest name: Detect package changes + permissions: + contents: read outputs: gti_changed: ${{ steps.check_versions.outputs.gti_changed }} scc_changed: ${{ steps.check_versions.outputs.scc_changed }} @@ -40,12 +45,13 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 # Fetch all history for proper version comparison + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.11' @@ -185,6 +191,8 @@ jobs: needs: detect_changes name: Change runs-on: ubuntu-latest + permissions: + contents: read if: ${{ needs.detect_changes.outputs.gti_changed == 'true' || needs.detect_changes.outputs.scc_changed == 'true' || needs.detect_changes.outputs.secops_changed == 'true' || @@ -340,7 +348,9 @@ jobs: needs: [detect_changes, display_summary] runs-on: ubuntu-latest name: Approve package publication - permissions: write-all + permissions: + deployments: write + contents: read environment: name: production # The workflow will continue only when approved if: ${{ needs.detect_changes.outputs.gti_changed == 'true' || @@ -367,7 +377,7 @@ jobs: steps: - name: Create deployment id: deployment - uses: bobheadxi/deployments@v1 + uses: bobheadxi/deployments@88ce5600046c82542f8246ac287d0a53c461bca3 # v1.4.0 with: step: start token: ${{ secrets.GITHUB_TOKEN }} @@ -431,7 +441,7 @@ jobs: - name: Update deployment status if: always() - uses: bobheadxi/deployments@v1 + uses: bobheadxi/deployments@88ce5600046c82542f8246ac287d0a53c461bca3 # v1.4.0 with: step: finish token: ${{ secrets.GITHUB_TOKEN }} @@ -443,13 +453,17 @@ jobs: needs: request_approval if: ${{ needs.request_approval.outputs.gti_changed == 'true' }} runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.11' @@ -471,13 +485,17 @@ jobs: needs: request_approval if: ${{ needs.request_approval.outputs.scc_changed == 'true' }} runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.11' @@ -499,13 +517,17 @@ jobs: needs: request_approval if: ${{ needs.request_approval.outputs.secops_changed == 'true' }} runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.11' @@ -539,13 +561,17 @@ jobs: needs: request_approval if: ${{ needs.request_approval.outputs.soar_changed == 'true' }} runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.11'