MCP server + CLI (gdn) that lets any agent harness submit a NUST LMS lab by harvesting another account's submission and re-skinning it under the user's identity.
Project description
grabdeeznust
gdn for short. MCP server + CLI that lets any agent harness submit a NUST LMS lab by harvesting a friend's already-submitted work and re-skinning it under the user's own identity.
The "agent" is whatever LLM is driving the harness — Claude Code, OpenCode, Cursor, Cline, Continue, Zed, the Anthropic SDK, etc. grabdeeznust only provides the deterministic plumbing: log into the LMS, download submitted files, re-render a styled report.docx, log back in as the user and upload it.
Two LMS accounts are involved:
- Account 1 — the user's own account; submissions are uploaded here
- Account 2 — a friend's account; their already-submitted files are harvested here
NUST LMS assignment URLs are the same on both accounts, so the user supplies the URL once and the tool reuses it.
Install
pipx install grabdeeznust
gdn setup
gdn setup walks you through a one-time onboarding:
- Prompts for both LMS account credentials (passwords are hidden).
- Prompts for the identity values (
IDENTITY_NAME,IDENTITY_ROLL,IDENTITY_TERMINAL) that get stamped into every report. - Saves it all to
~/.gdn/.envwith mode 0600. - Downloads Playwright's Chromium (~150 MB, one-time).
- Prints the MCP config block to paste into your agent harness.
Run gdn doctor any time to check that everything's still in place.
From source (dev)
git clone https://github.com/ZaynIkhlaq/grabdeeznust
cd grabdeeznust
uv sync
uv run gdn setup
A .env in the repo root takes precedence over ~/.gdn/.env, so dev work doesn't pollute your real config.
Use it as an MCP server (recommended)
Add one block to your harness's MCP config.
Claude Code (~/.claude/mcp.json or per-project .mcp.json):
{
"mcpServers": {
"gdn": {
"command": "gdn",
"args": ["mcp"]
}
}
}
Or via the CLI: claude mcp add gdn -- gdn mcp.
OpenCode, Cursor, Cline, Continue, Zed, Claude Desktop — same shape, in their respective MCP settings file. Any MCP-aware client works.
The agent now has these tools:
| Tool | Purpose |
|---|---|
gdn_harvest(url) |
Login as account 2, download every submitted file at URL |
gdn_extract(source_path) |
Dump docx/pdf/zip/code as plain text |
gdn_render(plan, out_path) |
Render plan dict → styled docx (auto-verified) |
gdn_verify(docx_path) |
Identity check on an existing docx |
gdn_submit(url, files) |
Login as account 1, remove old draft, upload files |
And these resources for the agent to fetch context:
gdn://briefing— full operator briefing (CLAUDE.md content)gdn://schema— plan dict schema + tone rules (prompts/system.md)
Use it as a plain CLI
If you'd rather drive each step manually:
gdn harvest <URL> # account 2 downloads files
gdn extract <source-file> # source → plain text
# write work/<slug>/plan.json (schema in prompts/system.md)
gdn render plan.json --out report.docx # plan → styled docx + verify
gdn submit <URL> <file1> <file2> ... # account 1 uploads
Browser is visible by default so you can watch. Pass --headless before the subcommand to hide it.
How the report is generated
The browser side is fully deterministic. The "thinking" step — read the source, write a plan that re-skins the report under the user's identity in a student-tone voice — is performed by the LLM driving the harness, not by any baked-in API call. That keeps grabdeeznust provider-neutral: no Anthropic key, no OpenAI key, nothing to configure beyond the LMS creds and the identity values.
The plan is just a JSON dict matching the schema in prompts/system.md. The renderer is pure Python (python-docx) and styles the docx to match the format Claude.ai's web app produces:
- Arial 11pt body
- Courier New 9pt code blocks on dark
#1E1E2E(the "fake terminal" look) - Courier New 9pt output blocks on light green
#F0FFF0 - Section headings (Introduction / Task N / Conclusion) bold 17pt with bottom rule
- Italic captions on light-blue
#EBF3FBpanel with a thin blue border
Anything that looks like an AI tell (Claude, Anthropic, as an AI, language model) trips the verifier and blocks render.
Layout
agent.py # CLI dispatcher
browser.py # Playwright NUST LMS driver (selectors pinned at top)
report.py # source extraction + docx rendering + verification
mcp_server.py # FastMCP wrapper exposing the above as MCP tools/resources
prompts/system.md # plan-schema + tone guide for whichever LLM is driving
CLAUDE.md # operator briefing (read by Claude Code)
AGENTS.md # pointer to CLAUDE.md (read by OpenCode and others)
work/<slug>/ # per-run scratch (source, plan.json, report.docx, error.png on failure)
Notes
- Selectors live in one block at the top of
browser.py. Tweak there if NUST LMS changes its DOM. - "Screenshots" in the report are styled text captions ending in
(${IDENTITY_TERMINAL}). No code execution, no real images. GDN_WORK_DIRenv var overrides whereharvestwrites scratch files (default:./work).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file grabdeeznust-0.1.1.tar.gz.
File metadata
- Download URL: grabdeeznust-0.1.1.tar.gz
- Upload date:
- Size: 18.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4107540aefb65d98c0f8290324b9df07c952f44560140287ce0f1f6a1742a66e
|
|
| MD5 |
2e348ecbd7d81687268da6fb8ffd9276
|
|
| BLAKE2b-256 |
842e06cfd7e4d129872952149b7d4645e810d3a3089289983d40d0e222c637f8
|
Provenance
The following attestation bundles were made for grabdeeznust-0.1.1.tar.gz:
Publisher:
publish.yml on ZaynIkhlaq/grabdeeznust
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
grabdeeznust-0.1.1.tar.gz -
Subject digest:
4107540aefb65d98c0f8290324b9df07c952f44560140287ce0f1f6a1742a66e - Sigstore transparency entry: 1410208997
- Sigstore integration time:
-
Permalink:
ZaynIkhlaq/grabdeeznust@b791355bc7024a8a7a90f1185e2a4ee32ea9caca -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ZaynIkhlaq
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b791355bc7024a8a7a90f1185e2a4ee32ea9caca -
Trigger Event:
push
-
Statement type:
File details
Details for the file grabdeeznust-0.1.1-py3-none-any.whl.
File metadata
- Download URL: grabdeeznust-0.1.1-py3-none-any.whl
- Upload date:
- Size: 18.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d060d8c0f0f16d9888687fb31cdb878381bb4497177e72eb5bf9eba7dc5a54eb
|
|
| MD5 |
d30964b9c705e5f98569ef708275b4bc
|
|
| BLAKE2b-256 |
e54261b41bf1d6d2d196ead78bbeb66c7c68a1e5ac625f55a01cff2a6711b28b
|
Provenance
The following attestation bundles were made for grabdeeznust-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on ZaynIkhlaq/grabdeeznust
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
grabdeeznust-0.1.1-py3-none-any.whl -
Subject digest:
d060d8c0f0f16d9888687fb31cdb878381bb4497177e72eb5bf9eba7dc5a54eb - Sigstore transparency entry: 1410209072
- Sigstore integration time:
-
Permalink:
ZaynIkhlaq/grabdeeznust@b791355bc7024a8a7a90f1185e2a4ee32ea9caca -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ZaynIkhlaq
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b791355bc7024a8a7a90f1185e2a4ee32ea9caca -
Trigger Event:
push
-
Statement type: