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