Skip to main content
Glama
README.md
# Product Manager Workflows

Calls, specs and feedback become deduped Linear issues, PRDs and weekly project updates.

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

```bash
uv tool install https://github.com/r28ai/product-manager-mcp/releases/download/v0.1.0/product_manager_mcp-0.1.0-py3-none-any.whl
claude mcp add pm -- product-manager-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/product-manager-mcp/releases/latest). If a desktop app cannot find `product-manager-mcp`, give it the full path from `which product-manager-mcp` (`where product-manager-mcp` on Windows).

Then ask your agent to **connect your apps**, or run `/mcp__pm__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
product-manager-mcp login            # each app in turn
product-manager-mcp login granola    # just one
product-manager-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 |
|---|---|---|
| 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` |
| 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` |
| 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` |
| 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` |
| GitHub | Your own key ([get one](https://github.com/settings/tokens/new?description=Charter&scopes=repo,read:user)), entered once. | `GITHUB_TOKEN` |
| Firecrawl | Your own key ([get one](https://www.firecrawl.dev/app/api-keys)), entered once. | `FIRECRAWL_API_KEY` |
| Stripe | Your own key ([get one](https://dashboard.stripe.com/apikeys)), entered once. | `STRIPE_API_KEY` |
| Tavily | Your own key ([get one](https://app.tavily.com/home)), entered once. | `TAVILY_API_KEY` |

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

## Workflows

| Workflow | What you get | Apps |
|---|---|---|
| **Call notes → deduped Linear issues** <br>`call_notes_to_deduped_linear_issues` | Feature requests from yesterday's calls land as issues, matched against what already exists, and #product sees the list. | Granola, Linear, Slack |
| **Customer requests attached to the issue they ask for** <br>`customer_requests_attached_to_the_issue_they_ask_for` | The roadmap gets ranked by who asked, not by who spoke loudest in planning. | Granola, Linear |
| **Discovery calls → PRD draft** <br>`discovery_calls_to_prd_draft` | A first PRD drafted from five interviews, with quotes, before the PM opens a blank doc. | Granola, Google Docs |
| **PRD doc → Linear project, milestones and issues** <br>`prd_doc_to_linear_project_milestones_and_issues` | The spec turns into a plan in one pass instead of an afternoon of copy-paste. | Google Docs, Linear |
| **Notion spec → Linear issues, linked back** <br>`notion_spec_to_linear_issues_linked_back` | Teams that write in Notion and ship in Linear stop keeping two lists. | Notion, Linear |
| **Weekly project update, written from the work** <br>`weekly_project_update_written_from_the_work` | The update reports what merged and what slipped, with health set from evidence rather than optimism. | Linear, GitHub, Slack |
| **Roadmap sheet that keeps itself true** <br>`roadmap_sheet_that_keeps_itself_true` | Leadership keeps its spreadsheet and the spreadsheet stops lying. | Linear, Google Sheets |
| **Feedback form → triaged customer needs** <br>`feedback_form_to_triaged_customer_needs` | Every response is either attached to an issue or logged as new, never left in a tab. | Google Forms, Linear, Google Sheets |
| **#feedback channel → Linear** <br>`feedback_channel_to_linear` | Feedback posted in Slack gets an issue and a checkmark, so nobody wonders if it was seen. | Slack, Linear |
| **Competitor changelog watch** <br>`competitor_changelog_watch` | A competitor ships something and a scoped issue exists before the sales team asks about it. | Firecrawl, Linear, Slack |
| **Cycle review doc and retro booking** <br>`cycle_review_doc_and_retro_booking` | Planned vs. done vs. carried over, written up and on the calendar before the retro. | Linear, Google Docs, Google Calendar |
| **Customer interview program** <br>`customer_interview_program` | Screener, scheduling and confirmations for ten interviews without a scheduling tool. | Google Forms, Google Calendar, Gmail |
| **Research synthesis from a folder of calls** <br>`research_synthesis_from_a_folder_of_calls` | Twenty calls become one synthesis page with every claim traceable to a note. | Granola, Notion, Linear |
| **Requests ranked by revenue** <br>`requests_ranked_by_revenue` | Each feature request carries the MRR of the customers behind it. | Linear, Stripe, Google Sheets |
| **Linear customers synced from Stripe** <br>`linear_customers_synced_from_stripe` | Linear's customer tiers and revenue reflect billing, so prioritisation uses real numbers. | Stripe, Linear |
| **Shipped → tell everyone who asked** <br>`shipped_to_tell_everyone_who_asked` | The loop gets closed with every requester, which is the cheapest retention there is. | Linear, Gmail |
| **Launch room in one go** <br>`launch_room_in_one_go` | Checklist page, launch-day event and channel with the right people, from the project. | Linear, Notion, Google Calendar, Slack |
| **Problem-space research brief** <br>`problem_space_research_brief` | A cited brief lands in the project's docs before the kickoff meeting. | Tavily, Linear, Slack |
| **Release notes for customers** <br>`release_notes_for_customers` | Engineering's release notes rewritten for users, from the issues that actually closed. | Linear, GitHub, Notion, Slack |
| **Linear digest to Slack** <br>`linear_digest_to_slack` | What moved, what's stuck, who owns it, with people actually @-mentioned. | Linear, Slack |

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__pm__call_notes_to_deduped_linear_issues "this week's customer calls, Linear team PROD"
```

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

8 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/product-manager-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": "granola-api-key",
      "description": "Granola: API key",
      "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": "google-client-secret",
      "description": "Google: OAuth client secret",
      "password": true
    },
    {
      "type": "promptString",
      "id": "notion-api-key",
      "description": "Notion: Integration secret (ntn_\u2026)",
      "password": true
    },
    {
      "type": "promptString",
      "id": "github-token",
      "description": "GitHub: Personal access token",
      "password": true
    },
    {
      "type": "promptString",
      "id": "firecrawl-api-key",
      "description": "Firecrawl: API key",
      "password": true
    },
    {
      "type": "promptString",
      "id": "stripe-api-key",
      "description": "Stripe: Secret or restricted key",
      "password": true
    },
    {
      "type": "promptString",
      "id": "tavily-api-key",
      "description": "Tavily: API key",
      "password": true
    }
  ],
  "servers": {
    "pm": {
      "type": "stdio",
      "command": "product-manager-mcp",
      "env": {
        "GRANOLA_API_KEY": "${input:granola-api-key}",
        "LINEAR_API_KEY": "${input:linear-api-key}",
        "SLACK_BOT_TOKEN": "${input:slack-bot-token}",
        "GOOGLE_CLIENT_SECRET": "${input:google-client-secret}",
        "NOTION_API_KEY": "${input:notion-api-key}",
        "GITHUB_TOKEN": "${input:github-token}",
        "FIRECRAWL_API_KEY": "${input:firecrawl-api-key}",
        "STRIPE_API_KEY": "${input:stripe-api-key}",
        "TAVILY_API_KEY": "${input:tavily-api-key}",
        "GOOGLE_CLIENT_ID": ""
      }
    }
  }
}
```

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

```json
{
  "mcpServers": {
    "pm": {
      "command": "product-manager-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.pm]
command = "product-manager-mcp"
required = true
startup_readiness = "catalog"
startup_timeout_sec = 30
```

Name the server `pm`. 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 49 tool schemas come to 74,058 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["product"].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 49 tools</summary>

- **Granola**: `granola_notes_list`, `granola_notes_get`, `granola_notes_transcript_get`, `granola_folders_list`
- **Linear**: `linear_search_issues`, `linear_issue_create`, `linear_customers_list`, `linear_customer_need_create`, `linear_project_create`, `linear_project_milestone_create`, `linear_project_get`, `linear_issues_list`, `linear_project_update_create`, `linear_projects_list`, `linear_project_milestones_list`, `linear_cycle_get`, `linear_customer_needs_list`, `linear_customer_create`, `linear_customer_update`, `linear_customer_get`, `linear_document_create`, `linear_users_list`
- **Slack**: `slack_chat_post_message`, `slack_conversations_history`, `slack_reactions_add`, `slack_conversations_create`, `slack_conversations_invite`, `slack_users_list`
- **Google Docs**: `gdocs_documents_create`, `gdocs_documents_batch_update`, `gdocs_documents_get`
- **Notion**: `notion_pages_retrieve_markdown`, `notion_comments_create`, `notion_pages_create`
- **GitHub**: `github_pulls_list`, `github_releases_generate_notes`
- **Google Sheets**: `gsheets_spreadsheets_values_update`, `gsheets_spreadsheets_values_append`
- **Google Forms**: `gforms_forms_responses_list`, `gforms_forms_create`
- **Firecrawl**: `firecrawl_monitor_create`, `firecrawl_monitor_checks_list`
- **Google Calendar**: `gcalendar_events_insert`
- **Gmail**: `gmail_messages_send`, `gmail_drafts_create`
- **Stripe**: `stripe_customers_list`, `stripe_subscriptions_list`
- **Tavily**: `tavily_research_create`, `tavily_research_get`

</details>

## License

Apache 2.0.

TDQS

B3.1/5.0

Scored across 51 tools

Disambiguation4/5

Most tools are clearly distinguished by service prefix and resource/action naming (e.g., linear_issues_list vs linear_search_issues, slack_chat_post_message vs slack_conversations_history). However, linear_project_update_create is misleading: it posts a status update on a project, not updates the project itself, and its description references a non-existent `project_update` tool, causing confusion.

Naming Consistency4/5

Tool names consistently use snake_case with a service prefix and action verb at the end (e.g., linear_issue_create, slack_conversations_invite, gdocs_documents_get). Minor deviations exist: connect and connection_status lack a service prefix, and notion_pages_retrieve_markdown uses 'retrieve_markdown' while other read tools use 'get'.

Tool Count2/5

With 51 tools spanning 14 different services, the set is far too large for a focused product manager server. While the breadth of integrations explains some volume, many tools are single-operation (e.g., only list or create) and could be consolidated, making the surface feel bloated and unwieldy.

Completeness2/5

Core CRUD operations are missing across nearly all resources: no update or delete for issues, projects, documents, Slack messages, or Gmail drafts. Several tools referenced in descriptions (linear_teams_list, linear_workflow_states_list, linear_project_update, gforms_forms_batch_update, github_releases_create) are absent, which will cause agent failures when following documented workflows.

Maintenance

ActivityMaintained
ResponsivenessNo issues