Skip to content

version to 1.1.0 #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ wheels/
.env.dora
.env.kodo

mcp_server/test.py
src/mcp_server/test.py
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# v1.1.0
- 支持 Bucket 为空

# v1.0.0

- 支持 Kodo 服务
Expand Down
122 changes: 58 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ Server 来访问七牛云存储、智能多媒体服务等。

关于访问七牛云存储详细情况请参考 [基于 MCP 使用大模型访问七牛云存储](https://developer.qiniu.com/kodo/12914/mcp-aimodel-kodo)。

## 安装

**前置要求**
## 环境要求

- Python 3.12 或更高版本
- uv 包管理器
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 函数完成工具或资源的注册。
Expand All @@ -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



File renamed without changes
2 changes: 0 additions & 2 deletions mcp_server/core/version/version.py

This file was deleted.

12 changes: 8 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"]
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use lazy % formatting in logging functions (logging-fstring-interpolation)

Details

lint 解释

logging-fstring-interpolation 是一个lint规则,用于检查在日志记录函数中使用懒惰的 % 格式化。这个规则建议使用更现代和易读的格式化方法,如 f-string 或 str.format()

错误用法

以下是一个使用 % 格式化的错误示例:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

user_id = 123
logger.debug("User ID: %d" % user_id)

在这个例子中,日志消息使用了 % 格式化方法。

正确用法

以下是一个使用 f-string 的正确示例:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

user_id = 123
logger.debug(f"User ID: {user_id}")

在这个例子中,日志消息使用了 f-string 格式化方法,这是一种更现代和易读的格式化方式。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

logger.info(f"Configured endpoint_url: {config.endpoint_url}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use lazy % formatting in logging functions (logging-fstring-interpolation)

Details

lint 解释

logging-fstring-interpolation 是一个lint规则,用于检查在日志记录函数中使用懒惰的字符串格式化(lazy string formatting)。懒惰的字符串格式化意味着只有在实际需要时才进行字符串格式化,而不是在记录日志时就立即进行。这可以提高性能,特别是在日志级别设置为不记录某些信息时。

错误用法

以下是一个使用非懒惰字符串格式化的示例:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

user_id = 123
username = "john_doe"
logger.debug("User %s logged in with ID %d" % (username, user_id))

在这个例子中,字符串格式化在记录日志时立即进行,即使日志级别设置为不记录 DEBUG 级别的信息。

正确用法

以下是一个使用懒惰字符串格式化的示例:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

user_id = 123
username = "john_doe"
logger.debug("User %s logged in with ID %d" % (username, user_id))

在这个例子中,字符串格式化在记录日志时立即进行,即使日志级别设置为不记录 DEBUG 级别的信息。

错误用法

以下是一个使用非懒惰字符串格式化的示例:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

user_id = 123
username = "john_doe"
logger.debug("User %s logged in with ID %d" % (username, user_id))

在这个例子中,字符串格式化在记录日志时立即进行,即使日志级别设置为不记录 DEBUG 级别的信息。

正确用法

以下是一个使用懒惰字符串格式化的示例:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

user_id = 123
username = "john_doe"
logger.debug("User %s logged in with ID %d" % (username, user_id))

在这个例子中,字符串格式化在记录日志时立即进行,即使日志级别设置为不记录 DEBUG 级别的信息。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

logger.info(f"Configured region_name: {config.region_name}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use lazy % formatting in logging functions (logging-fstring-interpolation)

Details

lint 解释

logging-fstring-interpolation 是一个lint规则,用于检查在日志记录函数中使用懒惰的字符串格式化(lazy string formatting)。懒惰的字符串格式化意味着只有在实际需要时才进行字符串格式化,而不是在记录日志时就立即进行。这可以提高性能,特别是在日志级别设置为不记录某些信息时。

错误用法

以下是一个使用非懒惰字符串格式化的示例:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

user_id = 123
username = "john_doe"
logger.debug("User %s logged in with ID %d" % (username, user_id))

在这个例子中,字符串格式化在记录日志时立即进行,即使日志级别设置为不记录 DEBUG 级别的信息。

正确用法

以下是一个使用懒惰字符串格式化的示例:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

user_id = 123
username = "john_doe"
logger.debug("User %s logged in with ID %d" % (username, user_id))

在这个例子中,字符串格式化在记录日志时立即进行,即使日志级别设置为不记录 DEBUG 级别的信息。

错误用法

以下是一个使用非懒惰字符串格式化的示例:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

user_id = 123
username = "john_doe"
logger.debug("User %s logged in with ID %d" % (username, user_id))

在这个例子中,字符串格式化在记录日志时立即进行,即使日志级别设置为不记录 DEBUG 级别的信息。

正确用法

以下是一个使用懒惰字符串格式化的示例:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

user_id = 123
username = "john_doe"
logger.debug("User %s logged in with ID %d" % (username, user_id))

在这个例子中,字符串格式化在记录日志时立即进行,即使日志级别设置为不记录 DEBUG 级别的信息。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ def get_object_url(
return object_urls

async def list_buckets(self, prefix: Optional[str] = None) -> List[dict]:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing function or method docstring (missing-function-docstring)

Details

lint 解释

这个lint结果表明在代码中缺少函数或方法的文档字符串(docstring)。文档字符串是用于描述函数、类或模块用途和行为的字符串,通常放在定义之前。它有助于其他开发者理解代码的功能。

错误用法

def calculate_sum(a, b):
    return a + b

在这个例子中,calculate_sum 函数缺少文档字符串。

正确用法

def calculate_sum(a, b):
    """
    计算两个数的和
    
    参数:
    a (int): 第一个加数
    b (int): 第二个加数
    
    返回:
    int: 两个数的和
    """
    return a + b

在这个例子中,calculate_sum 函数添加了一个文档字符串,描述了函数的功能、参数和返回值。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

"""
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(
Expand All @@ -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 = ""
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions src/mcp_server/core/version/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing module docstring (missing-module-docstring)

Details

lint 解释

missing-module-docstring 是一个常见的代码质量检查,用于确保每个模块(即 .py 文件)都有一个文档字符串。文档字符串是位于文件顶部的字符串,通常用三引号括起来,用于描述模块的功能和用途。

错误用法

以下是一个缺少模块文档字符串的示例:

# version.py

def get_version():
    return "1.0.0"

在这个例子中,version.py 文件没有包含任何文档字符串。

正确用法

以下是添加了模块文档字符串的正确示例:

"""
This module provides functions to manage and retrieve the version information of the application.
"""

def get_version():
    return "1.0.0"

在这个例子中,version.py 文件顶部包含了一个文档字符串,描述了该模块的功能。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

VERSION = '1.1.0'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final newline missing (missing-final-newline)

Details

lint 解释

这个lint结果表明在文件的末尾缺少一个换行符(final newline missing)。根据代码风格指南,每个文件应该以一个换行符结束,这样可以确保文件内容不会被其他文本意外地连接在一起。

错误用法

# 这是一个错误的示例,文件末尾没有换行符
def example_function():
    print("Hello, World!")

正确用法

# 这是一个正确的示例,文件末尾有一个换行符
def example_function():
    print("Hello, World!")

# 注意:在最后一行后面有一个空的换行符

💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

Empty file.
File renamed without changes.
File renamed without changes.
Empty file.
7 changes: 4 additions & 3 deletions mcp_server/tools/tools.py → src/mcp_server/tools/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from typing import List, Dict, Callable, Optional, Union, Awaitable
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

standard import "typing.List" should be placed before third party import "fastjsonschema" (wrong-import-order)

Details

lint 解释

这个lint结果表明在代码中,标准库的导入语句(如 typing.List)应该放在第三方库的导入语句之前。这样可以保持代码的可读性和一致性。

错误用法

import fastjsonschema
from typing import List

在这个示例中,fastjsonschema 是一个第三方库的导入语句,而 typing.List 是标准库的导入语句。正确的顺序应该是先导入标准库的模块,然后再导入第三方库的模块。

正确用法

from typing import List
import fastjsonschema

在这个示例中,typing.List 的导入语句放在了 fastjsonschema 之前,符合lint规则的要求。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

from dataclasses import dataclass
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

standard import "dataclasses.dataclass" should be placed before third party import "fastjsonschema" (wrong-import-order)

Details

lint 解释

这个lint结果表明在代码文件中,标准库的导入语句应该放在第三方库的导入语句之前。具体来说,dataclasses.dataclass 是Python标准库的一部分,而 fastjsonschema 是一个第三方库。

错误用法

import fastjsonschema
from dataclasses import dataclass

在这个示例中,fastjsonschema 的导入语句在 dataclasses 的导入语句之前,违反了lint规则。

正确用法

from dataclasses import dataclass
import fastjsonschema

在这个示例中,dataclasses 的导入语句放在了 fastjsonschema 的导入语句之前,符合lint规则。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

from mcp import types
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unable to import 'mcp' (import-error)

Details

lint 解释

这个lint结果表明在代码中尝试导入一个名为 mcp 的模块,但该模块无法被找到。这通常是因为模块路径配置不正确或者模块本身不存在。

错误用法

# 错误的导入方式
import mcp

正确用法

  1. 确保模块存在:

    • 确认 mcp 模块是否存在于你的项目目录中,或者是否安装在你的Python环境中。
  2. 检查模块路径:

    • 如果 mcp 是一个自定义模块,确保它位于你的Python路径中。可以通过以下方式添加路径:
      import sys
      sys.path.append('/path/to/your/module')
      import mcp
  3. 安装第三方模块:

    • 如果 mcp 是一个第三方模块,确保你已经通过包管理工具(如 pip)正确安装了它:
      pip install mcp
  4. 使用相对导入或绝对导入:

    • 确保你在正确的文件中进行导入。例如,如果你在一个包的子目录中,应该使用相对导入:
      from .. import mcp
    • 或者使用绝对导入:
      from your_package import mcp

通过以上步骤,你应该能够解决 Unable to import 'mcp' (import-error) 的问题。


💡 以上内容由 AI 辅助生成,如有疑问欢迎反馈交流

from mcp_server import consts

from .. import consts

logger = logging.getLogger(consts.LOGGER_NAME)

Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading