Skip to content

Commit 233256c

Browse files
michelle0927claudeGTFalcao
authored
feat(dappier): AI-optimized Dappier components for MCP (PipedreamHQ#21826)
* feat(dappier): AI-optimized Dappier action set for MCP (real-time search, recommendations, analytics) Initial AI-optimized Dappier components for the MCP tool surface, covering issue PipedreamHQ#21660 (real-time web search, AI content recommendations, data-model querying) plus the four Analytics API endpoints. Iterated against the MCP eval suite (evals/dappier) — 9/10 green on Sonnet 5 (trials: 1); the one failure is an upstream HTTP 500 on POST /app/v2/search, not a component defect. agent-audit: 100/100. - search-real-time-data (new, 0.0.1): real-time web/data search via a Dappier AI model (am_ id); returns a synthesized answer. Eval #9 passes. - get-ai-recommendations (new, 0.0.1): AI-ranked content recommendations for a data model (dm_ id); optional additive `fields` projection trims large article payloads. Eval #10 blocked by an upstream 500 (server-side). - get-ask-ai-analytics (new, 0.0.1): aggregate Ask AI widget analytics. Evals #1/#4/#5 pass. - get-ask-ai-logs (new, 0.0.1): raw Ask AI conversation logs with page/limit pagination + paging guidance. Evals #2/#6 pass. - get-sponsored-conversations-analytics (new, 0.0.1): sponsored-conversation (ad campaign) analytics. Eval #7 passes. - get-session-intelligence (new, 0.0.1): session intent/topic breakdowns. Evals #3/#8 pass. - dappier.app.mjs: shared analytics prop definitions + GET request methods. - common/utils.mjs: validateDateRange (365-day cap) + pluckFields projection helper. - common/constants.mjs: analytics interaction types, range + page-size bounds. App package.json bumped 0.0.1 -> 0.1.0 (minor -- new actions). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dappier): add trailing newline to package.json (eol-last) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dappier): address review — numArticlesRef max, own-prop projection, date default wording - get-ai-recommendations: numArticlesRef description said max 1000 but schema is max 100; corrected the doc. - common/utils.mjs: pluckFields now uses Object.hasOwn so a requested field name that collides with an inherited prop (e.g. toString) is not copied; own result fields still are. - dappier.app.mjs: reworded startDate default from the brittle/off-by-one '7 days before today' to 'the last 7 days (UTC), i.e. today and the six prior days', matching observed API behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dappier): resolve one-sided analytics date windows before sending Verified against the live API: omitting either start_date or end_date makes Dappier reset BOTH bounds to its default trailing-7-day window, silently discarding the bound the caller supplied — so a one-sided range returned the wrong period. resolveDateRange() now fills the missing bound (missing end -> today UTC; missing start -> 6 days before the end) and always sends both, so a supplied bound is honored; both-omitted still defers to the API default. All four analytics actions use it; start/end prop docs updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dappier): match analytics date-range cap to the API (inclusive days) Probed the live API: it accepts a 364-day start/end difference (365 inclusive days) and returns 400 at a 365-day difference (366 inclusive days). validateDateRange used '> 365 days difference', so a 365-day-difference window slipped past the fail-fast and hit a raw API 400. Now counts inclusive days (difference + 1) and caps at 365, matching the API exactly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(dappier): note session_intelligence wrapper in get-session-intelligence description The API nests all six breakdowns under a single top-level `session_intelligence` object (verified live). The description listed them as if they were root keys, so an agent would look for them at the root and miss the `session_intelligence.` prefix. Description-only; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(dappier): use camelCase widgetId in session-intelligence example The example told the agent to pass `widget_id`, but the input prop is `widgetId` (run() maps it to the `widget_id` query param). Match the example to the prop the agent actually sets. Description-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(dappier): use camelCase prop names in remaining action examples Same fix as get-session-intelligence, applied to the sibling descriptions: the agent-facing 'Example:' hints used API param names (start_date, end_date, campaign_id, data_model_id) instead of the camelCase input props (startDate, endDate, campaignId, dataModelId). API-mapping mentions stay snake_case. Description-only; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(dappier): camelCase input-guidance for model-id props The 'Provide a ...' input guidance used API param names — get-ai-recommendations said 'Provide a data_model_id' (prop is dataModelId) and search-real-time-data said 'Provide an ai_model_id' (prop is aiModelId). Use the camelCase input keys; snake_case is retained only where describing the API request mapping (query param / path template). Description-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Adding missing dependencies field --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: GTFalcao <gtfalcao96@gmail.com>
1 parent 40f87e4 commit 233256c

11 files changed

Lines changed: 743 additions & 9 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// x-pd-ai: optimized
2+
import app from "../../dappier.app.mjs";
3+
import { SEARCH_ALGORITHMS } from "../../common/constants.mjs";
4+
import { pluckFields } from "../../common/utils.mjs";
5+
6+
export default {
7+
key: "dappier-get-ai-recommendations",
8+
name: "Get AI Recommendations",
9+
description: "Get AI-ranked content recommendations for a Dappier data model (POST `/app/v2/search`, with `data_model_id` passed as a query parameter). Returns a structured `results` array of ranked articles (title, summary, url, score, source, pubdate, etc.). Provide a `dataModelId` (prefix `dm_`, e.g. `dm_01hpsxyfm2fwdt2zet9cg6fdxt` - a known real-time web search data model). NOTE `dm_` data-model IDs are distinct from the `am_` AI-model IDs used by **Search Real-Time Data**. There is no listing endpoint in the Dappier API; discover valid data model IDs in the Dappier Marketplace at https://platform.dappier.com/marketplace. Example: `dataModelId=dm_01hpsxyfm2fwdt2zet9cg6fdxt`, `query=top technology stories today` returns the top-ranked matching articles; pass `fields=[\"title\",\"url\",\"summary\"]` to trim each result to just those keys. [See the documentation](https://docs.dappier.com/api-reference/endpoint/ai-recommendations).",
10+
version: "0.0.1",
11+
type: "action",
12+
annotations: {
13+
destructiveHint: false,
14+
openWorldHint: true,
15+
readOnlyHint: true,
16+
},
17+
props: {
18+
app,
19+
dataModelId: {
20+
type: "string",
21+
label: "Data Model ID",
22+
description: "The Dappier data model ID to query (prefix `dm_`, e.g. `dm_01hpsxyfm2fwdt2zet9cg6fdxt`, a known real-time web search data model). This is a data-model ID, distinct from the `am_` AI-model IDs used by **Search Real-Time Data**. Discover valid IDs in the Dappier Marketplace: https://platform.dappier.com/marketplace (the Dappier API exposes no listing endpoint). Sent as the `data_model_id` query parameter.",
23+
},
24+
query: {
25+
propDefinition: [
26+
app,
27+
"query",
28+
],
29+
description: "The natural-language query or context used to rank recommendations. Example: `top technology stories today`.",
30+
},
31+
searchAlgorithm: {
32+
type: "string",
33+
label: "Search Algorithm",
34+
description: "Ranking algorithm to apply. One of: `most_recent`, `semantic`, `most_recent_semantic`, `trending`. Defaults to `semantic`.",
35+
options: SEARCH_ALGORITHMS,
36+
optional: true,
37+
},
38+
similarityTopK: {
39+
type: "integer",
40+
label: "Similarity Top K",
41+
description: "Number of top semantically similar items to consider. Min 1, max 1000. Defaults to 9.",
42+
min: 1,
43+
max: 1000,
44+
optional: true,
45+
},
46+
numResults: {
47+
type: "integer",
48+
label: "Number of Results",
49+
description: "Number of recommendation results to return. Min 1, max 100. Defaults to 10.",
50+
min: 1,
51+
max: 100,
52+
optional: true,
53+
},
54+
numArticlesRef: {
55+
type: "integer",
56+
label: "Number of Articles from Ref",
57+
description: "Minimum number of articles to return from the `ref` domain. Min 0 (0 = no domain-specific articles required), max 100. Defaults to 0.",
58+
min: 0,
59+
max: 100,
60+
optional: true,
61+
},
62+
ref: {
63+
type: "string",
64+
label: "Ref Domain",
65+
description: "Site domain to prioritize for display in results (e.g. `example.com`). Optional.",
66+
optional: true,
67+
},
68+
page: {
69+
type: "integer",
70+
label: "Page",
71+
description: "Result page number for pagination. Min 1. Defaults to 1.",
72+
min: 1,
73+
optional: true,
74+
},
75+
fields: {
76+
type: "string[]",
77+
label: "Fields",
78+
description: "Optional allow-list of keys to keep on each returned article, to trim large payloads (e.g. `title`, `url`, `summary`, `score`). Omit to return every field. Available keys: `author`, `image_url`, `preview_content`, `pubdate`, `pubdate_unix`, `score`, `site`, `site_domain`, `source_url`, `summary`, `title`, `url`.",
79+
optional: true,
80+
},
81+
},
82+
async run({ $ }) {
83+
const response = await this.app.getAiRecommendations({
84+
$,
85+
dataModelId: this.dataModelId,
86+
data: {
87+
query: this.query,
88+
search_algorithm: this.searchAlgorithm,
89+
similarity_top_k: this.similarityTopK,
90+
num_articles_ref: this.numArticlesRef,
91+
num_results: this.numResults,
92+
ref: this.ref,
93+
page: this.page,
94+
},
95+
});
96+
if (this.fields?.length && Array.isArray(response?.response?.results)) {
97+
response.response.results = pluckFields(response.response.results, this.fields);
98+
}
99+
const count = response?.response?.results?.length ?? 0;
100+
$.export("$summary", `Successfully retrieved ${count} AI recommendation${count === 1
101+
? ""
102+
: "s"} for query: "${this.query}"`);
103+
return response;
104+
},
105+
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// x-pd-ai: optimized
2+
import app from "../../dappier.app.mjs";
3+
import { resolveDateRange } from "../../common/utils.mjs";
4+
5+
export default {
6+
key: "dappier-get-ask-ai-analytics",
7+
name: "Get Ask AI Analytics",
8+
description: "Retrieve aggregated Ask AI widget analytics (GET `/v1/analytics/ask-ai`). Returns a `summary` (widget loads, viewable/engagement sessions, clicks, session duration, etc.) plus a `daily_breakdown`. All filters are optional; with no dates it defaults to the last 7 days (UTC). Use **Get Ask AI Logs** for the raw per-conversation rows behind these numbers. Example: call with no arguments to get the last-7-days summary, or pass `startDate=2026-07-01` and `endDate=2026-08-01` for a fixed window. [See the documentation](https://docs.dappier.com/api-reference/endpoint/ask-ai-analytics).",
9+
version: "0.0.1",
10+
type: "action",
11+
annotations: {
12+
destructiveHint: false,
13+
openWorldHint: true,
14+
readOnlyHint: true,
15+
},
16+
props: {
17+
app,
18+
startDate: {
19+
propDefinition: [
20+
app,
21+
"startDate",
22+
],
23+
},
24+
endDate: {
25+
propDefinition: [
26+
app,
27+
"endDate",
28+
],
29+
},
30+
widgetId: {
31+
propDefinition: [
32+
app,
33+
"widgetId",
34+
],
35+
},
36+
placementId: {
37+
propDefinition: [
38+
app,
39+
"placementId",
40+
],
41+
},
42+
deploymentType: {
43+
propDefinition: [
44+
app,
45+
"deploymentType",
46+
],
47+
},
48+
creativeId: {
49+
propDefinition: [
50+
app,
51+
"creativeId",
52+
],
53+
},
54+
lineItemId: {
55+
propDefinition: [
56+
app,
57+
"lineItemId",
58+
],
59+
},
60+
publisherId: {
61+
propDefinition: [
62+
app,
63+
"publisherId",
64+
],
65+
},
66+
},
67+
async run({ $ }) {
68+
const response = await this.app.getAskAiAnalytics({
69+
$,
70+
params: {
71+
...resolveDateRange(this.startDate, this.endDate),
72+
widget_id: this.widgetId,
73+
placement_id: this.placementId,
74+
deployment_type: this.deploymentType,
75+
creative_id: this.creativeId,
76+
line_item_id: this.lineItemId,
77+
publisher_id: this.publisherId,
78+
},
79+
});
80+
$.export("$summary", "Successfully retrieved Ask AI analytics");
81+
return response;
82+
},
83+
};
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// x-pd-ai: optimized
2+
import app from "../../dappier.app.mjs";
3+
import {
4+
INTERACTION_TYPES,
5+
LOGS_LIMIT_MAX,
6+
LOGS_LIMIT_MIN,
7+
} from "../../common/constants.mjs";
8+
import { resolveDateRange } from "../../common/utils.mjs";
9+
10+
export default {
11+
key: "dappier-get-ask-ai-logs",
12+
name: "Get Ask AI Logs",
13+
description: "Retrieve raw Ask AI conversation logs (GET `/v1/analytics/ask-ai/logs`). Returns a `data` array of individual conversation rows (timestamp, prompt, response, widget, referring URL, interaction type) sorted newest-first, plus `count` and `total_pages` for pagination. All filters are optional; with no dates it defaults to the last 7 days (UTC). This is the row-level detail behind **Get Ask AI Analytics**. If `total_pages` in the response is greater than 1, call again with an incremented `page` to fetch the remaining rows. Example: `limit=5` returns the 5 most recent conversations for the last 7 days; add `page=2` to get the next 5. [See the documentation](https://docs.dappier.com/api-reference/endpoint/ask-ai-logs).",
14+
version: "0.0.1",
15+
type: "action",
16+
annotations: {
17+
destructiveHint: false,
18+
openWorldHint: true,
19+
readOnlyHint: true,
20+
},
21+
props: {
22+
app,
23+
startDate: {
24+
propDefinition: [
25+
app,
26+
"startDate",
27+
],
28+
},
29+
endDate: {
30+
propDefinition: [
31+
app,
32+
"endDate",
33+
],
34+
},
35+
widgetId: {
36+
propDefinition: [
37+
app,
38+
"widgetId",
39+
],
40+
},
41+
placementId: {
42+
propDefinition: [
43+
app,
44+
"placementId",
45+
],
46+
},
47+
deploymentType: {
48+
propDefinition: [
49+
app,
50+
"deploymentType",
51+
],
52+
},
53+
interactionType: {
54+
type: "string",
55+
label: "Interaction Type",
56+
description: "Filter to a single interaction type. Optional - omit to include all types.",
57+
options: INTERACTION_TYPES,
58+
optional: true,
59+
},
60+
page: {
61+
type: "integer",
62+
label: "Page",
63+
description: "1-based page number for pagination. Increment to page through results (see `total_pages` in the response). Defaults to 1.",
64+
min: 1,
65+
optional: true,
66+
},
67+
limit: {
68+
type: "integer",
69+
label: "Limit",
70+
description: `Number of conversation rows per page. Min ${LOGS_LIMIT_MIN}, max ${LOGS_LIMIT_MAX}. Defaults to 50.`,
71+
min: LOGS_LIMIT_MIN,
72+
max: LOGS_LIMIT_MAX,
73+
optional: true,
74+
},
75+
},
76+
async run({ $ }) {
77+
const response = await this.app.getAskAiLogs({
78+
$,
79+
params: {
80+
...resolveDateRange(this.startDate, this.endDate),
81+
widget_id: this.widgetId,
82+
placement_id: this.placementId,
83+
deployment_type: this.deploymentType,
84+
interaction_type: this.interactionType,
85+
page: this.page,
86+
limit: this.limit,
87+
},
88+
});
89+
const count = response?.data?.length ?? 0;
90+
$.export("$summary", `Successfully retrieved ${count} Ask AI log row${count === 1
91+
? ""
92+
: "s"}`);
93+
return response;
94+
},
95+
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// x-pd-ai: optimized
2+
import app from "../../dappier.app.mjs";
3+
import { resolveDateRange } from "../../common/utils.mjs";
4+
5+
export default {
6+
key: "dappier-get-session-intelligence",
7+
name: "Get Session Intelligence",
8+
description: "Retrieve session-intelligence analytics for Ask AI widgets (GET `/v1/analytics/session-intelligence`). Returns a single top-level `session_intelligence` object wrapping all breakdowns - `session_intelligence.summary`, `.engagement_time` stats, `.intent_breakdown`, `.top_queried_topics`, `.category_distribution` (IAB categories), and a fixed 7-bucket `.session_engagement_distribution` (query-depth). All filters are optional; with no dates it defaults to the last 7 days (UTC). Example: call with no arguments for last-7-days intent and topic breakdowns, or pass `widgetId=wd_92831` to scope to a single widget. [See the documentation](https://docs.dappier.com/api-reference/endpoint/session-intelligence).",
9+
version: "0.0.1",
10+
type: "action",
11+
annotations: {
12+
destructiveHint: false,
13+
openWorldHint: true,
14+
readOnlyHint: true,
15+
},
16+
props: {
17+
app,
18+
startDate: {
19+
propDefinition: [
20+
app,
21+
"startDate",
22+
],
23+
},
24+
endDate: {
25+
propDefinition: [
26+
app,
27+
"endDate",
28+
],
29+
},
30+
widgetId: {
31+
propDefinition: [
32+
app,
33+
"widgetId",
34+
],
35+
},
36+
placementId: {
37+
propDefinition: [
38+
app,
39+
"placementId",
40+
],
41+
},
42+
deploymentType: {
43+
propDefinition: [
44+
app,
45+
"deploymentType",
46+
],
47+
},
48+
creativeId: {
49+
propDefinition: [
50+
app,
51+
"creativeId",
52+
],
53+
},
54+
lineItemId: {
55+
propDefinition: [
56+
app,
57+
"lineItemId",
58+
],
59+
},
60+
publisherId: {
61+
propDefinition: [
62+
app,
63+
"publisherId",
64+
],
65+
},
66+
},
67+
async run({ $ }) {
68+
const response = await this.app.getSessionIntelligence({
69+
$,
70+
params: {
71+
...resolveDateRange(this.startDate, this.endDate),
72+
widget_id: this.widgetId,
73+
placement_id: this.placementId,
74+
deployment_type: this.deploymentType,
75+
creative_id: this.creativeId,
76+
line_item_id: this.lineItemId,
77+
publisher_id: this.publisherId,
78+
},
79+
});
80+
$.export("$summary", "Successfully retrieved session intelligence analytics");
81+
return response;
82+
},
83+
};

0 commit comments

Comments
 (0)