induslms-agent 0.3.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 IndusLMS Agent contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,252 @@
1
+ Metadata-Version: 2.4
2
+ Name: induslms-agent
3
+ Version: 0.3.0
4
+ Summary: Read-only agent access to Indus LMS academics: announcements, assignments, shared resources, attendance
5
+ Author: IndusLMS Agent contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/StrangeSid/induslms-agent
8
+ Project-URL: Repository, https://github.com/StrangeSid/induslms-agent
9
+ Project-URL: Issues, https://github.com/StrangeSid/induslms-agent/issues
10
+ Keywords: lms,mcp,education,school,agent
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Topic :: Education
17
+ Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: requests>=2.31
22
+ Requires-Dist: mcp<2,>=1.0
23
+ Requires-Dist: msal>=1.30
24
+ Requires-Dist: python-dotenv>=1.0
25
+ Dynamic: license-file
26
+
27
+ # IndusLMS Agent
28
+
29
+ Read-only agent access to Indus LMS academics: **announcements, assignments, shared resources, notifications, attendance**. For `pi` + `opencode` harnesses via **MCP + Skill + CLI**.
30
+
31
+ ## Quickstart (one command)
32
+
33
+ ```bash
34
+ git clone https://github.com/StrangeSid/induslms-agent.git
35
+ cd induslms-agent
36
+ ./install.sh
37
+ ```
38
+
39
+ This creates `.venv`, installs the package, runs `lms.py login`,
40
+ merges MCP configs for pi + opencode, installs the skill, and runs
41
+ `lms.py doctor`. Restart your agent host afterwards.
42
+
43
+ Manual setup (if you prefer):
44
+
45
+ > Replace `/path/to/induslms-agent` with your checkout path and
46
+ > `you@indusschool.com` with your school email.
47
+
48
+ ```bash
49
+ cd /path/to/induslms-agent
50
+ python3 -m venv .venv && source .venv/bin/activate
51
+ pip install -e . # or: pip install -r requirements.txt
52
+
53
+ cp .env.example .env # then fill INDUSLMS_EMAIL/PASS/TENANT (.env auto-loaded)
54
+ # Authenticate once (token saved OUTSIDE the repo)
55
+ python3 lms.py login you@indusschool.com
56
+ # or: INDUSLMS_EMAIL=... INDUSLMS_PASS=... python3 lms.py login
57
+
58
+ # Health check
59
+ python3 lms.py doctor
60
+ # CLI
61
+ python3 lms.py courses
62
+ python3 lms.py assignments --course <course_id>
63
+ python3 lms.py resources --course <course_id>
64
+ python3 lms.py children <folder_id>
65
+ python3 lms.py download <resource_id> <file_id> --out /tmp/induslms-test
66
+ python3 lms.py notifications --unread-only --limit 5
67
+ python3 lms.py attendance && python3 lms.py attendance-day
68
+ ```
69
+
70
+ Tokens live at `~/.induslms_token.json` (override with `INDUSLMS_TOKEN_FILE`). Never committed — see `.gitignore`.
71
+
72
+ ## Credentials (`.env`) — used by programs, never sent to the LLM
73
+
74
+ ```bash
75
+ cp .env.example .env # then fill in your own values
76
+ ```
77
+
78
+ | Variable | Used by | Never leaves your machine |
79
+ |---|---|---|
80
+ | `INDUSLMS_EMAIL` / `INDUSLMS_PASS` | `lms.py login` only (one POST, then discarded) | ✅ |
81
+ | `INDUSLMS_TENANT` | Tenant fallback when the token has no roles | ✅ |
82
+ | `INDUSLMS_TOKEN_FILE` | Where the JWT is cached (`~/.induslms_token.json`) | ✅ |
83
+ | `INDUS_OUTLOOK_CLIENT_ID` | Graph device-code login (`outlook.py login`) | ✅ |
84
+
85
+ How it stays private:
86
+
87
+ * `lms.py`, `outlook.py`, `server.py` load `.env` via `python-dotenv` at startup — values live only in the local process environment.
88
+ * The MCP server exposes **no** login/token tools, and no tool ever returns passwords, tokens, or client IDs — tools return school data (assignments, mail, attendance) only.
89
+ * `.gitignore` blocks `.env`, `*token*.json`, and downloads, so credentials can't be committed by accident. Share only `.env.example` (empty values).
90
+
91
+ PyPI (after release): `pipx install induslms-agent` or `uvx induslms-agent doctor`,
92
+ then `induslms login you@indusschool.com` + `induslms-server` as your MCP command.
93
+
94
+ ## MCP server (recommended for agents)
95
+
96
+ Stdio, read-only tools only (no login, no mark-read, no submissions):
97
+
98
+ ```bash
99
+ python3 server.py
100
+ ```
101
+
102
+ Tools: `get_profile`, `list_courses`, `list_resources`, `get_resource`, `download_resource`, `assignments_overview`, `list_eol`, `list_assessments`, `list_notifications`, `get_attendance`, `get_attendance_day`, `list_announcements`, `list_calendar`, `schoolmail_search`, `schoolmail_read`, `schoolmail_folders`, `outlook_search`, `outlook_read`, `outlook_folders`, `od_resolve_link`, `od_browse`, `od_download`.
103
+
104
+ Run with `python3 server.py` (or `induslms-server` after `pip install`). Copy-paste
105
+ configs live in `examples/`: `.mcp.json` (Claude Code project scope),
106
+ `claude_desktop_config.json.example`, `opencode.jsonc.example`,
107
+ `mcp-uvx.json.example` (PyPI/uvx form).
108
+
109
+ ## School email (Apple Mail.app — macOS only)
110
+
111
+ Mail.app already holds the `School` account, so agents read it via osascript (JXA). No credentials, no app registration. On Linux/Windows `schoolmail_*` reports unavailable — use `outlook_*` instead:
112
+
113
+ ```bash
114
+ python3 mailapp.py folders
115
+ python3 mailapp.py search "assignment" --top 5
116
+ python3 mailapp.py search --sender teacher@indusschool.com --since 2026-09-01
117
+ python3 mailapp.py read Inbox:24203
118
+ ```
119
+
120
+ Refs are `Mailbox:id`. Override account/mailbox with `SCHOOL_MAIL_ACCOUNT` / `SCHOOL_MAILBOX`.
121
+
122
+ ### Register in pi
123
+
124
+ Create `~/.pi/agent/mcp.json` (same shape as `mcp.json` in this repo,
125
+ with `/path/to/induslms-agent` replaced by your checkout path):
126
+
127
+ ```json
128
+ { "mcpServers": { "induslms-academics": {
129
+ "command": "/path/to/induslms-agent/.venv/bin/python",
130
+ "args": ["/path/to/induslms-agent/server.py"],
131
+ "cwd": "/path/to/induslms-agent"
132
+ } } }
133
+ ```
134
+
135
+ Use the venv python — system python lacks `mcp`/`msal`. Restart pi afterwards.
136
+
137
+ ### Register in opencode
138
+
139
+ In `~/.config/opencode/opencode.jsonc` under `mcp` (replace
140
+ `/path/to/induslms-agent` with your checkout path):
141
+
142
+ ```json
143
+ "induslms-academics": {
144
+ "type": "local",
145
+ "command": ["/path/to/induslms-agent/.venv/bin/python", "/path/to/induslms-agent/server.py"],
146
+ "cwd": "/path/to/induslms-agent",
147
+ "enabled": true,
148
+ "timeout": 15000
149
+ }
150
+ ```
151
+
152
+ Restart opencode afterwards. Verify with a prompt like
153
+ "list my courses using induslms-academics".
154
+
155
+ ### Claude Code
156
+
157
+ ```bash
158
+ claude mcp add induslms-academics -- /path/to/induslms-agent/.venv/bin/python /path/to/induslms-agent/server.py
159
+ # or project scope: copy .mcp.json (replace paths), then: claude mcp list
160
+ ```
161
+
162
+ Skill: `bash scripts/install-skill.sh` also copies to `~/.claude/skills/`
163
+ (`INSTALL_PROJECT_SKILL=1` for `.claude/skills/`).
164
+
165
+ ### Claude Desktop
166
+
167
+ Copy `examples/claude_desktop_config.json.example` into
168
+ `~/Library/Application Support/Claude/claude_desktop_config.json`
169
+ (macOS; see file for Win/Linux paths), replace paths, relaunch.
170
+
171
+ ### OpenAI-compatible agents (code, no config file)
172
+
173
+ ```python
174
+ from agents.mcp import MCPServerStdio
175
+ async with MCPServerStdio(
176
+ params={"command": "/path/to/induslms-agent/.venv/bin/python",
177
+ "args": ["/path/to/induslms-agent/server.py"]},
178
+ cache_tools_list=True,
179
+ ) as server:
180
+ ...
181
+ ```
182
+
183
+ Hosted MCP / GPT Actions need a public HTTPS endpoint (not provided;
184
+ stdio-only by design).
185
+
186
+ ## Skill (workflow guidance)
187
+
188
+ `skills/induslms-academics/SKILL.md` teaches the workflow:
189
+ announcements → assignments → resources/download → attendance,
190
+ plus School inbox. Install (copies to pi, opencode, and Claude skills):
191
+
192
+ ```bash
193
+ bash scripts/install-skill.sh
194
+ ```
195
+
196
+ ### End-user prompt: refresh local school materials
197
+
198
+ `examples/update-resources.prompt.md` is a copy-paste template that
199
+ checks LMS + the School mailbox for new teacher materials, downloads
200
+ only what's missing into a `School/` folder, and parses everything to
201
+ text. Fill in your subjects/teachers, paste it into a fresh agent
202
+ session.
203
+
204
+ ## Key API notes (reverse-engineered, verified live)
205
+
206
+ * Base `https://api.induslms.com`, `Authorization: Bearer <access>` from `POST /api/v1/auth/login/`.
207
+ * Resources: `GET /api/v1/tenants/{tid}/resources/?course_id=` for top level; children via `?parent_resource_id={rid}` (NOT `?parent=`, which returns unfiltered results). Files: `.../resources/{rid}/files/{fid}/content/?disposition=attachment|inline` (binary, streamed).
208
+ * Assignments = EOL tests (`/eol-tests/my/`, 23 items) + student assessments (`/student/assessments/`, 6 items) + resources. `test-marks/me` is PYP-only (DP gets `BAD_REQUEST`) — handled gracefully.
209
+ * DP projects endpoint returns `[]` for this student; section fetcher ready for when IDs exist.
210
+ * Notifications support `?limit=&offset=`; unread filter is client-side (`is_read`) so `--limit 5 --unread-only` may return fewer rows — use a larger limit.
211
+ * Attendance has two shapes: session summary (`/students/me/attendance/`) and day breakdown (`/tenants/{tid}/students/me/attendance/day/`).
212
+
213
+ ## Security
214
+
215
+ * Read-only by design. The only POSTs in the codebase are `login` and the (CLI-unused) token-refresh helper — the MCP server exposes neither.
216
+ * Credentials via env/prompt only; tokens outside the repo; `.gitignore` blocks `*token*.json`, `.env`, downloads.
217
+
218
+ ## Outlook (school inbox via Microsoft Graph)
219
+
220
+ Read-only (`Mail.Read` delegated, device-code flow). Token cache at
221
+ `~/.indus_outlook_token.json` — never in repo.
222
+
223
+ ```bash
224
+ # 1. Entra ID -> App registrations -> New: allow public client flows,
225
+ # add delegated Mail.Read. Multitenant OK.
226
+ export INDUS_OUTLOOK_CLIENT_ID=<app/client id>
227
+ # ..or skip registration: export INDUS_USE_BUILTIN_CLIENT=1 (sign in as yourself)
228
+ python3 outlook.py login # approve code in browser
229
+ python3 outlook.py search "assignment" --top 5
230
+ python3 outlook.py search --sender teacher@indusschool.com --since 2026-09-01
231
+ python3 outlook.py read <message_id>
232
+ ```
233
+
234
+ MCP tools: `outlook_search`, `outlook_read`, `outlook_folders`
235
+ (same coverage; unconfigured → clear error, not crash).
236
+ School tenant blocks user consent → IT admin must grant admin consent first
237
+ (not needed with `INDUS_USE_BUILTIN_CLIENT=1`).
238
+
239
+ ## OneDrive / SharePoint (school files via Microsoft Graph)
240
+
241
+ Read-only (`Files.Read` + `Sites.Read.All`, same device-code flow and token
242
+ cache as Outlook — re-run login once to consent to the new scopes):
243
+
244
+ ```bash
245
+ python3 sharepoint.py login
246
+ python3 sharepoint.py resolve <sharing-link-from-mail> # shared file/folder metadata
247
+ python3 sharepoint.py browse / # your OneDrive root
248
+ python3 sharepoint.py download <item-id-or-link> --out /tmp/school
249
+ ```
250
+
251
+ MCP tools: `od_resolve_link`, `od_browse`, `od_download`. No local OneDrive
252
+ sync client needed — pure HTTPS, works on any OS.
@@ -0,0 +1,226 @@
1
+ # IndusLMS Agent
2
+
3
+ Read-only agent access to Indus LMS academics: **announcements, assignments, shared resources, notifications, attendance**. For `pi` + `opencode` harnesses via **MCP + Skill + CLI**.
4
+
5
+ ## Quickstart (one command)
6
+
7
+ ```bash
8
+ git clone https://github.com/StrangeSid/induslms-agent.git
9
+ cd induslms-agent
10
+ ./install.sh
11
+ ```
12
+
13
+ This creates `.venv`, installs the package, runs `lms.py login`,
14
+ merges MCP configs for pi + opencode, installs the skill, and runs
15
+ `lms.py doctor`. Restart your agent host afterwards.
16
+
17
+ Manual setup (if you prefer):
18
+
19
+ > Replace `/path/to/induslms-agent` with your checkout path and
20
+ > `you@indusschool.com` with your school email.
21
+
22
+ ```bash
23
+ cd /path/to/induslms-agent
24
+ python3 -m venv .venv && source .venv/bin/activate
25
+ pip install -e . # or: pip install -r requirements.txt
26
+
27
+ cp .env.example .env # then fill INDUSLMS_EMAIL/PASS/TENANT (.env auto-loaded)
28
+ # Authenticate once (token saved OUTSIDE the repo)
29
+ python3 lms.py login you@indusschool.com
30
+ # or: INDUSLMS_EMAIL=... INDUSLMS_PASS=... python3 lms.py login
31
+
32
+ # Health check
33
+ python3 lms.py doctor
34
+ # CLI
35
+ python3 lms.py courses
36
+ python3 lms.py assignments --course <course_id>
37
+ python3 lms.py resources --course <course_id>
38
+ python3 lms.py children <folder_id>
39
+ python3 lms.py download <resource_id> <file_id> --out /tmp/induslms-test
40
+ python3 lms.py notifications --unread-only --limit 5
41
+ python3 lms.py attendance && python3 lms.py attendance-day
42
+ ```
43
+
44
+ Tokens live at `~/.induslms_token.json` (override with `INDUSLMS_TOKEN_FILE`). Never committed — see `.gitignore`.
45
+
46
+ ## Credentials (`.env`) — used by programs, never sent to the LLM
47
+
48
+ ```bash
49
+ cp .env.example .env # then fill in your own values
50
+ ```
51
+
52
+ | Variable | Used by | Never leaves your machine |
53
+ |---|---|---|
54
+ | `INDUSLMS_EMAIL` / `INDUSLMS_PASS` | `lms.py login` only (one POST, then discarded) | ✅ |
55
+ | `INDUSLMS_TENANT` | Tenant fallback when the token has no roles | ✅ |
56
+ | `INDUSLMS_TOKEN_FILE` | Where the JWT is cached (`~/.induslms_token.json`) | ✅ |
57
+ | `INDUS_OUTLOOK_CLIENT_ID` | Graph device-code login (`outlook.py login`) | ✅ |
58
+
59
+ How it stays private:
60
+
61
+ * `lms.py`, `outlook.py`, `server.py` load `.env` via `python-dotenv` at startup — values live only in the local process environment.
62
+ * The MCP server exposes **no** login/token tools, and no tool ever returns passwords, tokens, or client IDs — tools return school data (assignments, mail, attendance) only.
63
+ * `.gitignore` blocks `.env`, `*token*.json`, and downloads, so credentials can't be committed by accident. Share only `.env.example` (empty values).
64
+
65
+ PyPI (after release): `pipx install induslms-agent` or `uvx induslms-agent doctor`,
66
+ then `induslms login you@indusschool.com` + `induslms-server` as your MCP command.
67
+
68
+ ## MCP server (recommended for agents)
69
+
70
+ Stdio, read-only tools only (no login, no mark-read, no submissions):
71
+
72
+ ```bash
73
+ python3 server.py
74
+ ```
75
+
76
+ Tools: `get_profile`, `list_courses`, `list_resources`, `get_resource`, `download_resource`, `assignments_overview`, `list_eol`, `list_assessments`, `list_notifications`, `get_attendance`, `get_attendance_day`, `list_announcements`, `list_calendar`, `schoolmail_search`, `schoolmail_read`, `schoolmail_folders`, `outlook_search`, `outlook_read`, `outlook_folders`, `od_resolve_link`, `od_browse`, `od_download`.
77
+
78
+ Run with `python3 server.py` (or `induslms-server` after `pip install`). Copy-paste
79
+ configs live in `examples/`: `.mcp.json` (Claude Code project scope),
80
+ `claude_desktop_config.json.example`, `opencode.jsonc.example`,
81
+ `mcp-uvx.json.example` (PyPI/uvx form).
82
+
83
+ ## School email (Apple Mail.app — macOS only)
84
+
85
+ Mail.app already holds the `School` account, so agents read it via osascript (JXA). No credentials, no app registration. On Linux/Windows `schoolmail_*` reports unavailable — use `outlook_*` instead:
86
+
87
+ ```bash
88
+ python3 mailapp.py folders
89
+ python3 mailapp.py search "assignment" --top 5
90
+ python3 mailapp.py search --sender teacher@indusschool.com --since 2026-09-01
91
+ python3 mailapp.py read Inbox:24203
92
+ ```
93
+
94
+ Refs are `Mailbox:id`. Override account/mailbox with `SCHOOL_MAIL_ACCOUNT` / `SCHOOL_MAILBOX`.
95
+
96
+ ### Register in pi
97
+
98
+ Create `~/.pi/agent/mcp.json` (same shape as `mcp.json` in this repo,
99
+ with `/path/to/induslms-agent` replaced by your checkout path):
100
+
101
+ ```json
102
+ { "mcpServers": { "induslms-academics": {
103
+ "command": "/path/to/induslms-agent/.venv/bin/python",
104
+ "args": ["/path/to/induslms-agent/server.py"],
105
+ "cwd": "/path/to/induslms-agent"
106
+ } } }
107
+ ```
108
+
109
+ Use the venv python — system python lacks `mcp`/`msal`. Restart pi afterwards.
110
+
111
+ ### Register in opencode
112
+
113
+ In `~/.config/opencode/opencode.jsonc` under `mcp` (replace
114
+ `/path/to/induslms-agent` with your checkout path):
115
+
116
+ ```json
117
+ "induslms-academics": {
118
+ "type": "local",
119
+ "command": ["/path/to/induslms-agent/.venv/bin/python", "/path/to/induslms-agent/server.py"],
120
+ "cwd": "/path/to/induslms-agent",
121
+ "enabled": true,
122
+ "timeout": 15000
123
+ }
124
+ ```
125
+
126
+ Restart opencode afterwards. Verify with a prompt like
127
+ "list my courses using induslms-academics".
128
+
129
+ ### Claude Code
130
+
131
+ ```bash
132
+ claude mcp add induslms-academics -- /path/to/induslms-agent/.venv/bin/python /path/to/induslms-agent/server.py
133
+ # or project scope: copy .mcp.json (replace paths), then: claude mcp list
134
+ ```
135
+
136
+ Skill: `bash scripts/install-skill.sh` also copies to `~/.claude/skills/`
137
+ (`INSTALL_PROJECT_SKILL=1` for `.claude/skills/`).
138
+
139
+ ### Claude Desktop
140
+
141
+ Copy `examples/claude_desktop_config.json.example` into
142
+ `~/Library/Application Support/Claude/claude_desktop_config.json`
143
+ (macOS; see file for Win/Linux paths), replace paths, relaunch.
144
+
145
+ ### OpenAI-compatible agents (code, no config file)
146
+
147
+ ```python
148
+ from agents.mcp import MCPServerStdio
149
+ async with MCPServerStdio(
150
+ params={"command": "/path/to/induslms-agent/.venv/bin/python",
151
+ "args": ["/path/to/induslms-agent/server.py"]},
152
+ cache_tools_list=True,
153
+ ) as server:
154
+ ...
155
+ ```
156
+
157
+ Hosted MCP / GPT Actions need a public HTTPS endpoint (not provided;
158
+ stdio-only by design).
159
+
160
+ ## Skill (workflow guidance)
161
+
162
+ `skills/induslms-academics/SKILL.md` teaches the workflow:
163
+ announcements → assignments → resources/download → attendance,
164
+ plus School inbox. Install (copies to pi, opencode, and Claude skills):
165
+
166
+ ```bash
167
+ bash scripts/install-skill.sh
168
+ ```
169
+
170
+ ### End-user prompt: refresh local school materials
171
+
172
+ `examples/update-resources.prompt.md` is a copy-paste template that
173
+ checks LMS + the School mailbox for new teacher materials, downloads
174
+ only what's missing into a `School/` folder, and parses everything to
175
+ text. Fill in your subjects/teachers, paste it into a fresh agent
176
+ session.
177
+
178
+ ## Key API notes (reverse-engineered, verified live)
179
+
180
+ * Base `https://api.induslms.com`, `Authorization: Bearer <access>` from `POST /api/v1/auth/login/`.
181
+ * Resources: `GET /api/v1/tenants/{tid}/resources/?course_id=` for top level; children via `?parent_resource_id={rid}` (NOT `?parent=`, which returns unfiltered results). Files: `.../resources/{rid}/files/{fid}/content/?disposition=attachment|inline` (binary, streamed).
182
+ * Assignments = EOL tests (`/eol-tests/my/`, 23 items) + student assessments (`/student/assessments/`, 6 items) + resources. `test-marks/me` is PYP-only (DP gets `BAD_REQUEST`) — handled gracefully.
183
+ * DP projects endpoint returns `[]` for this student; section fetcher ready for when IDs exist.
184
+ * Notifications support `?limit=&offset=`; unread filter is client-side (`is_read`) so `--limit 5 --unread-only` may return fewer rows — use a larger limit.
185
+ * Attendance has two shapes: session summary (`/students/me/attendance/`) and day breakdown (`/tenants/{tid}/students/me/attendance/day/`).
186
+
187
+ ## Security
188
+
189
+ * Read-only by design. The only POSTs in the codebase are `login` and the (CLI-unused) token-refresh helper — the MCP server exposes neither.
190
+ * Credentials via env/prompt only; tokens outside the repo; `.gitignore` blocks `*token*.json`, `.env`, downloads.
191
+
192
+ ## Outlook (school inbox via Microsoft Graph)
193
+
194
+ Read-only (`Mail.Read` delegated, device-code flow). Token cache at
195
+ `~/.indus_outlook_token.json` — never in repo.
196
+
197
+ ```bash
198
+ # 1. Entra ID -> App registrations -> New: allow public client flows,
199
+ # add delegated Mail.Read. Multitenant OK.
200
+ export INDUS_OUTLOOK_CLIENT_ID=<app/client id>
201
+ # ..or skip registration: export INDUS_USE_BUILTIN_CLIENT=1 (sign in as yourself)
202
+ python3 outlook.py login # approve code in browser
203
+ python3 outlook.py search "assignment" --top 5
204
+ python3 outlook.py search --sender teacher@indusschool.com --since 2026-09-01
205
+ python3 outlook.py read <message_id>
206
+ ```
207
+
208
+ MCP tools: `outlook_search`, `outlook_read`, `outlook_folders`
209
+ (same coverage; unconfigured → clear error, not crash).
210
+ School tenant blocks user consent → IT admin must grant admin consent first
211
+ (not needed with `INDUS_USE_BUILTIN_CLIENT=1`).
212
+
213
+ ## OneDrive / SharePoint (school files via Microsoft Graph)
214
+
215
+ Read-only (`Files.Read` + `Sites.Read.All`, same device-code flow and token
216
+ cache as Outlook — re-run login once to consent to the new scopes):
217
+
218
+ ```bash
219
+ python3 sharepoint.py login
220
+ python3 sharepoint.py resolve <sharing-link-from-mail> # shared file/folder metadata
221
+ python3 sharepoint.py browse / # your OneDrive root
222
+ python3 sharepoint.py download <item-id-or-link> --out /tmp/school
223
+ ```
224
+
225
+ MCP tools: `od_resolve_link`, `od_browse`, `od_download`. No local OneDrive
226
+ sync client needed — pure HTTPS, works on any OS.