#
tokens: 4812/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/cr7258-higress-ai-search-mcp-server-badge.png)](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 | 
```