edu-workspace-mcp 0.1.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/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/approvals/service.d.ts +9 -0
- package/dist/approvals/service.js +27 -0
- package/dist/approvals/service.js.map +1 -0
- package/dist/auth/bundled-oauth-client.d.ts +11 -0
- package/dist/auth/bundled-oauth-client.js +12 -0
- package/dist/auth/bundled-oauth-client.js.map +1 -0
- package/dist/auth/google-auth.d.ts +12 -0
- package/dist/auth/google-auth.js +145 -0
- package/dist/auth/google-auth.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +63 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -0
- package/dist/google/classroom.d.ts +39 -0
- package/dist/google/classroom.js +77 -0
- package/dist/google/classroom.js.map +1 -0
- package/dist/google/docs.d.ts +9 -0
- package/dist/google/docs.js +30 -0
- package/dist/google/docs.js.map +1 -0
- package/dist/google/drive.d.ts +4 -0
- package/dist/google/drive.js +61 -0
- package/dist/google/drive.js.map +1 -0
- package/dist/google/forms.d.ts +14 -0
- package/dist/google/forms.js +57 -0
- package/dist/google/forms.js.map +1 -0
- package/dist/google/sheets.d.ts +9 -0
- package/dist/google/sheets.js +33 -0
- package/dist/google/sheets.js.map +1 -0
- package/dist/google/slides.d.ts +9 -0
- package/dist/google/slides.js +63 -0
- package/dist/google/slides.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.js +211 -0
- package/dist/server.js.map +1 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 edu-workspace-mcp 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.
|
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# π edu-workspace-mcp
|
|
2
|
+
|
|
3
|
+
μμ°μ΄λ‘ Google Docs, Sheets, Slides, Forms, Driveμ Classroomμ μ¬μ©νλ κ΅μ‘ νΉν Model Context Protocol(MCP) μλ²μ
λλ€. Apps Script μμ΄ Google 곡μ APIλ₯Ό μ§μ νΈμΆν©λλ€.
|
|
4
|
+
|
|
5
|
+
[곡μ ννμ΄μ§](https://edu.jeld.kr/) Β· [κ°μΈμ 보μ²λ¦¬λ°©μΉ¨](https://edu.jeld.kr/privacy.html) Β· [μ΄μ©μ½κ΄](https://edu.jeld.kr/terms.html)
|
|
6
|
+
|
|
7
|
+
> νμ¬ λ²μ μ λ‘컬 stdio MCP λ² νμ
λλ€. κ³΅μ© Google λ°μ€ν¬ν± OAuth ν΄λΌμ΄μΈνΈκ° ν¬ν¨λμ΄ μμ΄ μ΅μ’
μ¬μ©μκ° Google Cloud νλ‘μ νΈλ₯Ό λ§λ€ νμκ° μμ΅λλ€. μ±μ μ ν μ 체 λ²μ λμ μ±μ΄ μ¬μ©νλ νμΌμ νμ λ `drive.file` κΆνμ μμ²ν©λλ€.
|
|
8
|
+
|
|
9
|
+
## ν μ μλ μΌ
|
|
10
|
+
|
|
11
|
+
- μμ
μλ£μ© Google Docs λ¬Έμ μμ±
|
|
12
|
+
- νμ λͺ
λ¨Β·λ£¨λΈλ¦Β·μ±μ ν ννμ Google Sheets μμ±
|
|
13
|
+
- μ°¨μλ³ Google Slides νλ μ ν
μ΄μ
μμ±
|
|
14
|
+
- μ λ΅Β·λ°°μ μ΄ ν¬ν¨λ Google Forms ν΄μ¦ μμ±
|
|
15
|
+
- Google Drive νμΌ κ²μ, ν΄λ μμ±, μΉμΈ ν 곡μ
|
|
16
|
+
- κ΅μ¬κ° λ΄λΉνλ Google Classroom μμ
μ‘°ν
|
|
17
|
+
- Classroom κ³Όμ μ΄μ μμ±, μ¬μ©μ μΉμΈ ν κ²μ
|
|
18
|
+
|
|
19
|
+
μμ μμ²:
|
|
20
|
+
|
|
21
|
+
> β`μνμ μν` λ¨μμ 5λ¬Έν ν΄μ¦μ νμ΅μ§, 6μ₯μ§λ¦¬ μμ
μ¬λΌμ΄λλ₯Ό λ§λ€κ³ `2νλ
κ³Όν` ν΄λμ€λ£Έμ κΈμμΌ μ€ν 6μ λ§κ° κ³Όμ μ΄μμΌλ‘ λ±λ‘ν΄μ€.β
|
|
22
|
+
|
|
23
|
+
## μ€μ κ΅μ‘ νμ© λ°λͺ¨
|
|
24
|
+
|
|
25
|
+
ν λ²μ μμ°μ΄ μμ²μΌλ‘ μ¬λ¬ Google κ΅μ‘ λꡬλ₯Ό μ°κ²°ν μ μμ΅λλ€.
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
κ΅μ¬ μμ²
|
|
29
|
+
β Drive μμ
ν΄λ μμ±
|
|
30
|
+
β Docs κ΅μ¬μ© μμ
μΒ·νμμ© νμ΅μ§ μμ±
|
|
31
|
+
β Slides μμ
μλ£ μμ±
|
|
32
|
+
β Forms μ λ΅Β·λ°°μ ν¬ν¨ ν΄μ¦ μμ±
|
|
33
|
+
β Classroom κ³Όμ μ΄μ μμ±
|
|
34
|
+
β κ΅μ¬κ° λμΒ·λ§κ°Β·μ²¨λΆλ₯Ό νμΈν λ€ κ²μ
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
λν λ°λͺ¨ ν둬ννΈ:
|
|
38
|
+
|
|
39
|
+
> 5νλ
κ³Όν βμνμ μνβ μμ
ν¨ν€μ§λ₯Ό λ§λ€μ΄μ€. Driveμ μ μ© ν΄λλ₯Ό λ§λ€κ³ , κ΅μ¬μ© μμ
μκ³Ό νμμ© νμ΅μ§λ Docsλ‘, 6μ₯ μμ
μλ£λ Slidesλ‘, 5λ¬Έν νμ±νκ°λ Forms ν΄μ¦λ‘ λ§λ€μ΄μ€. β5νλ
3λ° κ³Όνβ Classroomμλ κ³Όμ μ΄μκΉμ§λ§ λ§λ€κ³ κ²μ μ μ λμκ² νμΈλ°μμ€.
|
|
40
|
+
|
|
41
|
+
| κ΅μ‘ μ
무 | λ§λ€μ΄μ§λ κ²°κ³Ό | μ¬μ©νλ λꡬ |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| ν μ°¨μ μμ
ν¨ν€μ§ | μμ
μ, νμ΅μ§, μ¬λΌμ΄λ, ν΄μ¦, Classroom μ΄μ | Drive + Docs + Slides + Forms + Classroom |
|
|
44
|
+
| κ³Όμ μ€μ¬νκ° κ΄λ¦¬ | νκ°μ
λ ₯Β·νμλ³νν©Β·νλͺ©λ³νν© μνΈ | Sheets |
|
|
45
|
+
| μννκ° μ€κ³ | 4μμ€ λ£¨λΈλ¦ λ¬Έμμ νμ μκΈ°νκ°μ§ | Docs + Forms |
|
|
46
|
+
| νμ±νκ° λ°°ν¬ | μ λ΅Β·λ°°μ μ΄ μλ ν΄μ¦μ κ³Όμ μ΄μ | Forms + Classroom |
|
|
47
|
+
| νκΈ° μλ£ μ 리 | κ²μ κ²°κ³Όμ κ΅κ³ΌΒ·λ¨μλ³ Drive ν΄λ | Drive |
|
|
48
|
+
| λλ£ κ΅μ¬ νμ
| νμΈ μ μ°¨λ₯Ό κ±°μΉ λκΈΒ·μ½κΈ°Β·νΈμ§ κΆν | Drive 곡μ |
|
|
49
|
+
|
|
50
|
+
- [κ΅μ‘μλ₯Ό μν μ€μ νμ© μμμ λ³΅μ¬ κ°λ₯ν ν둬ννΈ](docs/EDUCATOR_USE_CASES.md)
|
|
51
|
+
- [μμ λͺ¨μκ³Ό μΆμ² 첫 λ°λͺ¨](examples/README.md)
|
|
52
|
+
- [μ€μ λ‘ μ€νλλ μμ
ν¨ν€μ§ ν΅ν© ν
μ€νΈ](src/tests/education-demo.test.ts)
|
|
53
|
+
|
|
54
|
+
> v0.1μ μ νμΌ μμ±κ³Ό Classroom μ΄μΒ·κ²μ νλ¦μ μ§μ€ν©λλ€. κΈ°μ‘΄ λ¬Έμμ λΆλΆ νΈμ§, Sheets μ°¨νΈΒ·λλ‘λ€μ΄Β·μ‘°κ±΄λΆ μμ, Forms μλ΅κ³Ό νμ μ μΆλ¬Ό λΆμμ μμ§ μ§μνμ§ μμ΅λλ€. μμΈν μ§μ λ²μλ [νμ© μμ λ¬Έμ](docs/EDUCATOR_USE_CASES.md#νμ¬-λ²μ μμ-κ°λ₯ν-λ²μ)λ₯Ό νμΈνμΈμ.
|
|
55
|
+
|
|
56
|
+
## MCP λꡬ
|
|
57
|
+
|
|
58
|
+
| λꡬ | λμ | μ±κ²© |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `workspace_get_auth_status` | Google μ°κ²° μνμ λ²μ νμΈ | μ½κΈ° |
|
|
61
|
+
| `classroom_list_courses` | κ΅μ¬κ° λ΄λΉνλ νμ± μμ
μ‘°ν | μ½κΈ° |
|
|
62
|
+
| `drive_search_files` | μ΄λ¦Β·μ νΒ·ν΄λλ‘ Drive κ²μ | μ½κΈ° |
|
|
63
|
+
| `drive_create_folder` | Drive ν΄λ μμ± | μμ± |
|
|
64
|
+
| `docs_create_document` | Docs λ¬Έμ μμ± | μμ± |
|
|
65
|
+
| `sheets_create_workbook` | μ¬λ¬ νκ³Ό μ΄κΈ° λ°μ΄ν°κ° μλ Sheets μμ± | μμ± |
|
|
66
|
+
| `slides_create_presentation` | μ λͺ©Β·λ³Έλ¬Έ μ¬λΌμ΄λ μμ± | μμ± |
|
|
67
|
+
| `forms_create_quiz` | λ¬ΈνΒ·μ λ΅Β·λ°°μ μ΄ μλ ν΄μ¦ μμ± | μμ± |
|
|
68
|
+
| `classroom_create_assignment_draft` | νμμκ² λ³΄μ΄μ§ μλ κ³Όμ μ΄μ μμ± | μμ± |
|
|
69
|
+
| `classroom_publish_assignment` | μΉμΈλ κ³Όμ μ΄μμ νμμκ² κ²μ | μΈλΆ λ³κ²½ |
|
|
70
|
+
| `drive_prepare_share` | 곡μ λμΒ·κΆν μΉμΈ μ€λΉ | μΉμΈ μ€λΉ |
|
|
71
|
+
| `drive_share_file` | μΉμΈλ Drive κΆν λ³κ²½ | μΈλΆ λ³κ²½ |
|
|
72
|
+
|
|
73
|
+
κ²μμ 곡μ λꡬμλ MCP μμ λ©νλ°μ΄ν°κ° ν¬ν¨λμ΄ μμ΅λλ€. μΉμΈ IDλ 15λΆ λμ μ ν¨νκ³ ν λ²λ§ μ¬μ©ν μ μμ΅λλ€.
|
|
74
|
+
|
|
75
|
+
## μꡬ μ¬ν
|
|
76
|
+
|
|
77
|
+
- Node.js 20 μ΄μ
|
|
78
|
+
- MCPλ₯Ό μ§μνλ λ°μ€ν¬ν± ν΄λΌμ΄μΈνΈ
|
|
79
|
+
- Google κ³μ
|
|
80
|
+
|
|
81
|
+
## λΉ λ₯Έ μ€μΉ β μ΅μ’
μ¬μ©μ
|
|
82
|
+
|
|
83
|
+
μ μ npm λ°°ν¬ ν macOS, Windows, Linuxμμ λ€μ λ λ¨κ³λ§ μ§νν©λλ€. Google Cloud Console μ€μ μ νμνμ§ μμ΅λλ€.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx -y edu-workspace-mcp setup
|
|
87
|
+
npx -y edu-workspace-mcp login
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
λΈλΌμ°μ μμ Google λ‘κ·ΈμΈκ³Ό κΆν μΉμΈμ λ§μΉκ³ MCP ν΄λΌμ΄μΈνΈμ μ°κ²°νλ©΄ λ©λλ€. νκ΅ κ³μ μμ κΈ°κ΄ κ΄λ¦¬μκ° μ 3μ μ±μ μ°¨λ¨ν κ²½μ°μλ κ΄λ¦¬μμκ² μ± νμ©μ μμ²ν΄μΌ ν©λλ€.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx -y edu-workspace-mcp doctor
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Google ν ν°μ `~/.edu-workspace-mcp/token.json`μ μ¬μ©μ μ μ© κΆνμΌλ‘ μ μ₯λ©λλ€. μ°κ²°μ ν΄μ νλ €λ©΄ λ€μμ μ€νν©λλ€.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx -y edu-workspace-mcp disconnect
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## MCP ν΄λΌμ΄μΈνΈ μ°κ²°
|
|
103
|
+
|
|
104
|
+
Claude Desktopκ³Ό Cursor κ³μ΄ JSON μ€μ :
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcpServers": {
|
|
109
|
+
"edu-workspace": {
|
|
110
|
+
"command": "npx",
|
|
111
|
+
"args": ["-y", "edu-workspace-mcp"]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
CodexΒ·ChatGPT Desktop κ³μ΄ TOML μ€μ :
|
|
118
|
+
|
|
119
|
+
```toml
|
|
120
|
+
[mcp_servers.edu_workspace]
|
|
121
|
+
command = "npx"
|
|
122
|
+
args = ["-y", "edu-workspace-mcp"]
|
|
123
|
+
default_tools_approval_mode = "writes"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
ChatGPT μΉμ μ¬μ©μ μ»΄ν¨ν°μ stdio νλ‘μΈμ€λ₯Ό μ€ννμ§ μμ΅λλ€. μΉ μ§μμ ν₯ν νΈμ€ν
ν Streamable HTTP μλ²λ‘ μ 곡ν κ³νμ
λλ€.
|
|
127
|
+
|
|
128
|
+
## μμ€μμ μ€ν
|
|
129
|
+
|
|
130
|
+
κ³΅κ° μ μ₯μμλ μ€μΉν μ±μ© κ³΅μ© OAuth client IDκ° ν¬ν¨λμ΄ μμ΅λλ€. μ΅μ’
μ¬μ©μλ λ³λ Google Cloud νλ‘μ νΈλ `credentials.json` μμ΄ `login`λ§ μ€νν©λλ€.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm install
|
|
134
|
+
npm run check
|
|
135
|
+
npm test
|
|
136
|
+
npm run test:mcp
|
|
137
|
+
node dist/cli.js setup
|
|
138
|
+
node dist/cli.js login
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
κ°λ° μ€ MCP μ€μ μμλ `command`μ `node`, `args`μ νλ‘μ νΈμ μ λ κ²½λ‘μΈ `dist/cli.js`λ₯Ό μ§μ ν μ μμ΅λλ€.
|
|
142
|
+
|
|
143
|
+
## 보μ λͺ¨λΈ
|
|
144
|
+
|
|
145
|
+
- νμν Google OAuth λ²μλ§ μμ²ν©λλ€.
|
|
146
|
+
- μ¬μ©μμ OAuth μ‘μΈμ€Β·κ°±μ ν ν°μ Gitμ ν¬ν¨λμ§ μκ³ λ‘컬 μ¬μ©μ ν΄λμλ§ μ μ₯λ©λλ€.
|
|
147
|
+
- μ€μΉν μ±μ λΉλ°μ μ μ§ν μ μλ κ³΅κ° ν΄λΌμ΄μΈνΈμ΄λ―λ‘ λ°°ν¬μ© λ°μ€ν¬ν± client IDμ client secretμ μ±μ ν¬ν¨λ©λλ€.
|
|
148
|
+
- κ²μΒ·μ‘°ν λꡬμλ `readOnlyHint`λ₯Ό νμν©λλ€.
|
|
149
|
+
- Classroom κ²μμ Drive 곡μ μλ `destructiveHint`λ₯Ό νμν©λλ€.
|
|
150
|
+
- Classroom κ³Όμ λ λ¨Όμ `DRAFT`λ‘ λ§λ€κ³ λ³λ κ²μ λꡬμμ μΌνμ± μΉμΈμ κ²μ¦ν©λλ€.
|
|
151
|
+
- Drive 곡μ λ μ€λΉμ νμ λ¨κ³λ₯Ό λΆλ¦¬ν©λλ€.
|
|
152
|
+
- μ€μ νμ λ°μ΄ν°κ° μλ λ³λ ν
μ€νΈ μμ
μμ λ¨Όμ κ²μ¦νμΈμ.
|
|
153
|
+
|
|
154
|
+
Google OAuth λΈλλ©κ³Ό κ°μΈμ 보μ²λ¦¬λ°©μΉ¨μ 곡κ°ν΄μΌ νλ©°, Google Workspace for Education κ΄λ¦¬μκ° μ 3μ μ±μ νμ©ν΄μΌ ν μ μμ΅λλ€.
|
|
155
|
+
|
|
156
|
+
μμ² λ²μμ μμΈ μ€λͺ
μ [OAuth λ²μ λ¬Έμ](docs/OAUTH_SCOPES.md), κ³΅κ° λ°μ΄ν° μ²λ¦¬ λ°©μΉ¨μ [κ°μΈμ 보μ²λ¦¬λ°©μΉ¨](https://edu.jeld.kr/privacy.html)μ μ°Έκ³ νμΈμ. κ³΅κ° λ°°ν¬λ₯Ό μν λ²μλ³ μ¬μ© μ¬μ μ λ°λͺ¨ μμ μλ리μ€λ [OAuth κ²μ μ μΆ κ°μ΄λ](docs/OAUTH_VERIFICATION_GUIDE.md)μ μ 리λμ΄ μμ΅λλ€.
|
|
157
|
+
|
|
158
|
+
## ν
μ€νΈ
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm run check # TypeScript μ μ κ²μ¬
|
|
162
|
+
npm test # λ¨μΒ·MCP μΈλ©λͺ¨λ¦¬ ν΅ν© ν
μ€νΈ
|
|
163
|
+
npm run test:mcp # μ€μ stdio νλ‘μΈμ€ μ°κ²° ν
μ€νΈ
|
|
164
|
+
npm audit --omit=dev
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Google API μ€κ³μ ν
μ€νΈμλ λ³λ ν
μ€νΈ κ³μ μ μ¬μ©νμΈμ. CIμμλ μΈλΆ κ³μ μ μ¬μ©νμ§ μκ³ Google API μ΄λν°λ₯Ό μ£Όμ
ν κ³μ½ ν
μ€νΈλ₯Ό μ€νν©λλ€.
|
|
168
|
+
|
|
169
|
+
## κ°λ° νν©
|
|
170
|
+
|
|
171
|
+
- [x] OAuth λ‘κ·ΈμΈΒ·ν ν° μ μ₯Β·μ°κ²° ν΄μ
|
|
172
|
+
- [x] Drive κ²μΒ·ν΄λ μμ±Β·μΉμΈ 곡μ
|
|
173
|
+
- [x] DocsΒ·SheetsΒ·SlidesΒ·Forms μμ±
|
|
174
|
+
- [x] Classroom μμ
μ‘°νΒ·κ³Όμ μ΄μΒ·μΉμΈ κ²μ
|
|
175
|
+
- [x] macOSΒ·WindowsΒ·Linux κ³΅ν΅ npm μ€ν ꡬ쑰
|
|
176
|
+
- [x] λ¨μΒ·MCP ν΅ν©Β·stdio μ€λͺ¨ν¬ ν
μ€νΈ
|
|
177
|
+
- [x] μ€κ³μ OAuthΒ·Classroom μ‘°νΒ·Sheets μμ± E2E ν
μ€νΈ
|
|
178
|
+
- [ ] Google OAuth κ³΅κ° μ± κ²μ¦
|
|
179
|
+
- [ ] νΈμ€ν
ν Streamable HTTP MCP
|
|
180
|
+
- [ ] CalendarΒ·GmailΒ·Tasks νμ₯
|
|
181
|
+
|
|
182
|
+
μ 체 μ ν κ³νμ [docs/IMPLEMENTATION_PLAN.md](docs/IMPLEMENTATION_PLAN.md)λ₯Ό μ°Έκ³ νμΈμ.
|
|
183
|
+
|
|
184
|
+
## λΌμ΄μ μ€
|
|
185
|
+
|
|
186
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type ApprovalRecord = {
|
|
2
|
+
id: string;
|
|
3
|
+
action: "classroom.publish" | "drive.share";
|
|
4
|
+
summary: Record<string, unknown>;
|
|
5
|
+
expiresAt: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function createApproval(action: ApprovalRecord["action"], summary: Record<string, unknown>): ApprovalRecord;
|
|
8
|
+
export declare function consumeApproval(id: string, action: ApprovalRecord["action"]): ApprovalRecord;
|
|
9
|
+
export declare function resetApprovalsForTests(): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
const approvals = new Map();
|
|
3
|
+
export function createApproval(action, summary) {
|
|
4
|
+
const record = {
|
|
5
|
+
id: randomUUID(),
|
|
6
|
+
action,
|
|
7
|
+
summary,
|
|
8
|
+
expiresAt: new Date(Date.now() + 15 * 60_000).toISOString()
|
|
9
|
+
};
|
|
10
|
+
approvals.set(record.id, record);
|
|
11
|
+
return record;
|
|
12
|
+
}
|
|
13
|
+
export function consumeApproval(id, action) {
|
|
14
|
+
const record = approvals.get(id);
|
|
15
|
+
if (!record || record.action !== action)
|
|
16
|
+
throw new Error("μΉμΈ μμ²μ μ°Ύμ μ μκ±°λ μμ
μ΄ μΌμΉνμ§ μμ΅λλ€.");
|
|
17
|
+
if (Date.parse(record.expiresAt) <= Date.now()) {
|
|
18
|
+
approvals.delete(id);
|
|
19
|
+
throw new Error("μΉμΈ μμ²μ΄ λ§λ£λμμ΅λλ€. μμ
μ΄μμ λ€μ μμ±νμΈμ.");
|
|
20
|
+
}
|
|
21
|
+
approvals.delete(id);
|
|
22
|
+
return record;
|
|
23
|
+
}
|
|
24
|
+
export function resetApprovalsForTests() {
|
|
25
|
+
approvals.clear();
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/approvals/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AASzC,MAAM,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;AAEpD,MAAM,UAAU,cAAc,CAAC,MAAgC,EAAE,OAAgC;IAC/F,MAAM,MAAM,GAAmB;QAC7B,EAAE,EAAE,UAAU,EAAE;QAChB,MAAM;QACN,OAAO;QACP,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE;KAC5D,CAAC;IACF,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,EAAU,EAAE,MAAgC;IAC1E,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC3F,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC/C,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IACD,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACrB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,SAAS,CAAC,KAAK,EAAE,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public desktop OAuth client used by the npm distribution.
|
|
3
|
+
*
|
|
4
|
+
* Google treats installed applications as unable to keep a client secret.
|
|
5
|
+
* Set these values once after creating the production Desktop OAuth client.
|
|
6
|
+
* Until then, developers can use credentials.json or environment variables.
|
|
7
|
+
*/
|
|
8
|
+
export declare const BUNDLED_GOOGLE_OAUTH_CLIENT: {
|
|
9
|
+
readonly clientId: "749884149912-4nl430614qdfm27clnqtj0imvvfr7j5g.apps.googleusercontent.com";
|
|
10
|
+
readonly clientSecret: "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";
|
|
11
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public desktop OAuth client used by the npm distribution.
|
|
3
|
+
*
|
|
4
|
+
* Google treats installed applications as unable to keep a client secret.
|
|
5
|
+
* Set these values once after creating the production Desktop OAuth client.
|
|
6
|
+
* Until then, developers can use credentials.json or environment variables.
|
|
7
|
+
*/
|
|
8
|
+
export const BUNDLED_GOOGLE_OAUTH_CLIENT = {
|
|
9
|
+
clientId: "749884149912-4nl430614qdfm27clnqtj0imvvfr7j5g.apps.googleusercontent.com",
|
|
10
|
+
clientSecret: "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"
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=bundled-oauth-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundled-oauth-client.js","sourceRoot":"","sources":["../../src/auth/bundled-oauth-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,QAAQ,EAAE,0EAA0E;IACpF,YAAY,EAAE,qCAAqC;CAC3C,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type AuthStatus = {
|
|
2
|
+
authenticated: boolean;
|
|
3
|
+
oauthClientConfigured: boolean;
|
|
4
|
+
oauthClientSource: "environment" | "credentials_file" | "bundled" | "missing";
|
|
5
|
+
credentialsPath: string;
|
|
6
|
+
tokenPath: string;
|
|
7
|
+
grantedScopes: string[];
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function getAuthStatus(): Promise<AuthStatus>;
|
|
11
|
+
export declare function login(): Promise<AuthStatus>;
|
|
12
|
+
export declare function getAuthorizedClient(): Promise<import("googleapis-common").OAuth2Client>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { google } from "googleapis";
|
|
5
|
+
import { credentialsPath, GOOGLE_SCOPES, tokenPath } from "../config.js";
|
|
6
|
+
import { BUNDLED_GOOGLE_OAUTH_CLIENT } from "./bundled-oauth-client.js";
|
|
7
|
+
async function readJson(filePath) {
|
|
8
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
9
|
+
}
|
|
10
|
+
async function loadClientConfig() {
|
|
11
|
+
const environmentClientId = process.env.EDU_WORKSPACE_GOOGLE_CLIENT_ID?.trim();
|
|
12
|
+
if (environmentClientId) {
|
|
13
|
+
return {
|
|
14
|
+
source: "environment",
|
|
15
|
+
config: {
|
|
16
|
+
client_id: environmentClientId,
|
|
17
|
+
client_secret: process.env.EDU_WORKSPACE_GOOGLE_CLIENT_SECRET?.trim() ?? "",
|
|
18
|
+
redirect_uris: ["http://127.0.0.1"]
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const credentials = (await readJson(credentialsPath()));
|
|
24
|
+
const clientConfig = credentials.installed ?? credentials.web;
|
|
25
|
+
if (clientConfig)
|
|
26
|
+
return { config: clientConfig, source: "credentials_file" };
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Continue to the bundled public client.
|
|
30
|
+
}
|
|
31
|
+
if (BUNDLED_GOOGLE_OAUTH_CLIENT.clientId) {
|
|
32
|
+
return {
|
|
33
|
+
source: "bundled",
|
|
34
|
+
config: {
|
|
35
|
+
client_id: BUNDLED_GOOGLE_OAUTH_CLIENT.clientId,
|
|
36
|
+
client_secret: BUNDLED_GOOGLE_OAUTH_CLIENT.clientSecret,
|
|
37
|
+
redirect_uris: ["http://127.0.0.1"]
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
throw new Error("κ³΅μ© Google OAuth μ±μ΄ μμ§ κ΅¬μ±λμ§ μμμ΅λλ€. κ°λ°μλ credentials.json λλ EDU_WORKSPACE_GOOGLE_CLIENT_IDλ₯Ό μ€μ νμΈμ.");
|
|
42
|
+
}
|
|
43
|
+
function createOAuthClient(config, redirectUri) {
|
|
44
|
+
return new google.auth.OAuth2(config.client_id, config.client_secret, redirectUri ?? config.redirect_uris[0]);
|
|
45
|
+
}
|
|
46
|
+
export async function getAuthStatus() {
|
|
47
|
+
const credentialFile = credentialsPath();
|
|
48
|
+
const tokenFile = tokenPath();
|
|
49
|
+
let oauthClientSource = "missing";
|
|
50
|
+
try {
|
|
51
|
+
oauthClientSource = (await loadClientConfig()).source;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Report missing configuration below.
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
if (oauthClientSource === "missing")
|
|
58
|
+
throw new Error("OAuth client missing");
|
|
59
|
+
const token = (await readJson(tokenFile));
|
|
60
|
+
return {
|
|
61
|
+
authenticated: true,
|
|
62
|
+
oauthClientConfigured: true,
|
|
63
|
+
oauthClientSource,
|
|
64
|
+
credentialsPath: credentialFile,
|
|
65
|
+
tokenPath: tokenFile,
|
|
66
|
+
grantedScopes: token.scope?.split(" ").filter(Boolean) ?? [],
|
|
67
|
+
message: "Google κ³μ μ΄ μ°κ²°λμ΄ μμ΅λλ€."
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return {
|
|
72
|
+
authenticated: false,
|
|
73
|
+
oauthClientConfigured: oauthClientSource !== "missing",
|
|
74
|
+
oauthClientSource,
|
|
75
|
+
credentialsPath: credentialFile,
|
|
76
|
+
tokenPath: tokenFile,
|
|
77
|
+
grantedScopes: [],
|
|
78
|
+
message: oauthClientSource === "missing"
|
|
79
|
+
? "κ³΅μ© Google OAuth μ±μ΄ μμ§ κ΅¬μ±λμ§ μμμ΅λλ€. κ°λ°μ© credentials.json λλ νκ²½ λ³μκ° νμν©λλ€."
|
|
80
|
+
: "Google κ³μ μ΄ μ°κ²°λμ§ μμμ΅λλ€. edu-workspace-mcp loginμ μ€ννμΈμ."
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function login() {
|
|
85
|
+
const credentialFile = credentialsPath();
|
|
86
|
+
const tokenFile = tokenPath();
|
|
87
|
+
const { config } = await loadClientConfig();
|
|
88
|
+
const callbackServer = createServer();
|
|
89
|
+
const authorizationCode = new Promise((resolve, reject) => {
|
|
90
|
+
callbackServer.once("request", (request, response) => {
|
|
91
|
+
const callbackUrl = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
92
|
+
const error = callbackUrl.searchParams.get("error");
|
|
93
|
+
const code = callbackUrl.searchParams.get("code");
|
|
94
|
+
if (error || !code) {
|
|
95
|
+
response.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
96
|
+
response.end("Google λ‘κ·ΈμΈμ μλ£νμ§ λͺ»νμ΅λλ€. ν°λ―Έλμμ λ€μ μλνμΈμ.");
|
|
97
|
+
reject(new Error(error ? `Google OAuth μ€λ₯: ${error}` : "OAuth μ½λ°±μ μΈμ¦ μ½λκ° μμ΅λλ€."));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
101
|
+
response.end("λ‘κ·ΈμΈμ΄ μλ£λμμ΅λλ€. μ΄ μ°½μ λ«κ³ ν°λ―Έλλ‘ λμκ°μΈμ.");
|
|
102
|
+
resolve(code);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
await new Promise((resolve, reject) => {
|
|
106
|
+
callbackServer.once("error", reject);
|
|
107
|
+
callbackServer.listen(0, "127.0.0.1", () => resolve());
|
|
108
|
+
});
|
|
109
|
+
const address = callbackServer.address();
|
|
110
|
+
if (!address || typeof address === "string") {
|
|
111
|
+
callbackServer.close();
|
|
112
|
+
throw new Error("OAuth μ½λ°± μλ²μ ν¬νΈλ₯Ό ν λΉνμ§ λͺ»νμ΅λλ€.");
|
|
113
|
+
}
|
|
114
|
+
const redirectUri = `http://127.0.0.1:${address.port}/oauth2callback`;
|
|
115
|
+
const client = createOAuthClient(config, redirectUri);
|
|
116
|
+
const authUrl = client.generateAuthUrl({
|
|
117
|
+
access_type: "offline",
|
|
118
|
+
prompt: "consent",
|
|
119
|
+
scope: [...GOOGLE_SCOPES]
|
|
120
|
+
});
|
|
121
|
+
console.log("λΈλΌμ°μ μμ μλ URLμ μ΄μ΄ Google λ‘κ·ΈμΈμ μλ£νμΈμ:\n");
|
|
122
|
+
console.log(authUrl);
|
|
123
|
+
let code;
|
|
124
|
+
try {
|
|
125
|
+
code = await authorizationCode;
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
callbackServer.close();
|
|
129
|
+
}
|
|
130
|
+
const { tokens } = await client.getToken(code);
|
|
131
|
+
if (!tokens.refresh_token) {
|
|
132
|
+
throw new Error("κ°±μ ν ν°μ λ°μ§ λͺ»νμ΅λλ€. Google κ³μ μμ κΈ°μ‘΄ μ± κΆνμ ν΄μ ν λ€ λ€μ λ‘κ·ΈμΈνμΈμ.");
|
|
133
|
+
}
|
|
134
|
+
await mkdir(path.dirname(tokenFile), { recursive: true });
|
|
135
|
+
await writeFile(tokenFile, JSON.stringify(tokens, null, 2), { mode: 0o600 });
|
|
136
|
+
return getAuthStatus();
|
|
137
|
+
}
|
|
138
|
+
export async function getAuthorizedClient() {
|
|
139
|
+
const tokenFile = tokenPath();
|
|
140
|
+
const { config } = await loadClientConfig();
|
|
141
|
+
const client = createOAuthClient(config);
|
|
142
|
+
client.setCredentials((await readJson(tokenFile)));
|
|
143
|
+
return client;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=google-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-auth.js","sourceRoot":"","sources":["../../src/auth/google-auth.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
3
|
+
import { APP_DIRECTORY, credentialsPath, tokenPath } from "./config.js";
|
|
4
|
+
import { getAuthStatus, getAuthorizedClient, login } from "./auth/google-auth.js";
|
|
5
|
+
function printHelp() {
|
|
6
|
+
console.log(`
|
|
7
|
+
edu-workspace-mcp
|
|
8
|
+
|
|
9
|
+
λͺ
λ Ήμ΄:
|
|
10
|
+
setup μ΄κΈ° ν΄λμ MCP μ€μ μμλ₯Ό 보μ¬μ€λλ€.
|
|
11
|
+
login Google OAuth λ‘κ·ΈμΈ ν ν ν°μ μμ ν λ‘컬 κ²½λ‘μ μ μ₯ν©λλ€.
|
|
12
|
+
doctor OAuth μ격 μ¦λͺ
Β·ν ν° μνλ₯Ό νμΈν©λλ€.
|
|
13
|
+
disconnect μ°κ²° ν΄μ λ°©λ²μ μλ΄ν©λλ€.
|
|
14
|
+
serve stdio MCP μλ²λ₯Ό μμν©λλ€. λͺ
λ Ήμ΄ μμ λμ κΈ°λ³Έ λμμ
λλ€.
|
|
15
|
+
`);
|
|
16
|
+
}
|
|
17
|
+
async function main() {
|
|
18
|
+
const command = process.argv[2] ?? "serve";
|
|
19
|
+
if (command === "serve") {
|
|
20
|
+
await import("./index.js");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (command === "setup") {
|
|
24
|
+
await mkdir(APP_DIRECTORY, { recursive: true });
|
|
25
|
+
const status = await getAuthStatus();
|
|
26
|
+
console.log(`\nμ€μ ν΄λ: ${APP_DIRECTORY}`);
|
|
27
|
+
console.log(`OAuth ν΄λΌμ΄μΈνΈ: ${status.oauthClientConfigured ? `${status.oauthClientSource} μ€μ μ¬μ©` : "μμ§ κ΅¬μ±λμ§ μμ"}`);
|
|
28
|
+
console.log(`\nCodex/ChatGPT Desktop μμ:\n[mcp_servers.edu_workspace]\ncommand = "npx"\nargs = ["-y", "edu-workspace-mcp"]`);
|
|
29
|
+
console.log(`\nClaude Desktop/Cursor μμ:\n{\n "mcpServers": {\n "edu-workspace": {\n "command": "npx",\n "args": ["-y", "edu-workspace-mcp"]\n }\n }\n}`);
|
|
30
|
+
console.log(status.oauthClientConfigured
|
|
31
|
+
? "\n`npx edu-workspace-mcp login`μΌλ‘ Google κ³μ μ μ°κ²°νμΈμ."
|
|
32
|
+
: `\nκ³΅κ° λ°°ν¬ μ μλ λ°°ν¬μκ° κ³΅μ© OAuth μ±μ μ€μ ν΄μΌ ν©λλ€. κ°λ° μ€μλ ${credentialsPath()}μ credentials.jsonμ λ μ μμ΅λλ€.`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (command === "login") {
|
|
36
|
+
const status = await login();
|
|
37
|
+
console.log(status.message);
|
|
38
|
+
console.log(`ν ν° μ μ₯ μμΉ: ${status.tokenPath}`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (command === "doctor") {
|
|
42
|
+
const status = await getAuthStatus();
|
|
43
|
+
console.log(JSON.stringify(status, null, 2));
|
|
44
|
+
if (status.authenticated) {
|
|
45
|
+
const client = await getAuthorizedClient();
|
|
46
|
+
await client.getAccessToken();
|
|
47
|
+
console.log("Google OAuth ν ν° κ°±μ : μ μ");
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (command === "disconnect") {
|
|
52
|
+
await rm(tokenPath(), { force: true });
|
|
53
|
+
console.log(`λ‘컬 Google ν ν°μ μ κ±°νμ΅λλ€: ${tokenPath()}`);
|
|
54
|
+
console.log("Google κ³μ μ μ 3μ μ± μ‘μΈμ€ νμ΄μ§μμλ μ΄ μ±μ κΆνμ μ² νν μ μμ΅λλ€.");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
printHelp();
|
|
58
|
+
}
|
|
59
|
+
main().catch((error) => {
|
|
60
|
+
console.error(error instanceof Error ? error.message : error);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const APP_NAME = "edu-workspace-mcp";
|
|
2
|
+
export declare const APP_DIRECTORY: string;
|
|
3
|
+
export declare const DEFAULT_CREDENTIALS_PATH: string;
|
|
4
|
+
export declare const DEFAULT_TOKEN_PATH: string;
|
|
5
|
+
export declare const GOOGLE_SCOPES: readonly ["https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/classroom.courses.readonly", "https://www.googleapis.com/auth/classroom.coursework.me"];
|
|
6
|
+
export declare function credentialsPath(): string;
|
|
7
|
+
export declare function tokenPath(): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const APP_NAME = "edu-workspace-mcp";
|
|
4
|
+
export const APP_DIRECTORY = path.join(os.homedir(), `.${APP_NAME}`);
|
|
5
|
+
export const DEFAULT_CREDENTIALS_PATH = path.join(APP_DIRECTORY, "credentials.json");
|
|
6
|
+
export const DEFAULT_TOKEN_PATH = path.join(APP_DIRECTORY, "token.json");
|
|
7
|
+
export const GOOGLE_SCOPES = [
|
|
8
|
+
"https://www.googleapis.com/auth/drive.file",
|
|
9
|
+
"https://www.googleapis.com/auth/classroom.courses.readonly",
|
|
10
|
+
"https://www.googleapis.com/auth/classroom.coursework.me"
|
|
11
|
+
];
|
|
12
|
+
export function credentialsPath() {
|
|
13
|
+
return process.env.EDU_WORKSPACE_CREDENTIALS_PATH ?? DEFAULT_CREDENTIALS_PATH;
|
|
14
|
+
}
|
|
15
|
+
export function tokenPath() {
|
|
16
|
+
return process.env.EDU_WORKSPACE_TOKEN_PATH ?? DEFAULT_TOKEN_PATH;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,CAAC,MAAM,QAAQ,GAAG,mBAAmB,CAAC;AAE5C,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,IAAI,QAAQ,EAAE,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,kBAAkB,CAAC,CAAC;AACrF,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;AAEzE,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,4CAA4C;IAC5C,4DAA4D;IAC5D,yDAAyD;CACjD,CAAC;AAEX,MAAM,UAAU,eAAe;IAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,8BAA8B,IAAI,wBAAwB,CAAC;AAChF,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,kBAAkB,CAAC;AACpE,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type CourseSummary = {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
section?: string;
|
|
5
|
+
state?: string;
|
|
6
|
+
alternateLink?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function listCourses(query?: string): Promise<CourseSummary[]>;
|
|
9
|
+
export type AssignmentDraftInput = {
|
|
10
|
+
courseId: string;
|
|
11
|
+
title: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
dueAt?: string;
|
|
14
|
+
maxPoints?: number;
|
|
15
|
+
materials?: Array<{
|
|
16
|
+
title: string;
|
|
17
|
+
url: string;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
20
|
+
export declare function createAssignmentDraft(input: AssignmentDraftInput): Promise<{
|
|
21
|
+
courseId: string;
|
|
22
|
+
courseWorkId: string | null | undefined;
|
|
23
|
+
title: string | null | undefined;
|
|
24
|
+
state: string | null | undefined;
|
|
25
|
+
alternateLink: string | null | undefined;
|
|
26
|
+
dueDate: import("googleapis").classroom_v1.Schema$Date | undefined;
|
|
27
|
+
dueTime: import("googleapis").classroom_v1.Schema$TimeOfDay | undefined;
|
|
28
|
+
materials: {
|
|
29
|
+
title: string;
|
|
30
|
+
url: string;
|
|
31
|
+
}[];
|
|
32
|
+
}>;
|
|
33
|
+
export declare function publishAssignment(courseId: string, courseWorkId: string): Promise<{
|
|
34
|
+
courseId: string;
|
|
35
|
+
courseWorkId: string;
|
|
36
|
+
state: string | null | undefined;
|
|
37
|
+
title: string | null | undefined;
|
|
38
|
+
alternateLink: string | null | undefined;
|
|
39
|
+
}>;
|