Skip to main content
Glama
README.md
# Chief of Staff

Morning brief, meeting prep, action items to owners and the weekly update, from your tools.

An MCP server with **20 workflows** across Google Calendar, Gmail, Linear, Slack, Granola, Google Docs, GitHub, Stripe, Notion, Google Forms, Google Sheets and Google Drive. Each workflow is a prompt your agent runs as a slash command, over the 45 tools it needs and no others.

```bash
uv tool install https://github.com/r28ai/chief-of-staff-mcp/releases/download/v0.1.0/chief_of_staff_mcp-0.1.0-py3-none-any.whl
claude mcp add cos -- chief-of-staff-mcp
```

It installs with [uv](https://docs.astral.sh/uv/) from this repository's release, with no git and nothing to build; nothing but Charter and the libraries it uses comes from PyPI. To update, run the install line from the [latest release](https://github.com/r28ai/chief-of-staff-mcp/releases/latest). If a desktop app cannot find `chief-of-staff-mcp`, give it the full path from `which chief-of-staff-mcp` (`where chief-of-staff-mcp` on Windows).

Then ask your agent to **connect your apps**, or run `/mcp__cos__setup`.

## Connect your apps

Ask the agent to connect one ("connect Linear"). It tells you where to get that app's key and the command that stores it, and the next call works, with no restart. The agent never asks for a key in the chat.

Or connect everything this server uses from a terminal:

```bash
chief-of-staff-mcp login            # each app in turn
chief-of-staff-mcp login linear     # just one
chief-of-staff-mcp status           # what is connected
```

Tokens and keys go to your operating system's keychain (macOS Keychain, Windows Credential Manager, the Secret Service on Linux), and are checked with one read-only call to the app's own API before they are kept. Every key, token and OAuth client is yours: we register no app with any of these services, and nothing passes through a server of ours, because there isn't one.

| App | How it connects | Or set |
|---|---|---|
| Google | Browser sign-in, over your own OAuth client ([make one](https://docs.r28.ai/charter/auth/setup/google)). | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` |
| Linear | Your own key ([get one](https://linear.app/settings/account/security)), entered once. | `LINEAR_API_KEY` |
| Slack | Your own key ([get one](https://docs.r28.ai/charter/auth/setup/slack)), entered once. A bot token from your own Slack app, which the guide sets up in about three minutes. | `SLACK_BOT_TOKEN` |
| Granola | Your own key ([get one](https://docs.granola.ai/help-center/sharing/integrations/granola-api)), entered once. In the Granola app: Settings → Connectors → API keys. Business plan or above. | `GRANOLA_API_KEY` |
| GitHub | Your own key ([get one](https://github.com/settings/tokens/new?description=Charter&scopes=repo,read:user)), entered once. | `GITHUB_TOKEN` |
| Stripe | Your own key ([get one](https://dashboard.stripe.com/apikeys)), entered once. | `STRIPE_API_KEY` |
| Notion | Your own key ([get one](https://www.notion.so/profile/integrations)), entered once. Then share the pages it should see with the integration. | `NOTION_API_KEY` |

A variable set in your client's config always wins over the keychain.

## Workflows

| Workflow | What you get | Apps |
|---|---|---|
| **Morning brief** <br>`morning_brief` | Today's meetings, emails that need you and issues due, in one message at 8am. | Google Calendar, Gmail, Linear, Slack |
| **Meeting prep pack** <br>`meeting_prep_pack` | Last time you met them, what they emailed since and what you promised. | Google Calendar, Gmail, Granola, Google Docs |
| **Action items → owners** <br>`action_items_to_owners` | Everything someone said they'd do is an assigned issue before the next meeting. | Granola, Linear, Slack |
| **Internal weekly update** <br>`internal_weekly_update` | Shipped, revenue, risks: drafted from the systems, edited by a human. | Linear, GitHub, Stripe, Google Docs, Slack |
| **Inbox triage into tasks and time blocks** <br>`inbox_triage_into_tasks_and_time_blocks` | Emails that are really tasks become issues with time held to do them. | Gmail, Linear, Google Calendar |
| **Email → calendar** <br>`email_to_calendar` | 'Does Thursday 3pm work?' becomes an invite and a confirmation. | Gmail, Google Calendar |
| **Meetings → Doc** <br>`meetings_to_doc` | A week of meetings summarised into a doc for the people who weren't there. | Google Calendar, Google Docs |
| **Decision log** <br>`decision_log` | Decisions made in channels get recorded with who, when and why. | Slack, Notion |
| **Offsite planning** <br>`offsite_planning` | Dates, dietary needs and the agenda gathered and published. | Google Forms, Google Calendar, Google Docs, Slack |
| **OKR tracker** <br>`okr_tracker` | Initiative progress rolled up to the sheet and posted as an update. | Linear, Google Sheets |
| **Fundraising data room** <br>`fundraising_data_room` | Folder assembled, revenue exported and access granted per investor. | Google Drive, Stripe, Google Sheets |
| **Unresolved doc comments → nudges** <br>`unresolved_doc_comments_to_nudges` | Comments that have waited three days get their owner pinged. | Google Drive, Slack |
| **Contract renewal calendar** <br>`contract_renewal_calendar` | Every contract's notice date is on a calendar 60 days ahead. | Google Drive, Google Docs, Google Calendar, Slack |
| **Offboarding access sweep** <br>`offboarding_access_sweep` | Files shared with someone who left get revoked, with a report. | Google Drive, Linear, Slack |
| **Sheet → calendar** <br>`sheet_to_calendar` | A schedule kept in a sheet becomes real events, with IDs written back. | Google Sheets, Google Calendar |
| **Inbox → sheet** <br>`inbox_to_sheet` | Orders, signups or applications that arrive by email become rows. | Gmail, Google Sheets |
| **Drive folder → Notion** <br>`drive_folder_to_notion` | A migration that usually takes a week of copy-paste. | Google Drive, Notion |
| **Granola → Notion meeting database** <br>`granola_to_notion_meeting_database` | Every meeting note in the team's Notion with attendees and decisions as properties. | Granola, Notion |
| **Notion tasks ↔ Linear** <br>`notion_tasks_and_linear` | Non-engineers file in Notion and see the Linear status reflected back. | Notion, Linear |
| **Spreadsheet backlog → Linear** <br>`spreadsheet_backlog_to_linear` | The backlog someone kept in a sheet moves to Linear, IDs written back. | Google Sheets, Linear |

Every prompt takes one optional argument, `details`: the repo, team, channel, customer or date range you mean, so the agent does not have to ask. In Claude Code, put it in quotes, or only its first word arrives:

```
/mcp__cos__morning_brief "post it to #me"
```

Reads run without asking. Before anything that creates, sends, changes or deletes, the prompt tells the agent to show you the call and wait.

2 of the 20 workflows need no Google or Granola credential.

## Other clients

**Claude Desktop**: install [uv](https://docs.astral.sh/uv/getting-started/installation/) if you have not, since Claude Desktop starts the server with it, then open the `.mcpb` from the [latest release](https://github.com/r28ai/chief-of-staff-mcp/releases/latest). Claude asks for any keys in its own settings and keeps them in your keychain. The first start takes a few seconds longer, while uv installs it.

**VS Code** (`.vscode/mcp.json`): VS Code asks for each key the first time the server starts and stores it securely. Leave out any you stored with `login`.

```json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "google-client-secret",
      "description": "Google: OAuth client secret",
      "password": true
    },
    {
      "type": "promptString",
      "id": "linear-api-key",
      "description": "Linear: Personal API key",
      "password": true
    },
    {
      "type": "promptString",
      "id": "slack-bot-token",
      "description": "Slack: Bot token (xoxb-\u2026)",
      "password": true
    },
    {
      "type": "promptString",
      "id": "granola-api-key",
      "description": "Granola: API key",
      "password": true
    },
    {
      "type": "promptString",
      "id": "github-token",
      "description": "GitHub: Personal access token",
      "password": true
    },
    {
      "type": "promptString",
      "id": "stripe-api-key",
      "description": "Stripe: Secret or restricted key",
      "password": true
    },
    {
      "type": "promptString",
      "id": "notion-api-key",
      "description": "Notion: Integration secret (ntn_\u2026)",
      "password": true
    }
  ],
  "servers": {
    "cos": {
      "type": "stdio",
      "command": "chief-of-staff-mcp",
      "env": {
        "GOOGLE_CLIENT_SECRET": "${input:google-client-secret}",
        "LINEAR_API_KEY": "${input:linear-api-key}",
        "SLACK_BOT_TOKEN": "${input:slack-bot-token}",
        "GRANOLA_API_KEY": "${input:granola-api-key}",
        "GITHUB_TOKEN": "${input:github-token}",
        "STRIPE_API_KEY": "${input:stripe-api-key}",
        "NOTION_API_KEY": "${input:notion-api-key}",
        "GOOGLE_CLIENT_ID": ""
      }
    }
  }
}
```

**Cursor** (`.cursor/mcp.json`) starts it the same way:

```json
{
  "mcpServers": {
    "cos": {
      "command": "chief-of-staff-mcp"
    }
  }
}
```

**Codex** (`~/.codex/config.toml`) starts a turn without waiting for a server unless it is `required`, and then the agent has none of its tools. `required = true` makes the session wait for it, and `startup_readiness = "catalog"` waits for its tool list rather than just its connection:

```toml
[mcp_servers.cos]
command = "chief-of-staff-mcp"
required = true
startup_readiness = "catalog"
startup_timeout_sec = 30
```

Name the server `cos`. A host builds each tool's name from that key, and a longer one can push a tool past the 64 characters a function name allows.

## Built with Charter

Every tool here is a [Charter](https://github.com/r28ai/charter) declaration: a Pydantic schema saying where each field goes on the wire. Charter's runtime builds the request, attaches and refreshes the credential, and trims the response before the model reads it. It runs in your process, with no proxy and no telemetry.

The 45 tool schemas come to 68,590 tokens.

The same tools work in your own agent, without MCP:

```python
from charter.adapters.openai import to_openai_tools
from charter_packs_mcp import FAMILIES

tools = FAMILIES["ops"].tools()
definitions = to_openai_tools(tools)   # or charter.adapters.langchain
```

Need an API that isn't here? [Write a pack](https://docs.r28.ai/charter/start/coding-agents): your coding agent writes the declarations, and Charter's conformance suite checks them.

<details>
<summary>All 45 tools</summary>

- **Google Calendar**: `gcalendar_events_list`, `gcalendar_events_get`, `gcalendar_events_quick_add`, `gcalendar_events_insert`
- **Gmail**: `gmail_threads_list`, `gmail_threads_modify`, `gmail_threads_get`, `gmail_drafts_create`, `gmail_messages_list`, `gmail_messages_get`
- **Linear**: `linear_issues_list`, `linear_users_list`, `linear_issue_create`, `linear_project_updates_list`, `linear_initiatives_list`, `linear_initiative_get`, `linear_initiative_update_create`, `linear_team_membership_delete`, `linear_search_issues`
- **Slack**: `slack_chat_post_message`, `slack_conversations_history`, `slack_users_list`
- **Granola**: `granola_notes_list`, `granola_notes_get`
- **Google Docs**: `gdocs_documents_create`, `gdocs_documents_get`
- **GitHub**: `github_releases_list`
- **Stripe**: `stripe_subscriptions_list`
- **Notion**: `notion_data_sources_query`, `notion_pages_create`, `notion_pages_update_markdown`, `notion_pages_update`
- **Google Forms**: `gforms_forms_create`, `gforms_forms_responses_list`
- **Google Sheets**: `gsheets_spreadsheets_values_update`, `gsheets_spreadsheets_create`, `gsheets_spreadsheets_values_get`, `gsheets_spreadsheets_values_append`
- **Google Drive**: `gdrive_files_list`, `gdrive_files_copy`, `gdrive_permissions_create`, `gdrive_comments_list`, `gdrive_permissions_list`, `gdrive_permissions_delete`, `gdrive_files_export`

</details>

## License

Apache 2.0.

TDQS

B3.3/5.0

Scored across 47 tools

Disambiguation4/5

Tools follow a resource+action pattern and most have clearly distinct purposes, with descriptions explicitly steering between similar pairs (e.g. events_insert vs events_quick_add, issues_list vs search_issues). A few near-neighbors exist (initiatives_list vs initiative_get, the various list tools across apps), but overlap is minimal and well-documented.

Naming Consistency4/5

The dominant convention is a predictable app_resource_action pattern (gdrive_files_list, linear_issues_list, slack_chat_post_message). Minor deviations exist — unprefixed meta tools (connect, connection_status), a four-segment notion_pages_update_markdown, and the awkward linear_initiative_update_create — but overall it stays consistent and readable.

Tool Count3/5

47 tools is heavy, well past the comfortable 3-15 range, and the surface is flat with no grouping to guide selection. However, the server genuinely aggregates ~12 distinct apps, so the count is roughly proportional rather than arbitrary bloat — borderline but defensible for this scope.

Completeness3/5

Coverage is broad, but several workflows dead-end: Gmail has no send, Calendar lacks update/delete, GitHub and Stripe expose only a single list tool each. Some descriptions reference operations not present in the tool set (documents_batch_update, forms_batch_update, pages_move), signaling real holes.

Maintenance

ActivityMaintained
ResponsivenessNo issues