searxng-http-mcp 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- searxng_http_mcp-1.0.0/.claude/rules/architecture.md +31 -0
- searxng_http_mcp-1.0.0/.claude/rules/commands.md +21 -0
- searxng_http_mcp-1.0.0/.claude/rules/workflow.md +16 -0
- searxng_http_mcp-1.0.0/.claude-plugin/marketplace.json +25 -0
- searxng_http_mcp-1.0.0/.dockerignore +22 -0
- searxng_http_mcp-1.0.0/.github/dependabot.yml +22 -0
- searxng_http_mcp-1.0.0/.github/workflows/build.yml +167 -0
- searxng_http_mcp-1.0.0/.github/workflows/codeql.yml +35 -0
- searxng_http_mcp-1.0.0/.github/workflows/dependabot-auto-merge.yml +33 -0
- searxng_http_mcp-1.0.0/.github/workflows/reusable-test.yml +66 -0
- searxng_http_mcp-1.0.0/.github/workflows/scorecard.yml +34 -0
- searxng_http_mcp-1.0.0/.github/workflows/test.yml +110 -0
- searxng_http_mcp-1.0.0/.gitignore +40 -0
- searxng_http_mcp-1.0.0/CLAUDE.md +3 -0
- searxng_http_mcp-1.0.0/CONTRIBUTING.md +91 -0
- searxng_http_mcp-1.0.0/Dockerfile +27 -0
- searxng_http_mcp-1.0.0/LICENSE +21 -0
- searxng_http_mcp-1.0.0/PKG-INFO +820 -0
- searxng_http_mcp-1.0.0/README.md +806 -0
- searxng_http_mcp-1.0.0/README.zh-CN.md +813 -0
- searxng_http_mcp-1.0.0/SECURITY.md +37 -0
- searxng_http_mcp-1.0.0/assets/banner-dark.svg +17 -0
- searxng_http_mcp-1.0.0/assets/banner-light.svg +16 -0
- searxng_http_mcp-1.0.0/assets/browser_login.svg +30 -0
- searxng_http_mcp-1.0.0/assets/icons/claude.svg +1 -0
- searxng_http_mcp-1.0.0/assets/icons/cline.svg +1 -0
- searxng_http_mcp-1.0.0/assets/icons/codex.svg +1 -0
- searxng_http_mcp-1.0.0/assets/icons/cursor.svg +1 -0
- searxng_http_mcp-1.0.0/assets/icons/githubcopilot.svg +1 -0
- searxng_http_mcp-1.0.0/assets/icons/hermes.svg +1 -0
- searxng_http_mcp-1.0.0/assets/icons/opencode.svg +1 -0
- searxng_http_mcp-1.0.0/assets/icons/windsurf.svg +1 -0
- searxng_http_mcp-1.0.0/docs/distribution.md +38 -0
- searxng_http_mcp-1.0.0/glama.json +41 -0
- searxng_http_mcp-1.0.0/mcp_server/__init__.py +0 -0
- searxng_http_mcp-1.0.0/mcp_server/app.py +50 -0
- searxng_http_mcp-1.0.0/mcp_server/auth.py +38 -0
- searxng_http_mcp-1.0.0/mcp_server/main.py +17 -0
- searxng_http_mcp-1.0.0/mcp_server/patch_settings.py +27 -0
- searxng_http_mcp-1.0.0/mcp_server/proxy.py +80 -0
- searxng_http_mcp-1.0.0/mcp_server/tools.py +341 -0
- searxng_http_mcp-1.0.0/plugins/local/.claude-plugin/plugin.json +11 -0
- searxng_http_mcp-1.0.0/plugins/local/.mcp.json +8 -0
- searxng_http_mcp-1.0.0/plugins/local/agents/web-searcher.md +104 -0
- searxng_http_mcp-1.0.0/plugins/local/skills/web-search-via-searxng/SKILL.md +89 -0
- searxng_http_mcp-1.0.0/plugins/remote/.claude-plugin/plugin.json +11 -0
- searxng_http_mcp-1.0.0/plugins/remote/.mcp.json +11 -0
- searxng_http_mcp-1.0.0/plugins/remote/agents/web-searcher.md +104 -0
- searxng_http_mcp-1.0.0/plugins/remote/skills/web-search-via-searxng/SKILL.md +89 -0
- searxng_http_mcp-1.0.0/pyproject.toml +33 -0
- searxng_http_mcp-1.0.0/scripts/entrypoint.sh +46 -0
- searxng_http_mcp-1.0.0/scripts/glama-settings.yml +11 -0
- searxng_http_mcp-1.0.0/scripts/glama-start.sh +24 -0
- searxng_http_mcp-1.0.0/scripts/healthcheck.sh +14 -0
- searxng_http_mcp-1.0.0/scripts/review-pr.sh +68 -0
- searxng_http_mcp-1.0.0/server.json +59 -0
- searxng_http_mcp-1.0.0/tests/__init__.py +0 -0
- searxng_http_mcp-1.0.0/tests/stdio_server_mock.py +66 -0
- searxng_http_mcp-1.0.0/tests/test_app.py +77 -0
- searxng_http_mcp-1.0.0/tests/test_auth.py +73 -0
- searxng_http_mcp-1.0.0/tests/test_http.py +168 -0
- searxng_http_mcp-1.0.0/tests/test_patch_settings.py +46 -0
- searxng_http_mcp-1.0.0/tests/test_proxy.py +89 -0
- searxng_http_mcp-1.0.0/tests/test_stdio.py +72 -0
- searxng_http_mcp-1.0.0/tests/test_tools.py +484 -0
- searxng_http_mcp-1.0.0/uv.lock +648 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## Architecture
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
mcp_server/ # MCP server package
|
|
5
|
+
main.py # Entry point (HTTP or stdio mode)
|
|
6
|
+
app.py # Starlette ASGI app (MCP + auth + reverse proxy)
|
|
7
|
+
tools.py # MCP tools (search, autocomplete, engine_info)
|
|
8
|
+
auth.py # API key + Basic Auth middleware
|
|
9
|
+
proxy.py # SearXNG reverse proxy
|
|
10
|
+
patch_settings.py # SearXNG settings patcher
|
|
11
|
+
tests/ # pytest + pytest-anyio
|
|
12
|
+
plugins/ # Claude Code plugin (local + remote variants)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Code Style
|
|
16
|
+
|
|
17
|
+
- Python 3.14+, type annotations with `Annotated` + `Field`
|
|
18
|
+
- Async throughout (`async def`, `httpx.AsyncClient`)
|
|
19
|
+
- No comments unless explaining non-obvious "why"
|
|
20
|
+
|
|
21
|
+
## Testing
|
|
22
|
+
|
|
23
|
+
- `pytest-anyio` for async tests (`@pytest.mark.anyio`)
|
|
24
|
+
- Mock `httpx.AsyncClient` for unit tests
|
|
25
|
+
- `stdio_client` / `streamable_http_client` for transport integration tests
|
|
26
|
+
- `plugins/local/` and `plugins/remote/` skills and agents must stay identical
|
|
27
|
+
|
|
28
|
+
## Gotchas
|
|
29
|
+
|
|
30
|
+
- `docs/superpowers/` is in `.gitignore` — local plugin skill files, not tracked in git
|
|
31
|
+
- `mcp_server` is not installed as a package — tests need `PYTHONPATH` set to project root
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## Commands
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
# Install dependencies
|
|
5
|
+
pip install "mcp[cli]" pytest pytest-anyio httpx pytest-cov pyyaml
|
|
6
|
+
|
|
7
|
+
# Run tests
|
|
8
|
+
pytest tests/ -v
|
|
9
|
+
|
|
10
|
+
# Run single test file
|
|
11
|
+
pytest tests/test_tools.py -v
|
|
12
|
+
|
|
13
|
+
# Run with coverage
|
|
14
|
+
pytest tests/ -v --cov=mcp_server --cov-report=term-missing --cov-fail-under=80
|
|
15
|
+
|
|
16
|
+
# Start HTTP server (requires SearXNG on port 8080)
|
|
17
|
+
python -m mcp_server.main
|
|
18
|
+
|
|
19
|
+
# Start stdio server
|
|
20
|
+
python -m mcp_server.main --stdio
|
|
21
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## Project Workflow
|
|
2
|
+
|
|
3
|
+
- Branching: `dev` is the development branch, `main` is the release branch. All changes go through PRs.
|
|
4
|
+
- PR rules: feature/fix branches → dev → main. PRs to main must come from dev only.
|
|
5
|
+
- Copilot review flow: After PR to dev, wait for CI to pass, then confirm Copilot review is submitted (use `gh api repos/{owner}/{repo}/pulls/{number}/reviews` to verify `copilot-pull-request-reviewer[bot]` has posted a review — don't just check comment count). Fix feedback on the same branch, resolve addressed comments, then merge. Delete local branch after merge (remote branch is auto-deleted by GitHub).
|
|
6
|
+
- CI: push triggers test.yml. PRs to main run policy checks only (source-branch verification + fork protections), no tests.
|
|
7
|
+
- Rebase all local branches (feat/fix/docs/ci etc.) onto latest dev before PR (`git fetch origin && git rebase origin/dev`).
|
|
8
|
+
- After push, wait for push-triggered test.yml CI to pass before creating PR.
|
|
9
|
+
- Merge strategy: always use merge commit (GitHub: "Create a merge commit", CLI: `gh pr merge --merge`). Never use squash or rebase merge.
|
|
10
|
+
- Commit style: Conventional Commits.
|
|
11
|
+
- `.github/` directory is protected — fork PRs cannot modify it.
|
|
12
|
+
- Review external PRs locally using `scripts/review-pr.sh` (runs tests in isolated Docker container). Never execute untrusted code directly on the host.
|
|
13
|
+
- When waiting for CI/Copilot review, use Bash tool's `run_in_background` parameter to poll status (e.g., `gh run watch`). Don't block the conversation with sleep.
|
|
14
|
+
- Copilot review comments must be actually fixed before resolving. Never batch-resolve without fixing.
|
|
15
|
+
- Manage GitHub Rulesets via `gh api repos/{owner}/{repo}/rulesets` — no need for Web UI.
|
|
16
|
+
- Reusable workflow status check names use the format `{caller-job} / {reusable-job}` (e.g., `call-test / test`). Keep this in mind when configuring required checks.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "searxng-http-mcp",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "whw23"
|
|
5
|
+
},
|
|
6
|
+
"description": "SearXNG metasearch engine MCP server",
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "searxng-http-mcp",
|
|
10
|
+
"source": "./plugins/local",
|
|
11
|
+
"description": "SearXNG MCP server (local Docker stdio) — zero-config, self-contained",
|
|
12
|
+
"version": "1.1.0",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"keywords": ["search", "searxng", "web-search", "mcp", "local", "docker"]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "searxng-http-mcp-remote",
|
|
18
|
+
"source": "./plugins/remote",
|
|
19
|
+
"description": "SearXNG MCP server (remote HTTP) — connect to a deployed instance",
|
|
20
|
+
"version": "1.1.0",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"keywords": ["search", "searxng", "web-search", "mcp", "remote", "http"]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.git
|
|
2
|
+
.github
|
|
3
|
+
.venv
|
|
4
|
+
.pytest_cache
|
|
5
|
+
.playwright-mcp
|
|
6
|
+
.claude
|
|
7
|
+
.claude-plugin
|
|
8
|
+
.vscode
|
|
9
|
+
__pycache__
|
|
10
|
+
tests
|
|
11
|
+
plugins
|
|
12
|
+
scripts/review-pr.sh
|
|
13
|
+
docs
|
|
14
|
+
assets
|
|
15
|
+
*.md
|
|
16
|
+
!README.md
|
|
17
|
+
LICENSE
|
|
18
|
+
.gitignore
|
|
19
|
+
.dockerignore
|
|
20
|
+
.mcp.json
|
|
21
|
+
.coverage
|
|
22
|
+
server.json
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "pip"
|
|
4
|
+
directory: "/"
|
|
5
|
+
target-branch: "dev"
|
|
6
|
+
schedule:
|
|
7
|
+
interval: "weekly"
|
|
8
|
+
open-pull-requests-limit: 5
|
|
9
|
+
|
|
10
|
+
- package-ecosystem: "github-actions"
|
|
11
|
+
directory: "/"
|
|
12
|
+
target-branch: "dev"
|
|
13
|
+
schedule:
|
|
14
|
+
interval: "weekly"
|
|
15
|
+
open-pull-requests-limit: 5
|
|
16
|
+
|
|
17
|
+
- package-ecosystem: "docker"
|
|
18
|
+
directory: "/"
|
|
19
|
+
target-branch: "dev"
|
|
20
|
+
schedule:
|
|
21
|
+
interval: "weekly"
|
|
22
|
+
open-pull-requests-limit: 5
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
name: Build and Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: "17 3 * * *"
|
|
8
|
+
|
|
9
|
+
permissions: read-all
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
IMAGE_NAME: ghcr.io/whw23/searxng-http-mcp
|
|
13
|
+
UPSTREAM_IMAGE: ghcr.io/searxng/searxng:latest
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
uses: ./.github/workflows/reusable-test.yml
|
|
18
|
+
|
|
19
|
+
build:
|
|
20
|
+
needs: test
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
packages: write
|
|
25
|
+
id-token: write
|
|
26
|
+
security-events: write
|
|
27
|
+
env:
|
|
28
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
29
|
+
|
|
30
|
+
steps:
|
|
31
|
+
- name: Checkout
|
|
32
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
33
|
+
|
|
34
|
+
- name: Check upstream image digest
|
|
35
|
+
id: upstream
|
|
36
|
+
run: |
|
|
37
|
+
docker pull $UPSTREAM_IMAGE
|
|
38
|
+
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $UPSTREAM_IMAGE)
|
|
39
|
+
echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"
|
|
40
|
+
|
|
41
|
+
UPSTREAM_VERSION=$(docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' $UPSTREAM_IMAGE | grep -oP '__SEARXNG_VERSION=\K.*' || echo "unknown")
|
|
42
|
+
echo "version=$UPSTREAM_VERSION" >> "$GITHUB_OUTPUT"
|
|
43
|
+
env:
|
|
44
|
+
UPSTREAM_IMAGE: ${{ env.UPSTREAM_IMAGE }}
|
|
45
|
+
|
|
46
|
+
- name: Restore cached upstream digest
|
|
47
|
+
if: github.event_name == 'schedule'
|
|
48
|
+
id: cache
|
|
49
|
+
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
|
50
|
+
with:
|
|
51
|
+
path: /tmp/upstream-digest
|
|
52
|
+
key: upstream-digest-${{ steps.upstream.outputs.digest }}
|
|
53
|
+
restore-keys: upstream-digest-
|
|
54
|
+
|
|
55
|
+
- name: Check if upstream changed
|
|
56
|
+
if: github.event_name == 'schedule'
|
|
57
|
+
id: changed
|
|
58
|
+
run: |
|
|
59
|
+
if [ -f /tmp/upstream-digest ] && [ "$(cat /tmp/upstream-digest)" = "${{ steps.upstream.outputs.digest }}" ]; then
|
|
60
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
61
|
+
echo "Upstream unchanged, skipping build."
|
|
62
|
+
else
|
|
63
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
- name: Decide whether to build
|
|
67
|
+
id: decide
|
|
68
|
+
run: |
|
|
69
|
+
if [ "$EVENT" != "schedule" ] || [ "$SKIP" != "true" ]; then
|
|
70
|
+
echo "should_build=true" >> "$GITHUB_OUTPUT"
|
|
71
|
+
else
|
|
72
|
+
echo "should_build=false" >> "$GITHUB_OUTPUT"
|
|
73
|
+
fi
|
|
74
|
+
env:
|
|
75
|
+
EVENT: ${{ github.event_name }}
|
|
76
|
+
SKIP: ${{ steps.changed.outputs.skip }}
|
|
77
|
+
|
|
78
|
+
- name: Save upstream digest
|
|
79
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
80
|
+
run: echo "${{ steps.upstream.outputs.digest }}" > /tmp/upstream-digest
|
|
81
|
+
|
|
82
|
+
- name: Set up QEMU
|
|
83
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
84
|
+
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
|
|
85
|
+
|
|
86
|
+
- name: Set up Docker Buildx
|
|
87
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
88
|
+
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
|
89
|
+
|
|
90
|
+
- name: Login to GHCR
|
|
91
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
92
|
+
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
|
93
|
+
with:
|
|
94
|
+
registry: ghcr.io
|
|
95
|
+
username: ${{ github.actor }}
|
|
96
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
97
|
+
|
|
98
|
+
- name: Generate tags
|
|
99
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
100
|
+
id: tags
|
|
101
|
+
run: |
|
|
102
|
+
SHORT_SHA=$(echo "$GITHUB_SHA" | cut -c1-7)
|
|
103
|
+
UPSTREAM_VERSION="${{ steps.upstream.outputs.version }}"
|
|
104
|
+
FULL_TAG="${UPSTREAM_VERSION}-${SHORT_SHA}"
|
|
105
|
+
echo "full_tag=$FULL_TAG" >> "$GITHUB_OUTPUT"
|
|
106
|
+
env:
|
|
107
|
+
GITHUB_SHA: ${{ github.sha }}
|
|
108
|
+
|
|
109
|
+
- name: Build and push
|
|
110
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
111
|
+
id: build
|
|
112
|
+
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
|
113
|
+
with:
|
|
114
|
+
context: .
|
|
115
|
+
push: true
|
|
116
|
+
platforms: linux/amd64,linux/arm64
|
|
117
|
+
tags: |
|
|
118
|
+
${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.full_tag }}
|
|
119
|
+
${{ env.IMAGE_NAME }}:latest
|
|
120
|
+
cache-from: type=gha
|
|
121
|
+
cache-to: type=gha,mode=max
|
|
122
|
+
|
|
123
|
+
- name: Scan image for vulnerabilities
|
|
124
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
125
|
+
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
|
126
|
+
with:
|
|
127
|
+
image-ref: ${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
|
128
|
+
format: sarif
|
|
129
|
+
output: trivy-results.sarif
|
|
130
|
+
severity: CRITICAL,HIGH
|
|
131
|
+
|
|
132
|
+
- name: Upload Trivy results to GitHub Security
|
|
133
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
134
|
+
uses: github/codeql-action/upload-sarif@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
|
|
135
|
+
with:
|
|
136
|
+
sarif_file: trivy-results.sarif
|
|
137
|
+
|
|
138
|
+
- name: Install Cosign
|
|
139
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
140
|
+
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
|
141
|
+
|
|
142
|
+
- name: Sign container image
|
|
143
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
144
|
+
run: |
|
|
145
|
+
cosign sign --yes "$IMAGE@$DIGEST"
|
|
146
|
+
env:
|
|
147
|
+
IMAGE: ${{ env.IMAGE_NAME }}
|
|
148
|
+
DIGEST: ${{ steps.build.outputs.digest }}
|
|
149
|
+
|
|
150
|
+
- name: Install mcp-publisher
|
|
151
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
152
|
+
run: |
|
|
153
|
+
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher
|
|
154
|
+
|
|
155
|
+
- name: Update server.json version
|
|
156
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
157
|
+
run: |
|
|
158
|
+
VERSION="$(date -u +%Y).${{ github.run_number }}.0"
|
|
159
|
+
jq --arg v "$VERSION" '.version = $v' server.json > server.tmp && mv server.tmp server.json
|
|
160
|
+
|
|
161
|
+
- name: Authenticate to MCP Registry
|
|
162
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
163
|
+
run: ./mcp-publisher login github-oidc
|
|
164
|
+
|
|
165
|
+
- name: Publish to MCP Registry
|
|
166
|
+
if: steps.decide.outputs.should_build == 'true'
|
|
167
|
+
run: ./mcp-publisher publish || echo "Publish failed (version may already exist), continuing."
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: CodeQL
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, dev]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [dev]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: "37 4 * * 1"
|
|
10
|
+
|
|
11
|
+
permissions: read-all
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
analyze:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
security-events: write
|
|
19
|
+
strategy:
|
|
20
|
+
matrix:
|
|
21
|
+
language: [python]
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout
|
|
24
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
25
|
+
|
|
26
|
+
- name: Initialize CodeQL
|
|
27
|
+
uses: github/codeql-action/init@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
|
|
28
|
+
with:
|
|
29
|
+
languages: ${{ matrix.language }}
|
|
30
|
+
|
|
31
|
+
- name: Autobuild
|
|
32
|
+
uses: github/codeql-action/autobuild@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
|
|
33
|
+
|
|
34
|
+
- name: Perform CodeQL Analysis
|
|
35
|
+
uses: github/codeql-action/analyze@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Dependabot Auto-Merge
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request_target:
|
|
5
|
+
branches: [dev]
|
|
6
|
+
|
|
7
|
+
permissions: read-all
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
auto-merge:
|
|
11
|
+
if: github.event.pull_request.user.login == 'dependabot[bot]'
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
pull-requests: write
|
|
16
|
+
steps:
|
|
17
|
+
- name: Fetch Dependabot metadata
|
|
18
|
+
id: metadata
|
|
19
|
+
uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2
|
|
20
|
+
with:
|
|
21
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
22
|
+
|
|
23
|
+
- name: Approve and auto-merge patch/minor/security updates
|
|
24
|
+
if: >-
|
|
25
|
+
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
|
|
26
|
+
steps.metadata.outputs.update-type == 'version-update:semver-minor' ||
|
|
27
|
+
steps.metadata.outputs.update-type == 'security-update'
|
|
28
|
+
run: |
|
|
29
|
+
gh pr review "$PR_URL" --approve
|
|
30
|
+
gh pr merge "$PR_URL" --auto --merge
|
|
31
|
+
env:
|
|
32
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
33
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: Reusable Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_call:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout
|
|
14
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 2
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.14"
|
|
22
|
+
cache: pip
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: pip install "mcp[cli]" pytest pytest-anyio httpx pytest-cov pyyaml
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: pytest tests/ -v --tb=short --junitxml=test-results.xml --cov=mcp_server --cov-report=term-missing --cov-report=xml:coverage.xml --cov-fail-under=80
|
|
29
|
+
|
|
30
|
+
- name: Coverage summary
|
|
31
|
+
if: always()
|
|
32
|
+
run: |
|
|
33
|
+
if [ -f coverage.xml ]; then
|
|
34
|
+
echo "## Coverage Report" >> "$GITHUB_STEP_SUMMARY"
|
|
35
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
36
|
+
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
37
|
+
python -m coverage report 2>/dev/null || echo "Coverage data not available"
|
|
38
|
+
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
- name: Test report summary
|
|
42
|
+
if: always()
|
|
43
|
+
uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # v2
|
|
44
|
+
with:
|
|
45
|
+
paths: test-results.xml
|
|
46
|
+
|
|
47
|
+
- name: Upload test artifacts
|
|
48
|
+
if: always()
|
|
49
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
50
|
+
with:
|
|
51
|
+
name: test-reports
|
|
52
|
+
path: |
|
|
53
|
+
test-results.xml
|
|
54
|
+
coverage.xml
|
|
55
|
+
|
|
56
|
+
consistency-test:
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
steps:
|
|
59
|
+
- name: Checkout
|
|
60
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
61
|
+
|
|
62
|
+
- name: Check plugin consistency (local vs remote)
|
|
63
|
+
run: |
|
|
64
|
+
diff -r plugins/local/skills plugins/remote/skills || { echo "::error::Skills differ between local and remote plugins"; exit 1; }
|
|
65
|
+
diff -r plugins/local/agents plugins/remote/agents || { echo "::error::Agents differ between local and remote plugins"; exit 1; }
|
|
66
|
+
echo "Plugins are consistent."
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: OpenSSF Scorecard
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: "23 4 * * 1"
|
|
8
|
+
|
|
9
|
+
permissions: read-all
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
analysis:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
security-events: write
|
|
17
|
+
id-token: write
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
21
|
+
with:
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
|
|
24
|
+
- name: Run Scorecard
|
|
25
|
+
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
|
|
26
|
+
with:
|
|
27
|
+
results_file: results.sarif
|
|
28
|
+
results_format: sarif
|
|
29
|
+
publish_results: true
|
|
30
|
+
|
|
31
|
+
- name: Upload SARIF
|
|
32
|
+
uses: github/codeql-action/upload-sarif@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
|
|
33
|
+
with:
|
|
34
|
+
sarif_file: results.sarif
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches-ignore: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [dev, main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
pull-requests: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
check-pr-source:
|
|
15
|
+
if: github.event_name == 'pull_request' && github.base_ref == 'main'
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- name: Verify PRs to main come from upstream dev
|
|
19
|
+
run: |
|
|
20
|
+
if [ "$IS_FORK" = "true" ]; then
|
|
21
|
+
echo "::error::Fork PRs cannot target main. Please PR to dev instead."
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
if [ "$HEAD_REF" != "dev" ]; then
|
|
25
|
+
echo "::error::PRs to main must come from the dev branch, got '$HEAD_REF'"
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
env:
|
|
29
|
+
HEAD_REF: ${{ github.head_ref }}
|
|
30
|
+
IS_FORK: ${{ github.event.pull_request.head.repo.fork }}
|
|
31
|
+
|
|
32
|
+
check-protected-files:
|
|
33
|
+
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- name: Block fork PRs that modify .github/
|
|
37
|
+
run: |
|
|
38
|
+
CHANGED=$(gh pr diff "$PR_NUMBER" --name-only --repo "$REPO")
|
|
39
|
+
PROTECTED=$(echo "$CHANGED" | grep -E '^\.github/' || true)
|
|
40
|
+
if [ -n "$PROTECTED" ]; then
|
|
41
|
+
echo "::error::Fork PRs cannot modify files in .github/. Changed: $PROTECTED"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
env:
|
|
45
|
+
GH_TOKEN: ${{ github.token }}
|
|
46
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
47
|
+
REPO: ${{ github.repository }}
|
|
48
|
+
|
|
49
|
+
check-changes:
|
|
50
|
+
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'dev')
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
outputs:
|
|
53
|
+
skip: ${{ steps.changes.outputs.skip }}
|
|
54
|
+
steps:
|
|
55
|
+
- name: Checkout
|
|
56
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
57
|
+
with:
|
|
58
|
+
fetch-depth: 0
|
|
59
|
+
|
|
60
|
+
- name: Check for docs-only changes
|
|
61
|
+
id: changes
|
|
62
|
+
run: |
|
|
63
|
+
if [ "$EVENT_NAME" = "pull_request" ]; then
|
|
64
|
+
FILES=$(gh pr diff "$PR_NUMBER" --name-only --repo "$REPO")
|
|
65
|
+
else
|
|
66
|
+
if [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then
|
|
67
|
+
FILES=$(git diff --name-only HEAD~1)
|
|
68
|
+
else
|
|
69
|
+
FILES=$(git diff --name-only "$BEFORE_SHA".."$AFTER_SHA" 2>/dev/null || git diff --name-only HEAD~1)
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
NON_DOCS=$(echo "$FILES" | grep -v -E '(^[^/]*\.md$|^docs/|^assets/|^LICENSE$)' || true)
|
|
73
|
+
if [ -z "$NON_DOCS" ]; then
|
|
74
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
75
|
+
echo "Only docs changed, skipping tests."
|
|
76
|
+
else
|
|
77
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
78
|
+
fi
|
|
79
|
+
env:
|
|
80
|
+
GH_TOKEN: ${{ github.token }}
|
|
81
|
+
EVENT_NAME: ${{ github.event_name }}
|
|
82
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
83
|
+
REPO: ${{ github.repository }}
|
|
84
|
+
BEFORE_SHA: ${{ github.event.before }}
|
|
85
|
+
AFTER_SHA: ${{ github.sha }}
|
|
86
|
+
|
|
87
|
+
call-test:
|
|
88
|
+
needs: check-changes
|
|
89
|
+
if: needs.check-changes.outputs.skip != 'true'
|
|
90
|
+
uses: ./.github/workflows/reusable-test.yml
|
|
91
|
+
|
|
92
|
+
test-gate:
|
|
93
|
+
needs: [check-changes, call-test]
|
|
94
|
+
if: always() && (github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'dev'))
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
steps:
|
|
97
|
+
- name: Evaluate test results
|
|
98
|
+
run: |
|
|
99
|
+
if [ "$SKIP" = "true" ]; then
|
|
100
|
+
echo "Tests skipped (docs-only changes)"
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
if [ "$RESULT" != "success" ]; then
|
|
104
|
+
echo "Tests failed or were cancelled"
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
echo "All tests passed"
|
|
108
|
+
env:
|
|
109
|
+
SKIP: ${{ needs.check-changes.outputs.skip }}
|
|
110
|
+
RESULT: ${{ needs.call-test.result }}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
*.egg-info/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg
|
|
10
|
+
|
|
11
|
+
# Virtual environment
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
|
|
16
|
+
# IDE
|
|
17
|
+
.idea/
|
|
18
|
+
.vscode/
|
|
19
|
+
*.swp
|
|
20
|
+
*.swo
|
|
21
|
+
|
|
22
|
+
# Environment
|
|
23
|
+
.env
|
|
24
|
+
.env.local
|
|
25
|
+
|
|
26
|
+
# OS
|
|
27
|
+
.DS_Store
|
|
28
|
+
Thumbs.db
|
|
29
|
+
|
|
30
|
+
# Docs (plugin caches)
|
|
31
|
+
docs/superpowers/
|
|
32
|
+
|
|
33
|
+
# Playwright MCP
|
|
34
|
+
.playwright-mcp/
|
|
35
|
+
|
|
36
|
+
# Test / Coverage
|
|
37
|
+
.pytest_cache/
|
|
38
|
+
.coverage
|
|
39
|
+
htmlcov/
|
|
40
|
+
.superpowers/
|