Metadata-Version: 2.4
Name: mcp-vos-auth
Version: 0.1.4
Summary: Token validation middleware for FastMCP tools
Project-URL: Repository, https://github.com/vinehoo/mcp-vos-auth
Author: vber
License: MIT
License-File: LICENSE
Keywords: authentication,fastmcp,mcp,middleware
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Requires-Dist: fastmcp<4,>=2.11
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pip>=26.1.2
Provides-Extra: test
Requires-Dist: pytest-asyncio>=0.24; extra == 'test'
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# mcp-vos-auth

用于 FastMCP 的工具调用鉴权 middleware。它在工具执行前通过 Vinehoo 接口验证 `token`，
再根据工具名查询允许调用该工具的角色；只有用户角色与工具角色存在交集时才执行工具。

## 安装

### 通过 PyPI 安装

```bash
pip install mcp-vos-auth
```

### 不通过 pip 使用

无论采用下面哪种方式，目标项目都必须提供本包的运行依赖：

```bash
pip install fastmcp httpx
```

#### 方式一：设置 `PYTHONPATH`（本地多项目开发推荐）

将本项目的 `src` 目录加入 Python 模块搜索路径：

```bash
export PYTHONPATH="/path/to/mcp-vos-auth/src:$PYTHONPATH"
python your_server.py
```

例如本仓库位于 `/Users/vber/Documents/codes/mcp-vos-auth` 时：

```bash
export PYTHONPATH="/Users/vber/Documents/codes/mcp-vos-auth/src:$PYTHONPATH"
python your_server.py
```

目标项目不需要修改导入语句：

```python
from mcp_vos_auth import register_token_auth
```

`PYTHONPATH` 只对当前终端会话有效。需要长期使用时，可将 `export` 命令加入 shell 配置，
或写入项目的启动脚本。

#### 方式二：在程序入口添加源码路径（仅建议临时调试）

```python
import sys

sys.path.insert(0, "/path/to/mcp-vos-auth/src")

from mcp_vos_auth import register_token_auth
```

这种方式会把本机绝对路径写入业务代码，不适合多人协作或生产部署。

#### 方式三：将包源码复制到目标项目

把本仓库的 `src/mcp_vos_auth` 目录完整复制到目标项目根目录：

```text
your-project/
├── your_server.py
└── mcp_vos_auth/
    ├── __init__.py
    └── middleware.py
```

然后正常导入：

```python
from mcp_vos_auth import register_token_auth
```

该方式部署简单，但复制后的源码不会自动同步本仓库后续的修复和升级，需要自行维护版本。

以上三种方式都不需要安装 `mcp-vos-auth` 本身。对于本地联调，优先使用 `PYTHONPATH`；
对于需要固定源码且不使用包管理的部署，可复制 `mcp_vos_auth` 目录。

## 使用

调用默认 token 校验接口前，需要配置 MCP secret：

```bash
export VINEHOO_MCP_SECRET="your-secret"
```

middleware 会将该值作为 `vinehoo-mcp-secret` 请求 header 发送。

```python
from fastmcp import Context, FastMCP
from mcp_vos_auth import get_token_data, register_token_auth

mcp = FastMCP("protected-server")

# 自动创建 middleware 并注册到当前 FastMCP 实例。
register_token_auth(mcp, platform="app")

@mcp.tool
def get_order(order_id: int, token: str) -> dict:
    return {"order_id": order_id}

if __name__ == "__main__":
    mcp.run()
```

工具函数可通过 FastMCP 注入的 `Context` 获取校验接口返回的完整 `data`，包括
`telephone`、`uid`、`userid`、`realname` 和 `roles`：

```python
@mcp.tool
async def current_user(token: str, ctx: Context) -> dict:
    user = await get_token_data(ctx)
    return user or {}
```

这些数据只保存在当前工具调用的 request state 中，不会跨请求或跨用户复用。

token 有效后，middleware 默认请求以下接口查询当前工具允许的角色：

```text
GET https://callback.vinehoo.com/go-wechat/wechat/v3/mcp/tools/role_ids?tool_name=<工具名>
```

该请求同时携带 `VINEHOO-Authorization: Bearer <token>` 和
`vinehoo-mcp-secret` header。用户的 `roles[].role_id` 与接口返回的 `data.role_ids`
存在任意一个相同角色时放行。`role_ids` 为空时拒绝调用，`role_id=1` 不做本地特殊放行。

不传 `platform` 时校验中台用户：

```python
register_token_auth(mcp)
```

默认保护所有工具。可排除公开工具，或只保护指定工具：

```python
register_token_auth(mcp, excluded_tools={"health", "version"})
# 或
register_token_auth(mcp, protected_tools={"get_order", "delete_order"})
```

其他配置：

```python
register_token_auth(
    mcp,
    token_parameter="access_token",
    timeout=3.0,
    unauthorized_message="无权限",
    role_ids_url="https://callback.vinehoo.com/go-wechat/wechat/v3/mcp/tools/role_ids",
)
```

如果工具使用 Pydantic 模型等嵌套参数，可以用点路径指定 token 的位置：

```python
@mcp.tool
async def submit_feedback(params: SubmitFeedbackInput) -> str:
    ...

register_token_auth(mcp, token_parameter="params.token")
```

`on_validation` 回调可获取工具名、完整工具参数和 token 校验结果。该回调在工具角色校验
之前执行。请勿在日志中记录完整 token：

```python
async def audit(tool_name, arguments, result):
    print(tool_name, result.valid, result.data)

register_token_auth(mcp, on_validation=audit)
```

安全策略为 fail-closed：缺少 token、任一接口返回非 200、`error_code != 0`、响应格式
异常、网络错误、超时、空工具角色或角色不匹配都会阻止工具执行。token 参数仍会传给工具
函数；如工具不需要使用它，可以保留该参数但不要记录或返回它。token 校验接口返回
`error_code == 11` 时，工具返回“MCP无请求权限”；其他 token 校验失败返回配置的无权限
提示；工具角色校验失败返回“无工具调用权限”。

## 构建和发布

```bash
python -m pip install build twine
python -m build
twine check dist/*
twine upload dist/*
```

发布前请在 `pyproject.toml` 中确认包名、作者、Repository URL 和版本号。

## 测试

```bash
python -m pip install -e '.[test]'
pytest
```
