Skip to content
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
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@ All notable changes to `sustech-cli` are documented in this file.

## [Unreleased]

## [0.10.0] - 2026-08-29

### Added

- Added public `online talks` and institutional `online contact` reads from
selected SUSTech Online pages, retaining community authority, source URL,
source update/fetch time, and freshness advisories in structured output.
- Added a local `sustech-mcp` `stdio` server with `33` typed public/local
read-only tools, `5` static JSON resources, `5` JSON resource templates, and
`4` prompts; it requires no hosted service, supports `--help` and
`--version`, and reuses CLI JSON output.

### Security

- MCP uses a typed command allowlist and rejects remote mutations, persistent
local TIS-plan edits, authenticated personal data, local private state,
browser/interactive flows, confirmation/output overrides, stdin secrets,
explicit credential files, secret reveal, command changes, timeouts, and
oversized inputs or outputs. Client cancellation now terminates the
underlying CLI subprocess.
- MCP resource templates now validate command names and identifier/path
variables before the CLI subprocess starts, instead of leaving malformed
template values to fail later inside downstream handlers.

## [0.9.0] - 2026-08-28

### Added
Expand Down
11 changes: 11 additions & 0 deletions NOTICE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,14 @@ Required Notice: Copyright dumixthestpd (https://github.com/dumixthestpd/sustech
The upstream project and this derivative work are distributed under the
PolyForm Noncommercial License 1.0.0. No affiliation with or endorsement by
Southern University of Science and Technology is implied.

The optional `online` commands retrieve selected public material from the
community-maintained SUSTech Online project:

- `SUSTech-CRA/sustech-online-ng`
- <https://mirrors.sustech.edu.cn/git/sustech-online/sustech-online-ng>

That upstream material is licensed under Creative Commons Attribution-
ShareAlike 4.0 International. Runtime output retains source links, community
authority labels, and license metadata. No affiliation with or endorsement by
SUSTech CRA is implied.
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Public data does not require an account:
```bash
sustech calendar day 2026-09-01
sustech faculty search "computer vision"
sustech online talks list --limit 10
sustech online contact search "教学"
sustech transit lines
sustech library search "graph neural networks" --limit 5
```
Expand Down Expand Up @@ -111,10 +113,15 @@ For an agent without Skill support, provide this short instruction:
> secrets, and never add `--confirm` without approval for the exact target.

A Skill is the onboarding layer; the CLI remains the executable source of
truth. An MCP server may be useful later for native tool registration or remote
execution, but wrapping every command in MCP now would duplicate the existing
JSON interface. A repository-level `AGENTS.md` alone would only help agents
that cloned the source.
truth. The package also ships a local `stdio` MCP entrypoint, `sustech-mcp`, for
clients that support native tools. It needs no hosted server and exposes `33`
typed public/local read-only tools plus JSON resources, resource templates, and
prompts for discovery, public campus data, library, faculty, transit, NCES,
papers, and selected SUSTech Online reads. Authenticated data, browser flows,
local writes, and remote mutations remain unavailable through MCP. See
[docs/MCP.md](docs/MCP.md) for configuration and the complete boundary.
A repository-level `AGENTS.md` alone would only help agents that cloned the
source.

## What it covers

Expand All @@ -128,8 +135,9 @@ version's exact command, authentication, network, and confirmation metadata.
| TIS | catalog, schedule, grades, exams, TIS-reported degree progress, conservative missing-course report, persistent planning, `tis plan solve/explain/recommend`, local degree audit, live classrooms, iCalendar | CAS login; selection/enrollment writes are confirm-gated |
| Blackboard | courses, deadlines, calendar reads, native calendar-link workflow, search, attachment download/sync, attempts, submission | CAS login for REST reads; the native calendar link is a separate stored secret and local writes are guarded |
| Library and campus services | Primo catalog search/detail, WS programs, eHall booking, library booking, PMS jobs and usage | Public catalog reads plus authenticated reads; booking and queue writes are confirm-gated |
| Research and courses | Crossref/OA papers, NCES browse and search | Public; OA downloads use guarded local paths; NCES remains community reference only |
| Research and courses | Crossref/OA papers, NCES browse and search, SUSTech Online talks | Public; OA downloads use guarded local paths; NCES and SUSTech Online remain community references only |
| Campus and device context | faculty, resources, transit, Wi-Fi status/events | Public or local |
| Community directory | Selected institutional SUSTech Online contacts with provenance and freshness advisories | Public community source; emergency, financial, personal, dining/chat, and professor-list sections are excluded |

For the structured TIS-reported `tis degree progress` response, the derived
`tis degree missing` report, and how both differ from local JSON
Expand All @@ -139,6 +147,8 @@ snapshot save/diff/change/watch workflow, see
[docs/ACADEMIC_SNAPSHOTS.md](docs/ACADEMIC_SNAPSHOTS.md). For the
`tis degree audit` requirements-file format, matching semantics, and current
runtime limits, see [docs/DEGREE_AUDIT.md](docs/DEGREE_AUDIT.md).
For the selected SUSTech Online source scope, provenance fields, freshness
labels, and contact exclusions, see [docs/ONLINE.md](docs/ONLINE.md).

Remote-state mutations are deliberately limited to these apply commands, all
of which require an exact target plus `--confirm`:
Expand Down Expand Up @@ -178,7 +188,7 @@ review. A successful envelope looks like this:
"ok": true,
"command": "version",
"data": {
"version": "0.9.0",
"version": "0.10.0",
"runtime": "node v22.19.0"
}
}
Expand Down
11 changes: 9 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ output renderer text | versioned JSON | streaming JSONL
missing-course classification, local degree audit, live classroom/context
helpers, multi-source ICS export, read-only plan explain/recommend
enrichment, and guarded enroll/cart/drop/bid write paths.
- `src/calendar`, `src/faculty`, `src/transit`, `src/resources`, and
`src/wifi` own public or local-only data sources.
- `src/calendar`, `src/faculty`, `src/online`, `src/transit`, `src/resources`,
and `src/wifi` own public or local-only data sources. `src/online` uses exact
path allowlists and retains community provenance, freshness, and license
metadata instead of presenting the source as official.
- `src/context` composes a truthful snapshot from whichever sources are
available, exposes Context v2 `level`/`live` enrichments, and marks missing
or partial inputs explicitly.
Expand All @@ -53,6 +55,11 @@ output renderer text | versioned JSON | streaming JSONL
masking, safe same-origin fetch, and bounded ICS parsing for the stored
Blackboard calendar subscription workflow.
- `src/core/capabilities.ts` is the machine-discoverable safety registry.
- `src/mcp` exposes a local `stdio` MCP adapter over that registry. It uses a
typed tool allowlist plus JSON resources and prompts, launches the packaged
CLI without a shell, propagates MCP cancellation to the child CLI process,
and rejects authenticated personal data, remote mutations, browser-assisted
flows, local private state, and known local state writes.
- Services must not write to stdout or stderr.
- Machine-readable output is versioned by `schemaVersion`.
- Text is the default; agents opt into `--json` or `--jsonl` explicitly.
Expand Down
186 changes: 186 additions & 0 deletions docs/MCP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Local MCP server

`sustech-cli` ships a local Model Context Protocol entrypoint named
`sustech-mcp`. It is `stdio` only: the client launches a local process and
speaks MCP over standard input and output. There is no hosted endpoint, open
port, background daemon, or shared multi-user server in this repo.

## Launch

After a global install, configure the client to launch:

```text
sustech-mcp
```

A typical local-command configuration looks like:

```json
{
"mcpServers": {
"sustech": {
"command": "sustech-mcp"
}
}
}
```

Without a global install, a client that accepts `command` plus `args` can run
the published package through npm:

```json
{
"command": "npm",
"args": ["exec", "--yes", "--package=sustech-cli", "--", "sustech-mcp"]
}
```

For a source checkout, run `npm run build` first and point the client at the
absolute path to `dist/mcp/server.js`.

The entrypoint behavior is intentionally narrow:

- `sustech-mcp` starts the MCP `stdio` server.
- `sustech-mcp --help` prints local usage text and exits.
- `sustech-mcp --version` prints the installed `sustech-cli` version and exits.
- Any other argument is rejected on stderr with exit code `2`.

## Tool surface

The server exposes `33` typed public/local read-only tools. It does not expose
a generic string runner such as `sustech_run`.

Core metadata:

- `sustech_discover`
- `sustech_describe`
- `sustech_version`
- `sustech_calendar_day`
- `sustech_consequences`
- `sustech_calendar_terms`
- `sustech_resources_list`
- `sustech_resources_search`
- `sustech_services_status`

Public research and catalog data:

- `sustech_papers_search`
- `sustech_nces_browse`
- `sustech_nces_search`
- `sustech_nces_course`
- `sustech_library_search`
- `sustech_library_detail`
- `sustech_library_search_url`

Public faculty and campus datasets:

- `sustech_faculty_departments`
- `sustech_faculty_list`
- `sustech_faculty_get`
- `sustech_faculty_search`
- `sustech_faculty_render`
- `sustech_transit_facilities`
- `sustech_transit_find`
- `sustech_transit_lines`
- `sustech_transit_schedule`
- `sustech_transit_stops`
- `sustech_transit_live`

Public SUSTech Online layer:

- `sustech_online_search`
- `sustech_online_talks_list`
- `sustech_online_talks_search`
- `sustech_online_talks_get`
- `sustech_online_contact_search`
- `sustech_online_contact_get`

All tools return the same versioned JSON envelope that the direct CLI already
uses, both as `structuredContent` and as a text fallback. This keeps the CLI as
the installed source of truth while giving MCP clients typed input schemas.

## Resources and prompts

The server also exposes JSON resources and reusable prompts.

Static resources (`5`):

- `sustech://version`
- `sustech://capabilities`
- `sustech://services`
- `sustech://consequences`
- `sustech://mcp/policy`

Resource templates (`5`):

- `sustech://faculty/{slug}`
- `sustech://command/{command}`
- `sustech://online/talk/{id}`
- `sustech://nces/course/{id}`
- `sustech://library/{context}/{docId}`

Prompts (`4`):

- `sustech_public_lookup`
- `sustech_guarded_cli_review`
- `sustech_course_research`
- `sustech_talk_digest`

The static policy resource documents the transport and safety boundary. The
template resources reuse the same typed CLI paths as the tool surface instead
of inventing a second parser. Template variables are validated locally before
the CLI subprocess starts. For commands with spaces, use normal URL encoding,
for example `sustech://command/calendar%20day`.

## Safety boundary

This MCP server is intentionally narrower than the CLI.

- No authenticated personal data is exposed through MCP.
- No local private state is exposed through MCP.
- No local file writes are exposed through MCP.
- No remote mutations are exposed through MCP.
- No browser-assisted or interactive flows are exposed through MCP.
- No generic shell or generic CLI runner is exposed through MCP.

In practice, that means MCP excludes commands and flags such as:

- authenticated TIS, Blackboard, booking, library-booking, PMS, profile, and
auth flows;
- `context --live`, `wifi status`, `wifi events`, and other local/private
machine state;
- persistent `tis plan` writes;
- downloads, exports, and other filesystem outputs;
- `--confirm`, `--browser`, `--interactive`, `--credentials-file`,
`--password-stdin`, `--url-stdin`, `--reveal`, and output-mode overrides.

The execution bridge validates the exact command name, blocks command-changing
arguments, validates resource-template variables before dispatch, launches the
packaged CLI without a shell, enforces size limits, and times out long-running
subprocesses.

If the MCP client cancels a request, the bridge aborts the underlying
`sustech` subprocess instead of leaving it running in the background.

Only protocol messages are written to standard output. Diagnostics stay on
standard error so they cannot corrupt the `stdio` stream.

## When to use the direct CLI instead

Use the direct `sustech` CLI whenever the task needs any of the following:

- authenticated campus data;
- remote apply/mutation workflows;
- preview/approval/`--confirm` sequences;
- local exports, downloads, or persistent plan edits;
- browser-assisted library fallback.

Those paths keep the repo's normal preview, explicit approval, apply, and
read-back verification model.

## Hosted deployments

This repo does not ship a hosted HTTP or Streamable HTTP MCP server. If a
future deployment needs cross-machine or shared access, it should be treated as
a separate product surface with its own authentication, authorization, rate
limits, audit logs, and server-side secret handling.
2 changes: 1 addition & 1 deletion docs/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ no real account mutation was attempted while completing this expansion.

Every mutation stays unavailable until its preview payload, confirmation gate,
success criteria, and post-action verification have fixture tests. As of
preview v0.9.0, guarded remote mutations include TIS enroll/cart/drop/bid,
v0.10.0, guarded remote mutations include TIS enroll/cart/drop/bid,
Blackboard submission, eHall and library booking create/cancel, and PMS queue
upload/delete. They require an exact typed target, fresh preflight, explicit
`--confirm`, and operation-specific read-back; an ambiguous result exits 5 with
Expand Down
69 changes: 69 additions & 0 deletions docs/ONLINE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# SUSTech Online public layer

The `online` commands read a deliberately small subset of the public,
community-maintained [SUSTech Online](https://sustech.online) manual. They do
not require a campus account:

```bash
sustech online talks list --since 2026-09-01 --limit 20
sustech online talks search "artificial intelligence" --limit 10
sustech online talks get 2026-07-30T10-00-00_François_Forget
sustech online contact search "教学" --limit 10
sustech online contact get teaching:教学工作部
sustech online search "library" --section contact
```

## Authority and freshness

SUSTech Online is a community source, not an official university system. Every
record retains:

- `authority: "community"`;
- the public page URL and repository path;
- page update time when the rendered site exposes it;
- the fetch time;
- the upstream `CC-BY-SA-4.0` license and link;
- explicit `COMMUNITY_MAINTAINED`, `AI_PROCESSED_SOURCE`,
`SOURCE_UPDATE_UNKNOWN`, and `STALE_SOURCE` advisories when applicable.

The talks source itself says its entries are compiled from public information
and processed by a model, so talk results always retain
`AI_PROCESSED_SOURCE`. Contact results do not receive that label unless the
source changes to say so. A missing rendered-page timestamp does not block a
raw public read, but it is reported as `SOURCE_UPDATE_UNKNOWN`.

Use these records for discovery and convenience. Recheck time-sensitive talk
details and important institutional contacts against the linked official page
before acting.

## Selected contact scope

The contact parser is an allowlist, not a full mirror of the source page. It
keeps selected institutional teaching, administration, general service, and
non-dining logistics records. It intentionally excludes:

- professor email lists;
- medical, safety, emergency, and psychological-crisis sections;
- dining/community-chat and QQ-group lists;
- reimbursement, bank-account, tax, and invoice information;
- postal examples, informal personal notes, and lost-and-found guidance.

This prevents a general campus search command from becoming an emergency or
financial authority. The CLI does not provide a dedicated emergency command.

## Network boundary

The client fetches only the exact allowlisted Markdown files from the public
`SUSTech-CRA/sustech-online-ng` repository and the matching rendered
`sustech.online` page used for update metadata. Redirects are rejected, final
origins and exact paths are checked, document size and timeout are bounded, and
talk identifiers can resolve to only one file in the talks directory.

Returned institutional links are limited to `sustech.edu.cn` subdomains and
the community site. Poster links are limited to those hosts plus the exact
image-mirror host currently used by the upstream talks archive; unrelated,
social, document-sharing, and deceptive lookalike domains are omitted.

Rendered-page metadata is optional; raw Markdown is required. Tests use frozen
synthetic fixtures and do not copy the upstream contact or talks content into
the package.
Loading