fodda-mcp 1.3.2 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +31 -0
- package/README.md +192 -20
- package/dist/a2aHandler.d.ts +20 -0
- package/dist/a2aHandler.d.ts.map +1 -0
- package/dist/a2aHandler.js +247 -0
- package/dist/a2aHandler.js.map +1 -0
- package/dist/agents/fodda-researcher/index.d.ts +35 -0
- package/dist/agents/fodda-researcher/index.d.ts.map +1 -0
- package/dist/agents/fodda-researcher/index.js +62 -0
- package/dist/agents/fodda-researcher/index.js.map +1 -0
- package/dist/agents/fodda-researcher/skills.d.ts +16 -0
- package/dist/agents/fodda-researcher/skills.d.ts.map +1 -0
- package/dist/agents/fodda-researcher/skills.js +163 -0
- package/dist/agents/fodda-researcher/skills.js.map +1 -0
- package/dist/brandTemplate.d.ts +14 -0
- package/dist/brandTemplate.d.ts.map +1 -0
- package/dist/brandTemplate.js +742 -0
- package/dist/brandTemplate.js.map +1 -0
- package/dist/catalogCache.d.ts +177 -0
- package/dist/catalogCache.d.ts.map +1 -0
- package/dist/catalogCache.js +708 -0
- package/dist/catalogCache.js.map +1 -0
- package/dist/editorialFill.d.ts +44 -0
- package/dist/editorialFill.d.ts.map +1 -0
- package/dist/editorialFill.js +377 -0
- package/dist/editorialFill.js.map +1 -0
- package/dist/enrichment.d.ts +74 -0
- package/dist/enrichment.d.ts.map +1 -0
- package/dist/enrichment.js +211 -0
- package/dist/enrichment.js.map +1 -0
- package/dist/errorHandling.d.ts +52 -0
- package/dist/errorHandling.d.ts.map +1 -0
- package/dist/errorHandling.js +327 -0
- package/dist/errorHandling.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +595 -410
- package/dist/index.js.map +1 -1
- package/dist/pricingCache.d.ts +99 -0
- package/dist/pricingCache.d.ts.map +1 -0
- package/dist/pricingCache.js +414 -0
- package/dist/pricingCache.js.map +1 -0
- package/dist/queryCache.d.ts +25 -0
- package/dist/queryCache.d.ts.map +1 -0
- package/dist/queryCache.js +194 -0
- package/dist/queryCache.js.map +1 -0
- package/dist/searchTemplate.d.ts +16 -0
- package/dist/searchTemplate.d.ts.map +1 -0
- package/dist/searchTemplate.js +318 -0
- package/dist/searchTemplate.js.map +1 -0
- package/dist/sessionTracker.d.ts +39 -0
- package/dist/sessionTracker.d.ts.map +1 -0
- package/dist/sessionTracker.js +187 -0
- package/dist/sessionTracker.js.map +1 -0
- package/dist/skillClient.d.ts +142 -0
- package/dist/skillClient.d.ts.map +1 -0
- package/dist/skillClient.js +256 -0
- package/dist/skillClient.js.map +1 -0
- package/dist/svgVisuals.d.ts +77 -0
- package/dist/svgVisuals.d.ts.map +1 -0
- package/dist/svgVisuals.js +323 -0
- package/dist/svgVisuals.js.map +1 -0
- package/dist/systemPrompt.d.ts +26 -0
- package/dist/systemPrompt.d.ts.map +1 -0
- package/dist/systemPrompt.js +546 -0
- package/dist/systemPrompt.js.map +1 -0
- package/dist/test-waverunner-response.d.ts +2 -0
- package/dist/test-waverunner-response.d.ts.map +1 -0
- package/dist/test-waverunner-response.js +20 -0
- package/dist/test-waverunner-response.js.map +1 -0
- package/dist/test_analyst.d.ts +2 -0
- package/dist/test_analyst.d.ts.map +1 -0
- package/dist/test_analyst.js +39 -0
- package/dist/test_analyst.js.map +1 -0
- package/dist/test_analyst_local.d.ts +2 -0
- package/dist/test_analyst_local.d.ts.map +1 -0
- package/dist/test_analyst_local.js +51 -0
- package/dist/test_analyst_local.js.map +1 -0
- package/dist/test_live_mcp.d.ts +2 -0
- package/dist/test_live_mcp.d.ts.map +1 -0
- package/dist/test_live_mcp.js +49 -0
- package/dist/test_live_mcp.js.map +1 -0
- package/dist/test_skills.d.ts +9 -0
- package/dist/test_skills.d.ts.map +1 -0
- package/dist/test_skills.js +267 -0
- package/dist/test_skills.js.map +1 -0
- package/dist/toolHandlers.d.ts +11 -0
- package/dist/toolHandlers.d.ts.map +1 -0
- package/dist/toolHandlers.js +2829 -0
- package/dist/toolHandlers.js.map +1 -0
- package/dist/tools.d.ts +80 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +89 -186
- package/dist/tools.js.map +1 -1
- package/dist/trialTracker.d.ts +58 -0
- package/dist/trialTracker.d.ts.map +1 -0
- package/dist/trialTracker.js +179 -0
- package/dist/trialTracker.js.map +1 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/verify_hmac.js +1 -1
- package/dist/verify_hmac.js.map +1 -1
- package/dist/verify_tools_endpoint.js +5 -5
- package/dist/verify_tools_endpoint.js.map +1 -1
- package/dist/widgetShell.d.ts +15 -0
- package/dist/widgetShell.d.ts.map +1 -0
- package/dist/widgetShell.js +151 -0
- package/dist/widgetShell.js.map +1 -0
- package/package.json +10 -6
- package/server.json +12 -17
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Tracker — in-memory search history and frustration detection.
|
|
3
|
+
*
|
|
4
|
+
* Tracks tool call patterns within a single MCP session to detect
|
|
5
|
+
* structural frustration signals (repeated queries, NO_MATCH streaks,
|
|
6
|
+
* graph bouncing). Hints are injected into response text for Claude
|
|
7
|
+
* to act on — the user never sees them directly.
|
|
8
|
+
*
|
|
9
|
+
* When aggregate frustration is high (3+ patterns triggered in one session),
|
|
10
|
+
* an alert is posted to #fodda-sales on Slack for the sales bot to enrich.
|
|
11
|
+
*
|
|
12
|
+
* Resets per MCP connection (stateless across sessions).
|
|
13
|
+
*/
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Slack posting — fire-and-forget frustration alerts
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const SLACK_BOT_USER_ID = 'U0AU49JG7AS';
|
|
18
|
+
/**
|
|
19
|
+
* Post a message to #fodda-sales via the Slack Bot Token.
|
|
20
|
+
* Fire-and-forget — errors are logged but never thrown.
|
|
21
|
+
*/
|
|
22
|
+
export async function postToSlack(text) {
|
|
23
|
+
const token = process.env.SLACK_BOT_TOKEN;
|
|
24
|
+
if (!token) {
|
|
25
|
+
console.error('[slack] SLACK_BOT_TOKEN not set — skipping Slack post');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const resp = await fetch('https://slack.com/api/chat.postMessage', {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
32
|
+
body: JSON.stringify({ channel: 'fodda-sales', text, unfurl_links: false }),
|
|
33
|
+
});
|
|
34
|
+
const body = await resp.json();
|
|
35
|
+
if (!body.ok) {
|
|
36
|
+
console.error(`[slack] Slack API error: ${body.error}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error(`[slack] Failed to post to Slack: ${err.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Session state — one instance per createServer() call
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
export function createSessionTracker() {
|
|
47
|
+
const sessionSearches = [];
|
|
48
|
+
let frustrationSlackSent = false; // Only post once per session
|
|
49
|
+
/**
|
|
50
|
+
* Record a search call after it completes.
|
|
51
|
+
*/
|
|
52
|
+
function trackSearch(query, graphId, resultCount) {
|
|
53
|
+
sessionSearches.push({
|
|
54
|
+
query: query.toLowerCase().trim(),
|
|
55
|
+
graphId,
|
|
56
|
+
resultCount,
|
|
57
|
+
timestamp: Date.now(),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Return the last N searches for context (used by Slack alerts).
|
|
62
|
+
*/
|
|
63
|
+
function getRecentSearches(n = 5) {
|
|
64
|
+
return sessionSearches.slice(-n);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Compute a frustration score and details from the recent search history.
|
|
68
|
+
* Returns null if no frustration patterns are detected.
|
|
69
|
+
*/
|
|
70
|
+
function getFrustrationDetails() {
|
|
71
|
+
if (sessionSearches.length < 2)
|
|
72
|
+
return null;
|
|
73
|
+
const recent = sessionSearches.slice(-5);
|
|
74
|
+
const last = recent[recent.length - 1];
|
|
75
|
+
if (!last)
|
|
76
|
+
return null;
|
|
77
|
+
let score = 0;
|
|
78
|
+
let dominantPattern = 'NO_MATCH';
|
|
79
|
+
// Pattern 1: Repeated similar queries (same graph, overlapping words)
|
|
80
|
+
const similarCount = recent.filter(s => s.graphId === last.graphId &&
|
|
81
|
+
overlapRatio(s.query, last.query) > 0.5).length;
|
|
82
|
+
if (similarCount >= 3) {
|
|
83
|
+
score++;
|
|
84
|
+
dominantPattern = 'LOW_YIELD';
|
|
85
|
+
}
|
|
86
|
+
// Pattern 2: NO_MATCH streak
|
|
87
|
+
const noMatchStreak = recent.filter(s => s.resultCount === 0).length;
|
|
88
|
+
if (noMatchStreak >= 2) {
|
|
89
|
+
score++;
|
|
90
|
+
dominantPattern = 'NO_MATCH';
|
|
91
|
+
}
|
|
92
|
+
// Pattern 3: Graph bouncing (same query, multiple graphs)
|
|
93
|
+
const graphsForSameQuery = [...new Set(recent.filter(s => overlapRatio(s.query, last.query) > 0.6).map(s => s.graphId))];
|
|
94
|
+
if (graphsForSameQuery.length >= 3) {
|
|
95
|
+
score++;
|
|
96
|
+
dominantPattern = 'GRAPH_BOUNCING';
|
|
97
|
+
}
|
|
98
|
+
if (score === 0)
|
|
99
|
+
return null;
|
|
100
|
+
return {
|
|
101
|
+
pattern: dominantPattern,
|
|
102
|
+
graphsTried: [...new Set(recent.map(s => s.graphId))],
|
|
103
|
+
recentQueries: [...new Set(recent.map(s => s.query))],
|
|
104
|
+
score,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Detect frustration patterns from the recent search history.
|
|
109
|
+
* Returns a hint string for Claude, or null if no frustration detected.
|
|
110
|
+
*/
|
|
111
|
+
function detectFrustration() {
|
|
112
|
+
if (sessionSearches.length < 2)
|
|
113
|
+
return null;
|
|
114
|
+
const recent = sessionSearches.slice(-5); // last 5 searches
|
|
115
|
+
const last = recent[recent.length - 1];
|
|
116
|
+
if (!last)
|
|
117
|
+
return null;
|
|
118
|
+
// Pattern 1: Repeated similar queries (same graph, overlapping words)
|
|
119
|
+
const similarCount = recent.filter(s => s.graphId === last.graphId &&
|
|
120
|
+
overlapRatio(s.query, last.query) > 0.5).length;
|
|
121
|
+
if (similarCount >= 3) {
|
|
122
|
+
return `⚠️ LOW YIELD: ${similarCount} similar searches in "${last.graphId}" ` +
|
|
123
|
+
`with declining results. Consider:\n` +
|
|
124
|
+
`1. Try a different graph — the topic may be covered elsewhere\n` +
|
|
125
|
+
`2. Broaden the search terms\n` +
|
|
126
|
+
`3. Use supplemental sources (Google Trends, Amazon) for external validation\n` +
|
|
127
|
+
`4. Ask the user to clarify what specific angle they need`;
|
|
128
|
+
}
|
|
129
|
+
// Pattern 2: NO_MATCH streak
|
|
130
|
+
const noMatchStreak = recent.filter(s => s.resultCount === 0).length;
|
|
131
|
+
if (noMatchStreak >= 2) {
|
|
132
|
+
const graphs = [...new Set(recent.map(s => s.graphId))];
|
|
133
|
+
return `⚠️ NO RESULTS for last ${noMatchStreak} searches. ` +
|
|
134
|
+
`Graphs tried: ${graphs.join(', ')}.\n` +
|
|
135
|
+
`This topic may not be in the curated knowledge graphs. Consider:\n` +
|
|
136
|
+
`1. Supplemental data sources for raw market/economic data\n` +
|
|
137
|
+
`2. Broader related terms that the graph may cover\n` +
|
|
138
|
+
`3. Checking if the user's actual need can be met differently`;
|
|
139
|
+
}
|
|
140
|
+
// Pattern 3: Graph bouncing (same query, multiple graphs)
|
|
141
|
+
const lastQuery = last.query;
|
|
142
|
+
const graphsForSameQuery = [...new Set(recent.filter(s => overlapRatio(s.query, lastQuery) > 0.6).map(s => s.graphId))];
|
|
143
|
+
if (graphsForSameQuery.length >= 3) {
|
|
144
|
+
return `⚠️ GRAPH BOUNCING: User searched "${lastQuery}" across ` +
|
|
145
|
+
`${graphsForSameQuery.length} graphs (${graphsForSameQuery.join(', ')}). ` +
|
|
146
|
+
`They may be unsure which graph to use. Help them narrow down.`;
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Post aggregate frustration to #fodda-sales on Slack.
|
|
152
|
+
* Only fires once per session, and only when frustration score >= 2
|
|
153
|
+
* (i.e., multiple patterns triggered — not every single NO_MATCH).
|
|
154
|
+
*/
|
|
155
|
+
function postFrustrationToSlack(userIdentifier) {
|
|
156
|
+
if (frustrationSlackSent)
|
|
157
|
+
return;
|
|
158
|
+
const details = getFrustrationDetails();
|
|
159
|
+
if (!details || details.score < 2)
|
|
160
|
+
return; // Only aggregate frustration
|
|
161
|
+
frustrationSlackSent = true;
|
|
162
|
+
const text = [
|
|
163
|
+
`<@${SLACK_BOT_USER_ID}> ⚠️ *Session Frustration Detected*`,
|
|
164
|
+
`👤 ${userIdentifier}`,
|
|
165
|
+
`🔍 Pattern: ${details.pattern}`,
|
|
166
|
+
`📊 Graphs tried: ${details.graphsTried.join(', ')}`,
|
|
167
|
+
`🔎 Queries: ${details.recentQueries.join(', ')}`,
|
|
168
|
+
`📈 Frustration score: ${details.score}/3`,
|
|
169
|
+
`→ User may be struggling. Check if content gaps or UX issues are involved.`,
|
|
170
|
+
].join('\n');
|
|
171
|
+
// Fire-and-forget — never await in the hot path
|
|
172
|
+
postToSlack(text).catch(() => { });
|
|
173
|
+
}
|
|
174
|
+
return { trackSearch, detectFrustration, getRecentSearches, getFrustrationDetails, postFrustrationToSlack };
|
|
175
|
+
}
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Utility — simple word-overlap ratio
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
function overlapRatio(a, b) {
|
|
180
|
+
const wordsA = new Set(a.split(/\s+/).filter(w => w.length > 2));
|
|
181
|
+
const wordsB = new Set(b.split(/\s+/).filter(w => w.length > 2));
|
|
182
|
+
if (wordsA.size === 0 || wordsB.size === 0)
|
|
183
|
+
return 0;
|
|
184
|
+
const intersection = [...wordsA].filter(w => wordsB.has(w)).length;
|
|
185
|
+
return intersection / Math.min(wordsA.size, wordsB.size);
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=sessionTracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionTracker.js","sourceRoot":"","sources":["../src/sessionTracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Client — Routes skill calls through the Fodda Core API.
|
|
3
|
+
*
|
|
4
|
+
* Architecture change (2026-05-03):
|
|
5
|
+
* BEFORE: MCP connected directly to external skill MCP servers.
|
|
6
|
+
* NOW: All skill calls route through Core API endpoints:
|
|
7
|
+
* - GET /v1/skills/{skill_id}/tools → discover available tools
|
|
8
|
+
* - POST /v1/skills/{skill_id}/execute → execute a specific tool
|
|
9
|
+
*
|
|
10
|
+
* The Core API handles auth, access control, billing (2 tokens/call),
|
|
11
|
+
* and upstream MCP connections. The MCP Server never talks to skill
|
|
12
|
+
* servers directly.
|
|
13
|
+
*
|
|
14
|
+
* Design decisions:
|
|
15
|
+
* - Fail-open — if discovery or execution fails, Fodda proceeds without skills
|
|
16
|
+
* - 15s timeout for execution (skill + API overhead)
|
|
17
|
+
* - 10s timeout for discovery
|
|
18
|
+
* - Structured input contract preserved for output-phase skills
|
|
19
|
+
*/
|
|
20
|
+
/** A single tool exposed by a skill, as returned by the Core API. */
|
|
21
|
+
export interface SkillToolDefinition {
|
|
22
|
+
/** Tool name as registered on the skill's MCP server */
|
|
23
|
+
name: string;
|
|
24
|
+
/** Human-readable description */
|
|
25
|
+
description: string;
|
|
26
|
+
/** JSON Schema for tool arguments */
|
|
27
|
+
inputSchema: Record<string, any>;
|
|
28
|
+
}
|
|
29
|
+
/** Discovery response from GET /v1/skills/{skill_id}/tools */
|
|
30
|
+
export interface DiscoveredSkill {
|
|
31
|
+
/** Skill identifier (matches graph_id) */
|
|
32
|
+
skill_id: string;
|
|
33
|
+
/** Human-readable skill name */
|
|
34
|
+
skill_name: string;
|
|
35
|
+
/** Available tools on this skill */
|
|
36
|
+
tools: SkillToolDefinition[];
|
|
37
|
+
/** Token cost per execution (default: 2) */
|
|
38
|
+
cost_per_call: number;
|
|
39
|
+
}
|
|
40
|
+
/** Minimal skill config — no longer needs mcp_url or auth tokens */
|
|
41
|
+
export interface SkillConfig {
|
|
42
|
+
/** Skill identifier (matches graph_id in catalog) */
|
|
43
|
+
id: string;
|
|
44
|
+
/** Human-readable name */
|
|
45
|
+
name: string;
|
|
46
|
+
/** When in the pipeline to call: 'output' (post-research) or 'research' (pre-search, future) */
|
|
47
|
+
phase: 'output' | 'research';
|
|
48
|
+
}
|
|
49
|
+
export interface SkillInput {
|
|
50
|
+
/** The user's original query */
|
|
51
|
+
query: string;
|
|
52
|
+
/** Primary graph ID that was searched */
|
|
53
|
+
graphId: string;
|
|
54
|
+
/** Graph metadata */
|
|
55
|
+
context: {
|
|
56
|
+
graphName: string;
|
|
57
|
+
curatorName: string;
|
|
58
|
+
domain: string;
|
|
59
|
+
};
|
|
60
|
+
/** Trend results from graph search */
|
|
61
|
+
trends: Array<{
|
|
62
|
+
name: string;
|
|
63
|
+
summary?: string;
|
|
64
|
+
signal_score?: number;
|
|
65
|
+
trendLifecycle?: string;
|
|
66
|
+
momentum?: string;
|
|
67
|
+
evidence_count?: number;
|
|
68
|
+
graphName?: string;
|
|
69
|
+
}>;
|
|
70
|
+
/** Evidence articles from graph search */
|
|
71
|
+
evidence: Array<{
|
|
72
|
+
title: string;
|
|
73
|
+
sourceUrl?: string;
|
|
74
|
+
brandNames?: string[];
|
|
75
|
+
place?: string;
|
|
76
|
+
snippet?: string;
|
|
77
|
+
}>;
|
|
78
|
+
/** Supplemental data (Google Trends, Census, etc.) */
|
|
79
|
+
supplemental?: Record<string, any> | undefined;
|
|
80
|
+
}
|
|
81
|
+
export interface SkillResult {
|
|
82
|
+
/** Skill identifier */
|
|
83
|
+
skillId: string;
|
|
84
|
+
/** Human-readable skill name */
|
|
85
|
+
skillName: string;
|
|
86
|
+
/** Whether the skill executed successfully */
|
|
87
|
+
success: boolean;
|
|
88
|
+
/** Transformed output from the skill (markdown or JSON string) */
|
|
89
|
+
output?: string;
|
|
90
|
+
/** Error message if the skill failed */
|
|
91
|
+
error?: string;
|
|
92
|
+
/** How long the skill took to execute */
|
|
93
|
+
durationMs: number;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Discover available tools for a skill by calling the Core API.
|
|
97
|
+
*
|
|
98
|
+
* GET /v1/skills/{skill_id}/tools
|
|
99
|
+
*
|
|
100
|
+
* Returns null on failure (fail-open — skill is silently skipped).
|
|
101
|
+
*/
|
|
102
|
+
export declare function discoverSkillTools(skillId: string, apiKey: string): Promise<DiscoveredSkill | null>;
|
|
103
|
+
/**
|
|
104
|
+
* Execute a specific tool on a skill via the Core API.
|
|
105
|
+
*
|
|
106
|
+
* POST /v1/skills/{skill_id}/execute
|
|
107
|
+
* Body: { tool: "tool_name", arguments: {...} }
|
|
108
|
+
*
|
|
109
|
+
* Returns the result text, or throws on error.
|
|
110
|
+
*/
|
|
111
|
+
export declare function executeSkillTool(skillId: string, toolName: string, args: Record<string, any>, apiKey: string, userId: string): Promise<{
|
|
112
|
+
output: string;
|
|
113
|
+
durationMs: number;
|
|
114
|
+
}>;
|
|
115
|
+
/**
|
|
116
|
+
* Map a skill execution error to a user-friendly message.
|
|
117
|
+
* Returns null if the error should be handled by the standard error path
|
|
118
|
+
* (e.g. CREDITS_EXHAUSTED).
|
|
119
|
+
*/
|
|
120
|
+
export declare function mapSkillError(err: any): string | null;
|
|
121
|
+
/**
|
|
122
|
+
* Call a skill's output-phase processing via the Core API.
|
|
123
|
+
*
|
|
124
|
+
* This wraps executeSkillTool() with the Fodda output data contract
|
|
125
|
+
* and returns a SkillResult for backward compatibility with the
|
|
126
|
+
* search_graph pipeline.
|
|
127
|
+
*
|
|
128
|
+
* Fail-open: returns a result with success=false on any error.
|
|
129
|
+
*/
|
|
130
|
+
export declare function callSkill(skill: SkillConfig, input: SkillInput, apiKey: string, userId: string): Promise<SkillResult>;
|
|
131
|
+
/**
|
|
132
|
+
* Call all enabled output-phase skills in parallel via the Core API.
|
|
133
|
+
* Returns results for all skills (success or failure).
|
|
134
|
+
* Never throws — individual failures are captured in the result objects.
|
|
135
|
+
*/
|
|
136
|
+
export declare function callOutputSkills(skills: SkillConfig[], input: SkillInput, apiKey: string, userId: string): Promise<SkillResult[]>;
|
|
137
|
+
/**
|
|
138
|
+
* Package Fodda search results into the standard skill input format.
|
|
139
|
+
* Call this after search_graph results are enriched but before widget rendering.
|
|
140
|
+
*/
|
|
141
|
+
export declare function buildSkillInput(query: string, data: any, effectiveGraphId: string, primaryGraphName: string, supplemental?: Record<string, any>): SkillInput;
|
|
142
|
+
//# sourceMappingURL=skillClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillClient.d.ts","sourceRoot":"","sources":["../src/skillClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Client — Routes skill calls through the Fodda Core API.
|
|
3
|
+
*
|
|
4
|
+
* Architecture change (2026-05-03):
|
|
5
|
+
* BEFORE: MCP connected directly to external skill MCP servers.
|
|
6
|
+
* NOW: All skill calls route through Core API endpoints:
|
|
7
|
+
* - GET /v1/skills/{skill_id}/tools → discover available tools
|
|
8
|
+
* - POST /v1/skills/{skill_id}/execute → execute a specific tool
|
|
9
|
+
*
|
|
10
|
+
* The Core API handles auth, access control, billing (2 tokens/call),
|
|
11
|
+
* and upstream MCP connections. The MCP Server never talks to skill
|
|
12
|
+
* servers directly.
|
|
13
|
+
*
|
|
14
|
+
* Design decisions:
|
|
15
|
+
* - Fail-open — if discovery or execution fails, Fodda proceeds without skills
|
|
16
|
+
* - 15s timeout for execution (skill + API overhead)
|
|
17
|
+
* - 10s timeout for discovery
|
|
18
|
+
* - Structured input contract preserved for output-phase skills
|
|
19
|
+
*/
|
|
20
|
+
import axios from 'axios';
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Constants
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
const API_BASE_URL = process.env.FODDA_API_URL || 'https://api.fodda.ai';
|
|
25
|
+
const DISCOVERY_TIMEOUT_MS = 10_000;
|
|
26
|
+
const EXECUTION_TIMEOUT_MS = 15_000;
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Discovery: Get available tools for a skill via Core API
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
/**
|
|
31
|
+
* Discover available tools for a skill by calling the Core API.
|
|
32
|
+
*
|
|
33
|
+
* GET /v1/skills/{skill_id}/tools
|
|
34
|
+
*
|
|
35
|
+
* Returns null on failure (fail-open — skill is silently skipped).
|
|
36
|
+
*/
|
|
37
|
+
export async function discoverSkillTools(skillId, apiKey) {
|
|
38
|
+
try {
|
|
39
|
+
const url = `${API_BASE_URL}/v1/skills/${encodeURIComponent(skillId)}/tools`;
|
|
40
|
+
const response = await axios.get(url, {
|
|
41
|
+
headers: {
|
|
42
|
+
'X-API-Key': apiKey,
|
|
43
|
+
},
|
|
44
|
+
timeout: DISCOVERY_TIMEOUT_MS,
|
|
45
|
+
});
|
|
46
|
+
const data = response.data;
|
|
47
|
+
if (!data || !Array.isArray(data.tools)) {
|
|
48
|
+
console.error(`[skillClient] Discovery for ${skillId}: invalid response shape`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
console.error(`[skillClient] Discovered ${data.tools.length} tool(s) for ${data.skill_name || skillId}`);
|
|
52
|
+
return {
|
|
53
|
+
skill_id: data.skill_id || skillId,
|
|
54
|
+
skill_name: data.skill_name || skillId,
|
|
55
|
+
tools: data.tools,
|
|
56
|
+
cost_per_call: data.cost_per_call ?? 2,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const status = err.response?.status;
|
|
61
|
+
const code = err.response?.data?.error?.code;
|
|
62
|
+
if (status === 404) {
|
|
63
|
+
console.error(`[skillClient] Skill "${skillId}" not found or not available (${code || '404'})`);
|
|
64
|
+
}
|
|
65
|
+
else if (status === 403) {
|
|
66
|
+
console.error(`[skillClient] Skill "${skillId}" access denied (${code || '403'}) — may be disabled or draft`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.error(`[skillClient] Discovery failed for ${skillId}: ${err.message}`);
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Execution: Call a skill tool via Core API
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
/**
|
|
78
|
+
* Execute a specific tool on a skill via the Core API.
|
|
79
|
+
*
|
|
80
|
+
* POST /v1/skills/{skill_id}/execute
|
|
81
|
+
* Body: { tool: "tool_name", arguments: {...} }
|
|
82
|
+
*
|
|
83
|
+
* Returns the result text, or throws on error.
|
|
84
|
+
*/
|
|
85
|
+
export async function executeSkillTool(skillId, toolName, args, apiKey, userId) {
|
|
86
|
+
const startTime = Date.now();
|
|
87
|
+
const url = `${API_BASE_URL}/v1/skills/${encodeURIComponent(skillId)}/execute`;
|
|
88
|
+
const response = await axios.post(url, {
|
|
89
|
+
tool: toolName,
|
|
90
|
+
arguments: args,
|
|
91
|
+
}, {
|
|
92
|
+
headers: {
|
|
93
|
+
'X-API-Key': apiKey,
|
|
94
|
+
'X-User-Id': userId,
|
|
95
|
+
'X-Fodda-Billing': 'mcp-orchestrated',
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
},
|
|
98
|
+
timeout: EXECUTION_TIMEOUT_MS,
|
|
99
|
+
});
|
|
100
|
+
const durationMs = Date.now() - startTime;
|
|
101
|
+
const data = response.data;
|
|
102
|
+
// Extract text from response — the API may return { result: "..." } or { result: { content: [...] } }
|
|
103
|
+
let output = '';
|
|
104
|
+
if (typeof data.result === 'string') {
|
|
105
|
+
output = data.result;
|
|
106
|
+
}
|
|
107
|
+
else if (data.result?.content) {
|
|
108
|
+
const contentArr = Array.isArray(data.result.content) ? data.result.content : [];
|
|
109
|
+
output = contentArr
|
|
110
|
+
.map((c) => (c.type === 'text' ? c.text : JSON.stringify(c)))
|
|
111
|
+
.join('\n');
|
|
112
|
+
}
|
|
113
|
+
else if (data.result) {
|
|
114
|
+
output = JSON.stringify(data.result);
|
|
115
|
+
}
|
|
116
|
+
console.error(`[skillClient] ${skillId}/${toolName} executed in ${durationMs}ms (${output.length} chars)`);
|
|
117
|
+
return { output, durationMs };
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Error mapping — Convert API error codes to user-friendly messages
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
/**
|
|
123
|
+
* Map a skill execution error to a user-friendly message.
|
|
124
|
+
* Returns null if the error should be handled by the standard error path
|
|
125
|
+
* (e.g. CREDITS_EXHAUSTED).
|
|
126
|
+
*/
|
|
127
|
+
export function mapSkillError(err) {
|
|
128
|
+
const status = err.response?.status;
|
|
129
|
+
const code = err.response?.data?.error?.code;
|
|
130
|
+
const message = err.response?.data?.error?.message;
|
|
131
|
+
switch (code) {
|
|
132
|
+
case 'GRAPH_DISABLED':
|
|
133
|
+
return '⚠️ This skill is currently disabled in your dashboard. Re-enable it at app.fodda.ai/graphs.';
|
|
134
|
+
case 'SKILL_NOT_FOUND':
|
|
135
|
+
return '⚠️ This skill is not currently available on the Fodda platform.';
|
|
136
|
+
case 'SKILL_NOT_AVAILABLE':
|
|
137
|
+
return '⚠️ This skill is not currently available — it may be in development.';
|
|
138
|
+
case 'SKILL_UPSTREAM_ERROR':
|
|
139
|
+
return '⚠️ The skill server encountered an error. Please try again in a moment.';
|
|
140
|
+
case 'CREDITS_EXHAUSTED':
|
|
141
|
+
return null; // Let the standard credit exhaustion handler deal with this
|
|
142
|
+
default:
|
|
143
|
+
if (status === 403) {
|
|
144
|
+
return `⚠️ Access denied: ${message || 'You may not have access to this skill.'}`;
|
|
145
|
+
}
|
|
146
|
+
if (status === 404) {
|
|
147
|
+
return '⚠️ This skill is not currently available.';
|
|
148
|
+
}
|
|
149
|
+
return null; // Unknown error — let caller handle
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Core: Call a single output-phase skill via Core API
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
/**
|
|
156
|
+
* Call a skill's output-phase processing via the Core API.
|
|
157
|
+
*
|
|
158
|
+
* This wraps executeSkillTool() with the Fodda output data contract
|
|
159
|
+
* and returns a SkillResult for backward compatibility with the
|
|
160
|
+
* search_graph pipeline.
|
|
161
|
+
*
|
|
162
|
+
* Fail-open: returns a result with success=false on any error.
|
|
163
|
+
*/
|
|
164
|
+
export async function callSkill(skill, input, apiKey, userId) {
|
|
165
|
+
const startTime = Date.now();
|
|
166
|
+
try {
|
|
167
|
+
const { output, durationMs } = await executeSkillTool(skill.id, 'process', // output-phase skills use a standard 'process' tool
|
|
168
|
+
{
|
|
169
|
+
fodda_output: input,
|
|
170
|
+
query: input.query,
|
|
171
|
+
trends: input.trends,
|
|
172
|
+
evidence: input.evidence,
|
|
173
|
+
}, apiKey, userId);
|
|
174
|
+
return {
|
|
175
|
+
skillId: skill.id,
|
|
176
|
+
skillName: skill.name,
|
|
177
|
+
success: true,
|
|
178
|
+
output,
|
|
179
|
+
durationMs,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
const durationMs = Date.now() - startTime;
|
|
184
|
+
const friendlyMsg = mapSkillError(err);
|
|
185
|
+
const errorMsg = friendlyMsg || err.response?.data?.error?.message || err.message;
|
|
186
|
+
console.error(`[skillClient] ${skill.name} failed after ${durationMs}ms: ${errorMsg}`);
|
|
187
|
+
return {
|
|
188
|
+
skillId: skill.id,
|
|
189
|
+
skillName: skill.name,
|
|
190
|
+
success: false,
|
|
191
|
+
error: errorMsg,
|
|
192
|
+
durationMs,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Batch: Call multiple output-phase skills in parallel
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
/**
|
|
200
|
+
* Call all enabled output-phase skills in parallel via the Core API.
|
|
201
|
+
* Returns results for all skills (success or failure).
|
|
202
|
+
* Never throws — individual failures are captured in the result objects.
|
|
203
|
+
*/
|
|
204
|
+
export async function callOutputSkills(skills, input, apiKey, userId) {
|
|
205
|
+
const outputSkills = skills.filter(s => s.phase === 'output');
|
|
206
|
+
if (outputSkills.length === 0)
|
|
207
|
+
return [];
|
|
208
|
+
console.error(`[skillClient] Calling ${outputSkills.length} output skill(s): ${outputSkills.map(s => s.name).join(', ')}`);
|
|
209
|
+
const results = await Promise.allSettled(outputSkills.map(skill => callSkill(skill, input, apiKey, userId)));
|
|
210
|
+
return results.map(r => r.status === 'fulfilled'
|
|
211
|
+
? r.value
|
|
212
|
+
: {
|
|
213
|
+
skillId: 'unknown',
|
|
214
|
+
skillName: 'unknown',
|
|
215
|
+
success: false,
|
|
216
|
+
error: r.reason?.message || 'Unknown error',
|
|
217
|
+
durationMs: 0,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Helpers: Build skill input from search results
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
/**
|
|
224
|
+
* Package Fodda search results into the standard skill input format.
|
|
225
|
+
* Call this after search_graph results are enriched but before widget rendering.
|
|
226
|
+
*/
|
|
227
|
+
export function buildSkillInput(query, data, effectiveGraphId, primaryGraphName, supplemental) {
|
|
228
|
+
const rows = data?.rows || [];
|
|
229
|
+
return {
|
|
230
|
+
query,
|
|
231
|
+
graphId: effectiveGraphId,
|
|
232
|
+
context: {
|
|
233
|
+
graphName: primaryGraphName,
|
|
234
|
+
curatorName: rows[0]?.curator || '',
|
|
235
|
+
domain: rows[0]?.domain || '',
|
|
236
|
+
},
|
|
237
|
+
trends: rows.map((r) => ({
|
|
238
|
+
name: r.trendName || r.display || r.name || r.title || '',
|
|
239
|
+
summary: r.summary || r.description || r.trendDescription || '',
|
|
240
|
+
signal_score: r.signal_score || r.score || 0,
|
|
241
|
+
trendLifecycle: r.trendLifecycle || 'unknown',
|
|
242
|
+
momentum: r.momentum || 'unknown',
|
|
243
|
+
evidence_count: r.evidence_count || r.evidenceCount || 0,
|
|
244
|
+
graphName: r.graphName || '',
|
|
245
|
+
})),
|
|
246
|
+
evidence: rows.flatMap((r) => (r.evidence || []).map((e) => ({
|
|
247
|
+
title: e.title || e.articleTitle || '',
|
|
248
|
+
sourceUrl: e.sourceUrl || e.url || '',
|
|
249
|
+
brandNames: e.brandNames || [],
|
|
250
|
+
place: e.place || e.geographical_region || '',
|
|
251
|
+
snippet: e.snippet || e.summary || '',
|
|
252
|
+
}))).slice(0, 50), // Cap evidence to avoid oversized payloads
|
|
253
|
+
supplemental: supplemental || undefined,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=skillClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillClient.js","sourceRoot":"","sources":["../src/skillClient.ts"],"names":[],"mappings":"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"}
|