Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@

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`,
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
@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

- 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

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
7 changes: 6 additions & 1 deletion src/adapters/providers/gcalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
13 changes: 8 additions & 5 deletions test/providers/google-workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})

Expand Down
Loading