porchopsdev
DocsCustomer graph tools
MCP · customer-graph

Customer graph tools

Read your Customer Graph from an external agent: look up a customer, search in natural language, walk a timeline, summarize a window, and list the runs that touched a customer.

The customer-graph server ships at v1 and is read-only. Point your client at its Streamable HTTP endpoint and authenticate with a token carrying at least customer.read. See the quickstart to connect.

endpoint
https://mcp-customer-graph.porchops.com/mcp

Tools

customer.search

read-only

Search the current app's Customer Graph. Pass a natural-language query in `q` (PorchOps translates it to filters internally) OR structured `filters` directly (skips the NL step). Returns up to 20 matching customers, freshest-active first, plus the filters that were applied. Read-only.

customer.read
Input
  • qstring
    A natural-language search query, e.g. 'paying customers from acme.com'. Up to 500 characters. When set, PorchOps translates it into structured filters internally. Provide q OR filters (at least one).
  • filtersobject
    Structured filters. Provide these directly to skip the natural-language step. Provide q OR filters (at least one).
  • filters.lifecycleStagesarray
    Match customers in ANY of these lifecycle stages (signup, trial, activated, paying, expanding, at_risk, churned, churned_recovered). Omit to match all stages.
  • filters.emailDomainstring
    Match customers whose primary email is at this domain, e.g. 'acme.com' (with or without a leading @). Case-insensitive.
  • filters.mrrMinDollarsnumber
    Only customers whose monthly recurring revenue is at least this many dollars.
  • filters.mrrMaxDollarsnumber
    Only customers whose monthly recurring revenue is at most this many dollars.
  • filters.textstring
    Free-text substring matched (case-insensitive) across the customer's name, primary email, and company name.
  • limitinteger
    Maximum number of matches to return. Defaults to 20; capped at 20 (top-20).
Output
  • matchesarray · required
    The matching customers, freshest-active first (up to the requested limit).
  • matches[].customerIdstring · required
    The matched customer's PorchOps local id (cus_-prefixed).
  • matches[].emailPrimarystring · required
    The customer's primary email address.
  • matches[].namestring | null · required
    The customer's name; null when unknown.
  • matches[].companyNamestring | null · required
    The customer's company name; null when unknown.
  • matches[].lifecycleStageenum · required
    The customer's current lifecycle stage.
    signuptrialactivatedpayingexpandingat_riskchurnedchurned_recovered
  • matches[].mrrDollarsnumber · required
    The customer's monthly recurring revenue in dollars.
  • matches[].lastActivityAtstring | null · required
    ISO 8601 timestamp of the customer's last recorded activity; null when unknown.
  • appliedFiltersobject · required
    The structured filters actually applied - either the caller's filters, or the filters translated from the natural-language query. Lets a BYOA client see how its query was interpreted.
  • appliedFilters.lifecycleStagesarray
    Match customers in ANY of these lifecycle stages (signup, trial, activated, paying, expanding, at_risk, churned, churned_recovered). Omit to match all stages.
  • appliedFilters.emailDomainstring
    Match customers whose primary email is at this domain, e.g. 'acme.com' (with or without a leading @). Case-insensitive.
  • appliedFilters.mrrMinDollarsnumber
    Only customers whose monthly recurring revenue is at least this many dollars.
  • appliedFilters.mrrMaxDollarsnumber
    Only customers whose monthly recurring revenue is at most this many dollars.
  • appliedFilters.textstring
    Free-text substring matched (case-insensitive) across the customer's name, primary email, and company name.
  • interpretedFromNaturalLanguageboolean · required
    True when the applied filters were translated from a natural-language q.
Example
request
{
  "q": "paying customers from heronsoftware.com quiet 30+ days"
}
response
{
  "matches": [
    {
      "customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF",
      "emailPrimary": "founder@heronsoftware.com",
      "name": "Heron Software",
      "companyName": "Heron Software",
      "lifecycleStage": "signup",
      "mrrDollars": 240,
      "lastActivityAt": "2026-05-14T09:32:00.000Z"
    }
  ],
  "appliedFilters": {
    "lifecycleStages": [
      "signup"
    ],
    "emailDomain": "heronsoftware.com",
    "mrrMinDollars": 240,
    "mrrMaxDollars": 240,
    "text": "example"
  },
  "interpretedFromNaturalLanguage": false
}

customer.read

read-only

Fetch one customer from the current app's Customer Graph by EITHER its PorchOps local id (cus_...), its Stripe customer id (cus_OABC...), OR its primary email. Provide exactly one key. Returns the customer's identity, lifecycle stage, value (MRR + LTV in dollars), and health signals, or customer: null when nothing matches. Read-only.

customer.read
Input
  • customerIdstring
    The customer's PorchOps local id (cus_-prefixed Crockford base32, e.g. cus_01HKQM4Z...). Use when you already hold the local id from a prior customer.search or customer.list. Provide exactly one of customerId, stripeCustomerId, or email.
  • stripeCustomerIdstring
    The customer's Stripe customer id (e.g. cus_OABC1234567890). Use when you have a Stripe id from a webhook payload or the Stripe dashboard. Provide exactly one of customerId, stripeCustomerId, or email.
  • emailstring
    The customer's primary email address (case-insensitive). Use when you only know the person's email. Provide exactly one of customerId, stripeCustomerId, or email.
Output
  • customerobject | null · required
    The matched customer, or null when no non-deleted customer in this app matches the supplied key.
  • customer.customerIdstring · required
    The customer's PorchOps local id (cus_-prefixed).
  • customer.emailPrimarystring · required
    The customer's primary email address.
  • customer.stripeCustomerIdstring | null · required
    The customer's Stripe customer id; null for a manually-entered customer.
  • customer.namestring | null · required
    The customer's full name; null when unknown.
  • customer.companyNamestring | null · required
    The customer's company name; null when unknown.
  • customer.lifecycleStageenum · required
    Where the customer sits in the funnel: one of signup, trial, activated, paying, expanding, at_risk, churned, or churned_recovered.
    signuptrialactivatedpayingexpandingat_riskchurnedchurned_recovered
  • customer.mrrDollarsnumber · required
    The customer's monthly recurring revenue in whole and fractional dollars.
  • customer.ltvDollarsnumber · required
    The customer's lifetime value to date, in dollars.
  • customer.healthScorenumber | null · required
    A 0-100 account-health score (higher is healthier); null until an agent has computed one.
  • customer.lastActivityAtstring | null · required
    ISO 8601 timestamp of the customer's most recent recorded activity; null when never recorded.
Example
request
{
  "customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF"
}
response
{
  "customer": {
    "customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF",
    "emailPrimary": "founder@heronsoftware.com",
    "stripeCustomerId": "cus_OABC1234567890",
    "name": "Heron Software",
    "companyName": "Heron Software",
    "lifecycleStage": "signup",
    "mrrDollars": 240,
    "ltvDollars": 5760,
    "healthScore": 82,
    "lastActivityAt": "2026-05-14T09:32:00.000Z"
  }
}

customer.timeline

read-only

Build one customer's activity timeline: payments (charges + invoices), support messages, agent runs, and Knowledge Base notes, merged newest-first. Each entry is tagged with its kind. Returns an empty list when the customer has no activity. Read-only.

customer.read
Input
  • customerIdstring · required
    The customer's PorchOps local id (cus_-prefixed). The timeline is built for this customer.
  • limitinteger
    Maximum number of timeline entries to return, newest-first across all activity types. Defaults to 50; capped at 200.
Output
  • entriesarray · required
    The customer's activity, newest-first across charges, invoices, messages, runs, and KB notes. Empty when the customer has no activity (or does not exist in this app).
Example
request
{
  "customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF"
}
response
{
  "entries": [
    {
      "kind": "charge",
      "at": "2026-05-14T09:32:00.000Z",
      "refId": "chg_01HKQM4Z8YB3T7VN2P9R6E4WQF",
      "title": "Payment recovered",
      "status": "completed",
      "amountCents": 1,
      "currency": "usd"
    }
  ]
}

customer.activity_summary

read-only

Summarize one customer's activity over a window: charge / invoice / message / run / KB-note counts, gross charge amount, and the last-touch timestamp, plus the customer's current lifecycle stage and MRR. Window defaults to the last 30 days. Returns found: false with a zeroed shell when the customer is not in this app. Read-only.

customer.read
Input
  • customerIdstring · required
    The customer's PorchOps local id (cus_-prefixed). The summary is computed for this customer.
  • windowStartstring
    ISO 8601 datetime at the start of the window. Defaults to 30 days before windowEnd when omitted.
  • windowEndstring
    ISO 8601 datetime at the end of the window. Defaults to the current time when omitted. Must be >= windowStart; the window span must be <= 365 days.
Output
  • foundboolean · required
    True when the customer exists in this app; false means the rest is a zeroed shell.
  • customerIdstring · required
    Echo of the requested customer id.
  • windowStartstring · required
    Resolved window start as an ISO 8601 datetime (UTC).
  • windowEndstring · required
    Resolved window end as an ISO 8601 datetime (UTC).
  • lifecycleStageenum | null · required
    The customer's CURRENT lifecycle stage (a snapshot, not a window quantity); null when the customer was not found.
    signuptrialactivatedpayingexpandingat_riskchurnedchurned_recovered
  • mrrDollarsnumber · required
    The customer's CURRENT monthly recurring revenue in dollars (snapshot); 0 when not found.
  • lastTouchAtstring | null · required
    ISO 8601 timestamp of the most recent activity within the window, across every primitive; null when the window is empty.
  • chargesobject · required
    Charge activity within the window.
  • charges.totalinteger · required
    Charges made in the window.
  • charges.succeededinteger · required
    Of those, how many succeeded.
  • charges.failedinteger · required
    Of those, how many failed.
  • charges.grossAmountCentsinteger · required
    Sum of charge amounts in the window, in cents (the customer's own revenue).
  • invoicesobject · required
    Invoice activity within the window.
  • invoices.totalinteger · required
    Invoices created in the window.
  • invoices.amountPaidCentsinteger · required
    Sum of amount paid across those invoices, in cents.
  • messagesobject · required
    Inbox (support) activity within the window.
  • messages.totalinteger · required
    Inbox messages received in the window.
  • messages.inboundinteger · required
    Of those, how many were inbound (from the customer).
  • messages.outboundinteger · required
    Of those, how many were outbound (operator replies).
  • runsobject · required
    Agent-run activity within the window.
  • runs.totalinteger · required
    Playbook runs that touched the customer in the window.
  • kbNotesobject · required
    Knowledge Base activity within the window.
  • kbNotes.totalinteger · required
    Knowledge Base notes attached to the customer in the window.
Example
request
{
  "customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF"
}
response
{
  "found": false,
  "customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF",
  "windowStart": "2026-05-14T09:32:00.000Z",
  "windowEnd": "2026-05-14T09:32:00.000Z",
  "lifecycleStage": "signup",
  "mrrDollars": 240,
  "lastTouchAt": "2026-05-14T09:32:00.000Z",
  "charges": {
    "total": 3,
    "succeeded": 1,
    "failed": 1,
    "grossAmountCents": 1
  },
  "invoices": {
    "total": 3,
    "amountPaidCents": 1
  },
  "messages": {
    "total": 3,
    "inbound": 1,
    "outbound": 1
  },
  "runs": {
    "total": 3
  },
  "kbNotes": {
    "total": 3
  }
}

customer.related_runs

read-only

List the playbook runs that touched one customer, newest-first, with status and cost (in PorchOps credits). Anchored on a customer id. Returns an empty list when the customer has no runs. Read-only. Requires both customer.read and agent.runs.read permissions.

customer.readagent.runs.read
Input
  • customerIdstring · required
    The customer's PorchOps local id (cus_-prefixed). Runs anchored on this customer are returned.
  • limitinteger
    Maximum number of runs to return, newest-first. Defaults to 20; capped at 100.
Output
  • runsarray · required
    The playbook runs anchored on this customer, newest-first. Empty when the customer has no runs (or does not exist in this app).
  • runs[].runIdstring · required
    The run's PorchOps id (run_-prefixed).
  • runs[].playbookIdstring · required
    The id of the playbook this run executed.
  • runs[].statusstring · required
    The run's current status: one of pending, running, awaiting_approval, completed, terminated, or failed.
  • runs[].startedAtstring | null · required
    ISO 8601 timestamp when the run started executing; null if it never started.
  • runs[].completedAtstring | null · required
    ISO 8601 timestamp when the run reached a terminal state; null while non-terminal.
  • runs[].costCreditsnumber · required
    The run's cost in PorchOps credits (units of agent work). Never a raw dollar cost, token count, or model name.
Example
request
{
  "customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF"
}
response
{
  "runs": [
    {
      "runId": "run_01HKQM4Z8YB3T7VN2P9R6E4WQF",
      "playbookId": "plb_01HKQM4Z8YB3T7VN2P9R6E4WQF",
      "status": "completed",
      "startedAt": "2026-05-14T09:32:00.000Z",
      "completedAt": "2026-05-14T09:32:00.000Z",
      "costCredits": 1
    }
  ]
}