gogcli-mcp-classroom 2.0.4 → 2.0.7

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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "Extended Google Classroom for Claude via gogcli — auth + full Classroom support (courses, rosters, coursework, submissions, announcements, topics, invitations)",
10
- "version": "2.0.4"
10
+ "version": "2.0.7"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "gogcli (Classroom)",
16
16
  "source": "./",
17
17
  "description": "Extended Google Classroom for Claude via gogcli — auth + full Classroom support (courses, rosters, coursework, submissions, announcements, topics, invitations)",
18
- "version": "2.0.4",
18
+ "version": "2.0.7",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gogcli-mcp-classroom",
3
3
  "displayName": "gogcli (Classroom)",
4
- "version": "2.0.4",
4
+ "version": "2.0.7",
5
5
  "description": "Extended Google Classroom for Claude via gogcli — auth + full Classroom support (courses, rosters, coursework, submissions, announcements, topics, invitations)",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/dist/index.js CHANGED
@@ -30864,6 +30864,11 @@ var EMPTY_COMPLETION_RESULT = {
30864
30864
  // ../gogcli-mcp/src/runner.ts
30865
30865
  import { spawn } from "node:child_process";
30866
30866
  var TIMEOUT_MS = 3e4;
30867
+ function envOrUndefined(key) {
30868
+ const value = process.env[key];
30869
+ if (!value || value.startsWith("${")) return void 0;
30870
+ return value;
30871
+ }
30867
30872
  function formatTimeout(ms) {
30868
30873
  const seconds = Math.round(ms / 1e3);
30869
30874
  if (seconds >= 60) {
@@ -30874,7 +30879,7 @@ function formatTimeout(ms) {
30874
30879
  }
30875
30880
  async function run(args, options = {}) {
30876
30881
  const { account, spawner = spawn, interactive = false, timeout } = options;
30877
- const effectiveAccount = account ?? process.env.GOG_ACCOUNT;
30882
+ const effectiveAccount = account ?? envOrUndefined("GOG_ACCOUNT");
30878
30883
  const fullArgs = ["--json", "--color=never"];
30879
30884
  if (!interactive) {
30880
30885
  fullArgs.push("--no-input");
@@ -30886,7 +30891,7 @@ async function run(args, options = {}) {
30886
30891
  const effectiveTimeout = timeout ?? TIMEOUT_MS;
30887
30892
  return new Promise((resolve, reject) => {
30888
30893
  const { GOG_ACCESS_TOKEN: _, ...cleanEnv } = process.env;
30889
- const child = spawner(process.env.GOG_PATH ?? "gog", fullArgs, { env: cleanEnv });
30894
+ const child = spawner(envOrUndefined("GOG_PATH") ?? "gog", fullArgs, { env: cleanEnv });
30890
30895
  const stdoutChunks = [];
30891
30896
  const stderrChunks = [];
30892
30897
  let settled = false;
@@ -31061,83 +31066,6 @@ function registerClassroomTools(server2) {
31061
31066
  }, async ({ courseId, account }) => {
31062
31067
  return runOrDiagnose(["classroom", "courses", "get", courseId], { account });
31063
31068
  });
31064
- server2.registerTool("gog_classroom_courses_create", {
31065
- description: "Create a new Google Classroom course.",
31066
- inputSchema: {
31067
- name: external_exports.string().describe("Course name"),
31068
- owner: external_exports.string().optional().describe('Owner user ID (default: "me")'),
31069
- section: external_exports.string().optional().describe("Section"),
31070
- descriptionHeading: external_exports.string().optional().describe("Description heading"),
31071
- description: external_exports.string().optional().describe("Description"),
31072
- room: external_exports.string().optional().describe("Room"),
31073
- state: external_exports.string().optional().describe("Course state: ACTIVE, ARCHIVED, PROVISIONED, DECLINED, SUSPENDED"),
31074
- account: accountParam
31075
- }
31076
- }, async ({ name, owner, section, descriptionHeading, description, room, state, account }) => {
31077
- const args = ["classroom", "courses", "create", `--name=${name}`];
31078
- if (owner) args.push(`--owner=${owner}`);
31079
- if (section) args.push(`--section=${section}`);
31080
- if (descriptionHeading) args.push(`--description-heading=${descriptionHeading}`);
31081
- if (description) args.push(`--description=${description}`);
31082
- if (room) args.push(`--room=${room}`);
31083
- if (state) args.push(`--state=${state}`);
31084
- return runOrDiagnose(args, { account });
31085
- });
31086
- server2.registerTool("gog_classroom_courses_update", {
31087
- description: "Update an existing Google Classroom course.",
31088
- annotations: { destructiveHint: true },
31089
- inputSchema: {
31090
- courseId: external_exports.string().describe("Course ID"),
31091
- name: external_exports.string().optional().describe("Course name"),
31092
- owner: external_exports.string().optional().describe("Owner user ID"),
31093
- section: external_exports.string().optional().describe("Section"),
31094
- descriptionHeading: external_exports.string().optional().describe("Description heading"),
31095
- description: external_exports.string().optional().describe("Description"),
31096
- room: external_exports.string().optional().describe("Room"),
31097
- state: external_exports.string().optional().describe("Course state: ACTIVE, ARCHIVED, PROVISIONED, DECLINED, SUSPENDED"),
31098
- account: accountParam
31099
- }
31100
- }, async ({ courseId, name, owner, section, descriptionHeading, description, room, state, account }) => {
31101
- const args = ["classroom", "courses", "update", courseId];
31102
- if (name) args.push(`--name=${name}`);
31103
- if (owner) args.push(`--owner=${owner}`);
31104
- if (section) args.push(`--section=${section}`);
31105
- if (descriptionHeading) args.push(`--description-heading=${descriptionHeading}`);
31106
- if (description) args.push(`--description=${description}`);
31107
- if (room) args.push(`--room=${room}`);
31108
- if (state) args.push(`--state=${state}`);
31109
- return runOrDiagnose(args, { account });
31110
- });
31111
- server2.registerTool("gog_classroom_courses_delete", {
31112
- description: "Delete a Google Classroom course.",
31113
- annotations: { destructiveHint: true },
31114
- inputSchema: {
31115
- courseId: external_exports.string().describe("Course ID"),
31116
- account: accountParam
31117
- }
31118
- }, async ({ courseId, account }) => {
31119
- return runOrDiagnose(["classroom", "courses", "delete", courseId], { account });
31120
- });
31121
- server2.registerTool("gog_classroom_courses_archive", {
31122
- description: "Archive a Google Classroom course.",
31123
- annotations: { destructiveHint: true },
31124
- inputSchema: {
31125
- courseId: external_exports.string().describe("Course ID"),
31126
- account: accountParam
31127
- }
31128
- }, async ({ courseId, account }) => {
31129
- return runOrDiagnose(["classroom", "courses", "archive", courseId], { account });
31130
- });
31131
- server2.registerTool("gog_classroom_courses_unarchive", {
31132
- description: "Unarchive a Google Classroom course (restore to ACTIVE).",
31133
- annotations: { destructiveHint: true },
31134
- inputSchema: {
31135
- courseId: external_exports.string().describe("Course ID"),
31136
- account: accountParam
31137
- }
31138
- }, async ({ courseId, account }) => {
31139
- return runOrDiagnose(["classroom", "courses", "unarchive", courseId], { account });
31140
- });
31141
31069
  server2.registerTool("gog_classroom_students_list", {
31142
31070
  description: "List students enrolled in a Google Classroom course.",
31143
31071
  annotations: { readOnlyHint: true },
@@ -31166,30 +31094,6 @@ function registerClassroomTools(server2) {
31166
31094
  }, async ({ courseId, userId, account }) => {
31167
31095
  return runOrDiagnose(["classroom", "students", "get", courseId, userId], { account });
31168
31096
  });
31169
- server2.registerTool("gog_classroom_students_add", {
31170
- description: "Add a student to a Google Classroom course.",
31171
- inputSchema: {
31172
- courseId: external_exports.string().describe("Course ID"),
31173
- userId: external_exports.string().describe('Student user ID (or "me")'),
31174
- enrollmentCode: external_exports.string().optional().describe("Enrollment code (required if adding self via code)"),
31175
- account: accountParam
31176
- }
31177
- }, async ({ courseId, userId, enrollmentCode, account }) => {
31178
- const args = ["classroom", "students", "add", courseId, userId];
31179
- if (enrollmentCode) args.push(`--enrollment-code=${enrollmentCode}`);
31180
- return runOrDiagnose(args, { account });
31181
- });
31182
- server2.registerTool("gog_classroom_students_remove", {
31183
- description: "Remove a student from a Google Classroom course.",
31184
- annotations: { destructiveHint: true },
31185
- inputSchema: {
31186
- courseId: external_exports.string().describe("Course ID"),
31187
- userId: external_exports.string().describe("Student user ID"),
31188
- account: accountParam
31189
- }
31190
- }, async ({ courseId, userId, account }) => {
31191
- return runOrDiagnose(["classroom", "students", "remove", courseId, userId], { account });
31192
- });
31193
31097
  server2.registerTool("gog_classroom_teachers_list", {
31194
31098
  description: "List teachers in a Google Classroom course.",
31195
31099
  annotations: { readOnlyHint: true },
@@ -31218,27 +31122,6 @@ function registerClassroomTools(server2) {
31218
31122
  }, async ({ courseId, userId, account }) => {
31219
31123
  return runOrDiagnose(["classroom", "teachers", "get", courseId, userId], { account });
31220
31124
  });
31221
- server2.registerTool("gog_classroom_teachers_add", {
31222
- description: "Add a teacher to a Google Classroom course.",
31223
- inputSchema: {
31224
- courseId: external_exports.string().describe("Course ID"),
31225
- userId: external_exports.string().describe("Teacher user ID"),
31226
- account: accountParam
31227
- }
31228
- }, async ({ courseId, userId, account }) => {
31229
- return runOrDiagnose(["classroom", "teachers", "add", courseId, userId], { account });
31230
- });
31231
- server2.registerTool("gog_classroom_teachers_remove", {
31232
- description: "Remove a teacher from a Google Classroom course.",
31233
- annotations: { destructiveHint: true },
31234
- inputSchema: {
31235
- courseId: external_exports.string().describe("Course ID"),
31236
- userId: external_exports.string().describe("Teacher user ID"),
31237
- account: accountParam
31238
- }
31239
- }, async ({ courseId, userId, account }) => {
31240
- return runOrDiagnose(["classroom", "teachers", "remove", courseId, userId], { account });
31241
- });
31242
31125
  server2.registerTool("gog_classroom_roster", {
31243
31126
  description: "List the full roster (students and/or teachers) of a Google Classroom course. Omit both flags to return both groups.",
31244
31127
  annotations: { readOnlyHint: true },
@@ -31296,78 +31179,6 @@ function registerClassroomTools(server2) {
31296
31179
  }, async ({ courseId, courseworkId, account }) => {
31297
31180
  return runOrDiagnose(["classroom", "coursework", "get", courseId, courseworkId], { account });
31298
31181
  });
31299
- server2.registerTool("gog_classroom_coursework_create", {
31300
- description: "Create a new coursework item (assignment, question, etc.) in a course.",
31301
- inputSchema: {
31302
- courseId: external_exports.string().describe("Course ID"),
31303
- title: external_exports.string().describe("Coursework title"),
31304
- description: external_exports.string().optional().describe("Description"),
31305
- type: external_exports.string().optional().describe("Work type (ASSIGNMENT, SHORT_ANSWER_QUESTION, MULTIPLE_CHOICE_QUESTION). Default: ASSIGNMENT"),
31306
- state: external_exports.string().optional().describe("State: PUBLISHED or DRAFT"),
31307
- maxPoints: external_exports.number().optional().describe("Max points"),
31308
- due: external_exports.string().optional().describe("Due datetime (combined date+time)"),
31309
- dueDate: external_exports.string().optional().describe("Due date (YYYY-MM-DD)"),
31310
- dueTime: external_exports.string().optional().describe("Due time (HH:MM)"),
31311
- scheduled: external_exports.string().optional().describe("Scheduled publish time"),
31312
- topic: external_exports.string().optional().describe("Topic ID"),
31313
- account: accountParam
31314
- }
31315
- }, async ({ courseId, title, description, type, state, maxPoints, due, dueDate, dueTime, scheduled, topic, account }) => {
31316
- const args = ["classroom", "coursework", "create", courseId, `--title=${title}`];
31317
- if (description) args.push(`--description=${description}`);
31318
- if (type) args.push(`--type=${type}`);
31319
- if (state) args.push(`--state=${state}`);
31320
- if (maxPoints !== void 0) args.push(`--max-points=${maxPoints}`);
31321
- if (due) args.push(`--due=${due}`);
31322
- if (dueDate) args.push(`--due-date=${dueDate}`);
31323
- if (dueTime) args.push(`--due-time=${dueTime}`);
31324
- if (scheduled) args.push(`--scheduled=${scheduled}`);
31325
- if (topic) args.push(`--topic=${topic}`);
31326
- return runOrDiagnose(args, { account });
31327
- });
31328
- server2.registerTool("gog_classroom_coursework_update", {
31329
- description: "Update an existing coursework item.",
31330
- annotations: { destructiveHint: true },
31331
- inputSchema: {
31332
- courseId: external_exports.string().describe("Course ID"),
31333
- courseworkId: external_exports.string().describe("Coursework ID"),
31334
- title: external_exports.string().optional().describe("New title"),
31335
- description: external_exports.string().optional().describe("New description"),
31336
- type: external_exports.string().optional().describe("Work type"),
31337
- state: external_exports.string().optional().describe("State: PUBLISHED or DRAFT"),
31338
- maxPoints: external_exports.number().optional().describe("Max points"),
31339
- due: external_exports.string().optional().describe("Due datetime"),
31340
- dueDate: external_exports.string().optional().describe("Due date (YYYY-MM-DD)"),
31341
- dueTime: external_exports.string().optional().describe("Due time (HH:MM)"),
31342
- scheduled: external_exports.string().optional().describe("Scheduled publish time"),
31343
- topic: external_exports.string().optional().describe("Topic ID"),
31344
- account: accountParam
31345
- }
31346
- }, async ({ courseId, courseworkId, title, description, type, state, maxPoints, due, dueDate, dueTime, scheduled, topic, account }) => {
31347
- const args = ["classroom", "coursework", "update", courseId, courseworkId];
31348
- if (title) args.push(`--title=${title}`);
31349
- if (description) args.push(`--description=${description}`);
31350
- if (type) args.push(`--type=${type}`);
31351
- if (state) args.push(`--state=${state}`);
31352
- if (maxPoints !== void 0) args.push(`--max-points=${maxPoints}`);
31353
- if (due) args.push(`--due=${due}`);
31354
- if (dueDate) args.push(`--due-date=${dueDate}`);
31355
- if (dueTime) args.push(`--due-time=${dueTime}`);
31356
- if (scheduled) args.push(`--scheduled=${scheduled}`);
31357
- if (topic) args.push(`--topic=${topic}`);
31358
- return runOrDiagnose(args, { account });
31359
- });
31360
- server2.registerTool("gog_classroom_coursework_delete", {
31361
- description: "Delete a coursework item.",
31362
- annotations: { destructiveHint: true },
31363
- inputSchema: {
31364
- courseId: external_exports.string().describe("Course ID"),
31365
- courseworkId: external_exports.string().describe("Coursework ID"),
31366
- account: accountParam
31367
- }
31368
- }, async ({ courseId, courseworkId, account }) => {
31369
- return runOrDiagnose(["classroom", "coursework", "delete", courseId, courseworkId], { account });
31370
- });
31371
31182
  server2.registerTool("gog_classroom_submissions_list", {
31372
31183
  description: "List student submissions for a coursework item.",
31373
31184
  annotations: { readOnlyHint: true },
@@ -31494,7 +31305,7 @@ function registerClassroomTools(server2) {
31494
31305
  inputSchema: {
31495
31306
  courseId: external_exports.string().describe("Course ID"),
31496
31307
  text: external_exports.string().describe("Announcement text"),
31497
- state: external_exports.string().optional().describe("State: PUBLISHED or DRAFT"),
31308
+ state: external_exports.enum(["PUBLISHED", "DRAFT"]).optional().describe("State"),
31498
31309
  scheduled: external_exports.string().optional().describe("Scheduled publish time"),
31499
31310
  account: accountParam
31500
31311
  }
@@ -31504,35 +31315,6 @@ function registerClassroomTools(server2) {
31504
31315
  if (scheduled) args.push(`--scheduled=${scheduled}`);
31505
31316
  return runOrDiagnose(args, { account });
31506
31317
  });
31507
- server2.registerTool("gog_classroom_announcements_update", {
31508
- description: "Update an existing announcement.",
31509
- annotations: { destructiveHint: true },
31510
- inputSchema: {
31511
- courseId: external_exports.string().describe("Course ID"),
31512
- announcementId: external_exports.string().describe("Announcement ID"),
31513
- text: external_exports.string().optional().describe("New text"),
31514
- state: external_exports.string().optional().describe("State: PUBLISHED or DRAFT"),
31515
- scheduled: external_exports.string().optional().describe("Scheduled publish time"),
31516
- account: accountParam
31517
- }
31518
- }, async ({ courseId, announcementId, text, state, scheduled, account }) => {
31519
- const args = ["classroom", "announcements", "update", courseId, announcementId];
31520
- if (text) args.push(`--text=${text}`);
31521
- if (state) args.push(`--state=${state}`);
31522
- if (scheduled) args.push(`--scheduled=${scheduled}`);
31523
- return runOrDiagnose(args, { account });
31524
- });
31525
- server2.registerTool("gog_classroom_announcements_delete", {
31526
- description: "Delete an announcement.",
31527
- annotations: { destructiveHint: true },
31528
- inputSchema: {
31529
- courseId: external_exports.string().describe("Course ID"),
31530
- announcementId: external_exports.string().describe("Announcement ID"),
31531
- account: accountParam
31532
- }
31533
- }, async ({ courseId, announcementId, account }) => {
31534
- return runOrDiagnose(["classroom", "announcements", "delete", courseId, announcementId], { account });
31535
- });
31536
31318
  server2.registerTool("gog_classroom_topics_list", {
31537
31319
  description: "List topics in a Google Classroom course.",
31538
31320
  annotations: { readOnlyHint: true },
@@ -31561,39 +31343,6 @@ function registerClassroomTools(server2) {
31561
31343
  }, async ({ courseId, topicId, account }) => {
31562
31344
  return runOrDiagnose(["classroom", "topics", "get", courseId, topicId], { account });
31563
31345
  });
31564
- server2.registerTool("gog_classroom_topics_create", {
31565
- description: "Create a topic in a Google Classroom course.",
31566
- inputSchema: {
31567
- courseId: external_exports.string().describe("Course ID"),
31568
- name: external_exports.string().describe("Topic name"),
31569
- account: accountParam
31570
- }
31571
- }, async ({ courseId, name, account }) => {
31572
- return runOrDiagnose(["classroom", "topics", "create", courseId, `--name=${name}`], { account });
31573
- });
31574
- server2.registerTool("gog_classroom_topics_update", {
31575
- description: "Rename an existing topic.",
31576
- annotations: { destructiveHint: true },
31577
- inputSchema: {
31578
- courseId: external_exports.string().describe("Course ID"),
31579
- topicId: external_exports.string().describe("Topic ID"),
31580
- name: external_exports.string().describe("New topic name"),
31581
- account: accountParam
31582
- }
31583
- }, async ({ courseId, topicId, name, account }) => {
31584
- return runOrDiagnose(["classroom", "topics", "update", courseId, topicId, `--name=${name}`], { account });
31585
- });
31586
- server2.registerTool("gog_classroom_topics_delete", {
31587
- description: "Delete a topic.",
31588
- annotations: { destructiveHint: true },
31589
- inputSchema: {
31590
- courseId: external_exports.string().describe("Course ID"),
31591
- topicId: external_exports.string().describe("Topic ID"),
31592
- account: accountParam
31593
- }
31594
- }, async ({ courseId, topicId, account }) => {
31595
- return runOrDiagnose(["classroom", "topics", "delete", courseId, topicId], { account });
31596
- });
31597
31346
  server2.registerTool("gog_classroom_invitations_list", {
31598
31347
  description: "List Google Classroom invitations.",
31599
31348
  annotations: { readOnlyHint: true },
@@ -31624,17 +31373,6 @@ function registerClassroomTools(server2) {
31624
31373
  }, async ({ invitationId, account }) => {
31625
31374
  return runOrDiagnose(["classroom", "invitations", "get", invitationId], { account });
31626
31375
  });
31627
- server2.registerTool("gog_classroom_invitations_create", {
31628
- description: "Create an invitation to a Google Classroom course.",
31629
- inputSchema: {
31630
- courseId: external_exports.string().describe("Course ID"),
31631
- userId: external_exports.string().describe("User ID to invite"),
31632
- role: external_exports.enum(["STUDENT", "TEACHER", "OWNER"]).describe("Role for the invited user"),
31633
- account: accountParam
31634
- }
31635
- }, async ({ courseId, userId, role, account }) => {
31636
- return runOrDiagnose(["classroom", "invitations", "create", courseId, userId, `--role=${role}`], { account });
31637
- });
31638
31376
  server2.registerTool("gog_classroom_invitations_accept", {
31639
31377
  description: "Accept a Google Classroom invitation.",
31640
31378
  inputSchema: {
@@ -31644,16 +31382,6 @@ function registerClassroomTools(server2) {
31644
31382
  }, async ({ invitationId, account }) => {
31645
31383
  return runOrDiagnose(["classroom", "invitations", "accept", invitationId], { account });
31646
31384
  });
31647
- server2.registerTool("gog_classroom_invitations_delete", {
31648
- description: "Delete (revoke) a Google Classroom invitation.",
31649
- annotations: { destructiveHint: true },
31650
- inputSchema: {
31651
- invitationId: external_exports.string().describe("Invitation ID"),
31652
- account: accountParam
31653
- }
31654
- }, async ({ invitationId, account }) => {
31655
- return runOrDiagnose(["classroom", "invitations", "delete", invitationId], { account });
31656
- });
31657
31385
  server2.registerTool("gog_classroom_profile_get", {
31658
31386
  description: "Get a Google Classroom user profile. Omit userId to fetch the authenticated user.",
31659
31387
  annotations: { readOnlyHint: true },
@@ -31668,6 +31396,7 @@ function registerClassroomTools(server2) {
31668
31396
  });
31669
31397
  server2.registerTool("gog_classroom_run", {
31670
31398
  description: "Run any gog classroom subcommand not covered by the other tools (guardians, guardian-invitations, materials, coursework assignees, announcement assignees, etc.). Run `gog classroom --help` for the full list, or `gog classroom <subcommand> --help` for flags.",
31399
+ annotations: { destructiveHint: true },
31671
31400
  inputSchema: {
31672
31401
  subcommand: external_exports.string().describe('The gog classroom subcommand to run, e.g. "guardians", "materials", "guardian-invitations"'),
31673
31402
  args: external_exports.array(external_exports.string()).describe("Additional positional args and flags"),
@@ -31679,7 +31408,7 @@ function registerClassroomTools(server2) {
31679
31408
  }
31680
31409
 
31681
31410
  // ../gogcli-mcp/src/server.ts
31682
- var VERSION = true ? "2.0.4" : "0.0.0";
31411
+ var VERSION = true ? "2.0.7" : "0.0.0";
31683
31412
  function createServer(options) {
31684
31413
  return new McpServer({
31685
31414
  name: options?.name ?? "gogcli",
@@ -31688,7 +31417,279 @@ function createServer(options) {
31688
31417
  }
31689
31418
 
31690
31419
  // src/tools/classroom-extra.ts
31691
- function registerExtraClassroomTools(_server) {
31420
+ var courseState = external_exports.enum(["ACTIVE", "ARCHIVED", "PROVISIONED", "DECLINED", "SUSPENDED"]);
31421
+ var workState = external_exports.enum(["PUBLISHED", "DRAFT"]);
31422
+ var workType = external_exports.enum(["ASSIGNMENT", "SHORT_ANSWER_QUESTION", "MULTIPLE_CHOICE_QUESTION"]);
31423
+ var courseSharedFields = {
31424
+ owner: external_exports.string().optional().describe('Owner user ID (default "me" on create)'),
31425
+ section: external_exports.string().optional().describe("Section"),
31426
+ descriptionHeading: external_exports.string().optional().describe("Description heading"),
31427
+ description: external_exports.string().optional().describe("Description"),
31428
+ room: external_exports.string().optional().describe("Room"),
31429
+ state: courseState.optional().describe("Course state")
31430
+ };
31431
+ var courseworkSharedFields = {
31432
+ description: external_exports.string().optional().describe("Description"),
31433
+ type: workType.optional().describe("Work type (default: ASSIGNMENT)"),
31434
+ state: workState.optional().describe("State"),
31435
+ maxPoints: external_exports.number().optional().describe("Max points"),
31436
+ due: external_exports.string().optional().describe("Due datetime (combined date+time)"),
31437
+ dueDate: external_exports.string().optional().describe("Due date (YYYY-MM-DD)"),
31438
+ dueTime: external_exports.string().optional().describe("Due time (HH:MM)"),
31439
+ scheduled: external_exports.string().optional().describe("Scheduled publish time"),
31440
+ topic: external_exports.string().optional().describe("Topic ID")
31441
+ };
31442
+ function registerExtraClassroomTools(server2) {
31443
+ server2.registerTool("gog_classroom_courses_create", {
31444
+ description: "Create a new Google Classroom course.",
31445
+ inputSchema: {
31446
+ name: external_exports.string().describe("Course name"),
31447
+ ...courseSharedFields,
31448
+ account: accountParam
31449
+ }
31450
+ }, async ({ name, owner, section, descriptionHeading, description, room, state, account }) => {
31451
+ const args = ["classroom", "courses", "create", `--name=${name}`];
31452
+ if (owner) args.push(`--owner=${owner}`);
31453
+ if (section) args.push(`--section=${section}`);
31454
+ if (descriptionHeading) args.push(`--description-heading=${descriptionHeading}`);
31455
+ if (description) args.push(`--description=${description}`);
31456
+ if (room) args.push(`--room=${room}`);
31457
+ if (state) args.push(`--state=${state}`);
31458
+ return runOrDiagnose(args, { account });
31459
+ });
31460
+ server2.registerTool("gog_classroom_courses_update", {
31461
+ description: "Update an existing Google Classroom course.",
31462
+ annotations: { destructiveHint: true },
31463
+ inputSchema: {
31464
+ courseId: external_exports.string().describe("Course ID"),
31465
+ name: external_exports.string().optional().describe("Course name"),
31466
+ ...courseSharedFields,
31467
+ account: accountParam
31468
+ }
31469
+ }, async ({ courseId, name, owner, section, descriptionHeading, description, room, state, account }) => {
31470
+ const args = ["classroom", "courses", "update", courseId];
31471
+ if (name) args.push(`--name=${name}`);
31472
+ if (owner) args.push(`--owner=${owner}`);
31473
+ if (section) args.push(`--section=${section}`);
31474
+ if (descriptionHeading) args.push(`--description-heading=${descriptionHeading}`);
31475
+ if (description) args.push(`--description=${description}`);
31476
+ if (room) args.push(`--room=${room}`);
31477
+ if (state) args.push(`--state=${state}`);
31478
+ return runOrDiagnose(args, { account });
31479
+ });
31480
+ server2.registerTool("gog_classroom_courses_delete", {
31481
+ description: "Delete a Google Classroom course.",
31482
+ annotations: { destructiveHint: true },
31483
+ inputSchema: {
31484
+ courseId: external_exports.string().describe("Course ID"),
31485
+ account: accountParam
31486
+ }
31487
+ }, async ({ courseId, account }) => {
31488
+ return runOrDiagnose(["classroom", "courses", "delete", courseId], { account });
31489
+ });
31490
+ server2.registerTool("gog_classroom_courses_archive", {
31491
+ description: "Archive a Google Classroom course.",
31492
+ annotations: { destructiveHint: true },
31493
+ inputSchema: {
31494
+ courseId: external_exports.string().describe("Course ID"),
31495
+ account: accountParam
31496
+ }
31497
+ }, async ({ courseId, account }) => {
31498
+ return runOrDiagnose(["classroom", "courses", "archive", courseId], { account });
31499
+ });
31500
+ server2.registerTool("gog_classroom_courses_unarchive", {
31501
+ description: "Unarchive a Google Classroom course (restore to ACTIVE).",
31502
+ inputSchema: {
31503
+ courseId: external_exports.string().describe("Course ID"),
31504
+ account: accountParam
31505
+ }
31506
+ }, async ({ courseId, account }) => {
31507
+ return runOrDiagnose(["classroom", "courses", "unarchive", courseId], { account });
31508
+ });
31509
+ server2.registerTool("gog_classroom_students_add", {
31510
+ description: "Add a student to a Google Classroom course.",
31511
+ inputSchema: {
31512
+ courseId: external_exports.string().describe("Course ID"),
31513
+ userId: external_exports.string().describe('Student user ID (or "me")'),
31514
+ enrollmentCode: external_exports.string().optional().describe("Enrollment code (required if adding self via code)"),
31515
+ account: accountParam
31516
+ }
31517
+ }, async ({ courseId, userId, enrollmentCode, account }) => {
31518
+ const args = ["classroom", "students", "add", courseId, userId];
31519
+ if (enrollmentCode) args.push(`--enrollment-code=${enrollmentCode}`);
31520
+ return runOrDiagnose(args, { account });
31521
+ });
31522
+ server2.registerTool("gog_classroom_students_remove", {
31523
+ description: "Remove a student from a Google Classroom course.",
31524
+ annotations: { destructiveHint: true },
31525
+ inputSchema: {
31526
+ courseId: external_exports.string().describe("Course ID"),
31527
+ userId: external_exports.string().describe("Student user ID"),
31528
+ account: accountParam
31529
+ }
31530
+ }, async ({ courseId, userId, account }) => {
31531
+ return runOrDiagnose(["classroom", "students", "remove", courseId, userId], { account });
31532
+ });
31533
+ server2.registerTool("gog_classroom_teachers_add", {
31534
+ description: "Add a teacher to a Google Classroom course.",
31535
+ inputSchema: {
31536
+ courseId: external_exports.string().describe("Course ID"),
31537
+ userId: external_exports.string().describe("Teacher user ID"),
31538
+ account: accountParam
31539
+ }
31540
+ }, async ({ courseId, userId, account }) => {
31541
+ return runOrDiagnose(["classroom", "teachers", "add", courseId, userId], { account });
31542
+ });
31543
+ server2.registerTool("gog_classroom_teachers_remove", {
31544
+ description: "Remove a teacher from a Google Classroom course.",
31545
+ annotations: { destructiveHint: true },
31546
+ inputSchema: {
31547
+ courseId: external_exports.string().describe("Course ID"),
31548
+ userId: external_exports.string().describe("Teacher user ID"),
31549
+ account: accountParam
31550
+ }
31551
+ }, async ({ courseId, userId, account }) => {
31552
+ return runOrDiagnose(["classroom", "teachers", "remove", courseId, userId], { account });
31553
+ });
31554
+ server2.registerTool("gog_classroom_coursework_create", {
31555
+ description: "Create a new coursework item (assignment, question, etc.) in a course.",
31556
+ inputSchema: {
31557
+ courseId: external_exports.string().describe("Course ID"),
31558
+ title: external_exports.string().describe("Coursework title"),
31559
+ ...courseworkSharedFields,
31560
+ account: accountParam
31561
+ }
31562
+ }, async ({ courseId, title, description, type, state, maxPoints, due, dueDate, dueTime, scheduled, topic, account }) => {
31563
+ const args = ["classroom", "coursework", "create", courseId, `--title=${title}`];
31564
+ if (description) args.push(`--description=${description}`);
31565
+ if (type) args.push(`--type=${type}`);
31566
+ if (state) args.push(`--state=${state}`);
31567
+ if (maxPoints !== void 0) args.push(`--max-points=${maxPoints}`);
31568
+ if (due) args.push(`--due=${due}`);
31569
+ if (dueDate) args.push(`--due-date=${dueDate}`);
31570
+ if (dueTime) args.push(`--due-time=${dueTime}`);
31571
+ if (scheduled) args.push(`--scheduled=${scheduled}`);
31572
+ if (topic) args.push(`--topic=${topic}`);
31573
+ return runOrDiagnose(args, { account });
31574
+ });
31575
+ server2.registerTool("gog_classroom_coursework_update", {
31576
+ description: "Update an existing coursework item.",
31577
+ annotations: { destructiveHint: true },
31578
+ inputSchema: {
31579
+ courseId: external_exports.string().describe("Course ID"),
31580
+ courseworkId: external_exports.string().describe("Coursework ID"),
31581
+ title: external_exports.string().optional().describe("New title"),
31582
+ ...courseworkSharedFields,
31583
+ account: accountParam
31584
+ }
31585
+ }, async ({ courseId, courseworkId, title, description, type, state, maxPoints, due, dueDate, dueTime, scheduled, topic, account }) => {
31586
+ const args = ["classroom", "coursework", "update", courseId, courseworkId];
31587
+ if (title) args.push(`--title=${title}`);
31588
+ if (description) args.push(`--description=${description}`);
31589
+ if (type) args.push(`--type=${type}`);
31590
+ if (state) args.push(`--state=${state}`);
31591
+ if (maxPoints !== void 0) args.push(`--max-points=${maxPoints}`);
31592
+ if (due) args.push(`--due=${due}`);
31593
+ if (dueDate) args.push(`--due-date=${dueDate}`);
31594
+ if (dueTime) args.push(`--due-time=${dueTime}`);
31595
+ if (scheduled) args.push(`--scheduled=${scheduled}`);
31596
+ if (topic) args.push(`--topic=${topic}`);
31597
+ return runOrDiagnose(args, { account });
31598
+ });
31599
+ server2.registerTool("gog_classroom_coursework_delete", {
31600
+ description: "Delete a coursework item.",
31601
+ annotations: { destructiveHint: true },
31602
+ inputSchema: {
31603
+ courseId: external_exports.string().describe("Course ID"),
31604
+ courseworkId: external_exports.string().describe("Coursework ID"),
31605
+ account: accountParam
31606
+ }
31607
+ }, async ({ courseId, courseworkId, account }) => {
31608
+ return runOrDiagnose(["classroom", "coursework", "delete", courseId, courseworkId], { account });
31609
+ });
31610
+ server2.registerTool("gog_classroom_announcements_update", {
31611
+ description: "Update an existing announcement.",
31612
+ annotations: { destructiveHint: true },
31613
+ inputSchema: {
31614
+ courseId: external_exports.string().describe("Course ID"),
31615
+ announcementId: external_exports.string().describe("Announcement ID"),
31616
+ text: external_exports.string().optional().describe("New text"),
31617
+ state: workState.optional().describe("State"),
31618
+ scheduled: external_exports.string().optional().describe("Scheduled publish time"),
31619
+ account: accountParam
31620
+ }
31621
+ }, async ({ courseId, announcementId, text, state, scheduled, account }) => {
31622
+ const args = ["classroom", "announcements", "update", courseId, announcementId];
31623
+ if (text) args.push(`--text=${text}`);
31624
+ if (state) args.push(`--state=${state}`);
31625
+ if (scheduled) args.push(`--scheduled=${scheduled}`);
31626
+ return runOrDiagnose(args, { account });
31627
+ });
31628
+ server2.registerTool("gog_classroom_announcements_delete", {
31629
+ description: "Delete an announcement.",
31630
+ annotations: { destructiveHint: true },
31631
+ inputSchema: {
31632
+ courseId: external_exports.string().describe("Course ID"),
31633
+ announcementId: external_exports.string().describe("Announcement ID"),
31634
+ account: accountParam
31635
+ }
31636
+ }, async ({ courseId, announcementId, account }) => {
31637
+ return runOrDiagnose(["classroom", "announcements", "delete", courseId, announcementId], { account });
31638
+ });
31639
+ server2.registerTool("gog_classroom_topics_create", {
31640
+ description: "Create a topic in a Google Classroom course.",
31641
+ inputSchema: {
31642
+ courseId: external_exports.string().describe("Course ID"),
31643
+ name: external_exports.string().describe("Topic name"),
31644
+ account: accountParam
31645
+ }
31646
+ }, async ({ courseId, name, account }) => {
31647
+ return runOrDiagnose(["classroom", "topics", "create", courseId, `--name=${name}`], { account });
31648
+ });
31649
+ server2.registerTool("gog_classroom_topics_update", {
31650
+ description: "Rename an existing topic.",
31651
+ annotations: { destructiveHint: true },
31652
+ inputSchema: {
31653
+ courseId: external_exports.string().describe("Course ID"),
31654
+ topicId: external_exports.string().describe("Topic ID"),
31655
+ name: external_exports.string().describe("New topic name"),
31656
+ account: accountParam
31657
+ }
31658
+ }, async ({ courseId, topicId, name, account }) => {
31659
+ return runOrDiagnose(["classroom", "topics", "update", courseId, topicId, `--name=${name}`], { account });
31660
+ });
31661
+ server2.registerTool("gog_classroom_topics_delete", {
31662
+ description: "Delete a topic.",
31663
+ annotations: { destructiveHint: true },
31664
+ inputSchema: {
31665
+ courseId: external_exports.string().describe("Course ID"),
31666
+ topicId: external_exports.string().describe("Topic ID"),
31667
+ account: accountParam
31668
+ }
31669
+ }, async ({ courseId, topicId, account }) => {
31670
+ return runOrDiagnose(["classroom", "topics", "delete", courseId, topicId], { account });
31671
+ });
31672
+ server2.registerTool("gog_classroom_invitations_create", {
31673
+ description: "Create an invitation to a Google Classroom course.",
31674
+ inputSchema: {
31675
+ courseId: external_exports.string().describe("Course ID"),
31676
+ userId: external_exports.string().describe("User ID to invite"),
31677
+ role: external_exports.enum(["STUDENT", "TEACHER", "OWNER"]).describe("Role for the invited user"),
31678
+ account: accountParam
31679
+ }
31680
+ }, async ({ courseId, userId, role, account }) => {
31681
+ return runOrDiagnose(["classroom", "invitations", "create", courseId, userId, `--role=${role}`], { account });
31682
+ });
31683
+ server2.registerTool("gog_classroom_invitations_delete", {
31684
+ description: "Delete (revoke) a Google Classroom invitation.",
31685
+ annotations: { destructiveHint: true },
31686
+ inputSchema: {
31687
+ invitationId: external_exports.string().describe("Invitation ID"),
31688
+ account: accountParam
31689
+ }
31690
+ }, async ({ invitationId, account }) => {
31691
+ return runOrDiagnose(["classroom", "invitations", "delete", invitationId], { account });
31692
+ });
31692
31693
  }
31693
31694
 
31694
31695
  // src/index.ts
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-classroom",
5
5
  "display_name": "gogcli (Classroom)",
6
- "version": "2.0.4",
6
+ "version": "2.0.7",
7
7
  "description": "Extended Google Classroom for Claude via gogcli — auth + full Classroom support (courses, rosters, coursework, submissions, announcements, topics, invitations)",
8
8
  "author": {
9
9
  "name": "Chris Hall",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-classroom",
3
- "version": "2.0.4",
3
+ "version": "2.0.7",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-classroom",
5
5
  "description": "Extended Google Classroom MCP server via gogcli — auth + full Classroom support",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "source": "github",
8
8
  "subfolder": "packages/gogcli-mcp-classroom"
9
9
  },
10
- "version": "2.0.4",
10
+ "version": "2.0.7",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-classroom",
15
- "version": "2.0.4",
15
+ "version": "2.0.7",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
@@ -1,5 +1,303 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { accountParam, runOrDiagnose } from '../../../gogcli-mcp/src/lib.js';
2
4
 
3
- export function registerExtraClassroomTools(_server: McpServer): void {
4
- /* no extras yet */
5
+ const courseState = z.enum(['ACTIVE', 'ARCHIVED', 'PROVISIONED', 'DECLINED', 'SUSPENDED']);
6
+ const workState = z.enum(['PUBLISHED', 'DRAFT']);
7
+ const workType = z.enum(['ASSIGNMENT', 'SHORT_ANSWER_QUESTION', 'MULTIPLE_CHOICE_QUESTION']);
8
+
9
+ // Fields shared by courses_create and courses_update. `name` is required on
10
+ // create, optional on update — keep it out of this fragment so each tool can
11
+ // declare its own rule.
12
+ const courseSharedFields = {
13
+ owner: z.string().optional().describe('Owner user ID (default "me" on create)'),
14
+ section: z.string().optional().describe('Section'),
15
+ descriptionHeading: z.string().optional().describe('Description heading'),
16
+ description: z.string().optional().describe('Description'),
17
+ room: z.string().optional().describe('Room'),
18
+ state: courseState.optional().describe('Course state'),
19
+ };
20
+
21
+ // Fields shared by coursework_create and coursework_update.
22
+ const courseworkSharedFields = {
23
+ description: z.string().optional().describe('Description'),
24
+ type: workType.optional().describe('Work type (default: ASSIGNMENT)'),
25
+ state: workState.optional().describe('State'),
26
+ maxPoints: z.number().optional().describe('Max points'),
27
+ due: z.string().optional().describe('Due datetime (combined date+time)'),
28
+ dueDate: z.string().optional().describe('Due date (YYYY-MM-DD)'),
29
+ dueTime: z.string().optional().describe('Due time (HH:MM)'),
30
+ scheduled: z.string().optional().describe('Scheduled publish time'),
31
+ topic: z.string().optional().describe('Topic ID'),
32
+ };
33
+
34
+ export function registerExtraClassroomTools(server: McpServer): void {
35
+ server.registerTool('gog_classroom_courses_create', {
36
+ description: 'Create a new Google Classroom course.',
37
+ inputSchema: {
38
+ name: z.string().describe('Course name'),
39
+ ...courseSharedFields,
40
+ account: accountParam,
41
+ },
42
+ }, async ({ name, owner, section, descriptionHeading, description, room, state, account }) => {
43
+ const args = ['classroom', 'courses', 'create', `--name=${name}`];
44
+ if (owner) args.push(`--owner=${owner}`);
45
+ if (section) args.push(`--section=${section}`);
46
+ if (descriptionHeading) args.push(`--description-heading=${descriptionHeading}`);
47
+ if (description) args.push(`--description=${description}`);
48
+ if (room) args.push(`--room=${room}`);
49
+ if (state) args.push(`--state=${state}`);
50
+ return runOrDiagnose(args, { account });
51
+ });
52
+
53
+ server.registerTool('gog_classroom_courses_update', {
54
+ description: 'Update an existing Google Classroom course.',
55
+ annotations: { destructiveHint: true },
56
+ inputSchema: {
57
+ courseId: z.string().describe('Course ID'),
58
+ name: z.string().optional().describe('Course name'),
59
+ ...courseSharedFields,
60
+ account: accountParam,
61
+ },
62
+ }, async ({ courseId, name, owner, section, descriptionHeading, description, room, state, account }) => {
63
+ const args = ['classroom', 'courses', 'update', courseId];
64
+ if (name) args.push(`--name=${name}`);
65
+ if (owner) args.push(`--owner=${owner}`);
66
+ if (section) args.push(`--section=${section}`);
67
+ if (descriptionHeading) args.push(`--description-heading=${descriptionHeading}`);
68
+ if (description) args.push(`--description=${description}`);
69
+ if (room) args.push(`--room=${room}`);
70
+ if (state) args.push(`--state=${state}`);
71
+ return runOrDiagnose(args, { account });
72
+ });
73
+
74
+ server.registerTool('gog_classroom_courses_delete', {
75
+ description: 'Delete a Google Classroom course.',
76
+ annotations: { destructiveHint: true },
77
+ inputSchema: {
78
+ courseId: z.string().describe('Course ID'),
79
+ account: accountParam,
80
+ },
81
+ }, async ({ courseId, account }) => {
82
+ return runOrDiagnose(['classroom', 'courses', 'delete', courseId], { account });
83
+ });
84
+
85
+ server.registerTool('gog_classroom_courses_archive', {
86
+ description: 'Archive a Google Classroom course.',
87
+ annotations: { destructiveHint: true },
88
+ inputSchema: {
89
+ courseId: z.string().describe('Course ID'),
90
+ account: accountParam,
91
+ },
92
+ }, async ({ courseId, account }) => {
93
+ return runOrDiagnose(['classroom', 'courses', 'archive', courseId], { account });
94
+ });
95
+
96
+ server.registerTool('gog_classroom_courses_unarchive', {
97
+ description: 'Unarchive a Google Classroom course (restore to ACTIVE).',
98
+ inputSchema: {
99
+ courseId: z.string().describe('Course ID'),
100
+ account: accountParam,
101
+ },
102
+ }, async ({ courseId, account }) => {
103
+ return runOrDiagnose(['classroom', 'courses', 'unarchive', courseId], { account });
104
+ });
105
+
106
+ server.registerTool('gog_classroom_students_add', {
107
+ description: 'Add a student to a Google Classroom course.',
108
+ inputSchema: {
109
+ courseId: z.string().describe('Course ID'),
110
+ userId: z.string().describe('Student user ID (or "me")'),
111
+ enrollmentCode: z.string().optional().describe('Enrollment code (required if adding self via code)'),
112
+ account: accountParam,
113
+ },
114
+ }, async ({ courseId, userId, enrollmentCode, account }) => {
115
+ const args = ['classroom', 'students', 'add', courseId, userId];
116
+ if (enrollmentCode) args.push(`--enrollment-code=${enrollmentCode}`);
117
+ return runOrDiagnose(args, { account });
118
+ });
119
+
120
+ server.registerTool('gog_classroom_students_remove', {
121
+ description: 'Remove a student from a Google Classroom course.',
122
+ annotations: { destructiveHint: true },
123
+ inputSchema: {
124
+ courseId: z.string().describe('Course ID'),
125
+ userId: z.string().describe('Student user ID'),
126
+ account: accountParam,
127
+ },
128
+ }, async ({ courseId, userId, account }) => {
129
+ return runOrDiagnose(['classroom', 'students', 'remove', courseId, userId], { account });
130
+ });
131
+
132
+ server.registerTool('gog_classroom_teachers_add', {
133
+ description: 'Add a teacher to a Google Classroom course.',
134
+ inputSchema: {
135
+ courseId: z.string().describe('Course ID'),
136
+ userId: z.string().describe('Teacher user ID'),
137
+ account: accountParam,
138
+ },
139
+ }, async ({ courseId, userId, account }) => {
140
+ return runOrDiagnose(['classroom', 'teachers', 'add', courseId, userId], { account });
141
+ });
142
+
143
+ server.registerTool('gog_classroom_teachers_remove', {
144
+ description: 'Remove a teacher from a Google Classroom course.',
145
+ annotations: { destructiveHint: true },
146
+ inputSchema: {
147
+ courseId: z.string().describe('Course ID'),
148
+ userId: z.string().describe('Teacher user ID'),
149
+ account: accountParam,
150
+ },
151
+ }, async ({ courseId, userId, account }) => {
152
+ return runOrDiagnose(['classroom', 'teachers', 'remove', courseId, userId], { account });
153
+ });
154
+
155
+ server.registerTool('gog_classroom_coursework_create', {
156
+ description: 'Create a new coursework item (assignment, question, etc.) in a course.',
157
+ inputSchema: {
158
+ courseId: z.string().describe('Course ID'),
159
+ title: z.string().describe('Coursework title'),
160
+ ...courseworkSharedFields,
161
+ account: accountParam,
162
+ },
163
+ }, async ({ courseId, title, description, type, state, maxPoints, due, dueDate, dueTime, scheduled, topic, account }) => {
164
+ const args = ['classroom', 'coursework', 'create', courseId, `--title=${title}`];
165
+ if (description) args.push(`--description=${description}`);
166
+ if (type) args.push(`--type=${type}`);
167
+ if (state) args.push(`--state=${state}`);
168
+ if (maxPoints !== undefined) args.push(`--max-points=${maxPoints}`);
169
+ if (due) args.push(`--due=${due}`);
170
+ if (dueDate) args.push(`--due-date=${dueDate}`);
171
+ if (dueTime) args.push(`--due-time=${dueTime}`);
172
+ if (scheduled) args.push(`--scheduled=${scheduled}`);
173
+ if (topic) args.push(`--topic=${topic}`);
174
+ return runOrDiagnose(args, { account });
175
+ });
176
+
177
+ server.registerTool('gog_classroom_coursework_update', {
178
+ description: 'Update an existing coursework item.',
179
+ annotations: { destructiveHint: true },
180
+ inputSchema: {
181
+ courseId: z.string().describe('Course ID'),
182
+ courseworkId: z.string().describe('Coursework ID'),
183
+ title: z.string().optional().describe('New title'),
184
+ ...courseworkSharedFields,
185
+ account: accountParam,
186
+ },
187
+ }, async ({ courseId, courseworkId, title, description, type, state, maxPoints, due, dueDate, dueTime, scheduled, topic, account }) => {
188
+ const args = ['classroom', 'coursework', 'update', courseId, courseworkId];
189
+ if (title) args.push(`--title=${title}`);
190
+ if (description) args.push(`--description=${description}`);
191
+ if (type) args.push(`--type=${type}`);
192
+ if (state) args.push(`--state=${state}`);
193
+ if (maxPoints !== undefined) args.push(`--max-points=${maxPoints}`);
194
+ if (due) args.push(`--due=${due}`);
195
+ if (dueDate) args.push(`--due-date=${dueDate}`);
196
+ if (dueTime) args.push(`--due-time=${dueTime}`);
197
+ if (scheduled) args.push(`--scheduled=${scheduled}`);
198
+ if (topic) args.push(`--topic=${topic}`);
199
+ return runOrDiagnose(args, { account });
200
+ });
201
+
202
+ server.registerTool('gog_classroom_coursework_delete', {
203
+ description: 'Delete a coursework item.',
204
+ annotations: { destructiveHint: true },
205
+ inputSchema: {
206
+ courseId: z.string().describe('Course ID'),
207
+ courseworkId: z.string().describe('Coursework ID'),
208
+ account: accountParam,
209
+ },
210
+ }, async ({ courseId, courseworkId, account }) => {
211
+ return runOrDiagnose(['classroom', 'coursework', 'delete', courseId, courseworkId], { account });
212
+ });
213
+
214
+ server.registerTool('gog_classroom_announcements_update', {
215
+ description: 'Update an existing announcement.',
216
+ annotations: { destructiveHint: true },
217
+ inputSchema: {
218
+ courseId: z.string().describe('Course ID'),
219
+ announcementId: z.string().describe('Announcement ID'),
220
+ text: z.string().optional().describe('New text'),
221
+ state: workState.optional().describe('State'),
222
+ scheduled: z.string().optional().describe('Scheduled publish time'),
223
+ account: accountParam,
224
+ },
225
+ }, async ({ courseId, announcementId, text, state, scheduled, account }) => {
226
+ const args = ['classroom', 'announcements', 'update', courseId, announcementId];
227
+ if (text) args.push(`--text=${text}`);
228
+ if (state) args.push(`--state=${state}`);
229
+ if (scheduled) args.push(`--scheduled=${scheduled}`);
230
+ return runOrDiagnose(args, { account });
231
+ });
232
+
233
+ server.registerTool('gog_classroom_announcements_delete', {
234
+ description: 'Delete an announcement.',
235
+ annotations: { destructiveHint: true },
236
+ inputSchema: {
237
+ courseId: z.string().describe('Course ID'),
238
+ announcementId: z.string().describe('Announcement ID'),
239
+ account: accountParam,
240
+ },
241
+ }, async ({ courseId, announcementId, account }) => {
242
+ return runOrDiagnose(['classroom', 'announcements', 'delete', courseId, announcementId], { account });
243
+ });
244
+
245
+ server.registerTool('gog_classroom_topics_create', {
246
+ description: 'Create a topic in a Google Classroom course.',
247
+ inputSchema: {
248
+ courseId: z.string().describe('Course ID'),
249
+ name: z.string().describe('Topic name'),
250
+ account: accountParam,
251
+ },
252
+ }, async ({ courseId, name, account }) => {
253
+ return runOrDiagnose(['classroom', 'topics', 'create', courseId, `--name=${name}`], { account });
254
+ });
255
+
256
+ server.registerTool('gog_classroom_topics_update', {
257
+ description: 'Rename an existing topic.',
258
+ annotations: { destructiveHint: true },
259
+ inputSchema: {
260
+ courseId: z.string().describe('Course ID'),
261
+ topicId: z.string().describe('Topic ID'),
262
+ name: z.string().describe('New topic name'),
263
+ account: accountParam,
264
+ },
265
+ }, async ({ courseId, topicId, name, account }) => {
266
+ return runOrDiagnose(['classroom', 'topics', 'update', courseId, topicId, `--name=${name}`], { account });
267
+ });
268
+
269
+ server.registerTool('gog_classroom_topics_delete', {
270
+ description: 'Delete a topic.',
271
+ annotations: { destructiveHint: true },
272
+ inputSchema: {
273
+ courseId: z.string().describe('Course ID'),
274
+ topicId: z.string().describe('Topic ID'),
275
+ account: accountParam,
276
+ },
277
+ }, async ({ courseId, topicId, account }) => {
278
+ return runOrDiagnose(['classroom', 'topics', 'delete', courseId, topicId], { account });
279
+ });
280
+
281
+ server.registerTool('gog_classroom_invitations_create', {
282
+ description: 'Create an invitation to a Google Classroom course.',
283
+ inputSchema: {
284
+ courseId: z.string().describe('Course ID'),
285
+ userId: z.string().describe('User ID to invite'),
286
+ role: z.enum(['STUDENT', 'TEACHER', 'OWNER']).describe('Role for the invited user'),
287
+ account: accountParam,
288
+ },
289
+ }, async ({ courseId, userId, role, account }) => {
290
+ return runOrDiagnose(['classroom', 'invitations', 'create', courseId, userId, `--role=${role}`], { account });
291
+ });
292
+
293
+ server.registerTool('gog_classroom_invitations_delete', {
294
+ description: 'Delete (revoke) a Google Classroom invitation.',
295
+ annotations: { destructiveHint: true },
296
+ inputSchema: {
297
+ invitationId: z.string().describe('Invitation ID'),
298
+ account: accountParam,
299
+ },
300
+ }, async ({ invitationId, account }) => {
301
+ return runOrDiagnose(['classroom', 'invitations', 'delete', invitationId], { account });
302
+ });
5
303
  }
@@ -1,12 +1,264 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
2
  import { registerExtraClassroomTools } from '../../src/tools/classroom-extra.js';
3
+ import * as lib from '../../../gogcli-mcp/src/lib.js';
4
+ import { setupExtrasHandlers, toText, type ToolHandler } from '../../../gogcli-mcp/tests/helpers/extras-harness.js';
4
5
 
5
- describe('registerExtraClassroomTools', () => {
6
- it('registers no tools (no extras yet)', () => {
7
- const server = new McpServer({ name: 'test', version: '0.0.0' });
8
- const spy = vi.spyOn(server, 'registerTool');
9
- registerExtraClassroomTools(server);
10
- expect(spy).not.toHaveBeenCalled();
6
+ vi.mock('../../../gogcli-mcp/src/lib.js', async (importOriginal) => {
7
+ const actual = await importOriginal<typeof lib>();
8
+ return {
9
+ ...actual,
10
+ runOrDiagnose: vi.fn(),
11
+ };
12
+ });
13
+
14
+ let handlers: Map<string, ToolHandler>;
15
+
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
19
+ handlers = setupExtrasHandlers(registerExtraClassroomTools);
20
+ });
21
+
22
+ describe('gog_classroom_courses_create', () => {
23
+ it('calls runOrDiagnose with required name only', async () => {
24
+ await handlers.get('gog_classroom_courses_create')!({ name: 'Math 101' });
25
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
26
+ ['classroom', 'courses', 'create', '--name=Math 101'],
27
+ { account: undefined },
28
+ );
29
+ });
30
+
31
+ it('passes all optional flags', async () => {
32
+ await handlers.get('gog_classroom_courses_create')!({
33
+ name: 'Math 101',
34
+ owner: 'me',
35
+ section: 'Section A',
36
+ descriptionHeading: 'Welcome',
37
+ description: 'Algebra',
38
+ room: 'R101',
39
+ state: 'ACTIVE',
40
+ });
41
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
42
+ ['classroom', 'courses', 'create', '--name=Math 101', '--owner=me', '--section=Section A', '--description-heading=Welcome', '--description=Algebra', '--room=R101', '--state=ACTIVE'],
43
+ { account: undefined },
44
+ );
45
+ });
46
+ });
47
+
48
+ describe('gog_classroom_courses_update', () => {
49
+ it('calls runOrDiagnose with courseId only', async () => {
50
+ await handlers.get('gog_classroom_courses_update')!({ courseId: 'c1' });
51
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'update', 'c1'], { account: undefined });
52
+ });
53
+
54
+ it('passes all optional flags', async () => {
55
+ await handlers.get('gog_classroom_courses_update')!({
56
+ courseId: 'c1',
57
+ name: 'New Name',
58
+ owner: 'me',
59
+ section: 'B',
60
+ descriptionHeading: 'Heading',
61
+ description: 'Desc',
62
+ room: 'R2',
63
+ state: 'ARCHIVED',
64
+ });
65
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
66
+ ['classroom', 'courses', 'update', 'c1', '--name=New Name', '--owner=me', '--section=B', '--description-heading=Heading', '--description=Desc', '--room=R2', '--state=ARCHIVED'],
67
+ { account: undefined },
68
+ );
69
+ });
70
+ });
71
+
72
+ describe('gog_classroom_courses_delete', () => {
73
+ it('calls runOrDiagnose with courseId', async () => {
74
+ await handlers.get('gog_classroom_courses_delete')!({ courseId: 'c1' });
75
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'delete', 'c1'], { account: undefined });
76
+ });
77
+ });
78
+
79
+ describe('gog_classroom_courses_archive', () => {
80
+ it('calls runOrDiagnose with courseId', async () => {
81
+ await handlers.get('gog_classroom_courses_archive')!({ courseId: 'c1' });
82
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'archive', 'c1'], { account: undefined });
83
+ });
84
+ });
85
+
86
+ describe('gog_classroom_courses_unarchive', () => {
87
+ it('calls runOrDiagnose with courseId', async () => {
88
+ await handlers.get('gog_classroom_courses_unarchive')!({ courseId: 'c1' });
89
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'unarchive', 'c1'], { account: undefined });
90
+ });
91
+ });
92
+
93
+ describe('gog_classroom_students_add', () => {
94
+ it('calls runOrDiagnose with courseId and userId', async () => {
95
+ await handlers.get('gog_classroom_students_add')!({ courseId: 'c1', userId: 'u1' });
96
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'students', 'add', 'c1', 'u1'], { account: undefined });
97
+ });
98
+
99
+ it('passes --enrollment-code when provided', async () => {
100
+ await handlers.get('gog_classroom_students_add')!({ courseId: 'c1', userId: 'u1', enrollmentCode: 'abc123' });
101
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
102
+ ['classroom', 'students', 'add', 'c1', 'u1', '--enrollment-code=abc123'],
103
+ { account: undefined },
104
+ );
105
+ });
106
+ });
107
+
108
+ describe('gog_classroom_students_remove', () => {
109
+ it('calls runOrDiagnose with courseId and userId', async () => {
110
+ await handlers.get('gog_classroom_students_remove')!({ courseId: 'c1', userId: 'u1' });
111
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'students', 'remove', 'c1', 'u1'], { account: undefined });
112
+ });
113
+ });
114
+
115
+ describe('gog_classroom_teachers_add', () => {
116
+ it('calls runOrDiagnose with courseId and userId', async () => {
117
+ await handlers.get('gog_classroom_teachers_add')!({ courseId: 'c1', userId: 'u1' });
118
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'teachers', 'add', 'c1', 'u1'], { account: undefined });
119
+ });
120
+ });
121
+
122
+ describe('gog_classroom_teachers_remove', () => {
123
+ it('calls runOrDiagnose with courseId and userId', async () => {
124
+ await handlers.get('gog_classroom_teachers_remove')!({ courseId: 'c1', userId: 'u1' });
125
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'teachers', 'remove', 'c1', 'u1'], { account: undefined });
126
+ });
127
+ });
128
+
129
+ describe('gog_classroom_coursework_create', () => {
130
+ it('calls runOrDiagnose with required title only', async () => {
131
+ await handlers.get('gog_classroom_coursework_create')!({ courseId: 'c1', title: 'HW1' });
132
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
133
+ ['classroom', 'coursework', 'create', 'c1', '--title=HW1'],
134
+ { account: undefined },
135
+ );
136
+ });
137
+
138
+ it('passes all optional flags', async () => {
139
+ await handlers.get('gog_classroom_coursework_create')!({
140
+ courseId: 'c1',
141
+ title: 'HW1',
142
+ description: 'Chapter 1',
143
+ type: 'ASSIGNMENT',
144
+ state: 'PUBLISHED',
145
+ maxPoints: 100,
146
+ due: '2026-05-01T23:59',
147
+ dueDate: '2026-05-01',
148
+ dueTime: '23:59',
149
+ scheduled: '2026-04-30T12:00',
150
+ topic: 't1',
151
+ });
152
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
153
+ ['classroom', 'coursework', 'create', 'c1', '--title=HW1', '--description=Chapter 1', '--type=ASSIGNMENT', '--state=PUBLISHED', '--max-points=100', '--due=2026-05-01T23:59', '--due-date=2026-05-01', '--due-time=23:59', '--scheduled=2026-04-30T12:00', '--topic=t1'],
154
+ { account: undefined },
155
+ );
156
+ });
157
+ });
158
+
159
+ describe('gog_classroom_coursework_update', () => {
160
+ it('calls runOrDiagnose with ids only', async () => {
161
+ await handlers.get('gog_classroom_coursework_update')!({ courseId: 'c1', courseworkId: 'w1' });
162
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'coursework', 'update', 'c1', 'w1'], { account: undefined });
163
+ });
164
+
165
+ it('passes all optional flags', async () => {
166
+ await handlers.get('gog_classroom_coursework_update')!({
167
+ courseId: 'c1',
168
+ courseworkId: 'w1',
169
+ title: 'New Title',
170
+ description: 'Desc',
171
+ type: 'SHORT_ANSWER_QUESTION',
172
+ state: 'DRAFT',
173
+ maxPoints: 50,
174
+ due: '2026-05-01T23:59',
175
+ dueDate: '2026-05-01',
176
+ dueTime: '23:59',
177
+ scheduled: '2026-04-30T12:00',
178
+ topic: 't1',
179
+ });
180
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
181
+ ['classroom', 'coursework', 'update', 'c1', 'w1', '--title=New Title', '--description=Desc', '--type=SHORT_ANSWER_QUESTION', '--state=DRAFT', '--max-points=50', '--due=2026-05-01T23:59', '--due-date=2026-05-01', '--due-time=23:59', '--scheduled=2026-04-30T12:00', '--topic=t1'],
182
+ { account: undefined },
183
+ );
184
+ });
185
+ });
186
+
187
+ describe('gog_classroom_coursework_delete', () => {
188
+ it('calls runOrDiagnose with ids', async () => {
189
+ await handlers.get('gog_classroom_coursework_delete')!({ courseId: 'c1', courseworkId: 'w1' });
190
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'coursework', 'delete', 'c1', 'w1'], { account: undefined });
191
+ });
192
+ });
193
+
194
+ describe('gog_classroom_announcements_update', () => {
195
+ it('calls runOrDiagnose with ids only', async () => {
196
+ await handlers.get('gog_classroom_announcements_update')!({ courseId: 'c1', announcementId: 'a1' });
197
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'announcements', 'update', 'c1', 'a1'], { account: undefined });
198
+ });
199
+
200
+ it('passes all optional flags', async () => {
201
+ await handlers.get('gog_classroom_announcements_update')!({
202
+ courseId: 'c1',
203
+ announcementId: 'a1',
204
+ text: 'edited',
205
+ state: 'PUBLISHED',
206
+ scheduled: '2026-05-01T12:00',
207
+ });
208
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
209
+ ['classroom', 'announcements', 'update', 'c1', 'a1', '--text=edited', '--state=PUBLISHED', '--scheduled=2026-05-01T12:00'],
210
+ { account: undefined },
211
+ );
212
+ });
213
+ });
214
+
215
+ describe('gog_classroom_announcements_delete', () => {
216
+ it('calls runOrDiagnose with ids', async () => {
217
+ await handlers.get('gog_classroom_announcements_delete')!({ courseId: 'c1', announcementId: 'a1' });
218
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'announcements', 'delete', 'c1', 'a1'], { account: undefined });
219
+ });
220
+ });
221
+
222
+ describe('gog_classroom_topics_create', () => {
223
+ it('calls runOrDiagnose with courseId and name', async () => {
224
+ await handlers.get('gog_classroom_topics_create')!({ courseId: 'c1', name: 'Week 1' });
225
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
226
+ ['classroom', 'topics', 'create', 'c1', '--name=Week 1'],
227
+ { account: undefined },
228
+ );
229
+ });
230
+ });
231
+
232
+ describe('gog_classroom_topics_update', () => {
233
+ it('calls runOrDiagnose with ids and name', async () => {
234
+ await handlers.get('gog_classroom_topics_update')!({ courseId: 'c1', topicId: 't1', name: 'Week 2' });
235
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
236
+ ['classroom', 'topics', 'update', 'c1', 't1', '--name=Week 2'],
237
+ { account: undefined },
238
+ );
239
+ });
240
+ });
241
+
242
+ describe('gog_classroom_topics_delete', () => {
243
+ it('calls runOrDiagnose with ids', async () => {
244
+ await handlers.get('gog_classroom_topics_delete')!({ courseId: 'c1', topicId: 't1' });
245
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'topics', 'delete', 'c1', 't1'], { account: undefined });
246
+ });
247
+ });
248
+
249
+ describe('gog_classroom_invitations_create', () => {
250
+ it('calls runOrDiagnose with courseId, userId, role', async () => {
251
+ await handlers.get('gog_classroom_invitations_create')!({ courseId: 'c1', userId: 'u1', role: 'STUDENT' });
252
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
253
+ ['classroom', 'invitations', 'create', 'c1', 'u1', '--role=STUDENT'],
254
+ { account: undefined },
255
+ );
256
+ });
257
+ });
258
+
259
+ describe('gog_classroom_invitations_delete', () => {
260
+ it('calls runOrDiagnose with invitationId', async () => {
261
+ await handlers.get('gog_classroom_invitations_delete')!({ invitationId: 'i1' });
262
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'invitations', 'delete', 'i1'], { account: undefined });
11
263
  });
12
264
  });