Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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
66 changes: 52 additions & 14 deletions .github/workflows/publish-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@
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 }}
Expand Down Expand Up @@ -40,12 +45,13 @@

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'

Expand Down Expand Up @@ -185,6 +191,8 @@
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' ||
Expand Down Expand Up @@ -340,7 +348,9 @@
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' ||
Expand All @@ -367,7 +377,7 @@
steps:
- name: Create deployment
id: deployment
uses: bobheadxi/deployments@v1
uses: bobheadxi/deployments@88ce5600046c82542f8246ac287d0a53c461bca3 # v1.4.0
with:
step: start
token: ${{ secrets.GITHUB_TOKEN }}
Expand Down Expand Up @@ -431,7 +441,7 @@

- name: Update deployment status
if: always()
uses: bobheadxi/deployments@v1
uses: bobheadxi/deployments@88ce5600046c82542f8246ac287d0a53c461bca3 # v1.4.0
with:
step: finish
token: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -443,13 +453,17 @@
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'

Expand All @@ -465,19 +479,23 @@
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
python -m build
python -m twine upload dist/*

Check notice on line 482 in .github/workflows/publish-packages.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

use-trusted-publishing

publish-packages.yml:482: prefer trusted publishing for authentication: this command

publish_scc:
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'

Expand All @@ -493,19 +511,23 @@
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
python -m build
python -m twine upload dist/*

Check notice on line 514 in .github/workflows/publish-packages.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

use-trusted-publishing

publish-packages.yml:514: prefer trusted publishing for authentication: this command

publish_secops:
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'

Expand All @@ -514,26 +536,42 @@
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__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
python -m build
python -m twine upload dist/*

Check notice on line 546 in .github/workflows/publish-packages.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

use-trusted-publishing

publish-packages.yml:546: prefer trusted publishing for authentication: this command

- 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/*

Check notice on line 558 in .github/workflows/publish-packages.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

use-trusted-publishing

publish-packages.yml:558: prefer trusted publishing for authentication: this command

publish_soar:
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'

Expand All @@ -549,4 +587,4 @@
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
python -m build
python -m twine upload dist/*

Check notice on line 590 in .github/workflows/publish-packages.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

use-trusted-publishing

publish-packages.yml:590: prefer trusted publishing for authentication: this command
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,9 @@ app_data.db
.gemini/

# devcontainer
.devcontainer/
.devcontainer/

# Local scratch and working notes
notes.md
notes.MD
NOTES.md
26 changes: 26 additions & 0 deletions server/secops-alias/README.md
Original file line number Diff line number Diff line change
@@ -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/)
37 changes: 37 additions & 0 deletions server/secops-alias/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
3 changes: 3 additions & 0 deletions server/secops/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
2 changes: 2 additions & 0 deletions server/secops/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
},
)
77 changes: 77 additions & 0 deletions server/secops/tests/test_packaging.py
Original file line number Diff line number Diff line change
@@ -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"
Loading