Metadata-Version: 2.5
Name: usmartsg-mcp
Version: 1.0.0
Summary: Local MCP server for uSmart SG trading with user-owned credentials and a read-only safety mode
Project-URL: Homepage, https://www.usmart.sg
Project-URL: Documentation, https://api-doc.usmart.sg/zh-cn/trade.html
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: mcp,openapi,singapore,trading,usmart
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
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
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.10
Requires-Dist: cryptography>=42
Requires-Dist: httpx2<3,>=2
Requires-Dist: mcp<3,>=2.0.0
Requires-Dist: tzdata>=2024.1; sys_platform == 'win32'
Requires-Dist: uvicorn>=0.31
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# usmartsg-mcp

面向 [uSmart SG](https://www.usmart.sg) OpenAPI 用户的本地交易 MCP Server。它通过标准
MCP Tools 向 Kiro、Codex、Claude Desktop、Cursor 等客户端提供订单预检、订单查询、下单和
改单能力。

本项目在本地以 stdio 运行：每位用户使用自己的 uSmart 账户和授权，凭证只保存在本机，
不随 PyPI 包分发，也不通过 MCP 工具参数传递。

> 当前仅支持 SG。服务可连接生产交易账户；启用写工具后可能产生真实资金交易。
> 本工具不提供投资建议，所有委托均由账户所有者负责。

## 主要特性

- 基于官方 MCP Python SDK，默认使用 stdio 传输；
- 使用 OAuth2 授权码 + PKCE：在浏览器用 uSmart 账户授权，令牌自动续期，无需自行申请 App ID 或密钥；
- 首次运行自动生成 OAuth2 配置，装完即可授权，无需手写 JSON；
- 凭证默认存放在 `~/.usmart`，支持 `USMART_HOME` 覆盖；
- 服务端只读模式：写工具在 MCP 协议层完全不注册；
- 写操作必须显式确认，且绝不自动重试；
- 下单前可查询每手股数、最大可买卖数量和购买力；
- 写操作在本地生成脱敏审计流水；
- 查询类工具返回结构化结果，失败统一为错误信封；
- 不提供行情服务，也不解析股票名称。

## 环境要求

- Python 3.10 或更高版本；
- [uv](https://docs.astral.sh/uv/getting-started/installation/)；
- 独立的 uSmart SG OpenAPI 权限：使用 uSmart 账户在浏览器完成 OAuth2 授权即可，无需自行申请
  App ID 或密钥。

PyPI 包不包含任何账户、Token 或密钥。

## 安装

推荐用 `uvx` 固定版本运行：

```bash
uvx --from "usmartsg-mcp==1.0.0" usmartsg-mcp
```

也可以先安装再运行：

```bash
pip install "usmartsg-mcp==1.0.0"
usmartsg-mcp
```

从源码安装：克隆本仓库后执行

```bash
cd usmartsg-mcp
uv sync --extra dev
uv run usmartsg-mcp
```

该命令会保持运行并等待 MCP stdio 消息，属于正常行为，应由 MCP 客户端启动。

## 快速开始

### 1. 首次运行自动生成配置

**不需要手工创建配置文件**：首次启动时，如果 `~/.usmart/config.json` 不存在，服务会按内置
默认值自动生成一份；随后调用 `oauth_login` 在浏览器完成授权即可使用。

| 配置项 | 生产（默认） | UAT（`USMART_ENV=uat`） |
|---|---|---|
| `baseUrl` | `https://open2-jy.usmartsg.com` | `https://open2-jy-uat.usmartsg.com` |
| `auth.oauth.baseUrl` | `https://jy.usmartsg.com` | `https://jy-uat.usmartsg.com` |
| `auth.oauth.clientId` | `ai-agent` | `agent-uat` |
| `auth.oauth.redirectUri` | `http://localhost:5100/callback` | 同左 |
| `auth.oauth.scopes` | `order:all` | 同左 |

生成后的目录：

```text
~/.usmart/                    # macOS/Linux 建议权限 700
├── config.json               # 自动生成：固定 OAuth2 配置，可按需修改
├── oauth_session.json        # OAuth 授权成功后自动生成（600）
├── credentials.json          # 可选：账户开启交易密码校验时创建，只写 tradePassword
└── audit/                    # 自动创建，写操作审计流水
```

- 在客户端 `env` 中加入 `"USMART_ENV": "uat"` 即可切换 UAT；
- 仅本地单用户（stdio）形态会自动生成配置；远程部署 / 目录版必须显式提供 `config.json`；
- OAuth 授权在浏览器完成，本机只保存授权会话，不保存账号密码；
  若账户开启交易密码校验且需要下单，可在 `~/.usmart/credentials.json` 写入
  `{"tradePassword": "<6 位数字>"}`；
- 删除 `~/.usmart/oauth_session.json` 相当于本地登出，下次调用会重新要求授权；
- 需要自定义时直接编辑 `~/.usmart/config.json`（OAuth2 参数见下方示例）。

OAuth2 模式的 `config.json`（默认已自动生成，通常无需改动；如需自定义可参考）：

```json
{
  "region": "SG",
  "baseUrl": "https://open2-jy.usmartsg.com",
  "auth": {
    "mode": "oauth2",
    "oauth": {
      "baseUrl": "https://jy.usmartsg.com",
      "clientId": "ai-agent",
      "redirectUri": "http://localhost:5100/callback",
      "scopes": ["order:all"]
    }
  },
  "lang": "1",
  "deviceType": "1",
  "appType": "12"
}
```

- `redirectUri` 必须与开放平台登记值逐字符一致（含端口），默认固定为
  `http://localhost:5100/callback`；请使用 `localhost`，`127.0.0.1` 形式会被 uSmart 网关拒绝；
- `configs/prod.local.config.json` 是同一份默认值的参考副本，可复制为 `~/.usmart/config.json`；
- 切换到 UAT 设置 `USMART_ENV=uat` 即可，无需手改地址。

可选的 `credentials.json`（只在账户开启交易密码校验且需要下单时创建）：

```json
{
  "tradePassword": "<可选的 6 位交易密码>"
}
```

```bash
chmod 600 ~/.usmart/credentials.json
```

凭证目录本身建议 `chmod 700 ~/.usmart`。Windows 用户应使用 `icacls` 限制目录仅当前账户
可访问。请勿把凭证目录放入代码仓库、云同步目录或聊天内容。

### 2. 接入客户端

Kiro（`~/.kiro/settings/mcp.json`，已有其他 Server 时合并 `usmart` 节点）：

```json
{
  "mcpServers": {
    "usmart": {
      "command": "uvx",
      "args": ["--from", "usmartsg-mcp==1.0.0", "usmartsg-mcp"],
      "env": { "USMART_READONLY": "1" },
      "disabled": false,
      "autoApprove": []
    }
  }
}
```

Claude Desktop（`claude_desktop_config.json`）：

```json
{
  "mcpServers": {
    "usmart": {
      "command": "uvx",
      "args": ["--from", "usmartsg-mcp==1.0.0", "usmartsg-mcp"],
      "env": { "USMART_READONLY": "1" }
    }
  }
}
```

Codex（`~/.codex/config.toml`）：

```toml
[mcp_servers.usmart]
command = "uvx"
args = ["--from", "usmartsg-mcp==1.0.0", "usmartsg-mcp"]
enabled = true
startup_timeout_sec = 60
enabled_tools = [
  "check_trade_credentials",
  "preview_order",
  "list_today_orders",
  "list_all_orders",
  "get_order_detail",
]
```

建议所有客户端先以 `USMART_READONLY=1` 接入，确认查询链路正常后再按需启用写工具。
不要把交易工具加入 `autoApprove`。如需 UAT 环境，在客户端 `env` 里追加 `"USMART_ENV": "uat"`。

### 3. 验证连接

客户端发现工具后，先调用：

```text
check_trade_credentials
```

该工具只检查本地文件并返回脱敏状态，不会发起授权、下单，也不会返回密码、密钥和 Token。
如显示「未授权」，依次调用 `oauth_login`（浏览器完成授权）和 `oauth_login_status`；
随后可调用 `list_today_orders` 验证只读查询链路。

## 远端部署

本包默认以本地 stdio 单用户方式运行，**无需**额外配置文件；如需自定义，见
[`configs/README.md`](configs/README.md)。

## 工具列表

| 工具 | 说明 | 风险类型 |
|---|---|---|
| `check_trade_credentials` | 检查本地凭证与授权状态，输出脱敏 | 本地只读 |
| `oauth_login` | 发起 OAuth2 授权，返回授权链接 | 安全操作 |
| `oauth_login_status` | 等待/查询授权结果并兑换令牌 | 安全操作 |
| `oauth_logout` | 删除本地 OAuth 会话 | 安全操作 |
| `clear_trade_session` | 清除进程内 Authorization 缓存 | 安全操作 |
| `preview_order` | 查询每手股数、可买卖量和购买力 | 远程只读 |
| `list_today_orders` | 查询今日订单 | 远程只读 |
| `list_all_orders` | 查询历史订单 | 远程只读 |
| `get_order_detail` | 查询订单明细 | 远程只读 |
| `place_order` | 提交真实委托 | 写操作 |
| `modify_order` | 改单或撤单 | 写操作 |

## 工具返回值

查询与预检工具返回结构化结果；数量、价格、金额均为十进制字符串，时间为 RFC 3339
（`+08:00`）。

订单（`list_today_orders` / `list_all_orders` 的 `orders`，以及 `get_order_detail` 的 `order`）：

| 字段 | 说明 |
|---|---|
| `order_id` | 订单号，即 `get_order_detail` 的 `entrust_id` |
| `serial_no` | 流水号，与 `order_id` 是不同的编号；上游未返回时为 `null` |
| `market`、`symbol`、`symbol_name` | 市场枚举、交易代码、标的名称 |
| `side`、`order_type`、`status` | 买卖方向、委托类型、订单状态枚举 |
| `quantity`、`filled_quantity` | 委托数量、已成交数量 |
| `price` | 当前委托价格；改过价的订单返回改单后的价格 |
| `filled_average_price` | 成交均价 |
| `filled_amount` | 成交金额（币种见 `currency`）；仅订单明细可能有值，订单列表恒为 `null` |
| `currency`、`order_session`、`created_at` | 币种、交易时段、创建时间 |

分页字段：`orders`、`page`（从 1 开始）、`page_size`、`total`、`has_more`。

`preview_order` 返回：

| 字段 | 说明 |
|---|---|
| `lot_size` | 每手股数 |
| `max_buy_quantity_with_margin` | 最大可买数量，含融资额度；不传 `entrust_price` 时为 `null` |
| `max_buy_quantity_cash_whole_lots` | 仅用现金、按整手取整的最大可买数量；不传 `entrust_price` 时为 `null` |
| `max_sell_quantity`、`max_sell_whole_shares` | 最大可卖数量 |
| `cash_purchasing_power`、`cash_currency` | 现金购买力及币种；`cash_currency` 只在港股（`HKD`）和美股（`USD`）返回 |
| `quantity_check` | 传入 `entrust_amount` 时返回，只校验整手，不校验资金是否足够 |

失败时返回 `isError=true`，文本内容是「SDK 固定前缀 + 紧凑 JSON」，例如：

```text
Error executing tool <tool_name>: {"error":{"code":"AUTH_EXPIRED","message":"uSmart 授权已失效。","recoverable":"reauth","hint":"重新完成 uSmart 授权后重试。"}}
```

`code` 取值包括 `AUTH_REQUIRED`、`AUTH_EXPIRED`、`PERMISSION_DENIED`、
`INVALID_ARGUMENT`、`INVALID_QUANTITY`、`CONFIRMATION_REQUIRED`、`RATE_LIMITED`、
`UPSTREAM_UNAVAILABLE`、`UPSTREAM_REJECTED`、`ORDER_OUTCOME_UNKNOWN`、`INTERNAL_ERROR`；
`recoverable` 说明是否可以重试或需要重新授权。

## 只读模式

设置以下环境变量后，`place_order` 和 `modify_order` 不会注册，在 MCP 协议层不存在：

```bash
USMART_READONLY=1
```

支持的开启值为 `1`、`true`、`yes`、`on`（不区分大小写）。

## 写操作安全机制

每次下单、改单或撤单都应确认市场、股票代码、买卖方向、数量、价格和委托类型。
服务端还会执行以下保护：

- 写操作必须显式传入 `confirmed=true`；
- 写操作出现超时或授权失效时不自动重试；
- 下单前校验整手数量（预检可用时）；
- 写操作写入 `~/.usmart/audit/`；
- 「已受理」不等于「已成交」（`accepted=true` 仅表示受理），必须通过订单查询核验。

本服务**不做本地金额或笔数限额**，因此金额较大或频繁下单时，请务必向用户复述完整订单摘要并
等待明确确认。

## 凭证与隐私

- 交易密码只从本地凭证文件读取，不进入 MCP 工具参数；
- refresh_token 保存在 `~/.usmart/oauth_session.json`（600），access_token 只驻留内存；
- MCP 工具不接受账号、密码、私钥、授权码或 Token 参数；
- 日志和工具结果不回显敏感凭证；
- macOS/Linux 会检查敏感文件的 POSIX 权限；Windows 无法执行同等校验，需自行配置 ACL；
- 请勿通过公网隧道暴露本地进程，也不要把凭证粘贴到聊天内容中。

## 常见问题

### 客户端找不到工具

确认 `uvx` 已安装且客户端能够执行：

```bash
uvx --from "usmartsg-mcp==1.0.0" usmartsg-mcp
```

该命令会保持运行并等待 MCP stdio 消息，属于正常行为。

### OAuth 授权后仍提示未授权

确认 `auth.oauth.redirectUri` 与开放平台登记值完全一致（含端口、路径和大小写），且
`oauth_login_status` 返回「授权成功」。删除 `~/.usmart/oauth_session.json` 相当于本地登出。

### 回调端口被占用

`oauth_login` 会监听 `redirectUri` 里的端口（默认 5100）。端口被占用时换一个端口，
并先在开放平台登记新的回调地址；不要使用非回环地址。

### 凭证检查失败

检查 `USMART_HOME` 指向的目录、`config.json` 是否存在且可读，以及文件权限是否符合要求。
OAuth 授权失败时优先核对 `auth.oauth.clientId` 和 `auth.oauth.redirectUri` 是否与开放平台
登记值一致。不要把真实凭证粘贴到聊天中。

### 只需要查询，不需要交易

始终设置 `USMART_READONLY=1`，此时写工具不会出现在客户端工具列表中。

## 开发与测试

```bash
uv sync --extra dev
uv run pytest tests -q
uv run ruff check src tests
uv build
uv run python scripts/check_artifact.py dist
```

测试使用临时凭证和本地 Mock，不访问真实 uSmart 服务；构建产物扫描会拒绝凭证文件和密钥材料。

## 许可证与免责声明

本项目采用 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 发布，
许可全文见 [LICENSE](LICENSE)。

本工具不构成投资建议，不保证委托成交或收益。AI 可能误解用户意图，使用者必须独立核对
每项交易参数，并对账户及交易结果承担责任。
