@jamestesting888/tum-mcp 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -2
- package/README.md +25 -24
- package/docs/development.md +11 -14
- package/docs/setup.md +28 -40
- package/docs/study.md +92 -27
- package/docs/tum-mcp.md +39 -55
- package/npm/backend/manifest.json +2 -2
- package/npm/backend/{tum_mcp-0.6.0-py3-none-any.whl → tum_mcp-0.7.0-py3-none-any.whl} +0 -0
- package/npm/lock.mjs +43 -0
- package/npm/npm-command.mjs +96 -0
- package/npm/package-manager.mjs +109 -0
- package/npm/runtime.mjs +3 -35
- package/npm/tum-mcp.mjs +45 -20
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 0.7.0 — 2026-10-26
|
|
4
|
+
|
|
5
|
+
- Share one course app between Study and Exercises, with a single workspace
|
|
6
|
+
selector in the sidebar footer and a responsive mobile outline.
|
|
7
|
+
- Add `prepare-exercises` for source-backed weekly exercises with optional hints,
|
|
8
|
+
explanations, solutions and prepared checks. Support editable tables, long math
|
|
9
|
+
and code drafts, graph selections/paths and line graph sample points. Open work
|
|
10
|
+
uses rubric comparison; the app does not execute code or grade proofs.
|
|
11
|
+
- Keep step navigation in place, shorten controls and show section sources through
|
|
12
|
+
compact reference icons. Preserve browser progress, drafts and passage comments.
|
|
13
|
+
- Use `.tum-mcp/` for private course state and a cited agent course handbook covering
|
|
14
|
+
course organization, schedules, coursework/platforms, assessment and bonus rules.
|
|
15
|
+
Track reviewed source versions, coverage gaps and changed supporting sources.
|
|
16
|
+
- Rename `sync-study` to `sync-course` and `publish-study` to `publish-course`;
|
|
17
|
+
retain managed migration for previous skill names and preserve personal overrides.
|
|
18
|
+
- Keep the shipped app a minimal reusable component template, explicitly distinct
|
|
19
|
+
from an example of good course teaching. Keep rich synthetic tests out of the package.
|
|
20
|
+
|
|
21
|
+
New projects use `app/`; existing `study/` websites remain supported. Run
|
|
22
|
+
`tum-mcp study upgrade PROJECT` explicitly to rename legacy `.study/` metadata;
|
|
23
|
+
it preserves private records and archives the old root instructions. The separate
|
|
24
|
+
`study build PROJECT --upgrade-layout` moves a verified legacy web folder to `app/`
|
|
25
|
+
with a backup. Neither package installation nor update migrates course folders or
|
|
26
|
+
rebuilds personal sites automatically. Exercise publication is opt-in.
|
|
27
|
+
|
|
28
|
+
## 0.6.1 — 2026-10-25
|
|
29
|
+
|
|
30
|
+
- Make `tum-mcp update` download and validate the latest npm release before
|
|
31
|
+
replacing the invoked installation and refreshing configured clients.
|
|
32
|
+
Preserve newer installed versions; `--local` refreshes without contacting npm.
|
|
33
|
+
- Make `tum-mcp uninstall` remove managed client setup and the npm package in
|
|
34
|
+
one command. Keep saved data by default; stop package removal on conflicts.
|
|
35
|
+
Client-only removal retains npm, and `--keep-package` supports managed cleanup.
|
|
36
|
+
- Run npm through Node on all platforms, serialize package operations, and use
|
|
37
|
+
an external worker while Windows replaces package files.
|
|
38
|
+
- Test actual global installations against a private synthetic registry, including
|
|
39
|
+
failed downloads/preparation/replacement, customized files, and data preservation.
|
|
40
|
+
|
|
3
41
|
## 0.6.0 — 2026-10-25
|
|
4
42
|
|
|
5
43
|
- Merge course setup and updates into `sync-study`, with recovery for incomplete
|
|
@@ -20,8 +58,8 @@
|
|
|
20
58
|
Reuse isolated test fixtures,
|
|
21
59
|
retain native/platform coverage and expose slow tests and failures in CI.
|
|
22
60
|
- Preserve Windows CRLF study instructions through repeated synchronization.
|
|
23
|
-
- Group Artemis and Zulip implementation by provider, retaining read-only
|
|
24
|
-
tools
|
|
61
|
+
- Group Artemis and Zulip implementation by provider, retaining read-only
|
|
62
|
+
course tools. Fix OCR with aliased macOS temporary paths.
|
|
25
63
|
|
|
26
64
|
Upgrading does not rebuild personal study sites automatically. Ask your agent to
|
|
27
65
|
update the prepared website; customized renderer files are preserved for review.
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Connect your coding agent to Moodle, Artemis and Zulip. Read course content and
|
|
4
4
|
build a private, source-backed study workspace. Unofficial; independent of TUM.
|
|
5
5
|
|
|
6
|
-
**
|
|
6
|
+
**Package: [@jamestesting888/tum-mcp](https://www.npmjs.com/package/@jamestesting888/tum-mcp).**
|
|
7
7
|
[Release notes](CHANGELOG.md).
|
|
8
8
|
|
|
9
9
|
## Agent setup
|
|
@@ -36,13 +36,9 @@ Select services with **Space**, then **Enter**. Choose an installed browser and
|
|
|
36
36
|
complete sign-in there. Only selected tabs open; Zulip needs no API key.
|
|
37
37
|
Enable the [course and channel scopes](docs/tum-mcp.md#connections-and-scopes) you want to use.
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| Claude Code | `--client claude` · MCP registration and skills |
|
|
43
|
-
| Cursor | `--client cursor` · MCP registration and all five skills; restart |
|
|
44
|
-
| Google Antigravity | `--client antigravity` · MCP registration and all five skills; restart |
|
|
45
|
-
| Other MCP clients | `--client generic` · configuration for manual installation |
|
|
39
|
+
Choose a client with `--client`: `codex`, `claude`, `cursor` or `antigravity`.
|
|
40
|
+
Setup configures MCP and installs all six skills; follow any activation or
|
|
41
|
+
restart prompts. Use `generic` for manual MCP setup.
|
|
46
42
|
|
|
47
43
|
For existing Cursor and Antigravity setups, run `tum-mcp update` to add the skills.
|
|
48
44
|
Updates and uninstall preserve customized files and report them for review.
|
|
@@ -51,7 +47,7 @@ Supports macOS, Windows, glibc Linux x86_64/ARM64 and WSL 3.x. In WSL, sign-in
|
|
|
51
47
|
opens a Windows browser. Add `--host windows` to setup for a Windows client that
|
|
52
48
|
uses the WSL backend. [Browser and WSL setup](docs/setup.md#wsl).
|
|
53
49
|
|
|
54
|
-
Setup supplies a private Python runtime.
|
|
50
|
+
Setup supplies a private Python runtime. Connections use encrypted local
|
|
55
51
|
files and need no native keyring, Git or Docker.
|
|
56
52
|
|
|
57
53
|
## Read and study
|
|
@@ -67,15 +63,16 @@ posting and read-state changes are unavailable.
|
|
|
67
63
|
|
|
68
64
|
### Skills and example requests
|
|
69
65
|
|
|
70
|
-
The package includes
|
|
66
|
+
The package includes six skills. Once you've chosen a course from
|
|
71
67
|
your connections, try requests like these:
|
|
72
68
|
|
|
73
69
|
| Skill | Use it to | Example request |
|
|
74
70
|
| --- | --- | --- |
|
|
75
71
|
| `read-content` | Read and explain course sources with citations | "Can you explain what we covered in last week's lecture?" |
|
|
76
|
-
| `sync-
|
|
72
|
+
| `sync-course` | Create or refresh a course project | "Set up a study project for this course." |
|
|
77
73
|
| `prepare-study` | Build lessons, flashcards and practice | "Make me a study website with flashcards and practice questions." |
|
|
78
|
-
| `
|
|
74
|
+
| `prepare-exercises` | Prepare optional hints, checks and worked solutions | "Help me work through this week's exercises in the same app." |
|
|
75
|
+
| `publish-course` | Put a prepared website on GitHub Pages | "Put my study site online so I can open it on my phone." |
|
|
79
76
|
| `audio-notes` | Save spoken lessons under `audio/` | "Turn this into audio notes I can listen to on the go." |
|
|
80
77
|
|
|
81
78
|
Useful follow-ups in an existing project:
|
|
@@ -90,11 +87,12 @@ Client setup installs managed copies of the
|
|
|
90
87
|
|
|
91
88
|
### Recommended study flow
|
|
92
89
|
|
|
93
|
-
1. **`sync-
|
|
90
|
+
1. **`sync-course`**: Set up a project for your course and collect its material.
|
|
94
91
|
Use it again when new material is available.
|
|
95
92
|
2. **`prepare-study`**: Turn that material into a study website with explanations,
|
|
96
|
-
flashcards and practice questions.
|
|
97
|
-
|
|
93
|
+
flashcards and practice questions. Use **`prepare-exercises`** for released sheets
|
|
94
|
+
with prepared interactive guidance. Both workspaces share one app; either can come first.
|
|
95
|
+
3. **`publish-course`** (optional): Put the website online so you can study on your
|
|
98
96
|
phone. Check the [publishing requirements](#publish-for-phone-access-optional) first.
|
|
99
97
|
4. **`audio-notes`** (optional): Turn the prepared content into spoken lessons.
|
|
100
98
|
Use them to revisit prepared topics while walking or commuting.
|
|
@@ -143,19 +141,20 @@ your project looks like this, with audio added when requested:
|
|
|
143
141
|
|
|
144
142
|
```text
|
|
145
143
|
my-course/
|
|
146
|
-
├── AGENTS.md
|
|
144
|
+
├── AGENTS.md Your rules and a short pointer to agent context
|
|
147
145
|
├── materials/
|
|
148
146
|
│ ├── content/ Lectures, slides and reading
|
|
149
147
|
│ ├── exercises/ Exercise sheets and practice material
|
|
150
148
|
│ ├── exams/ Available exam and revision material
|
|
151
149
|
│ └── other/ Announcements and course information
|
|
152
|
-
├──
|
|
150
|
+
├── app/
|
|
153
151
|
│ ├── content.json Lessons, flashcards and practice questions
|
|
154
152
|
│ └── index.html Your study website
|
|
155
153
|
├── audio/ Optional audio notes
|
|
156
154
|
│ ├── scripts/ Narration text
|
|
157
155
|
│ └── tracks/ Audio lessons
|
|
158
|
-
└── .
|
|
156
|
+
└── .tum-mcp/
|
|
157
|
+
├── course.md Cited course orientation, rules and unknowns
|
|
159
158
|
├── state.json Course identity and source versions
|
|
160
159
|
└── comments.json Passage comments, when added
|
|
161
160
|
```
|
|
@@ -167,11 +166,13 @@ stay local. Study progress is saved in your browser.
|
|
|
167
166
|
## Update
|
|
168
167
|
|
|
169
168
|
```sh
|
|
170
|
-
npm install --global @jamestesting888/tum-mcp@latest
|
|
171
169
|
tum-mcp update
|
|
172
170
|
```
|
|
173
171
|
|
|
174
|
-
`update`
|
|
172
|
+
`update` downloads the latest release, validates its backend, installs it in the
|
|
173
|
+
same npm location and refreshes configured clients. It never downgrades a newer
|
|
174
|
+
installation. Use `tum-mcp update --local` to refresh the installed version without checking npm.
|
|
175
|
+
From 0.6.0 or earlier, upgrade the npm package once to obtain this command.
|
|
175
176
|
Sessions, scopes and downloads carry forward. User-edited entries and skills are
|
|
176
177
|
preserved and reported for review. Reload the client; refresh its cached plugin
|
|
177
178
|
if needed. Study sites are updated separately through the study workflows.
|
|
@@ -179,14 +180,14 @@ if needed. Study sites are updated separately through the study workflows.
|
|
|
179
180
|
## Remove
|
|
180
181
|
|
|
181
182
|
```sh
|
|
182
|
-
tum-mcp uninstall
|
|
183
|
-
npm uninstall --global @jamestesting888/tum-mcp
|
|
183
|
+
tum-mcp uninstall
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
The npm package, managed registrations and unchanged skills are removed. Settings, sessions,
|
|
187
187
|
downloads and study projects are preserved. After closing clients, add
|
|
188
188
|
`--purge-runtime` to remove generated runtimes or `--forget-credentials` to
|
|
189
|
-
remove saved local
|
|
189
|
+
remove saved local credentials. [Removal details](docs/setup.md#update-and-remove).
|
|
190
|
+
Use `--client NAME` to remove just one client, or `--keep-package` to retain npm.
|
|
190
191
|
|
|
191
192
|
[Guide](docs/tum-mcp.md) · [Setup](docs/setup.md) · [Study workflows](docs/study.md) · [Development](docs/development.md)
|
|
192
193
|
|
package/docs/development.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
The npm package is the distribution. Its wheel contains one Python package,
|
|
4
4
|
`src/tum_mcp/`, with peer `moodle/`, `artemis/` and `zulip/` provider packages.
|
|
5
|
-
The retired standalone Moodle plugin and its checkout installer are no longer
|
|
6
|
-
maintained.
|
|
7
5
|
Client adapters still generate the configuration each host needs.
|
|
8
6
|
|
|
9
7
|
## Checks
|
|
@@ -19,6 +17,7 @@ npm test
|
|
|
19
17
|
npm pack --ignore-scripts --pack-destination dist
|
|
20
18
|
npm run test:consumer
|
|
21
19
|
npm run test:upgrade
|
|
20
|
+
npm run test:package-lifecycle
|
|
22
21
|
```
|
|
23
22
|
|
|
24
23
|
Python 3.13 is the managed runtime. Install Tesseract for OCR tests. Default tests
|
|
@@ -41,7 +40,7 @@ Each layer owns a different failure boundary:
|
|
|
41
40
|
| Public upgrade | Earlier published versions, repair, ownership conflicts and removal |
|
|
42
41
|
| Native platform | OS credential/ACL behavior; WSL Windows browser transport on both engines |
|
|
43
42
|
|
|
44
|
-
Study integration fixtures build the
|
|
43
|
+
Study integration fixtures build the private rich regression fixture once, then copy it into a fresh
|
|
45
44
|
project for each test. Launcher tests only need an HTML entry point. Neither
|
|
46
45
|
shares mutable course state. Client transaction tests use five small synthetic
|
|
47
46
|
skills, including nested binary assets; full-pack export, migration and installed
|
|
@@ -57,8 +56,11 @@ CI runs macOS, Windows and Linux, plus both distribution engines on WSL 3.x.
|
|
|
57
56
|
Consumer checks install the actual npm artifact, discover tools, build a study
|
|
58
57
|
site and test available Chromium browsers with temporary profiles and synthetic
|
|
59
58
|
pages. WSL verifies Windows browser transport. No live account is used.
|
|
60
|
-
Upgrade checks cover public 0.
|
|
59
|
+
Upgrade checks cover public 0.6.1, client refresh, customized-file conflicts,
|
|
61
60
|
failed preparation, runtime repair and removal that preserves work/credentials.
|
|
61
|
+
The package-lifecycle check uses a local synthetic registry and real isolated
|
|
62
|
+
global npm prefixes to verify downloads, backend validation, replacement,
|
|
63
|
+
failure ordering and one-command removal. It does not publish a test release.
|
|
62
64
|
All five jobs must pass on the exact pushed commit before completion is claimed.
|
|
63
65
|
|
|
64
66
|
Live Moodle checks are opt-in: `TUM_MOODLE_LIVE=1 uv run pytest tests/live -q`.
|
|
@@ -97,14 +99,7 @@ tag and artifact integrity before pushing the release documentation.
|
|
|
97
99
|
|
|
98
100
|
Client setup installs managed copies of the repository's skills; those copies are
|
|
99
101
|
outputs, not another source tree. Use `skills customize` for a personal override.
|
|
100
|
-
The Codex adapter generates a small host plugin
|
|
101
|
-
load skills and MCP together; it is separate from the removed top-level Moodle
|
|
102
|
-
plugin distribution.
|
|
103
|
-
|
|
104
|
-
Keep Moodle's existing native-store service/account identity, session paths and
|
|
105
|
-
download locations stable during migrations. Environment variable names and the
|
|
106
|
-
`tum-moodle` CLI alias remain compatible; its implementation now resides inside
|
|
107
|
-
`tum_mcp.moodle`. No automatic native-store secret extraction occurs.
|
|
102
|
+
The Codex adapter generates a small host plugin to load skills and MCP together.
|
|
108
103
|
|
|
109
104
|
Extraction uses bounded subprocesses. POSIX enforces CPU/process-group limits;
|
|
110
105
|
Windows terminates the worker and observed children. Study imports and preview
|
|
@@ -115,12 +110,14 @@ material remain untrusted data for the skill.
|
|
|
115
110
|
|
|
116
111
|
## Study renderer
|
|
117
112
|
|
|
118
|
-
Author `
|
|
113
|
+
Author `app/content.json` and optional `app/exercises.json`; legacy `study/` remains supported. Formula blocks use LaTeX compiled locally into safe
|
|
119
114
|
native MathML; legacy MathML remains accepted. Unit `path` labels group the outline
|
|
120
115
|
without changing learning-progress revisions. Shared modules render all units;
|
|
121
116
|
content changes do not require a framework or per-unit HTML.
|
|
122
117
|
|
|
123
|
-
|
|
118
|
+
Shared renderer assets live in `src/tum_mcp/bundled/course_app/site/`. The shipped
|
|
119
|
+
component preview is deliberately minimal; richer learning pilots live in
|
|
120
|
+
`tests/fixtures/course_app/`. The distributed design/teaching/verification contracts live under
|
|
124
121
|
`src/tum_mcp/bundled/skills/prepare-study/references/`. Local agent notes and
|
|
125
122
|
screenshots belong in ignored `docs/local/` or `.omo/`. Keep real course projects,
|
|
126
123
|
session files, databases, downloads and environment files out of artifacts.
|
package/docs/setup.md
CHANGED
|
@@ -20,7 +20,7 @@ tum-mcp doctor
|
|
|
20
20
|
|
|
21
21
|
Use `--client claude`, `cursor`, `antigravity`, or `generic` for another client.
|
|
22
22
|
Codex needs the plugin enabled and a restart when prompted. Cursor and
|
|
23
|
-
Antigravity setup installs all
|
|
23
|
+
Antigravity setup installs all six skills and MCP configuration; restart each
|
|
24
24
|
client afterward. Existing MCP-only setups gain the skills through `tum-mcp update`
|
|
25
25
|
or another setup run. Generic setup writes configuration for manual installation.
|
|
26
26
|
Setup preserves unrelated settings and refuses to
|
|
@@ -35,11 +35,10 @@ Run **`tum-mcp connect`**, with no arguments, in an interactive terminal:
|
|
|
35
35
|
3. Complete TUM sign-in/MFA in the opened window. Only selected service tabs open.
|
|
36
36
|
4. Return to the terminal for the saved-connection result.
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
No API key or
|
|
40
|
-
connection separate from your everyday browser. Ctrl+C closes it and preserves
|
|
38
|
+
All three services use the same sign-in and encrypted session storage flow.
|
|
39
|
+
No API key or native credential-store prompt is needed. A temporary browser
|
|
40
|
+
profile keeps the connection separate from your everyday browser. Ctrl+C closes it and preserves
|
|
41
41
|
connections that already completed. A dumb terminal uses numbered prompts.
|
|
42
|
-
Old service arguments and `--service`/`--auth` flags are no longer accepted.
|
|
43
42
|
|
|
44
43
|
Chrome, Edge, Brave, Chromium, Vivaldi and Opera are detected in their standard
|
|
45
44
|
locations. Selected preview channels and Opera GX are detected where installed.
|
|
@@ -113,19 +112,25 @@ browser discovery still requires functioning interop.
|
|
|
113
112
|
Sessions are encrypted with owner-restricted local key files. This avoids native
|
|
114
113
|
credential-store prompts, but anyone obtaining both key and ciphertext can
|
|
115
114
|
decrypt them. [Storage locations and recovery](tum-mcp.md#connections-and-scopes)
|
|
116
|
-
explain
|
|
115
|
+
explain where sessions are saved and how to back them up.
|
|
117
116
|
|
|
118
117
|
## Update and remove
|
|
119
118
|
|
|
120
119
|
```sh
|
|
121
|
-
npm install --global @jamestesting888/tum-mcp@latest
|
|
122
120
|
tum-mcp update
|
|
123
121
|
```
|
|
124
122
|
|
|
125
|
-
`update`
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
From 0.6.1, `update` retrieves the latest npm release, validates its backend in a
|
|
124
|
+
temporary installation, replaces the invoked package and refreshes previously
|
|
125
|
+
configured clients. It preserves the installation prefix and global/local mode.
|
|
126
|
+
It does not connect services, change scopes or register additional clients.
|
|
127
|
+
It never downgrades a newer installation. `update --local` skips npm and repairs
|
|
128
|
+
or refreshes the installed version. Source/pinned builds use this local mode;
|
|
129
|
+
temporary npx and linked source installations are not replaced automatically.
|
|
130
|
+
Automatic replacement also stops if the package directory contains configured
|
|
131
|
+
sessions, downloads or course work; move that data outside the package first.
|
|
132
|
+
For 0.6.0 and earlier, first install the newer npm package once.
|
|
133
|
+
`setup --client NAME` explicitly registers one client and can also refresh that client's managed
|
|
129
134
|
files. WSL Windows registrations keep their distribution/user/data paths.
|
|
130
135
|
|
|
131
136
|
Ownership receipts track the entries and files installed by TUM MCP. Unchanged
|
|
@@ -138,57 +143,40 @@ not selected, and a damaged runtime is repaired into a fresh generation. A newer
|
|
|
138
143
|
managed client is not silently downgraded by an older package; intentional
|
|
139
144
|
rollback starts by unregistering that client, then registering the reviewed version.
|
|
140
145
|
|
|
141
|
-
0.4.1/0.5.0 installations without receipts are adopted only after matching their
|
|
142
|
-
retained runtime's canonical client output. The seven former workflow skills and the three website skills are replaced by
|
|
143
|
-
`read-content`, `sync-study` and `prepare-study`; `audio-notes` adds
|
|
144
|
-
spoken lessons derived from the prepared website. Personal overrides
|
|
145
|
-
under retired names remain in the config `skills/` directory; review and merge
|
|
146
|
-
retrieval guidance into `read-content` before exporting a replacement pack.
|
|
147
146
|
Reload MCP servers in Cursor/Antigravity, restart Claude as appropriate, and
|
|
148
147
|
update/reinstall TUM MCP from the personal source in Codex Plugins if its cached
|
|
149
148
|
copy still shows the older skills. Writing source files does not activate a host.
|
|
150
149
|
|
|
151
150
|
Remove a single client with `tum-mcp uninstall --client cursor` (replace the
|
|
152
151
|
client as needed; add `--host windows` from WSL for a Windows client). To remove
|
|
153
|
-
all managed registrations
|
|
152
|
+
the npm package and all managed registrations:
|
|
154
153
|
|
|
155
154
|
```sh
|
|
156
|
-
tum-mcp uninstall
|
|
157
|
-
npm uninstall --global @jamestesting888/tum-mcp
|
|
155
|
+
tum-mcp uninstall
|
|
158
156
|
```
|
|
159
157
|
|
|
160
158
|
Custom edits and user-added files are preserved. Codex's local source/catalog
|
|
161
159
|
entry can be removed, but its cached/active plugin must also be removed or
|
|
162
|
-
disabled through Codex Plugins.
|
|
160
|
+
disabled through Codex Plugins. Package removal runs only after successful
|
|
161
|
+
client cleanup; conflicts preserve npm so you can review and retry. `--all`
|
|
162
|
+
is accepted explicitly, and `--keep-package` removes client setup without npm.
|
|
163
|
+
No npm lifecycle hook performs cleanup or reads credentials.
|
|
163
164
|
|
|
164
165
|
For explicit cleanup, **close coding clients first**, then run:
|
|
165
166
|
|
|
166
167
|
```sh
|
|
167
|
-
tum-mcp uninstall --
|
|
168
|
-
npm uninstall --global @jamestesting888/tum-mcp
|
|
168
|
+
tum-mcp uninstall --purge-runtime --forget-credentials
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
`--purge-runtime` deletes verified managed runtime generations after the Python
|
|
172
172
|
helper exits, and removes unchanged managed bootstrap/browser-driver caches.
|
|
173
173
|
Active tracked backends or unrecognized runtime files stop automatic removal.
|
|
174
174
|
Paths overlapping configured assignment work, downloads or Moodle data are
|
|
175
|
-
protected. `--forget-credentials` removes local
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
work are retained by these commands. No command recursively deletes the campus
|
|
175
|
+
protected. `--forget-credentials` removes saved local credentials and session
|
|
176
|
+
keys; it does not revoke remote credentials. Settings, custom skills, course
|
|
177
|
+
cache, downloads and assignment work are retained by these commands.
|
|
178
|
+
No command recursively deletes the campus
|
|
180
179
|
data root. Course audio and optional shared speech models/runtimes also remain.
|
|
181
180
|
Remove a shared speech cache only when explicitly requested and no other course
|
|
182
181
|
needs it; its host paths are documented in the audio skill. If removal is
|
|
183
|
-
interrupted, run
|
|
184
|
-
|
|
185
|
-
## Existing Moodle-only installations
|
|
186
|
-
|
|
187
|
-
The older `tum-moodle` commands and sixteen `moodle_*` tools remain compatible.
|
|
188
|
-
The separate source/plugin installer retains its original native credential
|
|
189
|
-
store: macOS Keychain, Windows Credential Manager, or Linux Secret Service.
|
|
190
|
-
Its native-store identity, existing session paths and downloads remain intact.
|
|
191
|
-
The combined `tum-mcp serve` uses local-file storage and directs all login
|
|
192
|
-
requests to `tum-mcp connect`. Reconnect once through that selector to create the
|
|
193
|
-
new session; no automatic extraction of older native-store secrets occurs.
|
|
194
|
-
Disable the older plugin when activating TUM MCP to avoid duplicate tools.
|
|
182
|
+
interrupted, run `tum-mcp setup --client NAME` to recover before reuse.
|
package/docs/study.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Course study projects
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Six focused skills cover source reading, course sync, Study preparation, Exercises preparation, audio and publishing.
|
|
4
4
|
They run in your local agent, using the connected
|
|
5
5
|
MCP tools to read course sources. The CLI validates and stores the resulting local
|
|
6
6
|
files; it does not run an AI model.
|
|
7
7
|
|
|
8
8
|
## Publish a study website
|
|
9
9
|
|
|
10
|
-
The optional `publish-
|
|
10
|
+
The optional `publish-course` skill makes a prepared site accessible from a phone
|
|
11
11
|
or another computer. Its GitHub repository is private; the Pages website is
|
|
12
12
|
public. An eligible paid GitHub plan is required for Pages from private repositories.
|
|
13
13
|
This workflow does not provide private website access. Review lesson text,
|
|
@@ -29,10 +29,16 @@ tum-mcp study publish --project PROJECT --public-site --repository OWNER/NAME
|
|
|
29
29
|
|
|
30
30
|
Names use `study-<course>-<semester>` in lowercase with hyphens. A collision gets
|
|
31
31
|
a stable course suffix; existing repositories are never made public. Settings in
|
|
32
|
-
`.
|
|
32
|
+
`.tum-mcp/github.json` bind updates to that course, semester and repository. A
|
|
33
33
|
failed setup retries the same saved repository rather than creating duplicates.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Study is the default hosted workspace, including for old settings. Exercise
|
|
36
|
+
statements, hints and full solutions need explicit `--include-exercises` on the
|
|
37
|
+
publish command after reviewing their public exposure. `--exclude-exercises`
|
|
38
|
+
returns a combined site to Study only, using the same repository and URL. An
|
|
39
|
+
Exercises-only site also requires explicit inclusion.
|
|
40
|
+
|
|
41
|
+
Only the selected generated website renderer and data are uploaded. Downloaded
|
|
36
42
|
originals, credentials, AGENTS.md, study inventory, passage comments, progress,
|
|
37
43
|
audio and arbitrary files stay local. Git itself is not required: the publisher
|
|
38
44
|
uses authenticated GitHub API calls through `gh`. Local publication settings do
|
|
@@ -63,7 +69,7 @@ and offline support are not included.
|
|
|
63
69
|
|
|
64
70
|
## Start with one course
|
|
65
71
|
|
|
66
|
-
Use `sync-
|
|
72
|
+
Use `sync-course` for both initial setup and later source updates. For a new project,
|
|
67
73
|
provide the course title and semester when known. The agent verifies
|
|
68
74
|
its availability, resolves source IDs and asks about ambiguous mappings. A project
|
|
69
75
|
cannot switch course or semester. Use a separate directory for another course.
|
|
@@ -86,18 +92,42 @@ pse-ws26/
|
|
|
86
92
|
exams/ Available revision material
|
|
87
93
|
exercises/ Released statements and practice references
|
|
88
94
|
other/ Announcements and course information
|
|
89
|
-
.
|
|
90
|
-
|
|
95
|
+
.tum-mcp/
|
|
96
|
+
course.md Cited course structure, logistics, rules and unknowns
|
|
97
|
+
state.json Course binding, source versions and coverage
|
|
98
|
+
notes/ Detailed course/agent notes when needed
|
|
99
|
+
app/ Shared Study and Exercises website
|
|
100
|
+
content.json Optional source-backed Study content
|
|
101
|
+
exercises.json Optional prepared exercise guidance
|
|
91
102
|
audio/ Optional category audio derived from the prepared website
|
|
92
103
|
```
|
|
93
104
|
|
|
94
|
-
`AGENTS.md`
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
`AGENTS.md` retains user rules and points agents to `.tum-mcp/course.md`. That short
|
|
106
|
+
handbook explains the course's goals, lectures/tutorials, weekly work and platforms,
|
|
107
|
+
assessment/bonus conditions, communication and preparation map. Claims carry source
|
|
108
|
+
links/locators and checked dates; unknowns and conflicting announcements stay visible.
|
|
109
|
+
Detailed source content remains in materials/. All TUM MCP course state, plans and
|
|
110
|
+
agent notes live under .tum-mcp/.
|
|
111
|
+
|
|
112
|
+
`sync-course` reads the relevant authoritative sources and maintains the handbook;
|
|
113
|
+
the CLI scaffolds it without pretending to understand the course. Its review receipt
|
|
114
|
+
records the local generation and hashes of supporting sources. `study inspect`
|
|
115
|
+
reports changed supporting sources and coverage gaps so refreshes can focus on
|
|
116
|
+
affected claims. A reviewed local snapshot does not establish live freshness or
|
|
117
|
+
complete understanding. See the bundled sync-course context reference for the full
|
|
118
|
+
source-review workflow.
|
|
119
|
+
|
|
120
|
+
To rename existing private `.study/` state explicitly, close other course processes
|
|
121
|
+
and run `tum-mcp study upgrade PROJECT`. It preserves unknown private files,
|
|
122
|
+
comments, publication settings and the full old root instructions as
|
|
123
|
+
`.tum-mcp/legacy-AGENTS.md`. Course identity, materials and browser progress stay the
|
|
124
|
+
same. Existing legacy state remains readable until upgrade; two state folders are
|
|
125
|
+
a conflict to reconcile, never silently merged. This differs from the web-folder
|
|
126
|
+
upgrade below.
|
|
97
127
|
|
|
98
128
|
## State-based workflow
|
|
99
129
|
|
|
100
|
-
`sync-
|
|
130
|
+
`sync-course` starts with `tum-mcp study status PROJECT`, a read-only local check:
|
|
101
131
|
|
|
102
132
|
| Mode | Action |
|
|
103
133
|
| --- | --- |
|
|
@@ -112,7 +142,7 @@ The underlying `study init` and `study sync` commands remain separate.
|
|
|
112
142
|
|
|
113
143
|
## Keep it current
|
|
114
144
|
|
|
115
|
-
Ask `sync-
|
|
145
|
+
Ask `sync-course` to compare the project with connected upstream sources. It
|
|
116
146
|
reuses the saved course, semester, source IDs and reliable revisions. It retrieves
|
|
117
147
|
changed material and updates course knowledge. Incomplete listings never imply withdrawal. Withdrawn sources
|
|
118
148
|
keep their local originals; edited or deleted managed files are reported for review.
|
|
@@ -127,10 +157,9 @@ restores files when possible and reports any remaining conflict.
|
|
|
127
157
|
|
|
128
158
|
## Prepare the website
|
|
129
159
|
|
|
130
|
-
Ask `prepare-study` to create or extend `
|
|
160
|
+
Ask `prepare-study` to create or extend the Study workspace in `app/`. Units follow the course's
|
|
131
161
|
lecture/week sequence when useful, or use consistent topics. For a single course, use a flat list or one category level followed by topics,
|
|
132
|
-
including several topics in one week.
|
|
133
|
-
legacy hierarchies. Every unit has:
|
|
162
|
+
including several topics in one week. Every unit has:
|
|
134
163
|
|
|
135
164
|
- A concise explanation with source links, assumptions and worked examples.
|
|
136
165
|
- Editable LaTeX formulas and inline variables, diagrams, code or comparison tables where they explain the concept.
|
|
@@ -173,7 +202,7 @@ glossaries, cards, questions and feedback. Code and dollar signs stay literal.
|
|
|
173
202
|
|
|
174
203
|
Select text in Content, Flashcards or Practice, then click **Comment** to record what is unclear.
|
|
175
204
|
You can also use Ctrl/Command+Alt+M after selecting text. The local preview saves
|
|
176
|
-
notes in `.
|
|
205
|
+
notes in `.tum-mcp/comments.json`, separately from learning progress. Click a
|
|
177
206
|
numbered passage marker or highlighted text to open its notes beside the quote.
|
|
178
207
|
Markers sit beside section headings or the current card/question position.
|
|
179
208
|
Notes can be edited, resolved or reopened; changed passages retain their original quotes.
|
|
@@ -186,7 +215,7 @@ automatically while the preview is open. Your draft is kept if a note changes
|
|
|
186
215
|
elsewhere; choose Use latest or Keep mine before saving. Reload after the agent
|
|
187
216
|
rebuilds the teaching content.
|
|
188
217
|
|
|
189
|
-
Content lives in `
|
|
218
|
+
Content lives in `app/content.json`; shared renderer modules and CSS keep the
|
|
190
219
|
site extensible. Source hashes identify affected units. A stale source stops a
|
|
191
220
|
build until the explanation is reviewed. Unchanged units retain their revision;
|
|
192
221
|
revised questions receive separate progress. Customized templates are preserved
|
|
@@ -210,11 +239,44 @@ or an unattended preview, use `tum-mcp study serve ./pse-ws26 --port 8765`.
|
|
|
210
239
|
If port 8765 is busy, stop the other preview or choose `tum-mcp study --port 8766`.
|
|
211
240
|
Keep the same address and port between sessions so browser progress remains available.
|
|
212
241
|
|
|
213
|
-
The preview binds `127.0.0.1`, serves `study/` and saves comments through a bounded
|
|
242
|
+
The preview binds `127.0.0.1`, serves `app/` (or the existing legacy `study/`) and saves comments through a bounded
|
|
214
243
|
same-origin endpoint. It makes no campus requests.
|
|
215
244
|
No course credentials are copied into the website. Browser storage holds progress
|
|
216
245
|
locally. Nothing is published by these commands.
|
|
217
246
|
|
|
247
|
+
## Prepare weekly exercises
|
|
248
|
+
|
|
249
|
+
Ask `prepare-exercises` to prepare released weekly sheets in the same app. Study
|
|
250
|
+
and Exercises can be prepared in either order. When both exist, a selector at the bottom of the sidebar
|
|
251
|
+
switches workspaces and remembers the current place. On phones it lives inside the
|
|
252
|
+
open course outline; there is no second switch in the reading area. Exercises are
|
|
253
|
+
listed directly under sheets, optionally grouped by category.
|
|
254
|
+
|
|
255
|
+
The original statement and subparts come first. Students can solve on paper, mark
|
|
256
|
+
an exercise Reviewed, reveal a worked solution directly, or open **Guidance**.
|
|
257
|
+
Prepared guidance offers one conceptual step, requested hints and specific repair
|
|
258
|
+
feedback. Numeric checks state units/tolerance; choice checks have four options.
|
|
259
|
+
Fillable tables check numeric cells and explicit text variants individually. Graph
|
|
260
|
+
questions support node/edge selections and ordered paths with a visible diagram.
|
|
261
|
+
Line graphs allow sample-point placement, coordinate edits and keyboard adjustment.
|
|
262
|
+
Long math and programming work use saved writing areas and prepared rubrics; code
|
|
263
|
+
is never executed, and symbolic work, proofs and programs are not automatically
|
|
264
|
+
graded. Arbitrary graph drawing can use paper and rubric comparison. Typing, hints and step-by-step guidance are optional. Reviewed records an
|
|
265
|
+
action, not mastery or an assessment result.
|
|
266
|
+
|
|
267
|
+
Drafts and assistance stay in this browser. Editing a checked answer clears its
|
|
268
|
+
verdict. Semantic changes start fresh progress for only that exercise, while retaining older
|
|
269
|
+
browser records; source-hash review alone preserves progress.
|
|
270
|
+
Reset on each workspace home affects only that workspace. Exercise passage comments
|
|
271
|
+
use the existing private comment service; hosted exports do not expose it.
|
|
272
|
+
|
|
273
|
+
Existing `study/` projects continue to work. New projects use `app/`. To upgrade a
|
|
274
|
+
verified legacy layout explicitly, run `tum-mcp study build PROJECT --upgrade-layout`.
|
|
275
|
+
It keeps the original tree at `.tum-mcp/layout-backup/`, preserves the course identity,
|
|
276
|
+
learning revisions, comments, settings and personal files, and stops on customized
|
|
277
|
+
generated files, an occupied backup or two layouts. Repeating a completed upgrade
|
|
278
|
+
is a no-op. Do not create `app/` alongside an existing `study/` by hand.
|
|
279
|
+
|
|
218
280
|
## Create and maintain audio notes
|
|
219
281
|
|
|
220
282
|
Use `prepare-study` to author the interactive course, then ask `audio-notes` to
|
|
@@ -225,7 +287,7 @@ Create audio notes from this prepared study website, one file per category.
|
|
|
225
287
|
Keep course outputs under audio/; prefer free ElevenLabs with local Kokoro fallback.
|
|
226
288
|
```
|
|
227
289
|
|
|
228
|
-
Audio Notes reads `
|
|
290
|
+
Audio Notes reads `app/content.json`, retaining the course's language, examples,
|
|
229
291
|
assumptions and limitations. It turns tables, formulas, code and diagram sequences
|
|
230
292
|
into explanations that work without a screen. It does not independently research
|
|
231
293
|
the course or create new subject matter. An audio-only request reuses the prepared
|
|
@@ -287,29 +349,32 @@ material with the previous verified coverage. This is an agent skill, not a new
|
|
|
287
349
|
|
|
288
350
|
## Packaged app template
|
|
289
351
|
|
|
290
|
-
The
|
|
352
|
+
The shared web-app template is packaged in `bundled/course_app/site/`, separately from skill instructions.
|
|
291
353
|
It includes the shell, navigation, flip cards, practice, diagrams and styling.
|
|
292
|
-
Agents author course content in `
|
|
354
|
+
Agents author course content in `app/content.json`; `study build` installs the
|
|
293
355
|
shared template, preserves customized files and checks sources. This keeps new
|
|
294
356
|
course sites consistent without rebuilding the interface. `tum-mcp study template`
|
|
295
357
|
shows the bundled files and their combined revision for reproducible development.
|
|
296
358
|
|
|
297
|
-
## Try the
|
|
359
|
+
## Try the component template
|
|
298
360
|
|
|
299
361
|
After installing a locally built development artifact:
|
|
300
362
|
|
|
301
363
|
```sh
|
|
302
|
-
tum-mcp study demo ./
|
|
303
|
-
tum-mcp study serve ./
|
|
364
|
+
tum-mcp study demo ./component-preview
|
|
365
|
+
tum-mcp study serve ./component-preview
|
|
304
366
|
```
|
|
305
367
|
|
|
306
|
-
This creates
|
|
307
|
-
|
|
368
|
+
This creates one small Study unit and one exercise covering prose, code, tables,
|
|
369
|
+
formulas, graph/process diagrams, cards and response controls. It is a reusable
|
|
370
|
+
component template, not necessarily a good teaching or question-authoring example.
|
|
371
|
+
Actual course authoring follows reviewed sources and the preparation skills. Rich
|
|
372
|
+
learning pilots stay in private test fixtures.
|
|
308
373
|
It uses no campus account. An existing edited demo is preserved.
|
|
309
374
|
|
|
310
375
|
## Local helper inputs
|
|
311
376
|
|
|
312
|
-
Agents use `tum-mcp study schema snapshot` and `
|
|
377
|
+
Agents use `tum-mcp study schema snapshot`, `schema content` and `schema exercises` for
|
|
313
378
|
validated input schemas. After authenticated tools verify availability, the agent
|
|
314
379
|
stages a source snapshot with exact course identity, coverage and resource revisions.
|
|
315
380
|
The helper does not authenticate the snapshot or grant any campus access itself.
|
package/docs/tum-mcp.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# TUM MCP guide
|
|
2
2
|
|
|
3
3
|
Package: `@jamestesting888/tum-mcp`, CLI: `tum-mcp`, backend: Python 3.13.
|
|
4
|
-
|
|
4
|
+
Development version **0.6.1 (unreleased)** adds npm package management to the
|
|
5
|
+
read-only course retrieval and six local course workflows.
|
|
5
6
|
|
|
6
7
|
## Current capabilities
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
The sixteen `moodle_*` tools provide materials, announcements, forums and
|
|
10
|
+
explicitly scoped private-record reads. Artemis adds
|
|
10
11
|
available-course metadata, lectures and learning units, student PDF attachments,
|
|
11
12
|
released non-quiz statements and permitted exact-commit feedback. Zulip provides
|
|
12
13
|
scoped channel/topic search and cited discussion reads.
|
|
13
14
|
|
|
14
15
|
Website access is read-only. There are no public exercise workspace, test-run,
|
|
15
|
-
submission, start, deployment, rebuild or push workflows.
|
|
16
|
-
remote gate rejects every action even if an old verification flag is enabled.
|
|
16
|
+
submission, start, deployment, rebuild or push workflows.
|
|
17
17
|
Local downloads, source inventories, study-site builds and checkpoint storage
|
|
18
18
|
are local operations; they do not post, submit or mark campus content as read.
|
|
19
19
|
|
|
@@ -67,22 +67,19 @@ and Python 3.13.15; an existing matching uv, including the verified cached downl
|
|
|
67
67
|
is reused. Source checkouts and system
|
|
68
68
|
Python are unnecessary for consumers. Supported bootstrap targets are macOS
|
|
69
69
|
arm64/x64, Windows arm64/x64, and glibc Linux arm64/x64; hands-on verification is
|
|
70
|
-
recorded separately.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
and
|
|
77
|
-
the
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`update` prepares the backend bundled in the invoked package, refreshes
|
|
81
|
-
previously configured clients and preserves older runtime generations.
|
|
70
|
+
recorded separately. Campus sessions use permission-restricted local keys.
|
|
71
|
+
CI checks clean installation, updates, removal, MCP discovery and synthetic
|
|
72
|
+
browser workflows on macOS, Windows, Linux and both WSL engines. See the
|
|
73
|
+
[development guide](development.md#checks) for the verification layers.
|
|
74
|
+
|
|
75
|
+
`update` downloads and validates the latest release, replaces the invoked npm
|
|
76
|
+
installation and refreshes previously configured clients. `update --local`
|
|
77
|
+
refreshes the installed version without npm. Retained runtimes keep pinned
|
|
78
|
+
clients working, and newer installed versions are not silently downgraded.
|
|
82
79
|
[Update and removal behavior](setup.md#update-and-remove) covers ownership
|
|
83
|
-
receipts,
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
receipts, conflict handling and explicit cleanup. `uninstall` removes managed
|
|
81
|
+
clients and npm; `--client NAME` or `--keep-package` retains the package.
|
|
82
|
+
Customized files and saved data are preserved. If interrupted setup leaves `setup.lock`,
|
|
86
83
|
confirm that no setup process is running before removing that directory locally.
|
|
87
84
|
An invalid or incomplete runtime is never selected for serving. Failed validation
|
|
88
85
|
clears its ready marker so explicit setup can rebuild it; older runtimes remain
|
|
@@ -108,10 +105,9 @@ tum-mcp scope zulip ACCOUNT_ID --channel CHANNEL_ID
|
|
|
108
105
|
`connect` takes no arguments or connection options. Move with up/down, toggle
|
|
109
106
|
services with Space, and press Enter. Choose a browser when more than one is
|
|
110
107
|
installed. Only selected tabs open in one temporary browser context. Moodle,
|
|
111
|
-
Artemis and Zulip all follow this flow
|
|
108
|
+
Artemis and Zulip all follow this flow.
|
|
112
109
|
Sign-in/MFA stays in the browser. `TERM=dumb` uses numbered prompts. Ctrl+C
|
|
113
|
-
cancels and retains completed connections.
|
|
114
|
-
`--service`/`--auth` flags are rejected before any browser opens.
|
|
110
|
+
cancels and retains completed connections.
|
|
115
111
|
|
|
116
112
|
Detection covers Chrome, Edge, Brave, Chromium, Vivaldi, Opera and selected
|
|
117
113
|
preview channels. Set `TUM_MCP_BROWSER` to an executable path for another
|
|
@@ -121,41 +117,30 @@ Only service-owned cookies are saved; identity-provider cookies are discarded
|
|
|
121
117
|
when the temporary browser closes. Existing browser profiles are never imported.
|
|
122
118
|
|
|
123
119
|
Zulip uses its read-only `/json/` routes with the browser session; no API key is
|
|
124
|
-
required. Reconnect through the same selector when a session expires.
|
|
125
|
-
|
|
126
|
-
onboarding. See [Zulip's dispatcher](https://github.com/zulip/zulip/blob/12.0/zerver/lib/rest.py).
|
|
120
|
+
required. Reconnect through the same selector when a session expires.
|
|
121
|
+
See [Zulip's dispatcher](https://github.com/zulip/zulip/blob/12.0/zerver/lib/rest.py).
|
|
127
122
|
Existing scopes survive reconnection; new identities start with empty scopes.
|
|
128
123
|
Private Zulip channels require `--private-channel ID` as well as `--channel ID`.
|
|
129
124
|
`--sensitive` enables own Artemis participation/feedback reads. It grants no
|
|
130
125
|
website mutation or local execution permission.
|
|
131
126
|
|
|
132
127
|
Settings live outside the installed package in `TUMMCP/config/preferences.json`.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
campus connections deliberately do not read or migrate native-store secrets;
|
|
136
|
-
reconnect each service once to use local-file storage. The combined MCP process
|
|
137
|
-
uses that storage for all `moodle_*` compatibility tools. The separate Moodle-only
|
|
138
|
-
installer keeps its native-store default.
|
|
139
|
-
Historical Git credentials remain local data for legacy cleanup; the current
|
|
140
|
-
package provides no assignment Git workflow.
|
|
141
|
-
|
|
142
|
-
New Moodle sessions live under `TUMMoodle/local-session/`; campus credentials live
|
|
143
|
-
under `TUMMCP/credentials/local/`. Each directory has an independent `session.key`.
|
|
128
|
+
Moodle sessions live under `TUMMoodle/local-session/`; Artemis and Zulip credentials
|
|
129
|
+
live under `TUMMCP/credentials/local/`. Each directory has an independent `session.key`.
|
|
144
130
|
Encrypted records and keys have owner-only permissions: 0700 directories / 0600
|
|
145
131
|
keys on POSIX; protected current-user DACLs on Windows through PowerShell. No
|
|
146
|
-
administrator privileges are requested. This avoids
|
|
132
|
+
administrator privileges are requested. This avoids native credential-store
|
|
147
133
|
prompts but protects less than a native store. A copy of both ciphertext and key
|
|
148
134
|
is sufficient to decrypt a session. Do not share, sync publicly, or package them.
|
|
149
135
|
|
|
150
136
|
Missing or damaged keys fail closed. Restore the matching key from a private
|
|
151
137
|
backup. To start fresh, stop clients and move the affected **local storage folder**
|
|
152
|
-
to a private backup before reconnecting
|
|
153
|
-
|
|
154
|
-
Native-store records remain available to the older installation for rollback.
|
|
138
|
+
to a private backup before reconnecting. Keep downloaded course materials when
|
|
139
|
+
repairing a login.
|
|
155
140
|
|
|
156
141
|
Zulip message text is retrieved live and is not persisted. Retrieved excerpts may
|
|
157
|
-
be transmitted to the coding agent's configured AI provider. Moodle
|
|
158
|
-
|
|
142
|
+
be transmitted to the coding agent's configured AI provider. Moodle caches
|
|
143
|
+
documents and extracted text locally in plaintext.
|
|
159
144
|
|
|
160
145
|
Course nicknames can be mapped in `mappings` using explicit per-service IDs.
|
|
161
146
|
The tools resolve one exact mapping; ambiguity never grants broader access.
|
|
@@ -177,10 +162,11 @@ in JSON for troubleshooting. Default diagnostics do not create state folders.
|
|
|
177
162
|
|
|
178
163
|
## Study workflows
|
|
179
164
|
|
|
180
|
-
`read-content` retrieves course information across the three websites. `sync-
|
|
165
|
+
`read-content` retrieves course information across the three websites. `sync-course`
|
|
181
166
|
uses read-only local status to initialize an explicitly chosen available course or
|
|
182
167
|
reconcile an existing project. Incomplete state is preserved for recovery.
|
|
183
|
-
`prepare-study`
|
|
168
|
+
`prepare-study` authors lessons; `prepare-exercises` authors guided released sheets
|
|
169
|
+
in the same local app. See the
|
|
184
170
|
[study guide](study.md) for examples, state structure and helper commands.
|
|
185
171
|
|
|
186
172
|
One project holds one course. Source revisions, coverage and course knowledge
|
|
@@ -191,21 +177,19 @@ progress are preserved during package updates/removal.
|
|
|
191
177
|
## Client and skill setup
|
|
192
178
|
|
|
193
179
|
`setup --client codex` prepares a local plugin/marketplace entry using the
|
|
194
|
-
portable Agent Plugins schemas. Enable it in
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
The old installation remains available for rollback. Setup reports required UI
|
|
198
|
-
steps and never equates writing files with activation.
|
|
180
|
+
portable Agent Plugins schemas. Enable it in Codex Plugins, restart, and verify
|
|
181
|
+
real tool and skill loading. Setup reports required UI steps and never equates
|
|
182
|
+
writing files with activation.
|
|
199
183
|
|
|
200
184
|
The Codex adapter uses a contained Node launcher to pin the managed Python in
|
|
201
185
|
isolated mode; portable stdio manifests require a bare or contained executable.
|
|
202
|
-
The shipped pack contains
|
|
203
|
-
`prepare-study`, `audio-notes` and `publish-
|
|
186
|
+
The shipped pack contains six skills: `read-content`, `sync-course`,
|
|
187
|
+
`prepare-study`, `prepare-exercises`, `audio-notes` and `publish-course`. Website-specific retrieval
|
|
204
188
|
guidance is loaded from references within read-content. Teaching, schema and design contracts live
|
|
205
189
|
with prepare-study; the renderer and synthetic example ship as reusable assets.
|
|
206
190
|
Audio Notes converts prepared content into category tracks and maintains coverage,
|
|
207
191
|
scripts, voice settings, candidates and archives under the course's `audio/` root.
|
|
208
|
-
Publish
|
|
192
|
+
Publish Course uses host GitHub CLI authentication to create a private repository
|
|
209
193
|
and a public Pages website after explicit publication consent. It chooses a course
|
|
210
194
|
and semester name, updates the same site after successful study builds, and shows
|
|
211
195
|
the hosted URL alongside the local launcher URL. No tokens or original course
|
|
@@ -220,7 +204,7 @@ routing quality is a separate evaluation from structural and discovery checks.
|
|
|
220
204
|
Claude's invocation metadata follows each workflow's selected policy; automatic
|
|
221
205
|
selection is enabled by default and never grants permission for a mutation.
|
|
222
206
|
|
|
223
|
-
`setup --client cursor` writes `~/.cursor/mcp.json` and installs all
|
|
207
|
+
`setup --client cursor` writes `~/.cursor/mcp.json` and installs all six skills
|
|
224
208
|
under `~/.cursor/skills/`.
|
|
225
209
|
`setup --client antigravity` installs the same skills under `~/.gemini/config/skills/`
|
|
226
210
|
and writes `~/.gemini/config/mcp_config.json`; an existing
|
|
@@ -291,5 +275,5 @@ it is described as verified. Test data and public fixtures are synthetic. Real
|
|
|
291
275
|
course material, local agent notes and screenshots stay under ignored local paths.
|
|
292
276
|
Maintainer checks include a reviewed tarball inventory, integrity checks and an
|
|
293
277
|
isolated consumer install. Never publish or stage npm packages as a test. Registry
|
|
294
|
-
publication
|
|
278
|
+
publication is an explicit maintainer action.
|
|
295
279
|
`tum-mcp status` reports registry state as `not_checked`; it reads local configuration.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"wheel": "tum_mcp-0.
|
|
3
|
-
"wheelSha256": "
|
|
2
|
+
"wheel": "tum_mcp-0.7.0-py3-none-any.whl",
|
|
3
|
+
"wheelSha256": "dabe306ea1695fa03fa119b2673cc93c0ee705b34ebabc6674958fe77f5757a6",
|
|
4
4
|
"requirementsSha256": "25f9efa7a4ffb0b97abcfeb9d4706d04808273ab1b35abcfaa1ef0b66e87591b"
|
|
5
5
|
}
|
|
Binary file
|
package/npm/lock.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { mkdir, rename, rm, rmdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { hostname } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export async function acquireLock(root, name = 'setup') {
|
|
8
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
9
|
+
const lock = join(root, `${name}.lock`);
|
|
10
|
+
const owner = { pid: process.pid, host: hostname(), nonce: randomUUID() };
|
|
11
|
+
const guard = join(root, `${name}.claim`);
|
|
12
|
+
try { await mkdir(guard, { mode: 0o700 }); }
|
|
13
|
+
catch { throw new Error(`Another ${name} is claiming the lock. Retry; an interrupted ${name}.claim needs local review.`); }
|
|
14
|
+
try {
|
|
15
|
+
try { await mkdir(lock, { mode: 0o700 }); }
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (error.code !== 'EEXIST') throw error;
|
|
18
|
+
let previous;
|
|
19
|
+
try { previous = JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8')); }
|
|
20
|
+
catch { throw new Error(`${name} lock has no readable owner. Confirm no operation is running before removing ${name}.lock.`); }
|
|
21
|
+
if (!Number.isSafeInteger(previous.pid) || previous.pid <= 0 || previous.host !== hostname()) {
|
|
22
|
+
throw new Error(`${name} lock belongs to an unknown owner; preserve it for local review.`);
|
|
23
|
+
}
|
|
24
|
+
let alive = true;
|
|
25
|
+
try { process.kill(previous.pid, 0); }
|
|
26
|
+
catch (failure) { if (failure.code === 'ESRCH') alive = false; }
|
|
27
|
+
if (alive) throw new Error(`Another ${name} is running; retry when it finishes.`);
|
|
28
|
+
const abandoned = `${lock}.${randomUUID()}.abandoned`;
|
|
29
|
+
await rename(lock, abandoned);
|
|
30
|
+
await rm(abandoned, { recursive: true, force: true });
|
|
31
|
+
try { await mkdir(lock, { mode: 0o700 }); }
|
|
32
|
+
catch { throw new Error(`Another ${name} acquired the lock; retry when it finishes.`); }
|
|
33
|
+
}
|
|
34
|
+
const temporary = join(lock, `${owner.nonce}.pending`);
|
|
35
|
+
await writeFile(temporary, JSON.stringify(owner), { mode: 0o600, flag: 'wx' });
|
|
36
|
+
await rename(temporary, join(lock, 'owner.json'));
|
|
37
|
+
} finally { await rmdir(guard); }
|
|
38
|
+
return async () => {
|
|
39
|
+
const current = JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8'));
|
|
40
|
+
if (current.nonce !== owner.nonce) throw new Error(`${name} lock ownership changed; it was preserved.`);
|
|
41
|
+
await rm(lock, { recursive: true, force: true });
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { delimiter, dirname, join, basename, resolve, relative, isAbsolute, sep } from 'node:path';
|
|
5
|
+
|
|
6
|
+
export const packageName = '@jamestesting888/tum-mcp';
|
|
7
|
+
|
|
8
|
+
function canonical(value) {
|
|
9
|
+
let path = resolve(value.replace(/^~(?=$|[/\\])/, homedir()));
|
|
10
|
+
const tail = [];
|
|
11
|
+
while (!existsSync(path)) { tail.unshift(basename(path)); path = dirname(path); }
|
|
12
|
+
return join(realpathSync(path), ...tail);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function packageStorage(root, dataDirectory) {
|
|
16
|
+
const home = homedir();
|
|
17
|
+
const base = process.platform === 'darwin' ? join(home, 'Library/Application Support')
|
|
18
|
+
: process.platform === 'win32' ? process.env.LOCALAPPDATA || join(home, 'AppData/Local')
|
|
19
|
+
: process.env.XDG_DATA_HOME || join(home, '.local/share');
|
|
20
|
+
const storage = {
|
|
21
|
+
TUM_MCP_DATA_DIR: canonical(dataDirectory),
|
|
22
|
+
TUM_MCP_CONFIG_DIR: canonical(process.env.TUM_MCP_CONFIG_DIR || join(dataDirectory, 'config')),
|
|
23
|
+
TUM_MOODLE_DATA_DIR: canonical(process.env.TUM_MOODLE_DATA_DIR || join(base, 'TUMMoodle')),
|
|
24
|
+
TUM_MOODLE_DOWNLOAD_DIR: canonical(process.env.TUM_MOODLE_DOWNLOAD_DIR || join(home, 'Downloads/TUM Moodle')),
|
|
25
|
+
};
|
|
26
|
+
const config = storage.TUM_MCP_CONFIG_DIR;
|
|
27
|
+
const settings = join(config, 'preferences.json');
|
|
28
|
+
let workspace = join(home, 'University');
|
|
29
|
+
if (existsSync(settings)) {
|
|
30
|
+
try {
|
|
31
|
+
const prefs = JSON.parse(readFileSync(settings, 'utf8'));
|
|
32
|
+
if (prefs.workspace_root !== undefined) {
|
|
33
|
+
if (typeof prefs.workspace_root !== 'string') throw new Error('Invalid storage path');
|
|
34
|
+
workspace = prefs.workspace_root;
|
|
35
|
+
}
|
|
36
|
+
} catch { throw new Error('Storage settings could not be verified; package files and saved data were preserved.'); }
|
|
37
|
+
}
|
|
38
|
+
for (const path of [...Object.values(storage), workspace]) {
|
|
39
|
+
const inside = relative(root, canonical(path));
|
|
40
|
+
if (inside === '' || (inside.split(sep)[0] !== '..' && !isAbsolute(inside))) {
|
|
41
|
+
throw new Error('Package installation overlaps saved data or course work. Move that data outside the package before update/removal.');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return storage;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function npmCli() {
|
|
48
|
+
const paths = (process.env.PATH || '').split(delimiter);
|
|
49
|
+
const candidates = [process.env.npm_execpath,
|
|
50
|
+
join(dirname(process.execPath), 'node_modules/npm/bin/npm-cli.js'),
|
|
51
|
+
join(dirname(dirname(process.execPath)), 'lib/node_modules/npm/bin/npm-cli.js'),
|
|
52
|
+
...paths.flatMap(path => [join(path, 'npm'), join(path, 'node_modules/npm/bin/npm-cli.js')])];
|
|
53
|
+
for (const path of candidates) {
|
|
54
|
+
if (!path || !existsSync(path)) continue;
|
|
55
|
+
const resolved = realpathSync(path);
|
|
56
|
+
if (basename(resolved) !== 'npm-cli.js') continue;
|
|
57
|
+
const manifest = join(dirname(dirname(resolved)), 'package.json');
|
|
58
|
+
if (existsSync(manifest) && JSON.parse(readFileSync(manifest, 'utf8')).name === 'npm') return resolved;
|
|
59
|
+
}
|
|
60
|
+
throw new Error('npm could not be located beside Node or on PATH. Install Node.js with npm, then retry.');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function runNpm(cli, args, capture = false) {
|
|
64
|
+
const result = spawnSync(process.execPath, [cli, ...args, '--ignore-scripts', '--no-audit', '--no-fund', '--no-update-notifier'], {
|
|
65
|
+
shell: false, stdio: capture ? ['ignore', 'pipe', 'inherit'] : 'inherit', encoding: 'utf8', timeout: 600000,
|
|
66
|
+
});
|
|
67
|
+
if (result.error || result.status !== 0) throw new Error('npm operation failed. Saved data and retained client runtimes were preserved; resolve the npm error and retry.');
|
|
68
|
+
return capture ? result.stdout.trim() : '';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function installationTarget(root) {
|
|
72
|
+
const modules = dirname(dirname(root));
|
|
73
|
+
if (basename(modules) !== 'node_modules' || modules.split(/[\\/]/).some(part => ['.pnpm', '_npx'].includes(part))) {
|
|
74
|
+
throw new Error('Package management requires an npm installation. Use update --local for a source/pinned build.');
|
|
75
|
+
}
|
|
76
|
+
if (`${basename(dirname(root))}/${basename(root)}` !== packageName) {
|
|
77
|
+
throw new Error('Automatic package management requires the original package name; aliases were preserved.');
|
|
78
|
+
}
|
|
79
|
+
const parent = dirname(modules);
|
|
80
|
+
const prefix = process.platform === 'win32' ? parent : dirname(parent);
|
|
81
|
+
const shim = process.platform === 'win32' ? join(prefix, 'tum-mcp.cmd') : join(prefix, 'bin/tum-mcp');
|
|
82
|
+
if (existsSync(shim)) {
|
|
83
|
+
const owned = process.platform === 'win32'
|
|
84
|
+
? readFileSync(shim, 'utf8').replaceAll('\\', '/').includes('node_modules/@jamestesting888/tum-mcp/npm/tum-mcp.mjs')
|
|
85
|
+
: realpathSync(shim) === join(root, 'npm/tum-mcp.mjs');
|
|
86
|
+
if (owned && (process.platform === 'win32' || basename(parent) === 'lib')) return { prefix, global: true };
|
|
87
|
+
}
|
|
88
|
+
const manifest = join(parent, 'package.json');
|
|
89
|
+
if (existsSync(manifest)) {
|
|
90
|
+
const project = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
91
|
+
if (['dependencies', 'devDependencies', 'optionalDependencies'].some(key => project[key]?.[packageName])) {
|
|
92
|
+
return { prefix: parent, global: false };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
throw new Error('The npm installation prefix could not be verified; package files were preserved.');
|
|
96
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { copyFile, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { basename, dirname, isAbsolute, join, relative, sep } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { acquireLock } from './lock.mjs';
|
|
8
|
+
import { installationTarget, npmCli, packageName, packageStorage, runNpm } from './npm-command.mjs';
|
|
9
|
+
|
|
10
|
+
function runLauncher(root, args, quiet = false) {
|
|
11
|
+
const result = spawnSync(process.execPath, [join(root, 'npm/tum-mcp.mjs'), ...args], {
|
|
12
|
+
stdio: quiet ? ['ignore', 'pipe', 'pipe'] : 'inherit', encoding: 'utf8', shell: false, timeout: 600000,
|
|
13
|
+
});
|
|
14
|
+
if (result.error || result.status !== 0) {
|
|
15
|
+
if (quiet) process.stderr.write((result.stdout || '') + (result.stderr || ''));
|
|
16
|
+
throw new Error(`${args[0]} did not complete. Saved data and customized client files were preserved; review the details and retry.`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function versionParts(version) {
|
|
21
|
+
const match = typeof version === 'string' && version.match(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*))?(?:\+[\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*)?$/);
|
|
22
|
+
if (!match) throw new Error('npm returned an invalid package version.');
|
|
23
|
+
const prerelease = match[4]?.split('.') || [];
|
|
24
|
+
if (prerelease.some(part => /^0\d+$/.test(part))) throw new Error('npm returned an invalid prerelease version.');
|
|
25
|
+
return { core: match.slice(1, 4).map(BigInt), prerelease };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function newerVersion(latest, current) {
|
|
29
|
+
const left = versionParts(latest), right = versionParts(current);
|
|
30
|
+
for (let i = 0; i < left.core.length; i++) if (left.core[i] !== right.core[i]) return left.core[i] > right.core[i];
|
|
31
|
+
const a = left.prerelease, b = right.prerelease;
|
|
32
|
+
if (!a.length || !b.length) return !a.length && Boolean(b.length);
|
|
33
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
34
|
+
if (a[i] === undefined) return false;
|
|
35
|
+
if (b[i] === undefined) return true;
|
|
36
|
+
if (a[i] === b[i]) continue;
|
|
37
|
+
const numericA = /^\d+$/.test(a[i]), numericB = /^\d+$/.test(b[i]);
|
|
38
|
+
if (numericA && numericB) return BigInt(a[i]) > BigInt(b[i]);
|
|
39
|
+
if (numericA !== numericB) return numericB;
|
|
40
|
+
return a[i] > b[i];
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function operate(job) {
|
|
46
|
+
const release = await acquireLock(job.target.prefix, 'tum-mcp-package');
|
|
47
|
+
try {
|
|
48
|
+
const scope = ['--prefix', job.target.prefix, `--global=${job.target.global}`];
|
|
49
|
+
if (job.action === 'uninstall') {
|
|
50
|
+
const configured = existsSync(join(job.dataDirectory, 'runtime.json')) ||
|
|
51
|
+
existsSync(join(job.configDirectory, 'clients'));
|
|
52
|
+
if (configured || job.args.some(arg => ['--purge-runtime', '--forget-credentials'].includes(arg))) {
|
|
53
|
+
runLauncher(job.root, ['uninstall', '--keep-package', ...job.args]);
|
|
54
|
+
}
|
|
55
|
+
runNpm(job.npm, ['uninstall', ...scope, packageName]);
|
|
56
|
+
console.log('TUM MCP package and managed client setup removed. Saved user data was retained unless explicitly requested.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const current = JSON.parse(readFileSync(join(job.root, 'package.json'), 'utf8')).version;
|
|
60
|
+
const latest = JSON.parse(runNpm(job.npm, ['view', `${packageName}@latest`, 'version', '--json', ...scope], true));
|
|
61
|
+
if (!newerVersion(latest, current)) {
|
|
62
|
+
console.log(`Installed ${current}; no newer npm release. Refreshing configured clients.`);
|
|
63
|
+
runLauncher(job.root, ['update', '--local']);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log(`Preparing TUM MCP ${latest}.`);
|
|
67
|
+
const packed = JSON.parse(runNpm(job.npm, ['pack', `${packageName}@${latest}`, '--pack-destination', job.temporary, '--json', ...scope], true));
|
|
68
|
+
if (!Array.isArray(packed) || packed.length !== 1 || packed[0].name !== packageName || packed[0].version !== latest || basename(packed[0].filename) !== packed[0].filename) {
|
|
69
|
+
throw new Error('npm package metadata did not match the selected release.');
|
|
70
|
+
}
|
|
71
|
+
const archive = join(job.temporary, packed[0].filename);
|
|
72
|
+
const staging = join(job.temporary, 'prepared');
|
|
73
|
+
runNpm(job.npm, ['install', '--prefix', staging, '--global=false', archive], true);
|
|
74
|
+
const prepared = join(staging, 'node_modules', packageName);
|
|
75
|
+
runLauncher(prepared, ['setup'], true);
|
|
76
|
+
// Only a healthy prepared release may replace the user's npm installation.
|
|
77
|
+
runNpm(job.npm, ['install', ...scope, job.target.global ? archive : `${packageName}@${latest}`]);
|
|
78
|
+
if (JSON.parse(readFileSync(join(job.root, 'package.json'), 'utf8')).version !== latest) throw new Error('npm did not install the selected version; client activation was stopped.');
|
|
79
|
+
runLauncher(job.root, ['update', '--local']);
|
|
80
|
+
console.log(`Updated TUM MCP to ${latest}. Restart clients to load refreshed skills.`);
|
|
81
|
+
} finally { await release(); }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function managePackage(action, args, dataDirectory) {
|
|
85
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
86
|
+
const inside = relative(root, process.cwd());
|
|
87
|
+
if (inside === '' || (inside.split(sep)[0] !== '..' && !isAbsolute(inside))) throw new Error('Run package update/removal from outside the installed package directory.');
|
|
88
|
+
const target = installationTarget(root);
|
|
89
|
+
const storage = packageStorage(root, dataDirectory);
|
|
90
|
+
const configDirectory = storage.TUM_MCP_CONFIG_DIR;
|
|
91
|
+
const npm = npmCli();
|
|
92
|
+
const temporary = await mkdtemp(join(tmpdir(), 'tum-mcp-package-'));
|
|
93
|
+
try {
|
|
94
|
+
for (const name of ['package-manager.mjs', 'npm-command.mjs', 'lock.mjs']) await copyFile(join(root, 'npm', name), join(temporary, name));
|
|
95
|
+
const job = { action, args, root, dataDirectory, configDirectory, target, npm, temporary };
|
|
96
|
+
const path = join(temporary, 'job.json');
|
|
97
|
+
await writeFile(path, JSON.stringify(job), { mode: 0o600, flag: 'wx' });
|
|
98
|
+
// This worker and its cwd survive Windows replacing/removing the package files.
|
|
99
|
+
const result = spawnSync(process.execPath, [join(temporary, 'package-manager.mjs'), '--job', path], {
|
|
100
|
+
cwd: temporary, stdio: 'inherit', shell: false, env: { ...process.env, ...storage },
|
|
101
|
+
});
|
|
102
|
+
return result.status ?? (result.signal === 'SIGINT' ? 130 : 2);
|
|
103
|
+
} finally { await rm(temporary, { recursive: true, force: true }); }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (process.argv[1] && process.argv[2] === '--job' && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
107
|
+
try { await operate(JSON.parse(readFileSync(process.argv[3], 'utf8'))); }
|
|
108
|
+
catch (error) { console.error(error.message); process.exitCode = 2; }
|
|
109
|
+
}
|
package/npm/runtime.mjs
CHANGED
|
@@ -2,10 +2,11 @@ import { spawnSync } from 'node:child_process';
|
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
4
4
|
import { chmod, mkdir, mkdtemp, rename, rm, rmdir, writeFile } from 'node:fs/promises';
|
|
5
|
-
import { homedir
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
6
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { download } from './download.mjs';
|
|
9
|
+
import { acquireLock } from './lock.mjs';
|
|
9
10
|
|
|
10
11
|
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
11
12
|
const pins = JSON.parse(readFileSync(join(packageRoot, 'npm/uv-artifacts.json'), 'utf8'));
|
|
@@ -109,40 +110,7 @@ async function uvExecutable() {
|
|
|
109
110
|
return cached;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
export
|
|
113
|
-
const lock = join(dataRoot(), 'setup.lock');
|
|
114
|
-
const owner = { pid: process.pid, host: hostname(), nonce: randomUUID() };
|
|
115
|
-
const guard = join(dataRoot(), 'setup.claim');
|
|
116
|
-
try { await mkdir(guard, { mode: 0o700 }); }
|
|
117
|
-
catch { throw new Error('Another setup is claiming the lock. Retry; an interrupted setup.claim needs local review.'); }
|
|
118
|
-
try {
|
|
119
|
-
try { await mkdir(lock, { mode: 0o700 }); }
|
|
120
|
-
catch (error) {
|
|
121
|
-
if (error.code !== 'EEXIST') throw error;
|
|
122
|
-
let previous;
|
|
123
|
-
try { previous = JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8')); }
|
|
124
|
-
catch { throw new Error('Setup lock has no readable owner. Confirm no setup is running before removing setup.lock.'); }
|
|
125
|
-
if (!Number.isSafeInteger(previous.pid) || previous.pid <= 0 || previous.host !== hostname()) {
|
|
126
|
-
throw new Error('Setup lock belongs to an unknown owner; preserve it for local review.');
|
|
127
|
-
}
|
|
128
|
-
let alive = true;
|
|
129
|
-
try { process.kill(previous.pid, 0); }
|
|
130
|
-
catch (failure) { if (failure.code === 'ESRCH') alive = false; }
|
|
131
|
-
if (alive) throw new Error('Another setup is running; retry when it finishes.');
|
|
132
|
-
const abandoned = `${lock}.${randomUUID()}.abandoned`;
|
|
133
|
-
await rename(lock, abandoned);
|
|
134
|
-
await rm(abandoned, { recursive: true, force: true });
|
|
135
|
-
try { await mkdir(lock, { mode: 0o700 }); }
|
|
136
|
-
catch { throw new Error('Another setup acquired the lock; retry when it finishes.'); }
|
|
137
|
-
}
|
|
138
|
-
await atomicWrite(join(lock, 'owner.json'), JSON.stringify(owner));
|
|
139
|
-
} finally { await rmdir(guard); }
|
|
140
|
-
return async () => {
|
|
141
|
-
const current = JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8'));
|
|
142
|
-
if (current.nonce !== owner.nonce) throw new Error('Setup lock ownership changed; it was preserved.');
|
|
143
|
-
await rm(lock, { recursive: true, force: true });
|
|
144
|
-
};
|
|
145
|
-
}
|
|
113
|
+
export function acquireSetupLock() { return acquireLock(dataRoot()); }
|
|
146
114
|
|
|
147
115
|
export async function ensureRuntime() {
|
|
148
116
|
const backend = join(packageRoot, 'npm/backend');
|
package/npm/tum-mcp.mjs
CHANGED
|
@@ -1,33 +1,58 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
|
-
import {
|
|
4
|
+
import { parseArgs } from 'node:util';
|
|
5
|
+
import { acquireSetupLock, currentPython, dataRoot, ensureRuntime, removeRuntimeFiles } from './runtime.mjs';
|
|
6
|
+
import { managePackage } from './package-manager.mjs';
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
let args = process.argv.slice(2);
|
|
7
9
|
const version = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
8
10
|
try {
|
|
9
11
|
if (args.length === 0 || (args.length === 1 && ['--help', '-h'].includes(args[0]))) {
|
|
10
12
|
console.log(`TUM MCP ${version}\nCommands: setup [--client codex|claude|cursor|antigravity|generic], connect, scope, discover, status, doctor [--json] [--live], serve, skills, study, logout, uninstall, update\nRun connect without arguments to select services and an installed Chromium browser. Run setup explicitly to provision the managed Python backend.`);
|
|
11
13
|
} else if (['--version', 'version'].includes(args[0])) {
|
|
12
14
|
console.log(version);
|
|
13
|
-
} else if (['setup', 'update'].includes(args[0]) && args.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
: 'Usage: tum-mcp setup [--client codex|claude|cursor|antigravity|generic] [--host local|windows]\nProvision the managed runtime and register explicitly selected clients.'
|
|
15
|
+
} else if (['setup', 'update', 'uninstall'].includes(args[0]) && args.some(arg => ['--help', '-h'].includes(arg))) {
|
|
16
|
+
const help = {
|
|
17
|
+
update: 'Usage: tum-mcp update [--local]\nInstall the latest npm release and refresh configured clients.\n--local refreshes this installed version without contacting npm.',
|
|
18
|
+
setup: 'Usage: tum-mcp setup [--client codex|claude|cursor|antigravity|generic] [--host local|windows]\nProvision the managed runtime and register explicitly selected clients.',
|
|
19
|
+
uninstall: 'Usage: tum-mcp uninstall [--all] [--purge-runtime] [--forget-credentials]\nRemove managed client setup and this npm installation. Saved data is retained by default.\n--client NAME removes one client only. --keep-package keeps npm installed.',
|
|
20
|
+
};
|
|
21
|
+
console.log(help[args[0]]);
|
|
17
22
|
} else {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
|
|
23
|
+
let packageAction = args[0] === 'update';
|
|
24
|
+
if (args[0] === 'update') {
|
|
25
|
+
if (args.length > 2 || (args.length === 2 && args[1] !== '--local')) throw new Error('Usage: tum-mcp update [--local]. Use --help for details.');
|
|
26
|
+
if (args[1] === '--local') { packageAction = false; args = ['update']; }
|
|
27
|
+
}
|
|
28
|
+
if (args[0] === 'uninstall') {
|
|
29
|
+
const { values } = parseArgs({ args: args.slice(1), options: {
|
|
30
|
+
all: { type: 'boolean' }, client: { type: 'string' }, host: { type: 'string' },
|
|
31
|
+
'keep-package': { type: 'boolean' }, 'purge-runtime': { type: 'boolean' }, 'forget-credentials': { type: 'boolean' },
|
|
32
|
+
} });
|
|
33
|
+
if (values.host && !['local', 'windows'].includes(values.host)) throw new Error('Choose --host local or windows.');
|
|
34
|
+
if (values.client !== undefined && !['codex', 'claude', 'cursor', 'antigravity', 'generic'].includes(values.client)) throw new Error('Choose a supported --client name.');
|
|
35
|
+
if (values.client && (values.all || values['purge-runtime'] || values['forget-credentials'])) throw new Error('Client-only removal cannot use --all or data/runtime cleanup options.');
|
|
36
|
+
if (!values.client && !values.all) args.push('--all');
|
|
37
|
+
packageAction = !values.client && !values['keep-package'];
|
|
38
|
+
args = args.filter(arg => arg !== '--keep-package');
|
|
39
|
+
}
|
|
40
|
+
if (packageAction) {
|
|
41
|
+
process.exitCode = await managePackage(args[0], args.slice(1), dataRoot());
|
|
42
|
+
} else {
|
|
43
|
+
let python = ['setup', 'update'].includes(args[0]) ? await ensureRuntime() : currentPython();
|
|
44
|
+
if (args[0] === 'uninstall' && !python) python = await ensureRuntime();
|
|
45
|
+
if (!python) throw new Error('SETUP_REQUIRED: Run tum-mcp setup first.');
|
|
46
|
+
const forwarded = args;
|
|
47
|
+
const env = { ...process.env, TUM_MCP_NPM_LAUNCHER: '1' }; delete env.PYTHONPATH; delete env.PYTHONHOME;
|
|
48
|
+
const purge = args[0] === 'uninstall' && args.includes('--purge-runtime');
|
|
49
|
+
const release = purge ? await acquireSetupLock() : null;
|
|
50
|
+
try {
|
|
51
|
+
const child = spawnSync(python, ['-I', '-m', 'tum_mcp', ...forwarded], { env, stdio: 'inherit', shell: false });
|
|
52
|
+
if (child.error) throw new Error('Managed backend could not start; run setup again.');
|
|
53
|
+
process.exitCode = child.status ?? (child.signal === 'SIGINT' ? 130 : 1);
|
|
54
|
+
if (purge && child.status === 0) await removeRuntimeFiles();
|
|
55
|
+
} finally { if (release) await release(); }
|
|
56
|
+
}
|
|
32
57
|
}
|
|
33
58
|
} catch (error) { console.error(error.message); process.exitCode = 2; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jamestesting888/tum-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Read-only TUM Moodle, Artemis and Zulip integration with source-backed study workflows",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"npm/tum-mcp.mjs",
|
|
15
15
|
"npm/runtime.mjs",
|
|
16
16
|
"npm/download.mjs",
|
|
17
|
+
"npm/lock.mjs",
|
|
18
|
+
"npm/npm-command.mjs",
|
|
19
|
+
"npm/package-manager.mjs",
|
|
17
20
|
"npm/uv-artifacts.json",
|
|
18
21
|
"npm/backend/*.whl",
|
|
19
22
|
"npm/backend/requirements.txt",
|
|
@@ -31,7 +34,8 @@
|
|
|
31
34
|
"prepack": "node npm/build.mjs",
|
|
32
35
|
"test": "node --test npm/test/*.test.mjs",
|
|
33
36
|
"test:consumer": "node npm/smoke-consumer.mjs",
|
|
34
|
-
"test:upgrade": "node npm/upgrade-consumer.mjs"
|
|
37
|
+
"test:upgrade": "node npm/upgrade-consumer.mjs",
|
|
38
|
+
"test:package-lifecycle": "node npm/package-consumer.mjs"
|
|
35
39
|
},
|
|
36
40
|
"publishConfig": {
|
|
37
41
|
"access": "public"
|