diff --git a/README.md b/README.md index b78608d..4c0c9d5 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ # Qiniu MCP Server +## 概述 + 基于七牛云产品构建的 Model Context Protocol (MCP) Server,支持用户在 AI 大模型客户端的上下文中通过该 MCP Server 来访问七牛云存储、智能多媒体服务等。 关于访问七牛云存储详细情况请参考 [基于 MCP 使用大模型访问七牛云存储](https://developer.qiniu.com/kodo/12914/mcp-aimodel-kodo)。 -## 前置要求 +## 安装 + +**前置要求** - Python 3.12 或更高版本 - uv 包管理器 @@ -16,8 +20,6 @@ Server 来访问七牛云存储、智能多媒体服务等。 curl -LsSf https://astral.sh/uv/install.sh | sh ``` -## 安装 - 1. 克隆仓库: ```bash @@ -82,9 +84,9 @@ uv --directory . run qiniu-mcp-server --transport sse --port 8000 ## 开发 -扩展功能,首先在 core 目录下新增一个业务目录(eg: 存储 -> storage),在此业务目录下完成功能拓展。 +扩展功能,首先在 core 目录下新增一个业务包目录(eg: 存储 -> storage),在此业务包目录下完成功能拓展。 在业务包目录下的 `__init__.py` 文件中定义 load 函数用于注册业务工具或者资源,最后在 `core` 目录下的 `__init__.py` -中调用此 load 函数完成工具和资源的注册。 +中调用此 load 函数完成工具或资源的注册。 ```shell core @@ -111,11 +113,11 @@ npx @modelcontextprotocol/inspector uv --directory . run qiniu-mcp-server 步骤: -1. 在 vscode 下载 cline 插件(下载后 cline 插件后在侧边栏会增加 cline 的图标) +1. 在 vscode 下载 Cline 插件(下载后 Cline 插件后在侧边栏会增加 Cline 的图标) 2. 配置大模型 -3. 配置 qiniu mcp - 1. 点击 cline 图标进入 cline 插件,选择 mcp server 模块 - 2. 选择 installed,点击 Advanced MCP Settings 配置 mcp server,参考下面配置信息 +3. 配置 qiniu MCP + 1. 点击 Cline 图标进入 Cline 插件,选择 MCP Server 模块 + 2. 选择 installed,点击 Advanced MCP Settings 配置 MCP Server,参考下面配置信息 ``` { "mcpServers": { @@ -127,19 +129,26 @@ npx @modelcontextprotocol/inspector uv --directory . run qiniu-mcp-server "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 中创建一个 chat 窗口,此时我们可以和 AI 进行交互来使用 qiniu-mcp-server ,下面给出几个示例: + 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 + - 列举 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/images/logo.png new file mode 100644 index 0000000..b2d344f Binary files /dev/null and b/images/logo.png differ diff --git a/mcp_server/README.md b/mcp_server/README.md new file mode 100644 index 0000000..70e885f --- /dev/null +++ b/mcp_server/README.md @@ -0,0 +1,116 @@ +# 七牛云 MCP 服务器 + +基于七牛云产品构建的 Model Context Protocol (MCP) 服务器,支持在 AI 大模型的上下文中直接访问和操作七牛云的服务。 + +## Keywords +七牛, Dora, Kodo, CDN + +## Tools + +### 存储工具 + +1. `ListBuckets` + - 查询当前用户配置的 Bucket + - Inputs: + - `prefix` (string, optional): Bucket 名称前缀,用于筛选特定前缀的 Bucket + - Returns: + - 满足条件的 Bucket 列表及其详细信息 + +2. `ListObjects` + - 列举指定 Bucket 中的文件列表 + - Inputs: + - `bucket` (string): Bucket 名称 + - `max_keys` (integer, optional): 单次返回的最大文件数量,默认为20,最大值为100 + - `prefix` (string, optional): 文件 Key 前缀,用于筛选特定前缀的文件 + - `start_after` (string, optional): 起始标记,指定列表的开始位置,可以是上一次列举的结果中最后一个文件的 Key + - Returns: + - 满足条件的文件列表及其详细信息 + +3. `GetObject` + - 获取 Bucket 中文件的内容 + - Inputs: + - `bucket` (string): Bucket 名称 + - `key` (string): 文件的 Key + - Returns: + - 文件内容 + +4. `GetObjectURL` + - 生成文件的访问链接,注意文件存储的 Bucket 必须绑定域名,七牛云测试域名不支持 HTTPS,需要用户自己处理为 HTTP。 + - Inputs: + - `bucket` (string): Bucket 名称 + - `key` (string): 文件的 Key + - `disable_ssl` (boolean, optional): 是否禁用 HTTPS,默认使用 HTTPS + - `expires` (integer, optional): 链接有效期,单位为秒 + - Returns: + - 对象的访问链接 + +### 图片处理工具 + +1. `ImageScaleByPercent` + - 按照指定百分比缩放图片 + - Inputs: + - `object_url` (string): 待处理图片的访问链接,图片必须存储在七牛云空间中 + - `percent` (integer): 缩放比例,范围为1%~999% + - Returns: + - `object_url`: 处理后图片的访问链接 + +2. `ImageScaleBySize` + - 按照指定宽度或高度缩放图片 + - Inputs: + - `object_url` (string): 待处理图片的访问链接,图片必须存储在七牛云空间中 + - `width` (integer, optional): 目标宽度,单位为像素 + - `height` (integer, optional): 目标高度,单位为像素 + - 注意:至少需要指定宽度或高度中的一个参数 + - Returns: + - `object_url`: 处理后图片的访问链接 + +3. `ImageRoundCorner` + - 为图片添加圆角效果 + - Inputs: + - `object_url` (string): 待处理图片的访问链接,图片必须存储在七牛云空间中 + - `radius_x` (string, optional): 水平方向圆角半径,可使用像素值或百分比 + - `radius_y` (string, optional): 垂直方向圆角半径,可使用像素值或百分比 + - 注意:如果只指定一个参数,另一个参数将自动使用相同的值 + - Returns: + - `object_url`: 处理后图片的访问链接 + +4. `ImageInfo` + - 获取图片的基本信息 + - Inputs: + - `object_url` (string): 图片的访问链接,图片必须存储在七牛云空间中 + - Returns: + - `size`: 图片大小,单位为字节 + - `width`: 图片的宽度,单位为像素 + - `height`: 图片的高度,单位为像素 + - `format`: 图片的格式,如 png 等 + - `color_model`: 图片的颜色模型,如 nrgba + +### CDN 工具 + +1. `CDNPrefetchUrls` + - 预先将指定资源缓存到CDN节点,提高用户访问速度 + - Inputs: + - `urls` (Array of string): 待预取资源的URL列表,最多支持60个URL + - Returns: + - 操作状态信息 + +2. `CDNRefresh` + - 刷新CDN节点上的缓存资源,确保内容更新 + - Inputs: + - `urls` (Array of string, optional): 需要刷新的具体URL列表,最多支持60个URL + - `dirs` (Array of string, optional): 需要刷新的目录列表,最多支持10个目录 + - 注意:必须提供`urls`或`dirs`中的至少一项 + - Returns: + - 操作状态信息 + +### 其他工具 + +1. `Version` + - 获取七牛 MCP Server 的版本信息 + - Inputs: 无 + - Returns: + - 服务器版本信息 + + + + diff --git a/mcp_server/core/media_processing/__init__.py b/mcp_server/core/media_processing/__init__.py index 3a4ae80..79bcca7 100644 --- a/mcp_server/core/media_processing/__init__.py +++ b/mcp_server/core/media_processing/__init__.py @@ -1,10 +1,10 @@ -from . import client +from . import processing from .tools import register_tools from ...config import config def load(cfg: config.Config): - cli = client.Client(cfg) + cli = processing.MediaProcessingService(cfg) register_tools(cli) diff --git a/mcp_server/core/media_processing/client.py b/mcp_server/core/media_processing/processing.py similarity index 98% rename from mcp_server/core/media_processing/client.py rename to mcp_server/core/media_processing/processing.py index 5dff8b5..4f715fe 100644 --- a/mcp_server/core/media_processing/client.py +++ b/mcp_server/core/media_processing/processing.py @@ -3,7 +3,7 @@ from ...config import config -class Client: +class MediaProcessingService: def __init__(self, cfg: config.Config): self.cfg = cfg self.auth = qiniu.Auth(cfg.access_key, cfg.secret_key) diff --git a/mcp_server/core/media_processing/tools.py b/mcp_server/core/media_processing/tools.py index e42ecb5..935a9dc 100644 --- a/mcp_server/core/media_processing/tools.py +++ b/mcp_server/core/media_processing/tools.py @@ -2,7 +2,7 @@ from mcp import types from . import utils -from .client import Client +from .processing import MediaProcessingService from ...consts import consts from ...tools import tools @@ -12,7 +12,7 @@ class _ToolImpl: - def __init__(self, cli: Client): + def __init__(self, cli: MediaProcessingService): self.client = cli @tools.tool_meta( @@ -259,7 +259,7 @@ def get_fop_status(self, **kwargs) -> list[types.TextContent]: return [types.TextContent(type="text", text=str(status))] -def register_tools(cli: Client): +def register_tools(cli: MediaProcessingService): tool_impl = _ToolImpl(cli) tools.auto_register_tools( [ diff --git a/mcp_server/core/media_processing/utils.py b/mcp_server/core/media_processing/utils.py index 9e3d972..db43bcf 100644 --- a/mcp_server/core/media_processing/utils.py +++ b/mcp_server/core/media_processing/utils.py @@ -11,7 +11,7 @@ def url_add_processing_func(url: str, func: str) -> str: url_info = parse.urlparse(url) new_query = _query_add_processing_func(url_info.query, func, func_prefix) - new_query = parse.quote(new_query, safe="") + new_query = parse.quote(new_query, safe='&=') url_info = url_info._replace(query=new_query) new_url = parse.urlunparse(url_info) return str(new_url) @@ -28,13 +28,19 @@ def _query_add_processing_func(query: str, func: str, func_prefix: str) -> str: # funcs 会放在第一个元素中 first_query = parse.unquote(queries[0]) - queries.remove(queries[0]) # funcs 不存在 if len(first_query) == 0: queries.insert(0, func) return "&".join(queries) + # first_query 有 = 说明不是 funcs + if first_query.find("=") >= 0: + queries.insert(0, func) + return "&".join(queries) + + queries.remove(queries[0]) + # 未找到当前类别的 func if first_query.find(func_prefix) < 0: func = first_query + "|" + func