@forcedream/mcp-server
Advanced tools
| import { z } from 'zod'; | ||
| const FD_API = process.env.FD_API_BASE || 'https://api.forcedream.ai'; | ||
| const SLUG = 'sentiment-v1'; | ||
| /** | ||
| * Zod input schema for the forcedream_generate_sentiment tool. text is required; | ||
| * max_wait_seconds bounds how long this call polls before returning a pollable task_id. | ||
| * A dedicated, named tool for ForceDream's sentiment-v1 agent, rather than requiring | ||
| * a caller to know the generic forcedream_invoke_agent + agent_slug pattern. | ||
| */ | ||
| export const generateSentimentSchema = { | ||
| text: z.string().describe('The customer feedback, review, or message to analyze.'), | ||
| max_wait_seconds: z.number().optional().describe('Max seconds to poll (default 60, agent typically takes up to 40s given 14-source verification plus LLM analysis). On timeout, returns task_id to poll later.'), | ||
| }; | ||
| function authHeader() { | ||
| const key = process.env.FD_API_KEY || ''; | ||
| return key ? { Authorization: `Bearer ${key}` } : {}; | ||
| } | ||
| async function postJson(url, body) { | ||
| const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeader() }, body: JSON.stringify(body) }); | ||
| let json = null; | ||
| try { | ||
| json = await res.json(); | ||
| } | ||
| catch { } | ||
| return { status: res.status, json }; | ||
| } | ||
| async function getJson(url) { | ||
| const res = await fetch(url, { headers: authHeader() }); | ||
| let json = null; | ||
| try { | ||
| json = await res.json(); | ||
| } | ||
| catch { } | ||
| return { status: res.status, json }; | ||
| } | ||
| const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); | ||
| // Invoke sentiment-v1 and wait (bounded) for the result. SPENDS balance -- needs | ||
| // FD_API_KEY. Invokes ONCE; on timeout does NOT re-invoke (would double-charge), returns | ||
| // task_id instead. Same real invoke/poll pattern as the other dedicated tools. | ||
| /** | ||
| * Invokes ForceDream's real sentiment-v1 agent and polls (bounded) for the result. Real, | ||
| * 14-source sentiment analysis -- VADER, AFINN, HuggingFace transformer, Google Perspective | ||
| * toxicity, Wikidata/OpenStreetMap entity verification, GDELT/Hacker News alignment, grammar, | ||
| * readability, language detection -- combined into a deterministic overall sentiment, urgency, | ||
| * and business impact score, never a fabricated opinion. SPENDS your balance -- requires | ||
| * FD_API_KEY. Invokes once; never re-invokes on timeout (would double-charge) -- returns a | ||
| * pollable task_id instead. Set FD_MOCK_MODE=true to test without spending real balance. | ||
| * @param args.text - The customer feedback, review, or message to analyze. | ||
| * @param args.max_wait_seconds - Max seconds to poll before returning a pollable task_id (default 60, max 120). | ||
| */ | ||
| export async function generateSentiment(args) { | ||
| if (process.env.FD_MOCK_MODE === 'true') { | ||
| return { | ||
| status: 'completed', | ||
| agent: SLUG, | ||
| task_id: 'mock_' + Date.now(), | ||
| output: { overall_sentiment: null, urgency: null, business_impact: null, note: 'Synthetic mock output. This is not a real sentiment analysis or real verification.' }, | ||
| charged_pence: 0, | ||
| mock: true, | ||
| message: 'MOCK MODE ACTIVE (FD_MOCK_MODE=true): no real analysis was performed or verified, no balance was spent, and this response has no real proof_id -- forcedream_verify_proof will correctly reject it if you try. Unset FD_MOCK_MODE to run for real.', | ||
| }; | ||
| } | ||
| if (!process.env.FD_API_KEY) { | ||
| return { status: 'error', agent: SLUG, message: 'FD_API_KEY is required to analyze (this spends your balance). Set it in the MCP server env. forcedream_search_agents and forcedream_verify_proof need no key.' }; | ||
| } | ||
| const maxWaitMs = Math.max(5, Math.min(120, args.max_wait_seconds ?? 60)) * 1000; | ||
| const inv = await postJson(`${FD_API}/v1/agents/${SLUG}/invoke`, { task: args.text }); | ||
| if (inv.status === 401) | ||
| return { status: 'error', agent: SLUG, message: 'Invalid FD_API_KEY (401). Check the key in the MCP server env.' }; | ||
| if (!inv.json?.task_id) | ||
| return { status: 'error', agent: SLUG, message: `Invoke failed (HTTP ${inv.status}): ${inv.json?.error || inv.json?.note || 'no task_id'}` }; | ||
| const taskId = inv.json.task_id; | ||
| const start = Date.now(); | ||
| let intervalMs = 2500; | ||
| while (Date.now() - start < maxWaitMs) { | ||
| await sleep(intervalMs); | ||
| const poll = await getJson(`${FD_API}/v1/agents/${SLUG}/result/${encodeURIComponent(taskId)}`); | ||
| const d = poll.json || {}; | ||
| const status = d.status || d.outcome; | ||
| if (status === 'completed' || status === 'succeeded' || d.ok === true) { | ||
| return { | ||
| status: 'completed', agent: SLUG, task_id: taskId, output: d.output, charged_pence: d.charged_pence, | ||
| proof_id: d.proof_id || taskId, | ||
| verify_proof_hint: `Verify trustlessly: call forcedream_verify_proof with task_id "${d.proof_id || taskId}". The signature proves authenticity without trusting ForceDream.`, | ||
| message: `Completed. Charged ${d.charged_pence}p. Cryptographically proven (proof_id ${d.proof_id || taskId}).`, | ||
| }; | ||
| } | ||
| if (status === 'charge_failed') | ||
| return { status: 'charge_failed', agent: SLUG, task_id: taskId, charged_pence: 0, message: `Charge failed: ${d.reason || 'insufficient_balance'}. Nothing charged or delivered. Top up and retry.` }; | ||
| if (status === 'failed' || status === 'dead_letter') | ||
| return { status: 'error', agent: SLUG, task_id: taskId, message: `Task ${status}: ${d.reason || d.last_error || 'unknown'}` }; | ||
| intervalMs = Math.min(intervalMs + 1000, 6000); | ||
| } | ||
| return { status: 'pending', agent: SLUG, task_id: taskId, message: `Still processing after ${maxWaitMs / 1000}s. Not re-invoked (would double-charge). Poll the result later with this task_id.` }; | ||
| } |
+21
-0
@@ -13,2 +13,3 @@ #!/usr/bin/env node | ||
| import { generateCode, generateCodeSchema } from './generate_code.js'; | ||
| import { generateSentiment, generateSentimentSchema } from './generate_sentiment.js'; | ||
| import { searchReliability, searchReliabilitySchema } from './search_reliability.js'; | ||
@@ -159,2 +160,22 @@ import { searchCosts, searchCostsSchema } from './search_costs.js'; | ||
| }); | ||
| // forcedream_generate_sentiment — dedicated, named tool for sentiment-v1 specifically. | ||
| // Spends balance (needs FD_API_KEY). Same real invoke/poll pattern as the other dedicated | ||
| // tools, fixed to this one agent so a caller doesn't need to know the generic agent_slug pattern. | ||
| server.registerTool('forcedream_generate_sentiment', { | ||
| title: 'Analyze sentiment (14-source verified)', | ||
| description: 'Real, 14-source sentiment analysis — not just an LLM\'s opinion. Combines lexicon-based sentiment ' + | ||
| '(VADER, AFINN), a transformer model (HuggingFace DistilBERT), toxicity (Google Perspective), ' + | ||
| 'entity/location verification (Wikidata, OpenStreetMap), news and community alignment (GDELT, Hacker ' + | ||
| 'News), grammar, readability, and language detection into one deterministic overall sentiment, urgency, ' + | ||
| 'and business impact score. SPENDS your balance — requires FD_API_KEY.', | ||
| inputSchema: generateSentimentSchema, | ||
| }, async ({ text, max_wait_seconds }) => { | ||
| try { | ||
| const result = await generateSentiment({ text, max_wait_seconds }); | ||
| return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; | ||
| } | ||
| catch (e) { | ||
| return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: e.message }, null, 2) }], isError: true }; | ||
| } | ||
| }); | ||
| // forcedream_check_fraud — real, direct, synchronous call (no polling). Spends balance | ||
@@ -161,0 +182,0 @@ // (needs FD_API_KEY). Uses the new, dedicated fd_live_-accepting backend endpoint. |
+1
-1
| { | ||
| "name": "@forcedream/mcp-server", | ||
| "version": "0.11.0", | ||
| "version": "0.12.0", | ||
| "mcpName": "io.github.forcedreamai/mcp-server", | ||
@@ -5,0 +5,0 @@ "description": "Discover, invoke, and trustlessly verify ForceDream AI agents with cryptographic proofs. ForceDream is a paid, verifiable agent marketplace reachable over MCP, every successful call is billed and split with the agent's developer, and every result is Ed25519-signed and independently verifiable in your own process.", |
+3
-2
@@ -19,3 +19,3 @@ # @forcedream/mcp-server | ||
| | **Auth for invoking** | `FD_API_KEY` env var | OAuth 2.1 + PKCE (standard MCP auth flow) | | ||
| | **Tools available** | All 13 real tools (same set as remote) | All 13 real tools (same set as local) | | ||
| | **Tools available** | All 14 real tools (same set as remote) | All 14 real tools (same set as local) | | ||
| | **Best for** | Claude Desktop, local dev | Any client with native remote-MCP + OAuth support | | ||
@@ -29,3 +29,3 @@ | ||
| **5 tools need no account** -- discovery and verification are always free. **8 tools spend your balance** -- generation, extraction, scoring, and specialist checks. | ||
| **5 tools need no account** -- discovery and verification are always free. **9 tools spend your balance** -- generation, extraction, scoring, sentiment analysis, and specialist checks. | ||
@@ -43,2 +43,3 @@ | Tool | Auth | What it does | | ||
| | `forcedream_generate_code` | key/OAuth | Generate code verified by 6 independent modules -- syntax, dependencies, security, OpenSSF supply-chain checks, complexity, and tests. Never a fabricated pass. | | ||
| | `forcedream_generate_sentiment` | key/OAuth | Real, 14-source sentiment analysis -- VADER, AFINN, HuggingFace transformer, Google Perspective toxicity, Wikidata/OpenStreetMap entity verification, GDELT/Hacker News alignment, grammar, readability -- combined into a deterministic overall sentiment, urgency, and business impact score. | | ||
| | `forcedream_security_scan` | key/OAuth | Real security scanning using OSV.dev CVE lookups and GitGuardian secret detection. | | ||
@@ -45,0 +46,0 @@ | `forcedream_check_fraud` | key/OAuth | Real-time fraud risk scoring using IP reputation and behavioural signals. | |
95144
8.69%20
5.26%1294
9.85%370
0.27%39
11.43%18
12.5%