Skip to content

Commit 9be9ca6

Browse files
author
Kaiserunix
committed
feat: add optional local NowCoder session auth
1 parent 858ee8c commit 9be9ca6

24 files changed

Lines changed: 662 additions & 53 deletions

PROVENANCE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This standalone workspace is generated from:
44

55
- Repository: https://github.com/ketherworks/oj-mcp-adapters
6-
- Commit: `3f7ee0e040d76f6dd5460d5926d8504c8cb20256`
6+
- Commit: `b23d435a4840f798e93c879a9e4f543529056a26`
77
- Provider source: `packages/nowcoder`
88
- Shared contract source: `packages/contracts`
99
- Shared MCP result helpers: `packages/server-common`

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# NowCoder ACM Problem MCP Server
22

3-
Hardened local MCP server for public NowCoder ACM problem pages.
3+
[简体中文](README.zh-CN.md)
4+
5+
Hardened local MCP server for allowlisted NowCoder ACM problem pages.
46

57
This is a standalone release workspace generated from the audited
6-
[Kether Works OJ adapter source](https://github.com/ketherworks/oj-mcp-adapters/tree/3f7ee0e040d76f6dd5460d5926d8504c8cb20256/packages/nowcoder).
8+
[Kether Works OJ adapter source](https://github.com/ketherworks/oj-mcp-adapters/tree/b23d435a4840f798e93c879a9e4f543529056a26/packages/nowcoder).
79
The release package bundles the shared OJ contract implementation, so its runtime does not depend
810
on unpublished workspace packages.
911

@@ -12,6 +14,7 @@ on unpublished workspace packages.
1214
- `oj_capabilities`
1315
- `oj_health`
1416
- `oj_fetch_problem`
17+
- `nowcoder_auth_status`
1518

1619
## Local Stdio
1720

@@ -44,9 +47,12 @@ publication are intentionally separate steps and are not claimed until their own
4447
## Policy
4548

4649
This project is unofficial and is not affiliated with or endorsed by NowCoder.
47-
It never bypasses anti-bot challenges and intentionally remains local stdio only.
50+
It supports an optional startup-injected local session, never bypasses anti-bot challenges, and
51+
intentionally remains local stdio only.
4852

49-
The server exposes no run or submit tool. It accepts no judge account credentials. See
53+
The server exposes no run or submit tool. The server accepts an optional NowCoder Cookie only from `NOWCODER_SESSION_COOKIE` at local
54+
stdio process startup. Inject it from a trusted secret manager; never put it in tool arguments,
55+
MCP configuration files, command-line arguments, logs, or committed files. See
5056
[SECURITY.md](SECURITY.md) for the security boundary and [PROVENANCE.md](PROVENANCE.md) for the
5157
canonical source revision.
5258

README.zh-CN.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# NowCoder MCP Server
2+
3+
[English](README.md)
4+
5+
面向牛客 ACM 题目页面的只读 MCP 适配器。本项目并非牛客官方项目,也不使用或声称拥有牛客官方 API。
6+
7+
## 工具
8+
9+
- `oj_capabilities`:报告已审计的导题能力、当前认证模式,以及明确不支持的操作。
10+
- `oj_health`:根据最近一次导题结果报告被动健康状态,不主动请求牛客。
11+
- `oj_fetch_problem`:通过白名单页面 URL 或规范题目 ID 返回 `OjProblemDocument`
12+
- `nowcoder_auth_status`:检查启动时注入的本地会话是否有效,不返回账号身份或 Cookie 数据。
13+
14+
当前不提供 `oj_search_problems`,因为还没有经过审计的稳定题目搜索契约。浏览器导入、用户资料、提交、运行和判题也尚未开放;配置登录态不会自动启用这些操作。
15+
16+
## 本地登录态
17+
18+
登录态是可选的。未配置会话时,服务读取牛客公开提供的页面;配置会话后,同一组白名单页面请求会携带用户提供的 Cookie 请求头,能力报告会标记为 `session_cookie``R1_private_read` 风险。
19+
20+
服务仅在进程启动时读取 `NOWCODER_SESSION_COOKIE`,其值应为完整的 Cookie 请求头。可信启动器应从密钥管理器中取得该值,例如 VS Code `SecretStorage`,并且只注入本地 stdio 子进程。
21+
22+
不要把 Cookie 放入 MCP 工具参数、`mcp.json`、VS Code 设置、命令行参数、Shell 历史、日志、Issue 或提交到 Git 的文件中。本适配器不会自动提取浏览器 Cookie;会话轮换后需要重启进程。
23+
24+
`nowcoder_auth_status` 只检查固定地址 `https://ac.nowcoder.com/`,并返回下列脱敏状态之一:
25+
26+
- `not_configured`
27+
- `authenticated`
28+
- `expired`
29+
- `challenge`
30+
- `unknown`
31+
32+
状态结果不包含 Cookie、账号身份或响应 HTML。
33+
34+
## 支持的地址
35+
36+
只接受精确主机 `ac.nowcoder.com` 上的 HTTPS 地址:
37+
38+
```text
39+
https://ac.nowcoder.com/acm/problem/<数字题号>
40+
https://ac.nowcoder.com/acm/contest/<数字比赛号>/<题目序号>
41+
```
42+
43+
查询参数和片段会被丢弃。其他牛客产品及旧版地址暂不支持,除非后续完成独立审计。
44+
45+
`nativeId` 只接受以下确定形式:
46+
47+
```text
48+
NC<正整数题号> -> /acm/problem/<题号>
49+
<正整数比赛号>/<大写或数字序号> -> /acm/contest/<比赛号>/<序号>
50+
```
51+
52+
裸数字、前导零、小写序号、额外路径,以及同时提供 `url``nativeId` 的请求都会被拒绝。
53+
54+
## 安全边界
55+
56+
- 每次请求和重定向前都会校验 URL 协议、主机名、端口、凭据和路径。
57+
- 可选 Cookie 仅在目标通过 `ac.nowcoder.com` 精确白名单后附加;重定向目标必须先通过校验,才能收到后续请求。
58+
- DNS A/AAAA 查询、响应读取和全部重定向共享 10 秒截止时间。经过校验的公网地址会固定到 TLS 连接,同时保留主机名验证和 SNI。
59+
- 最多允许两次白名单内重定向;响应上限为 2 MiB UTF-8 HTML;Cookie 上限为 16 KiB,并拒绝控制字符。
60+
- 遇到反爬挑战时返回 `challenge.required`,不使用浏览器自动化,也不尝试绕过挑战。
61+
- 题面从官方 ACM DOM 规范化,保留来源、文本块 SHA-256 哈希,并强制检查输入输出段落;页面结构漂移会返回 `upstream.schema_changed`
62+
- MCP 工具没有 Cookie 或凭据字段,错误和登录状态输出均经过脱敏。
63+
64+
## 传输方式
65+
66+
本包只提供本地 stdio。启用会话转发时不得部署为共享 HTTP 服务,以免成为凭据转发或页面抓取中继,同时保留 Node 传输层的 DNS 与 TLS 固定能力。
67+
68+
## 开发
69+
70+
需要 Node.js 22、TypeScript ESM/NodeNext、MCP SDK 1.29.0 和 Zod 4.3.6。
71+
72+
```powershell
73+
npm run build
74+
npm run typecheck
75+
npm run typecheck:test
76+
npm test
77+
npm run pack:check
78+
npm start
79+
```
80+
81+
测试只使用合成会话、静态页面夹具和本机回环 TLS 服务,不请求牛客、不访问浏览器密钥,也不部署远程服务。

SECURITY.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@ Report vulnerabilities through the private GitHub Security Advisory form for
44
`ketherworks/nowcoder-oj-mcp`. Do not include judge cookies, account tokens, source code,
55
or other secrets in a public issue.
66

7-
This server is read-only. It must not gain submission, code-execution, credential-forwarding, or
8-
challenge-bypass behavior without a separate threat model and explicit security review.
7+
This server is read-only. Its only credential-forwarding path is an optional Cookie read from
8+
`NOWCODER_SESSION_COOKIE` at local process startup and sent to an allowlisted
9+
`https://ac.nowcoder.com` request. The Cookie must never enter tool arguments, output, logs,
10+
files, cross-origin redirects, or a remotely hosted transport. The server must not gain automatic
11+
browser-cookie extraction, submission, code execution, or challenge-bypass behavior without a
12+
separate threat model and explicit security review.

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nowcoder-mcp-server-workspace",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"private": true,
55
"type": "module",
66
"workspaces": [

packages/nowcoder/README.md

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# NowCoder ACM Problem MCP Server
22

3-
Hardened local MCP server for public NowCoder ACM problem pages.
3+
[简体中文](README.zh-CN.md)
4+
5+
Hardened local MCP server for allowlisted NowCoder ACM problem pages.
46

57
This is a standalone release workspace generated from the audited
6-
[Kether Works OJ adapter source](https://github.com/ketherworks/oj-mcp-adapters/tree/3f7ee0e040d76f6dd5460d5926d8504c8cb20256/packages/nowcoder).
8+
[Kether Works OJ adapter source](https://github.com/ketherworks/oj-mcp-adapters/tree/b23d435a4840f798e93c879a9e4f543529056a26/packages/nowcoder).
79
The release package bundles the shared OJ contract implementation, so its runtime does not depend
810
on unpublished workspace packages.
911

@@ -12,6 +14,7 @@ on unpublished workspace packages.
1214
- `oj_capabilities`
1315
- `oj_health`
1416
- `oj_fetch_problem`
17+
- `nowcoder_auth_status`
1518

1619
## Local Stdio
1720

@@ -44,9 +47,12 @@ publication are intentionally separate steps and are not claimed until their own
4447
## Policy
4548

4649
This project is unofficial and is not affiliated with or endorsed by NowCoder.
47-
It never bypasses anti-bot challenges and intentionally remains local stdio only.
50+
It supports an optional startup-injected local session, never bypasses anti-bot challenges, and
51+
intentionally remains local stdio only.
4852

49-
The server exposes no run or submit tool. It accepts no judge account credentials. See
53+
The server exposes no run or submit tool. The server accepts an optional NowCoder Cookie only from `NOWCODER_SESSION_COOKIE` at local
54+
stdio process startup. Inject it from a trusted secret manager; never put it in tool arguments,
55+
MCP configuration files, command-line arguments, logs, or committed files. See
5056
[SECURITY.md](SECURITY.md) for the security boundary and [PROVENANCE.md](PROVENANCE.md) for the
5157
canonical source revision.
5258

@@ -66,15 +72,36 @@ this repository.
6672

6773
## Provider Implementation Details
6874

69-
Read-only MCP adapter for official public NowCoder/牛客 ACM problem pages. This package is an unofficial page adapter; it does not use or claim an official NowCoder API.
75+
[简体中文](README.zh-CN.md)
76+
77+
Read-only MCP adapter for official NowCoder/牛客 ACM problem pages. It is an unofficial page adapter and does not use or claim an official NowCoder API.
7078

7179
## Tools
7280

73-
- `oj_capabilities`: reports the single audited fetch capability and every unsupported operation.
81+
- `oj_capabilities`: reports the audited fetch capability, its active authentication mode, and every unsupported operation.
7482
- `oj_health`: reports passive health from the last fetch without making a network probe.
75-
- `oj_fetch_problem`: returns an `OjProblemDocument` from one allowlisted public page URL or canonical native ID.
83+
- `oj_fetch_problem`: returns an `OjProblemDocument` from one allowlisted page URL or canonical native ID.
84+
- `nowcoder_auth_status`: checks whether the startup-injected local session is accepted without returning account identity or Cookie data.
85+
86+
`oj_search_problems` is intentionally absent because no stable problem-search contract has been audited. Browser import, profiles, submissions, execution, and judging are also absent. Configuring a session does not enable those operations.
87+
88+
## Local Session
89+
90+
Authentication is optional. Without a session, the server reads pages that NowCoder exposes publicly. With a session, the same allowlisted page requests include the user-provided Cookie header and capabilities report `session_cookie` with `R1_private_read` risk.
91+
92+
The server reads a complete Cookie request-header value from `NOWCODER_SESSION_COOKIE` once at process startup. A trusted launcher should obtain the value from a secret manager, such as VS Code `SecretStorage`, and inject it only into the local stdio child process.
93+
94+
Do not put the Cookie in MCP tool arguments, `mcp.json`, VS Code settings, command-line arguments, shell history, logs, issues, or committed files. This adapter does not extract browser cookies automatically. Restart the process after rotating the session.
95+
96+
`nowcoder_auth_status` checks the fixed URL `https://ac.nowcoder.com/` and returns one redacted state:
97+
98+
- `not_configured`
99+
- `authenticated`
100+
- `expired`
101+
- `challenge`
102+
- `unknown`
76103

77-
`oj_search_problems` is intentionally absent. The public problem-list page is useful to humans, but no stable anonymous search contract has been audited. Browser import, cookies, authentication, profiles, submissions, execution, and judging are also absent.
104+
The status result never contains the Cookie, account identity, or response HTML.
78105

79106
## Accepted URLs
80107

@@ -85,7 +112,7 @@ https://ac.nowcoder.com/acm/problem/<numeric-id>
85112
https://ac.nowcoder.com/acm/contest/<numeric-contest-id>/<problem-index>
86113
```
87114

88-
Query strings and fragments are discarded. Other NowCoder products and legacy URL shapes are rejected until they can be independently audited.
115+
Query strings and fragments are discarded. Other NowCoder products and legacy URL shapes are rejected until independently audited.
89116

90117
The alternative `nativeId` input accepts exactly these deterministic forms:
91118

@@ -99,15 +126,17 @@ Numeric contest indexes are also accepted. Bare numbers, leading zeroes, lowerca
99126
## Safety
100127

101128
- URL scheme, hostname, port, credentials, and path are allowlisted before every request and redirect.
102-
- DNS A and AAAA queries use a cancellation-capable Node resolver under the shared deadline. Every answer must be public unicast; IANA non-global ranges and transition addresses with embedded non-global IPv4 targets are rejected. The complete validated dual-stack answer set is pinned into TLS fallback while preserving hostname verification and SNI.
129+
- The optional Cookie is attached only after the destination passes the exact `ac.nowcoder.com` allowlist. A redirect is validated before any follow-up request can receive it.
130+
- DNS A and AAAA queries use a cancellation-capable Node resolver under the shared deadline. Every answer must be public unicast; validated addresses are pinned into TLS fallback while preserving hostname verification and SNI.
103131
- Redirects are manual and limited to two allowlisted hops.
104-
- One 10-second deadline covers DNS, response body transfer, and every redirect hop; abort listeners and sockets are cleaned up when the operation settles. Responses are capped at 2 MiB of UTF-8 HTML.
105-
- Anti-bot pages produce `challenge.required`; the adapter never attempts to bypass them.
106-
- Responses are normalized from the same public ACM DOM used by Competitive Companion, with required input/output sections, source provenance, and SHA-256 hashes on text blocks. Missing required sections fail as `upstream.schema_changed`.
132+
- One 10-second deadline covers DNS, response-body transfer, and every redirect hop. Responses are capped at 2 MiB of UTF-8 HTML; the startup Cookie is capped at 16 KiB and control characters are rejected.
133+
- Anti-bot pages produce `challenge.required`; the adapter does not use browser automation or attempt to bypass them.
134+
- Responses are normalized from the official ACM DOM with required input/output sections, source provenance, and SHA-256 hashes on text blocks. Missing required sections fail as `upstream.schema_changed`.
135+
- Tool inputs have no Cookie or credential field, and errors and status results are deliberately redacted.
107136

108137
## Transport Choice
109138

110-
The package exposes stdio only. A Worker was deliberately omitted: a public HTTP deployment would become an anonymous NowCoder page-fetch relay, while the Worker runtime cannot provide the same explicit DNS resolution and TLS address pinning used by this Node adapter. Keeping the process local also avoids amplifying rate limits and challenge traffic. The implementation is stateless apart from passive last-fetch health, but that alone is not enough to make a public relay prudent.
139+
The package exposes local stdio only. It must not be deployed as a shared HTTP service while session forwarding is enabled. Keeping authenticated requests local prevents the server from becoming a credential-forwarding or page-fetch relay and preserves the Node transport's DNS and TLS pinning controls.
111140

112141
## Development
113142

@@ -122,4 +151,4 @@ npm run pack:check
122151
npm start
123152
```
124153

125-
Tests use static fixtures and loopback-only TLS servers. They do not contact NowCoder or deploy any service.
154+
Tests use synthetic sessions, static fixtures, and loopback-only TLS servers. They do not contact NowCoder, access browser secrets, or deploy any service.

0 commit comments

Comments
 (0)