# Directory Structure
```
├── .github
│ └── workflows
│ ├── pypi-publish.yaml
│ └── release.yml
├── .gitignore
├── .python-version
├── cliff.toml
├── LICENSE
├── Makefile
├── pyproject.toml
├── README.md
├── server.py
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
```
1 | 3.10
2 |
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | .venv
2 | __pycache__
3 | *.egg-info
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | [](https://mseep.ai/app/cr7258-higress-ai-search-mcp-server)
2 |
3 | # Higress AI-Search MCP Server
4 |
5 | ## Overview
6 |
7 | A Model Context Protocol (MCP) server that provides an AI search tool to enhance AI model responses with real-time search results from various search engines through [Higress](https://higress.cn/) [ai-search](https://github.com/alibaba/higress/blob/main/plugins/wasm-go/extensions/ai-search/README.md) feature.
8 |
9 | <a href="https://glama.ai/mcp/servers/gk0xde4wbp">
10 | <img width="380" height="200" src="https://glama.ai/mcp/servers/gk0xde4wbp/badge" alt="Higress AI-Search Server MCP server" />
11 | </a>
12 |
13 | ## Demo
14 |
15 | ### Cline
16 |
17 | https://github.com/user-attachments/assets/60a06d99-a46c-40fc-b156-793e395542bb
18 |
19 | ### Claude Desktop
20 |
21 | https://github.com/user-attachments/assets/5c9e639f-c21c-4738-ad71-1a88cc0bcb46
22 |
23 | ## Features
24 |
25 | - **Internet Search**: Google, Bing, Quark - for general web information
26 | - **Academic Search**: Arxiv - for scientific papers and research
27 | - **Internal Knowledge Search**
28 |
29 | ## Prerequisites
30 |
31 | - [uv](https://github.com/astral-sh/uv) for package installation.
32 | - Config Higress with [ai-search](https://github.com/alibaba/higress/blob/main/plugins/wasm-go/extensions/ai-search/README.md) plugin and [ai-proxy](https://github.com/alibaba/higress/blob/main/plugins/wasm-go/extensions/ai-proxy/README.md) plugin.
33 |
34 | ## Configuration
35 |
36 | The server can be configured using environment variables:
37 |
38 | - `HIGRESS_URL`(optional): URL for the Higress service (default: `http://localhost:8080/v1/chat/completions`).
39 | - `MODEL`(required): LLM model to use for generating responses.
40 | - `INTERNAL_KNOWLEDGE_BASES`(optional): Description of internal knowledge bases.
41 |
42 | ### Option 1: Using uvx
43 |
44 | Using uvx will automatically install the package from PyPI, no need to clone the repository locally.
45 |
46 | ```bash
47 | {
48 | "mcpServers": {
49 | "higress-ai-search-mcp-server": {
50 | "command": "uvx",
51 | "args": [
52 | "higress-ai-search-mcp-server"
53 | ],
54 | "env": {
55 | "HIGRESS_URL": "http://localhost:8080/v1/chat/completions",
56 | "MODEL": "qwen-turbo",
57 | "INTERNAL_KNOWLEDGE_BASES": "Employee handbook, company policies, internal process documents"
58 | }
59 | }
60 | }
61 | }
62 | ```
63 |
64 | ### Option 2: Using uv with local development
65 |
66 | Using uv requires cloning the repository locally and specifying the path to the source code.
67 |
68 | ```bash
69 | {
70 | "mcpServers": {
71 | "higress-ai-search-mcp-server": {
72 | "command": "uv",
73 | "args": [
74 | "--directory",
75 | "path/to/src/higress-ai-search-mcp-server",
76 | "run",
77 | "higress-ai-search-mcp-server"
78 | ],
79 | "env": {
80 | "HIGRESS_URL": "http://localhost:8080/v1/chat/completions",
81 | "MODEL": "qwen-turbo",
82 | "INTERNAL_KNOWLEDGE_BASES": "Employee handbook, company policies, internal process documents"
83 | }
84 | }
85 | }
86 | }
87 | ```
88 |
89 | ## License
90 |
91 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
1 | [project]
2 | name = "higress-ai-search-mcp-server"
3 | version = "1.0.0"
4 | description = "Higress ai-search MCP Server"
5 | readme = "README.md"
6 | requires-python = ">=3.10"
7 | dependencies = [
8 | "fastmcp>=0.4.1",
9 | "httpx>=0.24.0",
10 | "tomli>=2.2.1",
11 | "tomli-w>=1.2.0",
12 | ]
13 |
14 | [project.license]
15 | file = "LICENSE"
16 |
17 | [project.scripts]
18 | higress-ai-search-mcp-server = "server:main"
19 |
20 | [tool.setuptools]
21 | license-files = []
22 |
```
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write
13 | steps:
14 | - uses: actions/checkout@v4
15 | with:
16 | fetch-depth: 0
17 |
18 | - name: Set up Python
19 | uses: actions/setup-python@v4
20 | with:
21 | python-version: '3.x'
22 |
23 | - name: Install dependencies
24 | run: |
25 | python -m pip install --upgrade pip
26 | pip install git-cliff
27 |
28 | - name: Get version from tag
29 | id: get_version
30 | run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
31 |
32 | - name: Generate changelog
33 | run: |
34 | git-cliff --output CHANGELOG.md --latest
35 |
36 | - name: Create Release
37 | uses: softprops/action-gh-release@v1
38 | with:
39 | name: v${{ env.VERSION }}
40 | body_path: CHANGELOG.md
41 | draft: false
42 | prerelease: false
```
--------------------------------------------------------------------------------
/.github/workflows/pypi-publish.yaml:
--------------------------------------------------------------------------------
```yaml
1 | # This workflow will upload a Python Package using Twine when a release is created
2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: PyPI Publish
10 |
11 | on:
12 | workflow_run:
13 | workflows: ["Release"]
14 | types:
15 | - completed
16 |
17 | env:
18 | UV_PUBLISH_TOKEN: '${{ secrets.PYPI_API_TOKEN }}'
19 |
20 | jobs:
21 | deploy:
22 | runs-on: ubuntu-latest
23 | if: ${{ github.event.workflow_run.conclusion == 'success' }}
24 | steps:
25 | - uses: actions/checkout@v2
26 |
27 | - name: Set up Python
28 | uses: actions/setup-python@v2
29 | with:
30 | python-version: '3.10.x'
31 |
32 | - name: Install dependencies
33 | run: |
34 | python -m pip install uv
35 | uv sync
36 |
37 | - name: Build package
38 | run: uv build
39 |
40 | - name: Publish package
41 | run: uv publish
```
--------------------------------------------------------------------------------
/cliff.toml:
--------------------------------------------------------------------------------
```toml
1 | # git-cliff ~ configuration file
2 | # https://git-cliff.org/docs/configuration
3 |
4 | [changelog]
5 | # template for the changelog header
6 | header = """
7 | # Changelog\n
8 | """
9 | # template for the changelog body
10 | # https://keats.github.io/tera/docs/#introduction
11 | body = """
12 | {% if version %}\
13 | {% if previous.version %}\
14 | ## [{{ version | trim_start_matches(pat="v") }}]($REPO/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
15 | {% else %}\
16 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
17 | {% endif %}\
18 | {% else %}\
19 | ## [unreleased]
20 | {% endif %}\
21 | {% for group, commits in commits | group_by(attribute="group") %}
22 | ### {{ group | striptags | trim | upper_first }}
23 | {% for commit in commits
24 | | filter(attribute="scope")
25 | | sort(attribute="scope") %}
26 | - **({{commit.scope}})**{% if commit.breaking %} [**breaking**]{% endif %} \
27 | {{ commit.message }} - ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }})) - @{{ commit.author.name }}
28 | {%- endfor -%}
29 | {% raw %}\n{% endraw %}\
30 | {%- for commit in commits %}
31 | {%- if commit.scope -%}
32 | {% else -%}
33 | - {% if commit.breaking %} [**breaking**]{% endif %}\
34 | {{ commit.message }} - ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }})) - @{{ commit.author.name }}
35 | {% endif -%}
36 | {% endfor -%}
37 | {% endfor %}\n
38 | """
39 | # template for the changelog footer
40 | footer = """
41 | <!-- generated by git-cliff -->
42 | """
43 | # remove the leading and trailing whitespace from the templates
44 | trim = true
45 | # postprocessors
46 | postprocessors = [
47 | { pattern = '\$REPO', replace = "https://github.com/cr7258/higress-ai-search-mcp-server.git" }, # replace repository URL
48 | ]
49 |
50 | [git]
51 | # parse the commits based on https://www.conventionalcommits.org
52 | conventional_commits = true
53 | # filter out the commits that are not conventional
54 | filter_unconventional = true
55 | # process each line of a commit as an individual commit
56 | split_commits = false
57 | # regex for preprocessing the commit messages
58 | commit_preprocessors = [
59 | # { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/cr7258/higress-ai-search-mcp-server/issues/${2}))"}, # replace issue numbers
60 | ]
61 | # regex for parsing and grouping commits
62 | commit_parsers = [
63 | { message = "^feat", group = "<!-- 0 -->⛰️ Features" },
64 | { message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
65 | { message = "^doc", group = "<!-- 3 -->📚 Documentation" },
66 | { message = "^perf", group = "<!-- 4 -->⚡ Performance" },
67 | { message = "^refactor\\(clippy\\)", skip = true },
68 | { message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
69 | { message = "^style", group = "<!-- 5 -->🎨 Styling" },
70 | { message = "^test", group = "<!-- 6 -->🧪 Testing" },
71 | { message = "^chore\\(release\\): prepare for", skip = true },
72 | { message = "^chore\\(deps.*\\)", skip = true },
73 | { message = "^chore\\(pr\\)", skip = true },
74 | { message = "^chore\\(pull\\)", skip = true },
75 | { message = "^chore\\(npm\\).*yarn\\.lock", skip = true },
76 | { message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
77 | { body = ".*security", group = "<!-- 8 -->🛡️ Security" },
78 | { message = "^revert", group = "<!-- 9 -->◀️ Revert" },
79 | ]
80 |
81 | # filter out the commits that are not matched by commit parsers
82 | filter_commits = false
83 | # sort the tags topologically
84 | topo_order = false
85 | # sort the commits inside sections by oldest/newest order
86 | sort_commits = "oldest"
87 | # regex for matching git tags
88 | tag_pattern = "^v[0-9]"
89 | # regex for skipping tags
90 | skip_tags = ""
91 | # regex for ignoring tags
92 | ignore_tags = ""
93 | # use tag date instead of commit date
94 | date_order = true
95 | # path to git binary
96 | git_path = "git"
97 | # whether to use relaxed or strict semver parsing
98 | relaxed_semver = true
99 | # only show the changes for the current version
100 | tag_range = true
```
--------------------------------------------------------------------------------
/server.py:
--------------------------------------------------------------------------------
```python
1 | import os
2 | import json
3 | import httpx
4 | import inspect
5 | from fastmcp import FastMCP
6 | import logging
7 | from typing import Dict, Any, Optional
8 | from functools import wraps
9 |
10 | # Create MCP Server
11 | MCP_SERVER_NAME = "higress-ai-search-mcp-server"
12 | mcp = FastMCP(MCP_SERVER_NAME)
13 |
14 | # Configure logging
15 | logging.basicConfig(
16 | level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
17 | )
18 | logger = logging.getLogger(MCP_SERVER_NAME)
19 |
20 | # Get Higress configuration from environment variables
21 | HIGRESS_URL = os.getenv("HIGRESS_URL", "http://localhost:8080/v1/chat/completions")
22 |
23 | # Get MODEL from environment variables (required)
24 | MODEL = os.getenv("MODEL")
25 | if not MODEL:
26 | raise ValueError("MODEL environment variable is required. Please set it to the LLM model you want to use.")
27 |
28 | # Get knowledge base information from environment variables
29 | INTERNAL_KNOWLEDGE_BASES = os.getenv("INTERNAL_KNOWLEDGE_BASES", "")
30 | INTERNAL_KB_DESCRIPTION = f"👨💻 **Internal Knowledge Search**: {INTERNAL_KNOWLEDGE_BASES}" if INTERNAL_KNOWLEDGE_BASES else ""
31 |
32 | def dynamic_docstring(func):
33 | @wraps(func)
34 | async def wrapper(*args, **kwargs):
35 | return await func(*args, **kwargs)
36 |
37 | base_doc = """
38 | Enhance AI model responses with real-time search results from search engines.
39 |
40 | This tool sends a query to Higress, which integrates with various search engines to provide up-to-date information:
41 |
42 | 🌐 **Internet Search**: Google, Bing, Quark - for general web information
43 | 📖 **Academic Search**: Arxiv - for scientific papers and research
44 | {internal_knowledge}
45 |
46 | Args:
47 | query: The user's question or search query
48 |
49 | Returns:
50 | The enhanced AI response with search results incorporated
51 | """.format(internal_knowledge=INTERNAL_KB_DESCRIPTION)
52 |
53 | # Update the function's docstring
54 | wrapper.__doc__ = base_doc
55 | return wrapper
56 |
57 | @mcp.tool()
58 | @dynamic_docstring
59 | async def ai_search(query: str) -> Dict[str, Any]:
60 | """Dynamic docstring will be set by the decorator"""
61 | logger.info(f"Sending query to Higress: {query}")
62 |
63 | payload = {
64 | "model": MODEL,
65 | "messages": [
66 | {
67 | "role": "user",
68 | "content": query
69 | }
70 | ]
71 | }
72 |
73 | try:
74 | async with httpx.AsyncClient() as client:
75 | response = await client.post(
76 | HIGRESS_URL,
77 | json=payload,
78 | headers={"Content-Type": "application/json"},
79 | timeout=30.0 # 30 seconds timeout
80 | )
81 |
82 | if response.status_code != 200:
83 | logger.error(f"Error from Higress: {response.status_code} - {response.text}")
84 | return {
85 | "status": "error",
86 | "code": response.status_code,
87 | "message": f"Higress returned an error: {response.text}"
88 | }
89 |
90 | result = response.json()
91 | logger.info(f"Received response from Higress")
92 | return result
93 |
94 | except httpx.RequestError as e:
95 | logger.error(f"Request error: {str(e)}")
96 | return {
97 | "status": "error",
98 | "message": f"Failed to connect to Higress: {str(e)}"
99 | }
100 | except json.JSONDecodeError as e:
101 | logger.error(f"JSON decode error: {str(e)}")
102 | return {
103 | "status": "error",
104 | "message": f"Failed to parse Higress response: {str(e)}"
105 | }
106 | except Exception as e:
107 | logger.error(f"Unexpected error: {str(e)}")
108 | return {
109 | "status": "error",
110 | "message": f"An unexpected error occurred: {str(e)}"
111 | }
112 |
113 | def main():
114 | """Entry point for the MCP server when run as a module."""
115 | logger.info(f"Starting {MCP_SERVER_NAME} with Higress at {HIGRESS_URL}")
116 | mcp.run()
117 |
118 | if __name__ == "__main__":
119 | main()
120 |
```