Skip to main content
Glama

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.

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 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. 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:

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), entered once. In the Granola app: Settings → Connectors → API keys. Business plan or above.

GRANOLA_API_KEY

Linear

Your own key (get one), entered once.

LINEAR_API_KEY

Slack

Your own key (get one), 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).

GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET

Notion

Your own key (get one), entered once. Then share the pages it should see with the integration.

NOTION_API_KEY

GitHub

Your own key (get one), entered once.

GITHUB_TOKEN

Firecrawl

Your own key (get one), entered once.

FIRECRAWL_API_KEY

Stripe

Your own key (get one), entered once.

STRIPE_API_KEY

Tavily

Your own key (get one), entered once.

TAVILY_API_KEY

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

Related MCP server: Chief of Staff

Workflows

Workflow

What you get

Apps

Call notes → deduped Linear issues 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 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 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 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 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 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 roadmap_sheet_that_keeps_itself_true

Leadership keeps its spreadsheet and the spreadsheet stops lying.

Linear, Google Sheets

Feedback form → triaged customer needs 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 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 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 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 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 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 requests_ranked_by_revenue

Each feature request carries the MRR of the customers behind it.

Linear, Stripe, Google Sheets

Linear customers synced from Stripe 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 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 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 problem_space_research_brief

A cited brief lands in the project's docs before the kickoff meeting.

Tavily, Linear, Slack

Release notes for customers 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 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 if you have not, since Claude Desktop starts the server with it, then open the .mcpb from the latest release. 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.

{
  "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:

{
  "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:

[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 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:

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: your coding agent writes the declarations, and Charter's conformance suite checks them.

  • 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

License

Apache 2.0.

Available Tools

51 tools
connectA

Connect one app this server uses. For an app that issues keys, says where to get one and the terminal command that stores it. For Google, once the user's own OAuth client is set, starts the browser sign-in and returns at once: the user approves in the browser and the next call works. To see which apps are connected, call connection_status. Never ask the user for a key in the chat.

Inputs: app (required).

connection_statusA
Read-only

See which apps this server is connected to, and how to connect each one that is not. Changes nothing.

Inputs: no named parameters.

firecrawl_monitor_checks_listC
Read-onlyIdempotent

List checks for a monitor.

Inputs: monitorId (required), limit, offset, status.

firecrawl_monitor_createB

Create a scheduled monitor for scrape, crawl, or search targets.

Inputs: name (required), targets (required), schedule (required), goal, +4 more.

gcalendar_events_insertC

Create a calendar event; returns details of the event.

Inputs: event (required), calendarId (required), sendUpdates, maxAttendees, +3 more.

gdocs_documents_batch_updateA

Apply a list of edits to a document. Each request sets exactly one kind of edit — insert_text, replace_all_text, update_text_style, insert_table and so on. Edits apply in order against a document that shifts as they do: when inserting at several indices, order the requests back-to-front so earlier insertions do not move the positions later ones refer to.

Inputs: body (required), documentId (required).

gdocs_documents_createA

Create a blank document with a title. Only the title is honoured — the document is created empty. To add content, call this and then documents_batch_update with the returned documentId.

Inputs: body.

gdocs_documents_getA
Read-onlyIdempotent

Read a document's full structural content. The document ID is the long string in its URL, between '/d/' and '/edit'. Pass include_tabs_content=true for a document with several tabs, since the default response covers only the first.

Inputs: documentId (required), includeTabsContent, suggestionsViewMode.

gforms_forms_createA

Create a new form from a title. Only the title and the document title are honoured: the form is created with no description, no items and default settings. To add questions, call this and then forms_batch_update with the returned formId. Pass unpublished=true to create a form that does not yet accept responses.

Inputs: body (required), unpublished.

gforms_forms_responses_listA
Read-onlyIdempotent

List a form's submitted responses, newest page first, up to 5000 per page. The only supported filter is on submission time: pass filter='timestamp >= 2026-01-01T00:00:00Z' to read what has arrived since a point in time. Answers come back keyed by questionId.

Inputs: formId (required), filter, pageSize, pageToken.

github_pulls_listA
Read-onlyIdempotent

List pull requests. Filter by state, by base branch, or by head in the form 'user:branch'.

Inputs: repo (required), owner (required), base, head, +5 more.

github_releases_generate_notesA

Generate release-note text from the pull requests merged since a previous tag. This creates nothing — GitHub returns a name and a markdown body and saves neither — so it is safe to call for a draft changelog. Pass the result to releases_create to publish it.

Inputs: repo (required), owner (required), tagName (required), previousTagName, +2 more.

gmail_drafts_createC

Save an email draft to Gmail.

Inputs: body (required), userId.

gmail_messages_sendC

Send an email via the Gmail API.

Inputs: body (required), userId.

granola_folders_listA
Read-onlyIdempotent

List the folders this key can reach, sorted alphabetically. The listing is flat and each folder names its parent in parent_folder_id, so a hierarchy is reassembled from a full walk. Use it to find the folder_id that narrows a note listing or a webhook endpoint's deliveries.

Inputs: cursor, pageSize.

granola_notes_getA
Read-onlyIdempotent

Read one meeting note: its AI summary, the people who attended, the calendar event it was taken against, and the folders it belongs to. Pass include='transcript' to get the transcript inline as well. A transcript too large to inline answers 413 TRANSCRIPT_TOO_LARGE. Read it with notes_transcript_get instead.

Inputs: noteId (required), include.

granola_notes_listA
Read-onlyIdempotent

List meeting notes, filtered by when they were created or last updated and optionally narrowed to one folder and its subfolders. Returns each note's id, title, owner and timestamps, not its content. Fetch that with notes_get. Only notes that already have a generated AI summary appear here.

Inputs: cursor, folderId, pageSize, createdAfter, +2 more.

granola_notes_transcript_getA
Read-onlyIdempotent

Read a meeting transcript one page at a time. Use this for any long transcript, and whenever notes_get answers 413 TRANSCRIPT_TOO_LARGE. Each item is one line of speech with who said it and when.

Inputs: noteId (required), cursor, pageSize.

gsheets_spreadsheets_values_appendC

Appends values to a spreadsheet.

Inputs: range (required), valueRange (required), spreadsheetId (required), valueInputOption (required), +4 more.

gsheets_spreadsheets_values_updateC

Sets values in a range of a spreadsheet.

Inputs: range (required), valueRange (required), spreadsheetId (required), valueInputOption (required), +3 more.

linear_customer_createB

Create a customer. Only name is required.

Inputs: variables (required).

linear_customer_getC
Read-onlyIdempotent

Get one customer by its UUID.

Inputs: variables (required).

linear_customer_need_createA

Record a customer request, optionally attached to an issue or project. This is the one Linear mutation whose reply carries no object — it answers only with whether it worked.

Inputs: variables (required).

linear_customer_needs_listA
Read-onlyIdempotent

List customer requests. Filter by issue to see what a piece of work is wanted for, or by customer to see everything one company has asked for.

Inputs: variables.

linear_customers_listC
Read-onlyIdempotent

List customers — the companies whose requests Linear tracks against issues.

Inputs: variables.

linear_customer_updateC

Update a customer's name, owner, tier, revenue or size.

Inputs: variables (required).

linear_cycle_getB
Read-onlyIdempotent

Get one cycle with the issues in it.

Inputs: variables (required).

linear_document_createA

Create a document. Only title is required — a document can hang off a project, initiative, issue, cycle or team, or off nothing at all.

Inputs: variables (required).

linear_issue_createA

Create an issue. team_id and title are required; everything else is optional. The UUIDs for team, assignee, state and labels come from teams_list, users_list and workflow_states_list — Linear does not accept names here. Set parent_id to create a sub-issue.

Inputs: variables (required).

linear_issues_listA
Read-onlyIdempotent

List issues, optionally filtered. Conditions on one filter object combine with AND. To find a team's open work, filter on team.key and state.type.

Inputs: variables.

linear_project_createA

Create a project. name and at least one team UUID are required; team UUIDs come from teams_list.

Inputs: variables (required).

linear_project_getB
Read-onlyIdempotent

Get one project with its content, milestones and members. Accepts the project's UUID or the slug from its URL.

Inputs: variables (required).

linear_project_milestone_createC

Create a milestone inside a project.

Inputs: variables (required).

linear_project_milestones_listA
Read-onlyIdempotent

List project milestones. Filter by project to get one project's.

Inputs: variables.

linear_projects_listB
Read-onlyIdempotent

List projects in the workspace, with their status and progress.

Inputs: variables.

linear_project_update_createA

Post a status update on a project, optionally reporting health as 'onTrack', 'atRisk' or 'offTrack'. This writes a note; to change the project itself, use project_update.

Inputs: variables (required).

linear_search_issuesA
Read-onlyIdempotent

Search issues by text, across titles and descriptions. Set include_comments to search inside comments too. This is full-text search; to filter on fields such as state or assignee, use issues_list.

Inputs: variables (required).

linear_users_listA
Read-onlyIdempotent

List workspace members. Use this to resolve a person's name or email to the UUID that assignment expects.

Inputs: variables.

notion_comments_createA

Comment on a page or block, or reply to an existing discussion with its discussion_id. One or the other, never both.

Inputs: body (required).

notion_pages_createA

Create a page — as a subpage of another page, or as a row of a database by giving its data_source_id as the parent. Content comes as a markdown string Notion parses into blocks, or from a template: one or the other, never both.

Inputs: body (required), filterProperties.

notion_pages_retrieve_markdownA
Read-onlyIdempotent

Get a page's whole content as Markdown, rendered by Notion. One call instead of recursing through blocks_children_list, and the cheapest way to read a page you only need to understand rather than edit.

Inputs: pageId (required), includeTranscript.

slack_chat_post_messageA

Send a message to a Slack channel, private group, or DM. Provide text for a plain message; set thread_ts to reply inside an existing thread.

Inputs: channel (required), text, parse, blocks, +13 more.

slack_conversations_createB

Create a channel. Slack refuses a name that is already taken.

Inputs: name (required), isPrivate.

slack_conversations_historyB
Read-onlyIdempotent

Fetch recent messages from a Slack channel.

Inputs: channel (required), limit, cursor, latest, +3 more.

slack_conversations_inviteA

Add people to a channel. With force set, Slack adds everyone it can and reports the rest alongside a success, so read the result.

Inputs: users (required), channel (required), force.

slack_reactions_addC

Add an emoji reaction to a Slack message.

Inputs: name (required), channel (required), timestamp (required).

slack_users_listA
Read-onlyIdempotent

List members of the workspace. Use this to resolve a person's name to the user ID that Slack mentions and filters expect.

Inputs: limit, cursor, teamId, includeLocale.

stripe_customers_listA
Read-onlyIdempotent

List customers, most recently created first. Filter by email to find one.

Inputs: email, limit, endingBefore, startingAfter.

stripe_subscriptions_listB
Read-onlyIdempotent

List subscriptions. Filter by customer or status.

Inputs: limit, price, status, customer, +2 more.

tavily_research_createA

Create an async research task that searches, analyzes sources, and generates a cited report. Poll results with research_get.

Inputs: input (required), files, model, outputLength, +4 more.

tavily_research_getA
Read-onlyIdempotent

Retrieve the status and results of a research task by request_id. HTTP 202 means still running; poll until HTTP 200.

Inputs: requestId (required), includeUsage.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    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

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      B
      maintenance
      Runs 20 slash-command workflows across Google Calendar, Gmail, Linear, Slack, Granola, Google Docs, GitHub, Stripe, Notion, Google Forms, Sheets and Drive to produce morning briefs, meeting prep, action items assigned to owners and weekly updates. Reads proceed without asking, while anything that creates, sends, changes or deletes is shown for approval first.
      47
      1
      Apache 2.0
    • A
      license
      B
      quality
      B
      maintenance
      Enables freelancers and agencies to run eight back-office workflows across Stripe, Google Drive, Linear, Google Calendar, Gmail, GitHub, Google Docs, Granola, Google Sheets and Firecrawl, covering client onboarding, invoices from calendar and commits, status reports, scope-creep detection, site audits and overdue invoice chasers. Reads run freely, while anything that creates, sends, changes or deletes is shown for approval first, with credentials kept in your own OS keychain and no proxying through any third-party server.
      28
      Apache 2.0
    • A
      license
      B
      quality
      B
      maintenance
      Runs 13 agent slash-command workflows across Linear, GitHub, Google Docs, Slack, Firecrawl, Sheets, Tavily, Forms, Calendar, Gmail, Stripe, Granola and Notion — turning shipped features into blog and social drafts and handling competitor pricing, mention monitoring, SEO gaps, webinars, newsletters and launch-day tracking. Every credential is your own, kept in the OS keychain or client config, and anything that writes, sends or deletes is shown for approval first.
      29
      Apache 2.0