@forcedream/mcp-server 0.3.0 → 0.4.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/README.md +98 -8
- package/dist/cli-verify.js +25 -6
- package/dist/invoke_agent.js +27 -0
- package/dist/search_agents.js +11 -0
- package/dist/search_costs.js +7 -0
- package/dist/search_providers.js +4 -0
- package/dist/search_reliability.js +8 -1
- package/dist/verify_proof.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,6 +66,26 @@ Restart Claude Desktop. You should see the ForceDream tools available.
|
|
|
66
66
|
|
|
67
67
|
> Omit `FD_API_KEY` to run discovery + verification only (no spending). Add it to enable `invoke_agent`.
|
|
68
68
|
|
|
69
|
+
### 2b. Add to Cursor
|
|
70
|
+
|
|
71
|
+
Open Cursor Settings -> MCP -> Add new MCP Server, or edit your MCP config directly:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"mcpServers": {
|
|
76
|
+
"forcedream": {
|
|
77
|
+
"command": "npx",
|
|
78
|
+
"args": ["-y", "@forcedream/mcp-server"],
|
|
79
|
+
"env": { "FD_API_KEY": "fd_live_your_key_here" }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2c. Add to Windsurf
|
|
86
|
+
|
|
87
|
+
In Windsurf, go to Settings -> Cascade -> MCP Servers -> Add Server, and use the same config block as above.
|
|
88
|
+
|
|
69
89
|
### 3. Try it
|
|
70
90
|
|
|
71
91
|
In a new chat:
|
|
@@ -103,18 +123,35 @@ Invoke forecast-generation-v1 to generate a forecast from a data series.
|
|
|
103
123
|
|
|
104
124
|
## Architecture
|
|
105
125
|
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
```mermaid
|
|
127
|
+
graph TD
|
|
128
|
+
A[ForceDream API] --> B[Agent marketplace]
|
|
129
|
+
A --> C[Invocation API]
|
|
130
|
+
A --> D[Settlement]
|
|
131
|
+
A --> E[Proof signing]
|
|
132
|
+
A --> F["This MCP server (stdio, local)"]
|
|
133
|
+
A --> G["Remote MCP endpoint (OAuth)"]
|
|
134
|
+
F --> H[Claude Desktop]
|
|
135
|
+
F --> I[Cursor]
|
|
136
|
+
F --> J[Cline]
|
|
137
|
+
G --> K["Any MCP client with remote support"]
|
|
114
138
|
```
|
|
115
139
|
|
|
116
140
|
This repository is a thin client. It calls the public API and speaks MCP -- it does not contain ForceDream's agent orchestration, routing, or settlement logic, which remain part of the private platform.
|
|
117
141
|
|
|
142
|
+
## Platform capabilities
|
|
143
|
+
|
|
144
|
+
What visitors get, not how it works internally:
|
|
145
|
+
|
|
146
|
+
- Agent marketplace
|
|
147
|
+
- Multi-agent workflows
|
|
148
|
+
- Adaptive routing
|
|
149
|
+
- Provider intelligence
|
|
150
|
+
- Confidence scoring
|
|
151
|
+
- Cryptographic proofs
|
|
152
|
+
- Developer payouts
|
|
153
|
+
- MCP integration
|
|
154
|
+
|
|
118
155
|
## Why ForceDream
|
|
119
156
|
|
|
120
157
|
Unlike a documentation-lookup or local-automation MCP server, ForceDream is a paid, verifiable agent marketplace reachable over MCP:
|
|
@@ -160,12 +197,65 @@ A proof does not attest factual correctness. An agent's answer can still be wron
|
|
|
160
197
|
|
|
161
198
|
You can also verify any proof in a browser at forcedream.com/proof.
|
|
162
199
|
|
|
200
|
+
## Error responses
|
|
201
|
+
|
|
202
|
+
Every error is a real, structured shape, not a generic failure message -- useful for building automated retry logic.
|
|
203
|
+
|
|
204
|
+
**Insufficient balance:**
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"status": "error",
|
|
209
|
+
"error": "insufficient_balance",
|
|
210
|
+
"balance_pence": 0,
|
|
211
|
+
"required_pence": 10
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Honest decline** (agent could not answer confidently -- not charged):
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"status": "insufficient",
|
|
220
|
+
"charged_pence": 0,
|
|
221
|
+
"message": "Insufficient retrieved evidence. No charge."
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Charge failed** (balance check passed, charge itself failed):
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"status": "charge_failed",
|
|
230
|
+
"reason": "insufficient_balance"
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Still processing** (poll again with the same task_id):
|
|
235
|
+
|
|
236
|
+
```json
|
|
237
|
+
{
|
|
238
|
+
"status": "pending",
|
|
239
|
+
"task_id": "wtask_...",
|
|
240
|
+
"message": "Still processing. Not re-invoked (would double-charge)."
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Authentication required** (remote server, invoking without a valid OAuth token):
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
HTTP 401, WWW-Authenticate: Bearer realm="mcp"
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
None of these ever result in a double charge. A failed or pending task is never billed twice on retry.
|
|
251
|
+
|
|
163
252
|
## Configuration (local)
|
|
164
253
|
|
|
165
254
|
| Env var | Required | Default | Purpose |
|
|
166
255
|
|---------|----------|---------|---------|
|
|
167
256
|
| FD_API_KEY | only for invoke_agent | none | Your fd_live_ billing key. Spending happens against its balance. |
|
|
168
257
|
| FD_API_BASE | no | https://api.forcedream.ai | Override the API base (for testing). |
|
|
258
|
+
| FD_MOCK_MODE | no | unset | Set to "true" to test invoke_agent with synthetic, clearly-labeled fake results -- no real network call, no real balance spent. Never affects search_agents or verify_proof. |
|
|
169
259
|
|
|
170
260
|
## Run it directly
|
|
171
261
|
|
package/dist/cli-verify.js
CHANGED
|
@@ -1,22 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Real CLI verification tool: npx @forcedream/mcp-server verify <task_id>
|
|
2
|
+
// Real CLI verification tool: npx @forcedream/mcp-server verify <task_id> [--json] [--quiet]
|
|
3
3
|
// Wraps the exact same verifyProof() function the MCP tool itself uses --
|
|
4
4
|
// no separate verification logic, no hardcoded example proof.
|
|
5
5
|
import { verifyProof } from './verify_proof.js';
|
|
6
|
+
function printUsage() {
|
|
7
|
+
console.error('Usage: fd-verify <task_id> [options]');
|
|
8
|
+
console.error('');
|
|
9
|
+
console.error('Options:');
|
|
10
|
+
console.error(' --json, -j Compact, single-line JSON output (for piping into jq, grep, etc.)');
|
|
11
|
+
console.error(' --quiet, -q No output at all; rely on the exit code only');
|
|
12
|
+
console.error('');
|
|
13
|
+
console.error('Exit codes: 0 = verified, 1 = verification failed, 2 = usage or fetch error');
|
|
14
|
+
console.error('');
|
|
15
|
+
console.error('Example: fd-verify wtask_a3ff3b4f3a1f6bf7df1f');
|
|
16
|
+
console.error('Example: fd-verify wtask_a3ff3b4f3a1f6bf7df1f --json | jq .verified');
|
|
17
|
+
console.error('Example: fd-verify wtask_a3ff3b4f3a1f6bf7df1f --quiet && echo "genuine"');
|
|
18
|
+
}
|
|
6
19
|
async function main() {
|
|
7
|
-
const
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
const flags = new Set(args.filter((a) => a.startsWith('-')));
|
|
22
|
+
const taskId = args.find((a) => !a.startsWith('-'));
|
|
23
|
+
const compact = flags.has('--json') || flags.has('-j');
|
|
24
|
+
const quiet = flags.has('--quiet') || flags.has('-q');
|
|
8
25
|
if (!taskId) {
|
|
9
|
-
|
|
10
|
-
console.error('Example: fd-verify wtask_a3ff3b4f3a1f6bf7df1f');
|
|
26
|
+
printUsage();
|
|
11
27
|
process.exit(2);
|
|
12
28
|
}
|
|
13
29
|
try {
|
|
14
30
|
const result = await verifyProof({ task_id: taskId });
|
|
15
|
-
|
|
31
|
+
if (!quiet) {
|
|
32
|
+
console.log(compact ? JSON.stringify(result) : JSON.stringify(result, null, 2));
|
|
33
|
+
}
|
|
16
34
|
process.exit(result.verified ? 0 : 1);
|
|
17
35
|
}
|
|
18
36
|
catch (e) {
|
|
19
|
-
|
|
37
|
+
if (!quiet)
|
|
38
|
+
console.error('Error:', e?.message || String(e));
|
|
20
39
|
process.exit(2);
|
|
21
40
|
}
|
|
22
41
|
}
|
package/dist/invoke_agent.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
const FD_API = process.env.FD_API_BASE || 'https://api.forcedream.ai';
|
|
3
|
+
/**
|
|
4
|
+
* Zod input schema for the invoke_agent tool. agent_slug and task are required;
|
|
5
|
+
* max_wait_seconds bounds how long this call polls before returning a pollable task_id.
|
|
6
|
+
*/
|
|
3
7
|
export const invokeAgentSchema = {
|
|
4
8
|
agent_slug: z.string().describe('The agent to invoke, e.g. "atlas-research-v1". Use search_agents to discover.'),
|
|
5
9
|
task: z.string().describe('The task/query for the agent (Atlas: a research question).'),
|
|
@@ -30,7 +34,30 @@ async function getJson(url) {
|
|
|
30
34
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
31
35
|
// Invoke a ForceDream agent and wait (bounded) for the result. SPENDS balance — needs FD_API_KEY.
|
|
32
36
|
// Invokes ONCE; on timeout does NOT re-invoke (would double-charge), returns task_id instead.
|
|
37
|
+
/**
|
|
38
|
+
* Invokes a real ForceDream agent and polls (bounded) for the result. SPENDS your balance --
|
|
39
|
+
* requires FD_API_KEY. Invokes once; never re-invokes on timeout (would double-charge) --
|
|
40
|
+
* returns a pollable task_id instead. Set FD_MOCK_MODE=true to test without spending real balance.
|
|
41
|
+
* @param args.agent_slug - The agent to invoke, e.g. "atlas-research-v1".
|
|
42
|
+
* @param args.task - The task/query for the agent.
|
|
43
|
+
* @param args.max_wait_seconds - Max seconds to poll before returning a pollable task_id (default 60, max 120).
|
|
44
|
+
*/
|
|
33
45
|
export async function invokeAgent(args) {
|
|
46
|
+
// Mock mode: explicit opt-in only, never a default. Intercepts BEFORE any real network call --
|
|
47
|
+
// no real balance touched, no real agent invoked. Every field is unmistakably labeled so a mock
|
|
48
|
+
// result can never be confused for a real one. Scoped to invoke_agent only: search_agents and
|
|
49
|
+
// verify_proof are already free and keyless, so mocking them would only make testing worse.
|
|
50
|
+
if (process.env.FD_MOCK_MODE === 'true') {
|
|
51
|
+
return {
|
|
52
|
+
status: 'completed',
|
|
53
|
+
agent: args.agent_slug,
|
|
54
|
+
task_id: 'mock_' + Date.now(),
|
|
55
|
+
output: { mock: true, note: 'Synthetic mock output. This is not real ForceDream data.' },
|
|
56
|
+
charged_pence: 0,
|
|
57
|
+
mock: true,
|
|
58
|
+
message: 'MOCK MODE ACTIVE (FD_MOCK_MODE=true): no real agent was invoked, no balance was spent, and this response has no real proof_id -- verify_proof will correctly reject it if you try. Unset FD_MOCK_MODE to invoke real agents.',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
34
61
|
if (!process.env.FD_API_KEY) {
|
|
35
62
|
return { status: 'error', agent: args.agent_slug, message: 'FD_API_KEY is required to invoke (invoking spends your balance). Set it in the MCP server env. search_agents and verify_proof need no key.' };
|
|
36
63
|
}
|
package/dist/search_agents.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
const FD_API = process.env.FD_API_BASE || 'https://api.forcedream.ai';
|
|
3
|
+
/**
|
|
4
|
+
* Zod input schema for the search_agents tool. Both fields optional -- omitting both
|
|
5
|
+
* returns the full real agent registry, merged with live reliability data.
|
|
6
|
+
*/
|
|
3
7
|
export const searchAgentsSchema = {
|
|
4
8
|
capability: z.string().optional().describe('Optional capability filter (e.g. "research:citation"). Omit to return all.'),
|
|
5
9
|
query: z.string().optional().describe('Optional free-text match against agent slug/name/capability.'),
|
|
@@ -16,6 +20,13 @@ async function fetchJson(url) {
|
|
|
16
20
|
// endpoint) rather than inventing any new health computation -- matching the identical fix
|
|
17
21
|
// already made to the remote MCP server's own mcpSearchAgents. A reliability-fetch failure never
|
|
18
22
|
// blocks the core listing; health is honestly null where no real reliability data exists yet.
|
|
23
|
+
/**
|
|
24
|
+
* Discovers real ForceDream agents from /v1/agents/list, merges in real reliability data from
|
|
25
|
+
* /v1/agents/reliability, and applies client-side capability/query filters (the server has no
|
|
26
|
+
* working server-side capability filter). Keyless -- no account needed.
|
|
27
|
+
* @param args.capability - Optional exact capability match, e.g. "research:citation".
|
|
28
|
+
* @param args.query - Optional free-text match against slug, name, or capabilities.
|
|
29
|
+
*/
|
|
19
30
|
export async function searchAgents(args) {
|
|
20
31
|
const [data, relData] = await Promise.all([
|
|
21
32
|
fetchJson(`${FD_API}/v1/agents/list`),
|
package/dist/search_costs.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
const FD_API = process.env.FD_API_BASE || 'https://api.forcedream.ai';
|
|
3
|
+
/**
|
|
4
|
+
* Zod input schema for search_costs. Optional max_price_pence filter for budget-aware selection.
|
|
5
|
+
*/
|
|
3
6
|
export const searchCostsSchema = {
|
|
4
7
|
max_price_pence: z.number().optional().describe('Optional: only return agents at or under this price.'),
|
|
5
8
|
};
|
|
@@ -9,6 +12,10 @@ async function fetchJson(url) {
|
|
|
9
12
|
throw new Error(`fetch ${url} -> HTTP ${res.status}`);
|
|
10
13
|
return res.json();
|
|
11
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Fetches real price_per_call_pence for every registered agent from /v1/agents/list.
|
|
17
|
+
* @param args.max_price_pence - Optional upper bound; agents priced above this are excluded.
|
|
18
|
+
*/
|
|
12
19
|
export async function searchCosts(args) {
|
|
13
20
|
const data = await fetchJson(`${FD_API}/v1/agents/list`);
|
|
14
21
|
const agents = Array.isArray(data?.agents) ? data.agents : [];
|
package/dist/search_providers.js
CHANGED
|
@@ -5,6 +5,10 @@ async function fetchJson(url) {
|
|
|
5
5
|
throw new Error(`fetch ${url} -> HTTP ${res.status}`);
|
|
6
6
|
return res.json();
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Fetches real, live inference-provider health from /v1/intelligence/status --
|
|
10
|
+
* the same intelligence the platform's own adaptive routing uses internally.
|
|
11
|
+
*/
|
|
8
12
|
export async function searchProviders() {
|
|
9
13
|
const data = await fetchJson(`${FD_API}/v1/intelligence/status`);
|
|
10
14
|
const providers = Array.isArray(data?.providers) ? data.providers : [];
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
const FD_API = process.env.FD_API_BASE || 'https://api.forcedream.ai';
|
|
3
|
+
/**
|
|
4
|
+
* Zod input schema for search_reliability. Optional agent_slug filter; omit for all agents.
|
|
5
|
+
*/
|
|
3
6
|
export const searchReliabilitySchema = {
|
|
4
7
|
agent_slug: z.string().optional().describe('Optional: filter to one agent slug. Omit to return all.'),
|
|
5
8
|
};
|
|
@@ -9,7 +12,11 @@ async function fetchJson(url) {
|
|
|
9
12
|
throw new Error(`fetch ${url} -> HTTP ${res.status}`);
|
|
10
13
|
return res.json();
|
|
11
14
|
}
|
|
12
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Fetches real, system-measured reliability per agent from /v1/agents/reliability --
|
|
17
|
+
* the same real endpoint the remote MCP server's identical tool uses. No new computation.
|
|
18
|
+
* @param args.agent_slug - Optional filter to a single agent.
|
|
19
|
+
*/
|
|
13
20
|
export async function searchReliability(args) {
|
|
14
21
|
const data = await fetchJson(`${FD_API}/v1/agents/reliability`);
|
|
15
22
|
let agents = Array.isArray(data?.agents) ? data.agents : [];
|
package/dist/verify_proof.js
CHANGED
|
@@ -33,6 +33,13 @@ async function fetchJson(url) {
|
|
|
33
33
|
}
|
|
34
34
|
// Trustless verification: fetch public key (+ optionally proof), reconstruct
|
|
35
35
|
// signable, verify Ed25519 locally. Server never asked "is this valid?".
|
|
36
|
+
/**
|
|
37
|
+
* Trustlessly verifies a ForceDream proof's Ed25519 signature entirely client-side. Fetches the
|
|
38
|
+
* public key and (if only a task_id is given) the proof itself from public, keyless endpoints,
|
|
39
|
+
* then verifies locally -- ForceDream is never asked whether the proof is valid.
|
|
40
|
+
* @param args.task_id - Fetches the proof from the public endpoint if proof is not given directly.
|
|
41
|
+
* @param args.proof - A full proof object to verify directly, skipping the fetch.
|
|
42
|
+
*/
|
|
36
43
|
export async function verifyProof(args) {
|
|
37
44
|
let proof = args.proof;
|
|
38
45
|
if (!proof) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forcedream/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"mcpName": "io.github.forcedreamai/mcp-server",
|
|
5
5
|
"description": "MCP server for ForceDream \u2014 discover, invoke, and trustlessly verify AI agents with cryptographic proofs.",
|
|
6
6
|
"type": "module",
|