From 9dbf8e87430f5e269ec4049300e5159528eeb3ef Mon Sep 17 00:00:00 2001 From: fajarhide Date: Fri, 28 Aug 2026 21:45:53 +0700 Subject: [PATCH 1/2] release: 0.1.3 Google writes are the reason for this one. Gmail moves to gmail.modify and Calendar picks up calendar.events, so every Google connection reconnects once. GitHub gained review replies, api keys are checked before they are stored, and a Google 403 caused by a missing scope finally says reconnect. --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2243b60..c6f97b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,36 @@ Notable changes, newest first. Dates are the day the work merged. +## 0.1.3 - 2026-08-28 + +### Added + +- Gmail can write. `send_message`, `modify_message` and `trash_message` join the + four reads. The scope moves from `gmail.readonly` to `gmail.modify`, so every + existing Google connection re-consents once (#86). +- Google Calendar can write. `create_event`, `update_event` and `delete_event`, + which adds `calendar.events` beside `calendar.readonly`. `calendar.readonly` + stays because it is what lists the calendars (#86). +- GitHub can answer a review and close what it opened (#85). +- An api key is called against the vendor before it is stored, so a typo fails + at connect time rather than on the first tool call (#82, thanks + @mikemikimike). +- A manifest argument can hold a list of objects, which is what `attendees` on a + calendar event needed (#92). + +### Fixed + +- A Google 403 from a scope the grant never got now says `reauth_required` + instead of `upstream_error`, so the caller is told to reconnect. A 403 from an + API that was never enabled still reports upstream, because reconnecting does + not fix that one (#89). +- The key check runs under a deadline, and the executor maps its failures apart + from upstream ones (#91). + +### Changed + +- Hosted links point at `selat.dev`. The old host still answers (#93). + ## 0.1.2 - 2026-08-23 ### Added diff --git a/package.json b/package.json index 332d723..892cf5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fajarhide/selat", - "version": "0.1.2", + "version": "0.1.3", "description": "The unified tool gateway for AI agents: one credential, every upstream, over MCP and REST.", "type": "module", "license": "Apache-2.0", From 60356fb4357949827ff611033a0afa31e2045edb Mon Sep 17 00:00:00 2001 From: fajarhide Date: Fri, 28 Aug 2026 21:49:30 +0700 Subject: [PATCH 2/2] fix(gcal): ask for the calendar list, not every calendar calendar.events already covers all six event tools, reads included, because they all go through /calendars/{id}/events. The one call outside that is list_calendars on /users/me/calendarList, and calendar.calendarlist.readonly is exactly that and nothing more. calendar.readonly covered both, which is why it was there. It also grants "See and download any calendar you can access" to satisfy one list call, and the Google consent screen asks in writing why a narrower scope will not do. For this one it will. --- .gitignore | 3 +++ CHANGELOG.md | 8 ++++++-- src/adapters/providers/gcalendar.ts | 7 ++++++- test/providers/google-workspace.test.ts | 13 ++++++++----- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 4c053d3..b16f274 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ Screenshot*.png # are published. This repository has a public remote, so the whole directory is # ignored rather than named file by file. docs/ + +# Working notes and local skills, not part of what this repo ships. +.claude/ diff --git a/CHANGELOG.md b/CHANGELOG.md index c6f97b9..e57c560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,7 @@ Notable changes, newest first. Dates are the day the work merged. four reads. The scope moves from `gmail.readonly` to `gmail.modify`, so every existing Google connection re-consents once (#86). - Google Calendar can write. `create_event`, `update_event` and `delete_event`, - which adds `calendar.events` beside `calendar.readonly`. `calendar.readonly` - stays because it is what lists the calendars (#86). + on `calendar.events` (#86). - GitHub can answer a review and close what it opened (#85). - An api key is called against the vendor before it is stored, so a typo fails at connect time rather than on the first tool call (#82, thanks @@ -30,6 +29,11 @@ Notable changes, newest first. Dates are the day the work merged. ### Changed +- Calendar asks for `calendar.calendarlist.readonly` where it used to ask for + `calendar.readonly`. `calendar.events` already covers every event tool, + reads included, and the only call outside it is `list_calendars` on + `/users/me/calendarList`. The old pair read every calendar wholesale to + satisfy one list call. - Hosted links point at `selat.dev`. The old host still answers (#93). ## 0.1.2 - 2026-08-23 diff --git a/src/adapters/providers/gcalendar.ts b/src/adapters/providers/gcalendar.ts index edba2d7..a20b459 100644 --- a/src/adapters/providers/gcalendar.ts +++ b/src/adapters/providers/gcalendar.ts @@ -13,7 +13,12 @@ export const gcalendarManifest: ProviderManifest = { // which is the only thing list_calendars calls, so dropping it would break a // tool that works today. scopes: [ - 'https://www.googleapis.com/auth/calendar.readonly', + // calendar.events covers reads as well as writes, and every event tool + // goes through /calendars/{id}/events. The only call outside that is + // list_calendars on /users/me/calendarList, which is all the second + // scope grants. calendar.readonly would cover both and is what this + // used to ask for, but it reads every calendar wholesale for one list. + 'https://www.googleapis.com/auth/calendar.calendarlist.readonly', 'https://www.googleapis.com/auth/calendar.events', ], auth: { type: 'bearer' }, diff --git a/test/providers/google-workspace.test.ts b/test/providers/google-workspace.test.ts index 66ff9f0..f157dbe 100644 --- a/test/providers/google-workspace.test.ts +++ b/test/providers/google-workspace.test.ts @@ -57,17 +57,20 @@ describe('one google application, three prefixes', () => { expect(new Set(scopes)).toEqual( new Set([ 'https://www.googleapis.com/auth/gmail.modify', - 'https://www.googleapis.com/auth/calendar.readonly', + 'https://www.googleapis.com/auth/calendar.calendarlist.readonly', 'https://www.googleapis.com/auth/calendar.events', 'https://www.googleapis.com/auth/drive', ]), ) }) - it('keeps calendar.readonly beside calendar.events, because list_calendars needs it', () => { - // calendar.events does not grant calendarList.list, so dropping the - // readonly scope would break a tool that works today. - expect(gcal.scopes).toContain('https://www.googleapis.com/auth/calendar.readonly') + it('pairs calendar.events with the calendar list, and never asks for calendar.readonly', () => { + // calendar.events covers every event tool, reads included, but not + // calendarList.list. calendar.readonly would cover both and is what this + // used to ask for. It reads every calendar wholesale to satisfy one list + // call, which is the kind of ask a Google reviewer rejects. + expect(gcal.scopes).toContain('https://www.googleapis.com/auth/calendar.calendarlist.readonly') + expect(gcal.scopes).not.toContain('https://www.googleapis.com/auth/calendar.readonly') }) })