diff --git a/.gitignore b/.gitignore index 52ece20..baf63d7 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,4 @@ wheels/ .env.dora .env.kodo -mcp_server/test.py \ No newline at end of file +src/mcp_server/test.py \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c00e50c..1d09718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v1.1.0 +- 支持 Bucket 为空 + # v1.0.0 - 支持 Kodo 服务 diff --git a/README.md b/README.md index 4c0c9d5..74005ad 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,7 @@ Server 来访问七牛云存储、智能多媒体服务等。 关于访问七牛云存储详细情况请参考 [基于 MCP 使用大模型访问七牛云存储](https://developer.qiniu.com/kodo/12914/mcp-aimodel-kodo)。 -## 安装 - -**前置要求** +## 环境要求 - Python 3.12 或更高版本 - uv 包管理器 @@ -20,6 +18,48 @@ Server 来访问七牛云存储、智能多媒体服务等。 curl -LsSf https://astral.sh/uv/install.sh | sh ``` +## 在 Cline 中使用: + +步骤: + +1. 在 vscode 下载 Cline 插件(下载后 Cline 插件后在侧边栏会增加 Cline 的图标) +2. 配置大模型 +3. 配置 qiniu MCP + 1. 点击 Cline 图标进入 Cline 插件,选择 MCP Server 模块 + 2. 选择 installed,点击 Advanced MCP Settings 配置 MCP Server,参考下面配置信息 + ``` + { + "mcpServers": { + "qiniu": { + "command": "uvx", + "args": [ + "qiniu-mcp-server" + ], + "env": { + "QINIU_ACCESS_KEY": "YOUR_ACCESS_KEY", + "QINIU_SECRET_KEY": "YOUR_SECRET_KEY", + "QINIU_REGION_NAME": "YOUR_REGION_NAME", + "QINIU_ENDPOINT_URL": "YOUR_ENDPOINT_URL", + "QINIU_BUCKETS": "YOUR_BUCKET_A,YOUR_BUCKET_B" + }, + "disabled": false + } + } + } + ``` + 3. 点击 qiniu MCP Server 的链接开关进行连接 +4. 在 Cline 中创建一个聊天窗口,此时我们可以和 AI 进行交互来使用 qiniu-mcp-server ,下面给出几个示例: + - 列举 qiniu 的资源信息 + - 列举 qiniu 中所有的 Bucket + - 列举 qiniu 中 xxx Bucket 的文件 + - 读取 qiniu xxx Bucket 中 yyy 的文件内容 + - 对 qiniu xxx Bucket 中 yyy 的图片切个宽200像素的圆角 + - 刷新下 qiniu 的这个 CDN 链接:https://developer.qiniu.com/test.txt + +注: +cursor 中创建 MCP Server 可直接使用上述配置。 + +## 开发 1. 克隆仓库: ```bash @@ -43,16 +83,14 @@ source .venv/bin/activate # Linux/macOS uv pip install -e . ``` -## 配置 - -1. 复制环境变量模板: +4. 配置 +复制环境变量模板: ```bash cp .env.example .env ``` -2. 编辑 `.env` 文件,配置以下参数: - +编辑 `.env` 文件,配置以下参数: ```bash # S3/Kodo 认证信息 QINIU_ACCESS_KEY=your_access_key @@ -66,24 +104,6 @@ QINIU_ENDPOINT_URL=endpoint_url # eg:https://s3.your_region.qiniucs.com QINIU_BUCKETS=bucket1,bucket2,bucket3 ``` -## 使用方法 - -### 启动服务器 - -1. 使用标准输入输出(stdio)模式启动(默认): - -```bash -uv --directory . run qiniu-mcp-server -``` - -2. 使用 SSE 模式启动(用于 Web 应用): - -```bash -uv --directory . run qiniu-mcp-server --transport sse --port 8000 -``` - -## 开发 - 扩展功能,首先在 core 目录下新增一个业务包目录(eg: 存储 -> storage),在此业务包目录下完成功能拓展。 在业务包目录下的 `__init__.py` 文件中定义 load 函数用于注册业务工具或者资源,最后在 `core` 目录下的 `__init__.py` 中调用此 load 函数完成工具或资源的注册。 @@ -109,46 +129,20 @@ core npx @modelcontextprotocol/inspector uv --directory . run qiniu-mcp-server ``` -### 使用 cline 测试: +### 本地启动 MCP Server 示例 -步骤: +1. 使用标准输入输出(stdio)模式启动(默认): + +```bash +uv --directory . run qiniu-mcp-server +``` + +2. 使用 SSE 模式启动(用于 Web 应用): + +```bash +uv --directory . run qiniu-mcp-server --transport sse --port 8000 +``` -1. 在 vscode 下载 Cline 插件(下载后 Cline 插件后在侧边栏会增加 Cline 的图标) -2. 配置大模型 -3. 配置 qiniu MCP - 1. 点击 Cline 图标进入 Cline 插件,选择 MCP Server 模块 - 2. 选择 installed,点击 Advanced MCP Settings 配置 MCP Server,参考下面配置信息 - ``` - { - "mcpServers": { - "qiniu": { - "command": "uv", - "args": [ - "--directory", - "/Users/yangsen/Workspace/App/qiniu-mcp", # 此处选择项目存储的绝对路径 - "run", - "qiniu-mcp-server" - ], - "env": { # 此处以环境变量方式填写你的 qiniu mcp server 的配置信息,如果是从插件市场下载,可以通过此方式配置,如果是从本地源码安装,也可以通过上述方式在 .env 文件中配置 - "QINIU_ACCESS_KEY": "YOUR_ACCESS_KEY", - "QINIU_SECRET_KEY": "YOUR_SECRET_KEY", - "QINIU_REGION_NAME": "YOUR_REGION_NAME", - "QINIU_ENDPOINT_URL": "YOUR_ENDPOINT_URL", - "QINIU_BUCKETS": "YOUR_BUCKET_A,YOUR_BUCKET_B" - }, - "disabled": false - } - } - } - ``` - 3. 点击 qiniu MCP Server 的链接开关进行连接 -4. 在 Cline 中创建一个聊天窗口,此时我们可以和 AI 进行交互来使用 qiniu-mcp-server ,下面给出几个示例: - - 列举 qiniu 的资源信息 - - 列举 qiniu 中所有的 Bucket - - 列举 qiniu 中 xxx Bucket 的文件 - - 读取 qiniu xxx Bucket 中 yyy 的文件内容 - - 对 qiniu xxx Bucket 中 yyy 的图片切个宽200像素的圆角 - - 刷新下 qiniu 的这个 CDN 链接:https://developer.qiniu.com/test.txt diff --git a/images/logo.png b/docs/images/logo.png similarity index 100% rename from images/logo.png rename to docs/images/logo.png diff --git a/mcp_server/core/version/version.py b/mcp_server/core/version/version.py deleted file mode 100644 index 6dbfd65..0000000 --- a/mcp_server/core/version/version.py +++ /dev/null @@ -1,2 +0,0 @@ - -VERSION = '1.0.0' \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 329e1d4..ed8e0fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,12 @@ [project] name = "qiniu-mcp-server" -version = "1.0.0" +version = "1.1.0" description = "A MCP server project of Qiniu." requires-python = ">=3.12" +authors = [ + { name = "Qiniu", email = "sdk@qiniu.com" }, +] +keywords = ["qiniu", "mcp", "llm"] dependencies = [ "aioboto3>=13.2.0", "fastjsonschema>=2.21.1", @@ -18,8 +22,8 @@ dependencies = [ requires = [ "hatchling",] build-backend = "hatchling.build" -[tool.hatch.build.targets.wheel] -packages = ["mcp_server"] - [project.scripts] qiniu-mcp-server = "mcp_server:main" + +[tool.hatch.build.targets.wheel] +packages = ["src/mcp_server"] \ No newline at end of file diff --git a/mcp_server/README.md b/src/mcp_server/README.md similarity index 100% rename from mcp_server/README.md rename to src/mcp_server/README.md diff --git a/mcp_server/__init__.py b/src/mcp_server/__init__.py similarity index 100% rename from mcp_server/__init__.py rename to src/mcp_server/__init__.py diff --git a/mcp_server/application.py b/src/mcp_server/application.py similarity index 100% rename from mcp_server/application.py rename to src/mcp_server/application.py diff --git a/mcp_server/config/__init__.py b/src/mcp_server/config/__init__.py similarity index 100% rename from mcp_server/config/__init__.py rename to src/mcp_server/config/__init__.py diff --git a/mcp_server/config/config.py b/src/mcp_server/config/config.py similarity index 93% rename from mcp_server/config/config.py rename to src/mcp_server/config/config.py index 806bf7d..a761ca1 100644 --- a/mcp_server/config/config.py +++ b/src/mcp_server/config/config.py @@ -52,10 +52,6 @@ def load_config() -> Config: logger.error("QINIU_REGION_NAME is not configured") raise ValueError("QINIU_REGION_NAME is not configured") - if not config.buckets: - logger.error("QINIU_BUCKETS is not configured") - raise ValueError("QINIU_BUCKETS is not configured") - logger.info(f"Configured access_key: {config.access_key}") logger.info(f"Configured endpoint_url: {config.endpoint_url}") logger.info(f"Configured region_name: {config.region_name}") diff --git a/mcp_server/consts/__init__.py b/src/mcp_server/consts/__init__.py similarity index 100% rename from mcp_server/consts/__init__.py rename to src/mcp_server/consts/__init__.py diff --git a/mcp_server/consts/consts.py b/src/mcp_server/consts/consts.py similarity index 100% rename from mcp_server/consts/consts.py rename to src/mcp_server/consts/consts.py diff --git a/mcp_server/core/__init__.py b/src/mcp_server/core/__init__.py similarity index 100% rename from mcp_server/core/__init__.py rename to src/mcp_server/core/__init__.py diff --git a/mcp_server/core/cdn/__init__.py b/src/mcp_server/core/cdn/__init__.py similarity index 100% rename from mcp_server/core/cdn/__init__.py rename to src/mcp_server/core/cdn/__init__.py diff --git a/mcp_server/core/cdn/cdn.py b/src/mcp_server/core/cdn/cdn.py similarity index 100% rename from mcp_server/core/cdn/cdn.py rename to src/mcp_server/core/cdn/cdn.py diff --git a/mcp_server/core/cdn/tools.py b/src/mcp_server/core/cdn/tools.py similarity index 100% rename from mcp_server/core/cdn/tools.py rename to src/mcp_server/core/cdn/tools.py diff --git a/mcp_server/core/media_processing/__init__.py b/src/mcp_server/core/media_processing/__init__.py similarity index 100% rename from mcp_server/core/media_processing/__init__.py rename to src/mcp_server/core/media_processing/__init__.py diff --git a/mcp_server/core/media_processing/processing.py b/src/mcp_server/core/media_processing/processing.py similarity index 100% rename from mcp_server/core/media_processing/processing.py rename to src/mcp_server/core/media_processing/processing.py diff --git a/mcp_server/core/media_processing/tools.py b/src/mcp_server/core/media_processing/tools.py similarity index 100% rename from mcp_server/core/media_processing/tools.py rename to src/mcp_server/core/media_processing/tools.py diff --git a/mcp_server/core/media_processing/utils.py b/src/mcp_server/core/media_processing/utils.py similarity index 100% rename from mcp_server/core/media_processing/utils.py rename to src/mcp_server/core/media_processing/utils.py diff --git a/mcp_server/core/storage/__init__.py b/src/mcp_server/core/storage/__init__.py similarity index 100% rename from mcp_server/core/storage/__init__.py rename to src/mcp_server/core/storage/__init__.py diff --git a/mcp_server/core/storage/resource.py b/src/mcp_server/core/storage/resource.py similarity index 100% rename from mcp_server/core/storage/resource.py rename to src/mcp_server/core/storage/resource.py diff --git a/mcp_server/core/storage/storage.py b/src/mcp_server/core/storage/storage.py similarity index 89% rename from mcp_server/core/storage/storage.py rename to src/mcp_server/core/storage/storage.py index 88d1697..76e55f7 100644 --- a/mcp_server/core/storage/storage.py +++ b/src/mcp_server/core/storage/storage.py @@ -95,9 +95,9 @@ def get_object_url( return object_urls async def list_buckets(self, prefix: Optional[str] = None) -> List[dict]: - """ - List S3 buckets using async client with pagination - """ + if not self.config.buckets or len(self.config.buckets) == 0: + return [] + max_buckets = 50 async with self.s3_session.client( @@ -107,32 +107,22 @@ async def list_buckets(self, prefix: Optional[str] = None) -> List[dict]: endpoint_url=self.config.endpoint_url, region_name=self.config.region_name, ) as s3: - if self.config.buckets: - # If buckets are configured, only return those - response = await s3.list_buckets() - all_buckets = response.get("Buckets", []) + # If buckets are configured, only return those + response = await s3.list_buckets() + all_buckets = response.get("Buckets", []) + configured_bucket_list = [ + bucket + for bucket in all_buckets + if bucket["Name"] in self.config.buckets + ] + + if prefix: configured_bucket_list = [ - bucket - for bucket in all_buckets - if bucket["Name"] in self.config.buckets + b for b in configured_bucket_list if b["Name"] > prefix ] - if prefix: - configured_bucket_list = [ - b for b in configured_bucket_list if b["Name"] > prefix - ] - - return configured_bucket_list[:max_buckets] - else: - # Default behavior if no buckets configured - response = await s3.list_buckets() - buckets = response.get("Buckets", []) - - if prefix: - buckets = [b for b in buckets if b["Name"] > prefix] - - return buckets[:max_buckets] + return configured_bucket_list[:max_buckets] async def list_objects( self, bucket: str, prefix: str = "", max_keys: int = 20, start_after: str = "" diff --git a/mcp_server/core/storage/tools.py b/src/mcp_server/core/storage/tools.py similarity index 100% rename from mcp_server/core/storage/tools.py rename to src/mcp_server/core/storage/tools.py diff --git a/mcp_server/core/version/__init__.py b/src/mcp_server/core/version/__init__.py similarity index 100% rename from mcp_server/core/version/__init__.py rename to src/mcp_server/core/version/__init__.py diff --git a/mcp_server/core/version/tools.py b/src/mcp_server/core/version/tools.py similarity index 100% rename from mcp_server/core/version/tools.py rename to src/mcp_server/core/version/tools.py diff --git a/src/mcp_server/core/version/version.py b/src/mcp_server/core/version/version.py new file mode 100644 index 0000000..0812c05 --- /dev/null +++ b/src/mcp_server/core/version/version.py @@ -0,0 +1,2 @@ + +VERSION = '1.1.0' \ No newline at end of file diff --git a/src/mcp_server/resource/__init__.py b/src/mcp_server/resource/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mcp_server/resource/resource.py b/src/mcp_server/resource/resource.py similarity index 100% rename from mcp_server/resource/resource.py rename to src/mcp_server/resource/resource.py diff --git a/mcp_server/server.py b/src/mcp_server/server.py similarity index 100% rename from mcp_server/server.py rename to src/mcp_server/server.py diff --git a/src/mcp_server/tools/__init__.py b/src/mcp_server/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mcp_server/tools/tools.py b/src/mcp_server/tools/tools.py similarity index 97% rename from mcp_server/tools/tools.py rename to src/mcp_server/tools/tools.py index 81e912e..8295af6 100644 --- a/mcp_server/tools/tools.py +++ b/src/mcp_server/tools/tools.py @@ -7,7 +7,8 @@ from typing import List, Dict, Callable, Optional, Union, Awaitable from dataclasses import dataclass from mcp import types -from mcp_server import consts + +from .. import consts logger = logging.getLogger(consts.LOGGER_NAME) @@ -36,8 +37,8 @@ def all_tools() -> List[types.Tool]: def register_tool( - meta: types.Tool, - func: Union[ToolFunc, AsyncToolFunc], + meta: types.Tool, + func: Union[ToolFunc, AsyncToolFunc], ) -> None: """注册工具,禁止重复名称""" name = meta.name diff --git a/uv.lock b/uv.lock index d36ad18..6bcfb0c 100644 --- a/uv.lock +++ b/uv.lock @@ -686,8 +686,8 @@ wheels = [ ] [[package]] -name = "qiniu-mcp" -version = "0.1.0" +name = "qiniu-mcp-server" +version = "1.0.0" source = { editable = "." } dependencies = [ { name = "aioboto3" },