MCP App Store
by KeenEthics

Overview

The DEF 14 plugin connects ChatGPT to DEF 14’s shareholder activism intelligence platform. It enables users to research historical activist campaigns, arguments, tactics, and outcomes using DEF 14’s proprietary dataset of structured and unstructured activism materials. Users can identify relevant precedents, compare how activist investors have framed specific arguments, and surface campaign context for use in research and analysis. DEF 14’s underlying data has been processed, cleaned, enriched, and converted into structured metadata to support targeted retrieval. All tools are read-only and operate within the authenticated user’s permissions.

Tools

ask_corpus

ChatGPT
Answer from semantically retrieved corpus evidence only. Call search_materials first and use this tool only afterwards, with the same scope. Citations contain evidence quotes, locators, and a server-formatted citation string per evidence item to show to users as-is; opaque references and stable identifiers remain in versioned internal metadata. Explicit filters are hard constraints. Discover exact taxonomy values and their counts with list_filters (tool ask_corpus, field tone, tacticCategory, tacticSubcategory, toolsUsed, legalManeuver, targetAudience or targetCompanyCountry) before filtering; matching is case-insensitive and an unknown value returns INVALID_ARGUMENT. Page-level filters use filterScope "page" by default (evidence must come from a page that satisfies all of them together; "document" lets each match a different page). filters.documentIds (doc_v2 values from earlier results) restricts the answer to specific documents.

calculate_statistics

ChatGPT
Exact, server-side aggregation over the COMPLETE filtered population of one source: campaigns, demands_outcomes (the demands, outcomes, and settlements of campaigns), activists, target_companies, materials (one row per document), or material_pages (one row per page or slide). Use it instead of paging query_ tools whenever the question asks for a count, total, average, median, percentile, rate, minimum, maximum, or a comparison across groups (for example how many campaigns match, the average campaign return, how many demands or outcomes of a given category, or statistics by activist or target); never compute totals or rates from the first page of a query_ result. It does not retrieve records and does not join sources; use query_ tools for records. filters use exactly the filters and aliases of the matching query tool (call list_filters for names, types, and the aggregatable, groupable, unnestable, grain, and operations fields; for the materials sources use tool statistics_materials or statistics_material_pages). metrics is a list of {field, operation, as?, percentile?, where?}: operations are count, count_distinct, sum, avg, min, max, median, stddev (sample, n-1), and percentile (0 < p < 1, linear interpolation); field "" is allowed for count only. Allowed operations by field type: numbers all; text count and count_distinct; dates count, count_distinct, min, and max; booleans count, sum (number of true), and avg (the rate, a proportion between 0 and 1). A metric's where restricts which rows only that metric aggregates (for example a conditional count), while filters define the population and rowsMatched. Percentages: use avg on a boolean field, or divide a conditional count by a count in your answer using the exact numbers returned. groupBy (up to 3 keys) returns one row per group and null group keys form one group; a key is a field name, or {field, granularity} to group a date field by year, quarter, or month (for example statistics by year), returned as <field>.<granularity> with values like 2024, 2024-Q1, or 2024-01. Text keys group case-insensitively (like text filters), each group showing one spelling, and count_distinct on text ignores case too. orderBy (a metric alias or a groupBy key name) is applied with the groupBy keys as tiebreaker for a total, deterministic order. Multi-valued fields (array columns, nominees, ...) and protected identifiers are rejected with INVALID_ARGUMENT because they would double-count or expose identifiers, except the unnestable fields of the materials sources (see below). limit is the number of groups per page (default 100, maximum 1000) with at most 10 metrics. metadata.complete is always true on success: every statistic was computed over all rowsMatched rows of aggregationLevel (what one row is, so count(*) counts that); pagination only cuts the list of groups (groupsTotal, groupsReturned, truncated, nextCursor) and never the population. Nulls are excluded from numeric aggregations (nullHandling exclude); sum, avg, min, max, median, stddev, and percentile are null when there is no non-null value, and count is 0. A cursor expires after a few minutes or when the data changes (CURSOR_EXPIRED: repeat the request without it). SCOPE_TOO_LARGE means narrow the filters or reduce groupBy. avg, median, percentile, and stddev are rounded to 6 decimals; sums, minima, and maxima are exact (a value with more digits than a JSON number holds is returned as an exact decimal string). Outcomes follow the resolved campaign scope when private metadata is carried, like the demands and outcomes list. Materials sources: filters are those of search_materials (including documentIds, a list of doc_v2 documentId values, and dateFrom/dateTo) with unknown taxonomy values rejected (list_filters lists them with counts); multi-valued fields (tacticSubcategory, toolsUsed, legalManeuver, targetAudience, advisors, advisingIndividuals, leadingActivistPrincipals, tones) accept a match-any array or {"array-contains": "value"} in filters and in a metric where, and groupBy {"field": "toolsUsed", "unnest": true} makes one …

compare_documents

ChatGPT
Compare two to five materials with separately retrieved bilateral evidence. Citations contain evidence quotes, locators, and a server-formatted citation string per evidence item to show to users as-is; internal references and stable identifiers remain in versioned internal metadata.

get_activist_campaign_returns

ChatGPT
Return the Platform's Campaign returns table of one activist: one row per campaign with the announcement-date return, stock performance at 1w, 2w, 1m, 3m, 6m, 1y and 3y, and total return; returns are fractions (0.05 = 5%). Use it for requests such as "X's campaign returns" or "X's best campaigns by return". Narrow and rank the rows with campaignReturns.filters and campaignReturns.sort (nulls sort last); at most 100 rows are returned per call, and matched and hasMore say whether more rows matched. Call list_filters with tool get_activist_campaign_returns for the exact fields. Identify the activist with the activistRef returned by search_activism_entities or query_activists; activist_CIK is optional for direct lookup.

get_activist_profile

ChatGPT
Return one complete activist profile with strategy, key people, campaign statistics, dated campaign history, and outcome summary. Resolve an activist first when its identity is unresolved with search_activism_entities (or find it with query_activists). Identify the activist with exactly one of: the opaque activistRef returned for the resolved activist (use it when the client cannot forward the private response metadata; it expires after 30 minutes), the resolver's private _meta passed unchanged, or a direct activist_CIK when the CIK is already known. At least one is required; a call with none fails with MISSING_REQUIRED_CONTEXT. IDs never appear in visible output. When campaigns are returned, the private response metadata carries campaign scope for documentary follow-up.

get_campaign

ChatGPT
Return the complete permitted source row for one resolved campaign, with readable activist and target context. Pass the campaignRef returned by search_activism_entities or query_campaigns for the resolved or user-chosen campaign candidate (or, if your client supports it, forward the full protected metadata from search_activism_entities unchanged); campaign_id is private routing context and must never be supplied as an argument. Request optional objectives, tactics, ownership, outcomes, or source summary with include. outcomes returns every demand and outcome line of the campaign from the demands and outcomes data, at most 200, each with granularDemand, demandFamily, demandCategory, granularOutcome, outcomeFamily, outcomeCategory and every other field that is not null (null and empty fields are omitted), oldest outcome first, plus outcomeSummary (count of all the campaign's lines, byOutcome, byDemand) and outcomesTruncated (true when count exceeds the lines returned); use query_demands_outcome to filter or page them across campaigns. This tool does not return event, phase, price-series, benchmark, or evolution history. An Active lifecycle status is the source flag as of the last data refresh and is dated by lastFilingDate, its latest filing: when statusFreshness is HISTORICAL (no filing in the last 90 days) or DATE_UNKNOWN, say the campaign was active as of lastFilingDate instead of presenting it as currently active.

get_campaign_evolution

ChatGPT
Return chart-ready market, event, phase-category, outcome, and performance data for one resolved campaign. Pass the campaignRef returned by search_activism_entities or query_campaigns for the resolved or user-chosen campaign candidate (or, if your client supports it, forward the protected campaign metadata unchanged); campaign IDs are private routing context and must never be supplied as arguments. Target and SPY series use platform adjusted closes, dates use America/New_York market sessions, and source timestamps are normalized to UTC. Missing observations remain null with reasons. Daily total-return data is marked unavailable when the platform path has no authorized source; no volume or internal IDs are returned. This tool returns data only and renders nothing. When the user asks to see the evolution, build the chart yourself as a single self-contained, read-only interactive page (for example an HTML artifact; never an editing UI) using only this response. If the client cannot render HTML, summarize the same figures in a table instead. Apply the user's own requests for colors, ranges, or options over these defaults. Data rules: use market and benchmark indexedValue as returned (already 100 at methodology.baseTradingDate; do not rebase, recompute, interpolate, or fill nulls, leave gaps). Returns are fractions; show percentages, and the target-minus-benchmark gap in percentage points. Use each event's chartTradingDate, formType, title, and eventWindow (one-session marketReturn, benchmarkReturn, excessReturn, defined by methodology.eventWindowDefinition; skip events where status is missing). A series point whose values are null is a gap and carries a missingReason; every date is a session in methodology.marketTimeZone. Take outcomes from outcomes (outcomeDate, outcomeFamily, outcomeCategory, granularOutcome, activistResult, milestoneRole, and the demandFamily and demandCategory they answer; one entry per outcome line of demands_outcomes_data, dated by outcomeDate or undated) and never invent one. outcomes is capped, so when missingData reports series "outcomes" with a cap reason, say that only the first outcomes in the range are shown and point to query_demands_outcome for the rest. Do not invent events or numbers. Ranges: to offer several ranges (for example the campaign period, through a given year, and full history) call the tool once with explicit date_from and date_to covering the widest range, then filter client-side; the base date and returns stay stable. Series are capped at 300 sessions and thinned evenly, so check methodology.marketSeriesWindow.sampled and say so when true. Events are limited to 100: the 5 oldest plus the most recent ones that fit the response size budget: check methodology.eventSelection.truncated and say how many were omitted (omittedEvents) when true; returns and phases still use every matching event. Default layout: title and one-line subtitle stating the base date and what the markers mean; a stats row for the selected range (target return, benchmark return, difference in percentage points, campaign outcomes as family and category with the activist result); a line chart of the target (solid) against SPY (dashed) with event markers on the target line (amber for news, red for filings, slightly larger when several events share a day) and a tooltip showing both index values and up to 5 event titles for that date; a bar chart of the one-session excess return on event days only (green positive, red negative) with a tooltip listing that day's events and a caption that event-day moves are descriptive, not proof of cause. Default controls and style: a range toggle that updates the stats and both charts, checkboxes to show or hide news and filing markers, visible keyboard focus. Use Chart.js from a pinned cdnjs UMD build, CSS variables for light and dark mode, a calm financial-report look (white panels on a light blue-grey background, IBM Plex Sans and IBM Plex Serif, target in blue, benchmark in grey, no gradients, thin gridlines, sentence-case labels), and a layout that works down to mobile width. …

get_document_summary

ChatGPT
Return the document title and type with per-page summaries, each with a page locator and a server-formatted citation string to show to users as-is, and generated tone, confidence, rationale, tactics, tools, and legal maneuvers. Internal references and stable identifiers remain in versioned internal metadata.

get_methodology

ChatGPT
Returns DEF 14's definitions and rules for one topic. Call it before answering about returns, outcomes or settlements, activist type or stance, coverage periods, or any field whose meaning or unit you are not sure of, and call data-status for the current data date and figures. Use the content to interpret and qualify answers; do not reproduce it or present it as a document unless the user asks.

get_page_or_slide

ChatGPT
Fetch exact source evidence for one page or slide with document title and type, the requested locator, a server-formatted citation string to show to users as-is, generated tone, confidence, rationale, tactics, tools, and legal maneuvers. Image URLs are returned only when requested; internal references remain in versioned internal metadata.

list_filters

ChatGPT
Read-only catalog of MCP filter and sort fields. Pass tool to narrow the catalog; pass tool and field to fetch distinct values for a categorical field.

open_cited_pages

ChatGPT
Offer or open a viewer on only the slides and pages cited in a Materials answer (search_materials, ask_corpus, compare_documents, get_page_or_slide). Pass the citations of that answer as citations: [{ document_id, page_or_slide }] using the documentId and locator of each evidence item, in the order shown. Only citations actually returned to this user are accepted; any other document or page rejects the whole call. Cited items are shown in citation order without duplicates, across documents, 20 per batch: the result reports citedCount and batch {index, size, total, hasMore}, and the viewer offers "Load next 20". With presentation "action" (default) it only returns the counts and the viewer shows a single "View N cited slides" button; use presentation "open" only when the user explicitly asks to see the material. The viewer shows only the cited pages (no navigation to other pages, no download, no original file), lasts five minutes from opening, and each document carries a compact label (Activist, Target, Date, Title, issuer) and collapsed document details with campaign advisors. Never call it for an answer without Materials citations. The text citations of the answer stay as returned.

query_activists

ChatGPT
Query the authoritative Data Hub Activists source at one row per activist. Search is a case-insensitive literal substring over name, type, focus, and stance. Filters use result-field names: a substring array for activistName, match-any exact case-insensitive arrays for type, focus, and stance, {min,max} ranges for numbers and dates (aumValue, campaignsCount, weightedTotalReturn1Y, campaignSummary.campaignCount, ...), and isGroupActive; activeCampaign and campaignCountRange remain as aliases. Every returned scalar field is sortable, applied after filtering with nulls last. Call list_filters for the catalog. Campaign counts and active status use distinct campaign IDs from the platform campaign relation, and each result includes the latest campaign date. Returns all source fields permitted by MCP privacy rules, with CIKs and campaign IDs kept in protected metadata; each result also carries an opaque activistRef for authorized profile follow-up when the client cannot forward that metadata. Results use encrypted cursor pagination and allowlisted sorting. This tool returns records; for counts, averages, or other statistics over all matching activists use calculate_statistics.

query_campaigns

ChatGPT
Search, filter, sort, and paginate one record per campaign from the authoritative campaign source. Filters use result-field names: {min,max} ranges for numbers and dates (for example firstFilingDate, totalReturn, firstFilingPctOfClass), match-any arrays for categorical fields (sector, industry, activistType, primaryFormType), substring arrays for names (activistGroupName, targetCompanyName), and booleans (isCampaignActive); Documented aliases remain (status, activistName); sending an alias with its canonical name is INVALID_ARGUMENT. sort applies after filtering, with nulls last. dateOverlap is lifecycle overlap (campaigns running at any time in the window, including ones that started earlier); use firstFilingDate {min,max} for campaigns that started in a period. geography matches the target company's country, state, or city and tactic matches tactic categories of linked materials; neither is a returned field or sortable. For one activist's campaign returns table (stock performance by period per campaign), use get_activist_profile with includes ["campaign_returns"] instead. Call list_filters for the full catalog and allowed values. Carry the protected metadata unchanged for authorized follow-up; each result also carries an opaque campaignRef for get_campaign and get_campaign_evolution when the client cannot forward it; use it to list a campaign that search_activism_entities did not return as a candidate. Demands and outcomes are not campaign fields. Choose include_outcomes none, summary or full to nest every campaign's demand and outcome lines from the demands and outcomes data: both return outcomeSummary (count of all the campaign's lines, byOutcome by outcome family and category, byDemand by demand family and category), outcomes (oldest outcome first; null and empty fields omitted) and outcomesTruncated (true when count exceeds the lines returned); summary lines are compact (granularDemand, demandFamily, demandCategory, granularOutcome, outcomeFamily, outcomeCategory, plus dates, activistResult, activistAttribution and milestoneRole when present; text trimmed to 200 characters) and capped at 12 per campaign, full lines carry every non-null field and are capped at 10 per campaign; use get_campaign with include outcomes for up to 200 complete lines of one campaign; hasOutcomes only keeps campaigns that have recorded demands or outcomes and does not narrow the nested lines. Use query_demands_outcome to filter or page the lines themselves. This tool returns records; for counts, totals, averages, medians, rates, or comparisons across groups use calculate_statistics, which aggregates every matching campaign instead of one page.

query_demands_outcome

ChatGPT
Query the demands and outcomes linked to activist campaigns: the demands the activist made and the outcomes they led to, from the demands and outcomes data (several demands and outcomes per campaign), together with settlement records (a settlement is one type of outcome), each with readable campaign, activist, and target context. Rows come from two sources joined to their campaign by campaign_id: demand_outcome rows (one row per demand or outcome of a campaign, with its demand and outcome family and category) and settlement rows (the settlement agreement detail of a Settled outcome, which may repeat fields already present on the demand_outcome rows). Each result includes every source field permitted by MCP privacy policy under outcome and an outcome.recordSource value identifying demand_outcome or settlement rows. Filter outcome.recordSource to demand_outcome to count or list each demand or outcome once, or to settlement for the agreement details only. Use list_filters with tool query_demands_outcome to discover the visible columns of both sources, including the family and category columns; its returnedOnlyFields (such as settlement.link_to_agreement and settlement.committee_assignments) are returned and selectable with fields but not filterable. Filter the other columns as demand_outcome.<column_name> or settlement.<column_name> and sort scalar integer/numeric/date/boolean columns with the same prefixes. Settlement rows are wide: when comparing or aggregating many rows, set fields to an array of demand_outcome.<column_name> and settlement.<column_name> values to return only those columns (recordSource and campaign context are always retained); with fields, pageSize may be up to 100. totalCount is the number of rows matching the filters across all pages, so use it for counts instead of counting results; to compare individual records completely, keep requesting pages with nextCursor until it is absent instead of reporting a partial sample; for sums, averages, rates, or comparisons across groups use calculate_statistics with source demands_outcomes, which aggregates every matching row on the server, and never compute them from a page of results. Static filters use result-field names (campaign.activistName, campaign.targetName, outcomeDate {min,max}; outcome.recordSource is per-row and filter-only) and aliases activistName, targetName, outcomeDateFrom, and outcomeDateTo. sort {field, direction} applies after filtering; sortDirection remains a legacy alias for outcomeDate. Carry protected metadata from search_activism_entities or a single query_campaigns result to restrict this search to that resolved campaign; campaign IDs are never tool arguments. Pages sort by outcome date by default with nulls last and an encrypted stable continuation cursor.

query_target_companies

ChatGPT
Search one authoritative Master Data Companies row per target, including every source field permitted by MCP policy. The source ticker is returned separately from the current ticker resolved from target-company data. Name search uses the canonical company name. Ticker and exchange filters use the current listing; geography matches country, state, or city case-insensitively. Results also include distinct campaign metrics and an opaque campaignScopeRef for authorized materials follow-up. Financial metrics (marketCap, revenue, ebitda, margins, pe, dividendYield, evEbitda, priceToBookValue) are a master-data snapshot dated by financialsAsOf; financialsFreshness is CURRENT within 90 days, HISTORICAL when older, and DATE_UNKNOWN without a date. Never present HISTORICAL or DATE_UNKNOWN metrics as current values: state them as of financialsAsOf or say the date is unknown.

search_activism_entities

ChatGPT
Resolve a specific activist, target company, or campaign when its identity is unresolved before a structured or corpus lookup. Match canonical names, explicit aliases, tickers, CIKs, and campaign descriptions; the score is only a 0–100 ranking scale, not a confidence percentage. The result is either resolved or ambiguous. When it is ambiguous, present the readable ranked choices and ask the user to choose, without calling downstream tools. After a resolved campaign, carry the private response metadata unchanged into the next tool: use the protected campaign scope for search_materials, ask_corpus, and document tools. Reuse a valid resolved entity context for up to 30 minutes; resolve again when the entity changes, ambiguity remains, or that context expires. Broad thematic searches such as finding presentations discussing conglomerate discounts bypass this tool; broad thematic searches should go directly to search_materials. Entity identifiers are returned only in protected metadata, never in candidate labels or error text. Every campaign candidate, including those in an ambiguous result, carries an opaque campaignRef that get_campaign and get_campaign_evolution accept when the client cannot forward the private response metadata, and every activist candidate likewise carries an opaque activistRef that get_activist_profile accepts; both expire after 30 minutes. When the result is ambiguous, ask the user to choose first, then pass the chosen candidate's campaignRef or activistRef; the ambiguous result's metadata may be forwarded with it, but it blocks every call that does not name one of its candidates.

search_materials

ChatGPT
Semantic and lexical search over completed materials. For nominee, argument, evidence, filing, or material questions, call this tool first and call ask_corpus only afterwards with the same scope. Results contain readable document metadata, a server-formatted citation string for each result and evidence item to show to users as-is, snippets, and evidence quotes with locators. Opaque references and stable identifiers are kept in versioned internal metadata for authorized follow-up. Use exact activistGroupName and targetCompanyName filters copied from the visible catalog; these hard constraints do not resolve aliases. Use explicit filters as hard constraints, including targetCompanyCountry, advisors, advisingIndividuals, and leadingActivistPrincipals when the user asks for those metadata fields. advisors, advisingIndividuals, and leadingActivistPrincipals ignore case and punctuation (advisors also ignore trailing legal forms such as LLC; people also ignore middle initials, common nicknames such as Jeff or Jim, and a trailing (Firm) affiliation); a value missing from the catalog yields empty results plus filterDiagnostics with suggestions to retry, while empty results without filterDiagnostics mean the filters were valid but matched no material. Use metadataBoosts for available tone, tactic, tools, legal-maneuver, and audience signals. Discover exact taxonomy values and their counts with list_filters (tool search_materials, field tone, tacticCategory, tacticSubcategory, toolsUsed, legalManeuver, targetAudience or targetCompanyCountry) before filtering; matching is case-insensitive and an unknown value returns INVALID_ARGUMENT naming the closest known values, if any; use one only if it means what the user asked. Page-level filters (tone, tacticCategory, tacticSubcategory, toolsUsed, legalManeuver) use filterScope "page" by default: evidence comes only from a page that satisfies all of them together; set filterScope "document" to let each filter match a different page. Each result carries matchingPageCount (pages satisfying the page-level filters) and toneCounts, toolsUsedCounts, tacticCategoryCounts (pages per value over the whole document). With any filter, filters.documentIds or a campaign scope the response has an exact totalCount; nextCursor (pass it as cursor with the same request) pages through the ranked candidate pool of up to 500 evidence units, and totalCountExceedsReachable means more documents match than that pool holds, so narrow the filters. A cursor expires after 5 minutes and also when the ranked documents changed (data changed between pages): CURSOR_EXPIRED means repeat the request without the cursor. A warnings array means a taxonomy filter could not be validated and was matched as given, so an empty result may be a misspelled value. Use filters.documentIds (doc_v2 values from earlier results) to search inside specific documents. Pass campaignScopeRef from query_target_companies for a company follow-up; campaign IDs are never accepted directly. Unreadable results in either campaign-scoped search path are silently omitted.

ask_corpus

Claude

calculate_statistics

Claude

compare_documents

Claude

get_activist_profile

Claude

get_campaign

Claude

get_campaign_evolution

Claude

get_document_summary

Claude

get_page_or_slide

Claude

list_filters

Claude

query_activists

Claude

query_campaign_outcomes

Claude

query_campaigns

Claude

query_demands_outcome

Claude

query_target_companies

Claude

search_activism_entities

Claude

search_materials

Claude

Capabilities

Interactive

Example Prompts

Click any prompt to copy it.

Similar Apps