@blocks-network/mcp-server 0.1.62 → 0.1.64
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 +26 -0
- package/README.md +32 -20
- package/dist/agent-status.d.ts +49 -0
- package/dist/agent-status.js +40 -0
- package/dist/billing.d.ts +50 -0
- package/dist/billing.js +72 -0
- package/dist/index.js +50 -4
- package/dist/protocol-headers.d.ts +11 -0
- package/dist/protocol-headers.js +11 -0
- package/dist/registry-list.d.ts +2 -4
- package/dist/registry-list.js +3 -4
- package/dist/tools.d.ts +47 -2
- package/dist/tools.js +169 -3
- package/package.json +1 -1
- package/src/agent-status.ts +89 -0
- package/src/billing.ts +122 -0
- package/src/index.ts +102 -3
- package/src/protocol-headers.ts +12 -0
- package/src/registry-list.ts +7 -5
- package/src/tools.ts +248 -5
- package/tests/billing.test.ts +203 -0
- package/tests/download-artifact.test.ts +142 -0
- package/tests/get-agent-status.test.ts +108 -0
- package/tests/helpers.ts +75 -13
- package/tests/list-agents.test.ts +6 -6
- package/tests/registry-list.test.ts +5 -5
- package/tests/task-lifecycle.test.ts +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Blocks Network Software Development Kit License Agreement
|
|
2
|
+
Copyright © 2026 Blocks Network Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
Subject to the terms and conditions of the license, you are hereby granted
|
|
5
|
+
a non-exclusive, worldwide, royalty-free license to (a) copy and modify
|
|
6
|
+
the software in source code or binary form for use with the software services
|
|
7
|
+
and interfaces provided by Blocks Network, and (b) redistribute unmodified copies
|
|
8
|
+
of the software to third parties. The software may not be incorporated in
|
|
9
|
+
or used to provide any product or service competitive with the products
|
|
10
|
+
and services of Blocks Network.
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this license shall be included
|
|
13
|
+
in or with all copies or substantial portions of the software.
|
|
14
|
+
|
|
15
|
+
This license does not grant you permission to use the trade names, trademarks,
|
|
16
|
+
service marks, or product names of Blocks Network, except as required for reasonable
|
|
17
|
+
and customary use in describing the origin of the software and reproducing
|
|
18
|
+
the content of this license.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF
|
|
21
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
22
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
23
|
+
EVENT SHALL BLOCKS NETWORK OR THE AUTHORS OR COPYRIGHT HOLDERS OF THE SOFTWARE BE
|
|
24
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
25
|
+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
26
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,29 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
MCP (Model Context Protocol) server that exposes Blocks Network consumer operations as tools for AI assistants.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
cd mcp
|
|
9
|
-
npm install
|
|
10
|
-
npm run build
|
|
11
|
-
```
|
|
5
|
+
Get API Key: https://app.blocks.ai/manage/api-keys
|
|
12
6
|
|
|
13
7
|
## Environment Variables
|
|
14
8
|
|
|
15
9
|
| Variable | Required | Description |
|
|
16
10
|
|----------|----------|-------------|
|
|
17
11
|
| `BLOCKS_API_KEY` | Yes | Your Blocks Network API key |
|
|
12
|
+
| `BLOCKS_ORG_ID` | For billing tools | Your consumer org ID (required by `check_balance` and `request_topup`). Find it in the dashboard URL or `blocks whoami --json`. |
|
|
18
13
|
| `BLOCKS_MCP_FILE_ROOT` | No | Allowed root directory for file uploads (default: cwd) |
|
|
19
14
|
|
|
20
15
|
All other configuration (keys, endpoints) is resolved automatically from CDM.
|
|
21
16
|
|
|
22
17
|
## Installation
|
|
23
18
|
|
|
19
|
+
```bash
|
|
20
|
+
npm i @blocks-network/mcp-server
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`BLOCKS_ORG_ID` in the snippets below is only required by the billing tools (`check_balance`, `request_topup`); omit it if you don't plan to use them.
|
|
24
|
+
|
|
24
25
|
### Claude Code (CLI)
|
|
25
26
|
|
|
26
27
|
```bash
|
|
27
|
-
claude mcp add blocks-network --
|
|
28
|
+
claude mcp add blocks-network -- npx @blocks-network/mcp-server
|
|
28
29
|
```
|
|
29
30
|
|
|
30
31
|
Or add to your `.claude/settings.json`:
|
|
@@ -33,10 +34,11 @@ Or add to your `.claude/settings.json`:
|
|
|
33
34
|
{
|
|
34
35
|
"mcpServers": {
|
|
35
36
|
"blocks-network": {
|
|
36
|
-
"command": "
|
|
37
|
-
"args": ["
|
|
37
|
+
"command": "npx",
|
|
38
|
+
"args": ["@blocks-network/mcp-server"],
|
|
38
39
|
"env": {
|
|
39
|
-
"BLOCKS_API_KEY": "your-api-key"
|
|
40
|
+
"BLOCKS_API_KEY": "your-api-key",
|
|
41
|
+
"BLOCKS_ORG_ID": "your-consumer-org-id"
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -51,10 +53,11 @@ Add to your `claude_desktop_config.json`:
|
|
|
51
53
|
{
|
|
52
54
|
"mcpServers": {
|
|
53
55
|
"blocks-network": {
|
|
54
|
-
"command": "
|
|
55
|
-
"args": ["
|
|
56
|
+
"command": "npx",
|
|
57
|
+
"args": ["@blocks-network/mcp-server"],
|
|
56
58
|
"env": {
|
|
57
|
-
"BLOCKS_API_KEY": "your-api-key"
|
|
59
|
+
"BLOCKS_API_KEY": "your-api-key",
|
|
60
|
+
"BLOCKS_ORG_ID": "your-consumer-org-id"
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
}
|
|
@@ -73,10 +76,11 @@ Create an `mcp.json` file:
|
|
|
73
76
|
{
|
|
74
77
|
"mcpServers": {
|
|
75
78
|
"blocks-network": {
|
|
76
|
-
"command": "
|
|
77
|
-
"args": ["
|
|
79
|
+
"command": "npx",
|
|
80
|
+
"args": ["@blocks-network/mcp-server"],
|
|
78
81
|
"env": {
|
|
79
|
-
"BLOCKS_API_KEY": "your-api-key"
|
|
82
|
+
"BLOCKS_API_KEY": "your-api-key",
|
|
83
|
+
"BLOCKS_ORG_ID": "your-consumer-org-id"
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
}
|
|
@@ -91,10 +95,11 @@ Add to your `~/.gemini/settings.json`:
|
|
|
91
95
|
{
|
|
92
96
|
"mcpServers": {
|
|
93
97
|
"blocks-network": {
|
|
94
|
-
"command": "
|
|
95
|
-
"args": ["
|
|
98
|
+
"command": "npx",
|
|
99
|
+
"args": ["@blocks-network/mcp-server"],
|
|
96
100
|
"env": {
|
|
97
|
-
"BLOCKS_API_KEY": "your-api-key"
|
|
101
|
+
"BLOCKS_API_KEY": "your-api-key",
|
|
102
|
+
"BLOCKS_ORG_ID": "your-consumer-org-id"
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
}
|
|
@@ -109,9 +114,16 @@ Add to your `~/.gemini/settings.json`:
|
|
|
109
114
|
| `get_task` | Get the current status of a task |
|
|
110
115
|
| `list_tasks` | List tasks, optionally filtered by agent or state |
|
|
111
116
|
| `cancel_task` | Cancel a running task |
|
|
117
|
+
| `pause_task` | Pause a running pipe task |
|
|
118
|
+
| `resume_task` | Resume a paused pipe task |
|
|
119
|
+
| `retry_task` | Retry a failed task |
|
|
112
120
|
| `list_agents` | List available agents in the registry |
|
|
113
121
|
| `get_agent_card` | Get the full agent card for a specific agent |
|
|
122
|
+
| `get_agent_status` | Check live availability for agents (online instance count and total task count). Per-instance live activity counters (`activeTasks`, `concurrentTasksPerInstance`, `startedAt`, `totalActiveTasks`) are reserved in the response shape but currently return `0` — the backend does not yet populate them. |
|
|
114
123
|
| `connect_task` | Connect to an existing task and stream events |
|
|
124
|
+
| `download_artifact` | Download a single task artifact by file name (inline content or save to disk) |
|
|
125
|
+
| `check_balance` | Get the consumer billing balance for the configured org |
|
|
126
|
+
| `request_topup` | Create a Stripe Checkout URL to add USD to the consumer balance (user completes payment in a browser). Minimum top-up is `$5` (platform `MIN_BILLING_AMOUNT`). |
|
|
115
127
|
|
|
116
128
|
## Development
|
|
117
129
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent presence/availability helper.
|
|
3
|
+
*
|
|
4
|
+
* Calls GET /api/v1/agent-status with the Blocks-Protocol-Version
|
|
5
|
+
* header. Backend route is optionalAuth so the API key is forwarded
|
|
6
|
+
* when available but not required.
|
|
7
|
+
*
|
|
8
|
+
* Response shape mirrors the service's agent-status types.
|
|
9
|
+
*/
|
|
10
|
+
/** MUST stay in sync with the service's agent-status types. */
|
|
11
|
+
export declare const MAX_AGENT_NAMES = 50;
|
|
12
|
+
export declare const AGENT_NAME_PATTERN: RegExp;
|
|
13
|
+
export interface AgentInstanceStatus {
|
|
14
|
+
instanceId: string;
|
|
15
|
+
uuid: string;
|
|
16
|
+
online: true;
|
|
17
|
+
/**
|
|
18
|
+
* Reserved — backend currently returns 0 (live activity counters are not
|
|
19
|
+
* yet populated by the agent-status service). Do not use for routing or
|
|
20
|
+
* availability decisions.
|
|
21
|
+
*/
|
|
22
|
+
activeTasks: number;
|
|
23
|
+
/** Reserved — backend currently returns 0. See `activeTasks`. */
|
|
24
|
+
concurrentTasksPerInstance: number;
|
|
25
|
+
/** Reserved — backend currently returns 0. See `activeTasks`. */
|
|
26
|
+
startedAt: number;
|
|
27
|
+
sdkVersion: string | null;
|
|
28
|
+
cliVersion: string | null;
|
|
29
|
+
preferredProtocolVersion: string | null;
|
|
30
|
+
protocolVersions: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface AgentStatus {
|
|
33
|
+
agentName: string;
|
|
34
|
+
instances: AgentInstanceStatus[];
|
|
35
|
+
onlineCount: number;
|
|
36
|
+
/** Reserved — backend currently returns 0. See `AgentInstanceStatus.activeTasks`. */
|
|
37
|
+
totalActiveTasks: number;
|
|
38
|
+
taskCount: number;
|
|
39
|
+
}
|
|
40
|
+
export interface AgentStatusResponse {
|
|
41
|
+
agents: Record<string, AgentStatus>;
|
|
42
|
+
}
|
|
43
|
+
export interface FetchAgentStatusOptions {
|
|
44
|
+
baseUrl: string;
|
|
45
|
+
agentNames: string[];
|
|
46
|
+
apiKey?: string;
|
|
47
|
+
fetchImpl?: typeof fetch;
|
|
48
|
+
}
|
|
49
|
+
export declare function fetchAgentStatus(opts: FetchAgentStatusOptions): Promise<AgentStatusResponse>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent presence/availability helper.
|
|
3
|
+
*
|
|
4
|
+
* Calls GET /api/v1/agent-status with the Blocks-Protocol-Version
|
|
5
|
+
* header. Backend route is optionalAuth so the API key is forwarded
|
|
6
|
+
* when available but not required.
|
|
7
|
+
*
|
|
8
|
+
* Response shape mirrors the service's agent-status types.
|
|
9
|
+
*/
|
|
10
|
+
import { PROTOCOL_VERSION_HEADER, CURRENT_PROTOCOL_VERSION, } from './protocol-headers.js';
|
|
11
|
+
/** MUST stay in sync with the service's agent-status types. */
|
|
12
|
+
export const MAX_AGENT_NAMES = 50;
|
|
13
|
+
export const AGENT_NAME_PATTERN = /^[a-zA-Z0-9_]+$/;
|
|
14
|
+
export async function fetchAgentStatus(opts) {
|
|
15
|
+
const names = opts.agentNames.map((n) => n.trim()).filter(Boolean);
|
|
16
|
+
if (names.length === 0) {
|
|
17
|
+
throw new Error('agentNames must contain at least one value');
|
|
18
|
+
}
|
|
19
|
+
if (names.length > MAX_AGENT_NAMES) {
|
|
20
|
+
throw new Error(`agentNames must be at most ${MAX_AGENT_NAMES} values`);
|
|
21
|
+
}
|
|
22
|
+
for (const n of names) {
|
|
23
|
+
if (!AGENT_NAME_PATTERN.test(n)) {
|
|
24
|
+
throw new Error(`Invalid agent name: ${n}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const params = new URLSearchParams({ agentNames: names.join(',') });
|
|
28
|
+
const url = `${opts.baseUrl.replace(/\/+$/, '')}/api/v1/agent-status?${params}`;
|
|
29
|
+
const headers = {
|
|
30
|
+
[PROTOCOL_VERSION_HEADER]: CURRENT_PROTOCOL_VERSION,
|
|
31
|
+
};
|
|
32
|
+
if (opts.apiKey)
|
|
33
|
+
headers['Authorization'] = `Bearer ${opts.apiKey}`;
|
|
34
|
+
const fetchFn = opts.fetchImpl ?? fetch;
|
|
35
|
+
const response = await fetchFn(url, { headers });
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`Agent status failed: HTTP ${response.status}`);
|
|
38
|
+
}
|
|
39
|
+
return (await response.json());
|
|
40
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billing helpers — direct HTTP to the routes that accept Bearer
|
|
3
|
+
* API-key auth (`requireAuth` middleware path on the backend).
|
|
4
|
+
*
|
|
5
|
+
* GET /api/v1/billing/:orgId/consumer/balance
|
|
6
|
+
* POST /api/v1/billing/:orgId/consumer/topup
|
|
7
|
+
*
|
|
8
|
+
* Other billing routes (ledger, usage-summary, dashboard-summary,
|
|
9
|
+
* topup-from-earnings) are session-only on the backend and are not
|
|
10
|
+
* callable from an MCP server holding only an API key.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Platform minimum top-up amount in USD. MUST stay in sync with
|
|
14
|
+
* the service's MIN_BILLING_AMOUNT
|
|
15
|
+
* (decimal-dollar string `'5'`). The backend rejects values below this floor
|
|
16
|
+
* via `stripeMoneyAtLeast(MIN_BILLING_AMOUNT, ...)`; we mirror it here so MCP
|
|
17
|
+
* callers get the real contract up front instead of a runtime HTTP 400.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MIN_TOPUP_AMOUNT_USD = 5;
|
|
20
|
+
export interface ConsumerBalance {
|
|
21
|
+
/** Ledger balance as a decimal-dollar string (e.g. "12.34"). */
|
|
22
|
+
balance: string;
|
|
23
|
+
/** Currently reserved (held for in-flight tasks). */
|
|
24
|
+
reservedBalance: string;
|
|
25
|
+
/** balance - reservedBalance. */
|
|
26
|
+
availableBalance: string;
|
|
27
|
+
/** ISO timestamp of when the balance snapshot was taken. */
|
|
28
|
+
updatedAt: string;
|
|
29
|
+
}
|
|
30
|
+
export interface TopUpSession {
|
|
31
|
+
/** Stripe Checkout URL the user opens in a browser to complete payment. */
|
|
32
|
+
checkoutUrl: string;
|
|
33
|
+
/** Stripe Checkout session id. */
|
|
34
|
+
sessionId: string;
|
|
35
|
+
}
|
|
36
|
+
export interface BillingClientBase {
|
|
37
|
+
baseUrl: string;
|
|
38
|
+
orgId: string;
|
|
39
|
+
apiKey: string;
|
|
40
|
+
fetchImpl?: typeof fetch;
|
|
41
|
+
}
|
|
42
|
+
export declare function getConsumerBalance(opts: BillingClientBase): Promise<ConsumerBalance>;
|
|
43
|
+
export interface CreateTopUpOptions extends BillingClientBase {
|
|
44
|
+
/**
|
|
45
|
+
* Whole-dollar (or whole-cent decimal) USD amount, e.g. 25 or 19.99.
|
|
46
|
+
* Must be at least `MIN_TOPUP_AMOUNT_USD` ($5) — backend rejects below this.
|
|
47
|
+
*/
|
|
48
|
+
amountUsd: number;
|
|
49
|
+
}
|
|
50
|
+
export declare function createConsumerTopUp(opts: CreateTopUpOptions): Promise<TopUpSession>;
|
package/dist/billing.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billing helpers — direct HTTP to the routes that accept Bearer
|
|
3
|
+
* API-key auth (`requireAuth` middleware path on the backend).
|
|
4
|
+
*
|
|
5
|
+
* GET /api/v1/billing/:orgId/consumer/balance
|
|
6
|
+
* POST /api/v1/billing/:orgId/consumer/topup
|
|
7
|
+
*
|
|
8
|
+
* Other billing routes (ledger, usage-summary, dashboard-summary,
|
|
9
|
+
* topup-from-earnings) are session-only on the backend and are not
|
|
10
|
+
* callable from an MCP server holding only an API key.
|
|
11
|
+
*/
|
|
12
|
+
import { PROTOCOL_VERSION_HEADER, CURRENT_PROTOCOL_VERSION, } from './protocol-headers.js';
|
|
13
|
+
/**
|
|
14
|
+
* Platform minimum top-up amount in USD. MUST stay in sync with
|
|
15
|
+
* the service's MIN_BILLING_AMOUNT
|
|
16
|
+
* (decimal-dollar string `'5'`). The backend rejects values below this floor
|
|
17
|
+
* via `stripeMoneyAtLeast(MIN_BILLING_AMOUNT, ...)`; we mirror it here so MCP
|
|
18
|
+
* callers get the real contract up front instead of a runtime HTTP 400.
|
|
19
|
+
*/
|
|
20
|
+
export const MIN_TOPUP_AMOUNT_USD = 5;
|
|
21
|
+
function buildHeaders(apiKey) {
|
|
22
|
+
return {
|
|
23
|
+
[PROTOCOL_VERSION_HEADER]: CURRENT_PROTOCOL_VERSION,
|
|
24
|
+
Authorization: `Bearer ${apiKey}`,
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function billingUrl(baseUrl, orgId, suffix) {
|
|
29
|
+
return `${baseUrl.replace(/\/+$/, '')}/api/v1/billing/${encodeURIComponent(orgId)}${suffix}`;
|
|
30
|
+
}
|
|
31
|
+
export async function getConsumerBalance(opts) {
|
|
32
|
+
const url = billingUrl(opts.baseUrl, opts.orgId, '/consumer/balance');
|
|
33
|
+
const fetchFn = opts.fetchImpl ?? fetch;
|
|
34
|
+
const response = await fetchFn(url, {
|
|
35
|
+
method: 'GET',
|
|
36
|
+
headers: buildHeaders(opts.apiKey),
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`Balance lookup failed: HTTP ${response.status}`);
|
|
40
|
+
}
|
|
41
|
+
return (await response.json());
|
|
42
|
+
}
|
|
43
|
+
export async function createConsumerTopUp(opts) {
|
|
44
|
+
if (!Number.isFinite(opts.amountUsd) || opts.amountUsd <= 0) {
|
|
45
|
+
throw new Error('amountUsd must be a positive finite number');
|
|
46
|
+
}
|
|
47
|
+
if (Math.round(opts.amountUsd * 100) / 100 !== opts.amountUsd) {
|
|
48
|
+
throw new Error('amountUsd must be a whole-cent value (no sub-cent fractions)');
|
|
49
|
+
}
|
|
50
|
+
if (opts.amountUsd < MIN_TOPUP_AMOUNT_USD) {
|
|
51
|
+
throw new Error(`amountUsd must be at least $${MIN_TOPUP_AMOUNT_USD}.00 (platform minimum)`);
|
|
52
|
+
}
|
|
53
|
+
const amount = opts.amountUsd.toFixed(2);
|
|
54
|
+
const url = billingUrl(opts.baseUrl, opts.orgId, '/consumer/topup');
|
|
55
|
+
const fetchFn = opts.fetchImpl ?? fetch;
|
|
56
|
+
const response = await fetchFn(url, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: buildHeaders(opts.apiKey),
|
|
59
|
+
body: JSON.stringify({ amount }),
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
let detail = '';
|
|
63
|
+
try {
|
|
64
|
+
detail = await response.text();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// ignore body decode failures
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Top-up failed: HTTP ${response.status}${detail ? ` — ${detail}` : ''}`);
|
|
70
|
+
}
|
|
71
|
+
return (await response.json());
|
|
72
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { resolve, relative } from 'node:path';
|
|
3
|
-
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { resolve, relative, dirname } from 'node:path';
|
|
3
|
+
import { realpathSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { TaskClient, textPart, filePartFromPath, fetchCdmConfig, getAgent, BLOCKS_MAX_UPLOAD_BYTES, } from '@blocks-network/sdk';
|
|
8
8
|
import { listAgentsAuthenticated } from './registry-list.js';
|
|
9
|
-
import {
|
|
9
|
+
import { fetchAgentStatus, MAX_AGENT_NAMES } from './agent-status.js';
|
|
10
|
+
import { getConsumerBalance, createConsumerTopUp } from './billing.js';
|
|
11
|
+
import { sendTask, getTask, listTasks, cancelTask, pauseTask, resumeTask, retryTask, listAgents, getAgentCard, getAgentStatus, connectTask, downloadArtifact, checkBalance, requestTopup, defaultFileSize, } from './tools.js';
|
|
10
12
|
const ALLOWED_ROOT = process.env.BLOCKS_MCP_FILE_ROOT ?? process.cwd();
|
|
11
13
|
if (!process.env.BLOCKS_API_KEY) {
|
|
12
14
|
console.error('Warning: BLOCKS_API_KEY is not set. Private agents and paid tasks will not work.');
|
|
@@ -27,6 +29,19 @@ function validateFilePath(filePath) {
|
|
|
27
29
|
}
|
|
28
30
|
return real;
|
|
29
31
|
}
|
|
32
|
+
function resolveSavePath(filePath) {
|
|
33
|
+
const root = realpathSync(resolve(ALLOWED_ROOT));
|
|
34
|
+
const resolved = resolve(root, filePath);
|
|
35
|
+
const rel = relative(root, resolved);
|
|
36
|
+
if (rel.startsWith('..') || resolve(root, rel) !== resolved) {
|
|
37
|
+
throw new Error(`Save path must be within ${ALLOWED_ROOT}`);
|
|
38
|
+
}
|
|
39
|
+
mkdirSync(dirname(resolved), { recursive: true });
|
|
40
|
+
return resolved;
|
|
41
|
+
}
|
|
42
|
+
function writeFile(filePath, data) {
|
|
43
|
+
writeFileSync(filePath, data);
|
|
44
|
+
}
|
|
30
45
|
const taskClients = new Map();
|
|
31
46
|
let resolvedBaseUrl;
|
|
32
47
|
async function getBaseUrl() {
|
|
@@ -52,10 +67,16 @@ async function getTaskClient(billingMode = 'free') {
|
|
|
52
67
|
const deps = {
|
|
53
68
|
getBaseUrl,
|
|
54
69
|
getApiKey: () => process.env.BLOCKS_API_KEY,
|
|
70
|
+
getOrgId: () => process.env.BLOCKS_ORG_ID,
|
|
55
71
|
getTaskClient,
|
|
56
72
|
getAgentByName: (agentName, options) => getAgent(agentName, options),
|
|
57
73
|
listAgents: listAgentsAuthenticated,
|
|
74
|
+
fetchAgentStatus,
|
|
75
|
+
getConsumerBalance,
|
|
76
|
+
createConsumerTopUp,
|
|
58
77
|
validateFilePath,
|
|
78
|
+
resolveSavePath,
|
|
79
|
+
writeFile,
|
|
59
80
|
fileSize: defaultFileSize,
|
|
60
81
|
maxUploadBytes: BLOCKS_MAX_UPLOAD_BYTES,
|
|
61
82
|
filePartFromPath,
|
|
@@ -87,8 +108,11 @@ server.tool('list_tasks', 'List tasks, optionally filtered by agent or state', {
|
|
|
87
108
|
limit: z.number().optional().describe('Max results to return'),
|
|
88
109
|
}, (params) => listTasks(params, deps));
|
|
89
110
|
server.tool('cancel_task', 'Cancel a running task', { taskId: z.string().describe('The task ID to cancel') }, (params) => cancelTask(params, deps));
|
|
111
|
+
server.tool('pause_task', 'Pause a running pipe task. Resume later with resume_task.', { taskId: z.string().describe('The task ID to pause') }, (params) => pauseTask(params, deps));
|
|
112
|
+
server.tool('resume_task', 'Resume a paused pipe task', { taskId: z.string().describe('The task ID to resume') }, (params) => resumeTask(params, deps));
|
|
113
|
+
server.tool('retry_task', 'Retry a failed task', { taskId: z.string().describe('The task ID to retry') }, (params) => retryTask(params, deps));
|
|
90
114
|
server.tool('list_agents', 'List available agents in the Blocks Network registry. Use listing="private" with an API key to discover your private agents.', {
|
|
91
|
-
|
|
115
|
+
tag: z.string().optional().describe('Filter by tag slug'),
|
|
92
116
|
listing: z
|
|
93
117
|
.enum(['public', 'private'])
|
|
94
118
|
.optional()
|
|
@@ -96,10 +120,32 @@ server.tool('list_agents', 'List available agents in the Blocks Network registry
|
|
|
96
120
|
limit: z.number().optional().describe('Max results to return'),
|
|
97
121
|
}, (params) => listAgents(params, deps));
|
|
98
122
|
server.tool('get_agent_card', 'Get the full agent card for a specific agent', { agentName: z.string().describe('Agent name to look up') }, (params) => getAgentCard(params, deps));
|
|
123
|
+
server.tool('get_agent_status', 'Check live availability for one or more agents: how many instances are online, total task count for the agent, and SDK/CLI versions per instance. (Per-instance live activity counters such as `activeTasks`, `concurrentTasksPerInstance`, `startedAt`, and `totalActiveTasks` are reserved in the response shape but not yet populated by the backend — they currently return 0.)', {
|
|
124
|
+
agentNames: z
|
|
125
|
+
.array(z.string())
|
|
126
|
+
.min(1)
|
|
127
|
+
.max(MAX_AGENT_NAMES)
|
|
128
|
+
.describe(`Agent names to check (1-${MAX_AGENT_NAMES})`),
|
|
129
|
+
}, (params) => getAgentStatus(params, deps));
|
|
130
|
+
server.tool('check_balance', 'Get the consumer billing balance for the configured org (BLOCKS_ORG_ID): ledger balance, active reservations, and available balance.', {}, (_params) => checkBalance({}, deps));
|
|
131
|
+
server.tool('request_topup', 'Create a Stripe Checkout session to add USD to the consumer balance. Returns a URL the user opens in a browser to complete payment; the MCP server does not handle payment itself. Minimum top-up is $5 (platform `MIN_BILLING_AMOUNT`).', {
|
|
132
|
+
amountUsd: z
|
|
133
|
+
.number()
|
|
134
|
+
.positive()
|
|
135
|
+
.describe('Amount to add in USD (e.g. 25 or 19.99). Whole-cent precision. Must be at least $5 (platform minimum).'),
|
|
136
|
+
}, (params) => requestTopup(params, deps));
|
|
99
137
|
server.tool('connect_task', 'Connect to an existing task, stream events and data until completion', {
|
|
100
138
|
taskId: z.string().describe('The task ID to connect to'),
|
|
101
139
|
timeoutMs: z.number().optional().describe('Timeout in milliseconds (default: 60000)'),
|
|
102
140
|
}, (params) => connectTask(params, deps));
|
|
141
|
+
server.tool('download_artifact', 'Download a single artifact from a task by file name. If savePath is provided, writes the artifact to disk under BLOCKS_MCP_FILE_ROOT; otherwise returns the content inline (text decoded, binary base64-encoded).', {
|
|
142
|
+
taskId: z.string().describe('The task ID that produced the artifact'),
|
|
143
|
+
fileName: z.string().describe('Artifact file name as listed by get_task'),
|
|
144
|
+
savePath: z
|
|
145
|
+
.string()
|
|
146
|
+
.optional()
|
|
147
|
+
.describe('Optional path (relative to BLOCKS_MCP_FILE_ROOT) to write the artifact to'),
|
|
148
|
+
}, (params) => downloadArtifact(params, deps));
|
|
103
149
|
function shutdown() {
|
|
104
150
|
for (const client of taskClients.values()) {
|
|
105
151
|
client.destroy();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared protocol-version header constants for direct-HTTP backend
|
|
3
|
+
* callers (`registry-list`, `agent-status`, `billing`).
|
|
4
|
+
*
|
|
5
|
+
* MUST stay in sync with
|
|
6
|
+
* blocks-sdk/sdks/node/src/runtime/protocol-version.ts (the canonical
|
|
7
|
+
* source). Until the SDK exports these from its public surface, both
|
|
8
|
+
* files must update atomically when the protocol version bumps.
|
|
9
|
+
*/
|
|
10
|
+
export declare const PROTOCOL_VERSION_HEADER = "Blocks-Protocol-Version";
|
|
11
|
+
export declare const CURRENT_PROTOCOL_VERSION = "2026-05-01";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared protocol-version header constants for direct-HTTP backend
|
|
3
|
+
* callers (`registry-list`, `agent-status`, `billing`).
|
|
4
|
+
*
|
|
5
|
+
* MUST stay in sync with
|
|
6
|
+
* blocks-sdk/sdks/node/src/runtime/protocol-version.ts (the canonical
|
|
7
|
+
* source). Until the SDK exports these from its public surface, both
|
|
8
|
+
* files must update atomically when the protocol version bumps.
|
|
9
|
+
*/
|
|
10
|
+
export const PROTOCOL_VERSION_HEADER = 'Blocks-Protocol-Version';
|
|
11
|
+
export const CURRENT_PROTOCOL_VERSION = '2026-05-01';
|
package/dist/registry-list.d.ts
CHANGED
|
@@ -5,15 +5,13 @@
|
|
|
5
5
|
* header (required by the backend protocol-version middleware) and an
|
|
6
6
|
* optional Bearer API key for private/owned listings.
|
|
7
7
|
*/
|
|
8
|
-
export declare const PROTOCOL_VERSION_HEADER = "Blocks-Protocol-Version";
|
|
9
|
-
export declare const CURRENT_PROTOCOL_VERSION = "2026-05-01";
|
|
10
8
|
export interface AgentListEntry {
|
|
11
9
|
agentName: string;
|
|
12
10
|
name?: string;
|
|
13
11
|
description?: string;
|
|
14
12
|
listing?: string;
|
|
15
13
|
billingMode?: string;
|
|
16
|
-
|
|
14
|
+
tags?: Array<{
|
|
17
15
|
id: string;
|
|
18
16
|
name: string;
|
|
19
17
|
}>;
|
|
@@ -21,7 +19,7 @@ export interface AgentListEntry {
|
|
|
21
19
|
export interface ListAgentsOptions {
|
|
22
20
|
baseUrl: string;
|
|
23
21
|
apiKey?: string;
|
|
24
|
-
|
|
22
|
+
tag?: string;
|
|
25
23
|
listing?: 'public' | 'private';
|
|
26
24
|
limit?: number;
|
|
27
25
|
fetchImpl?: typeof fetch;
|
package/dist/registry-list.js
CHANGED
|
@@ -5,12 +5,11 @@
|
|
|
5
5
|
* header (required by the backend protocol-version middleware) and an
|
|
6
6
|
* optional Bearer API key for private/owned listings.
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
export const CURRENT_PROTOCOL_VERSION = '2026-05-01';
|
|
8
|
+
import { PROTOCOL_VERSION_HEADER, CURRENT_PROTOCOL_VERSION, } from './protocol-headers.js';
|
|
10
9
|
export async function listAgentsAuthenticated(opts) {
|
|
11
10
|
const params = new URLSearchParams({ include: 'full' });
|
|
12
|
-
if (opts.
|
|
13
|
-
params.set('
|
|
11
|
+
if (opts.tag)
|
|
12
|
+
params.set('tag', opts.tag);
|
|
14
13
|
if (opts.listing) {
|
|
15
14
|
params.set('listing', opts.listing);
|
|
16
15
|
if (opts.listing === 'private')
|
package/dist/tools.d.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { textPart, filePartFromPath, type TaskInfo, type SendMessageRequestPart } from '@blocks-network/sdk';
|
|
9
9
|
import type { ListAgentsResult } from './registry-list.js';
|
|
10
|
+
import type { AgentStatusResponse } from './agent-status.js';
|
|
11
|
+
import type { ConsumerBalance, TopUpSession } from './billing.js';
|
|
10
12
|
export interface ArtifactRef {
|
|
11
13
|
fileName?: string;
|
|
12
14
|
}
|
|
@@ -65,6 +67,9 @@ export interface TaskClientLike {
|
|
|
65
67
|
totalCount?: number;
|
|
66
68
|
}>;
|
|
67
69
|
cancelTask(taskId: string): Promise<unknown>;
|
|
70
|
+
pauseTask(taskId: string): Promise<unknown>;
|
|
71
|
+
resumeTask(taskId: string): Promise<unknown>;
|
|
72
|
+
retryTask(taskId: string): Promise<unknown>;
|
|
68
73
|
connect(params: {
|
|
69
74
|
taskId: string;
|
|
70
75
|
}): Promise<TaskSessionLike>;
|
|
@@ -100,15 +105,34 @@ export interface ToolDeps {
|
|
|
100
105
|
listAgents(options: {
|
|
101
106
|
baseUrl: string;
|
|
102
107
|
apiKey?: string;
|
|
103
|
-
|
|
108
|
+
tag?: string;
|
|
104
109
|
listing?: 'public' | 'private';
|
|
105
110
|
limit?: number;
|
|
106
111
|
}): Promise<ListAgentsResult>;
|
|
107
112
|
validateFilePath(filePath: string): string;
|
|
113
|
+
resolveSavePath(filePath: string): string;
|
|
114
|
+
writeFile(filePath: string, data: Uint8Array): void;
|
|
108
115
|
fileSize(path: string): number;
|
|
109
116
|
maxUploadBytes: number;
|
|
110
117
|
filePartFromPath: typeof filePartFromPath;
|
|
111
118
|
textPart: typeof textPart;
|
|
119
|
+
getOrgId(): string | undefined;
|
|
120
|
+
fetchAgentStatus(options: {
|
|
121
|
+
baseUrl: string;
|
|
122
|
+
apiKey?: string;
|
|
123
|
+
agentNames: string[];
|
|
124
|
+
}): Promise<AgentStatusResponse>;
|
|
125
|
+
getConsumerBalance(options: {
|
|
126
|
+
baseUrl: string;
|
|
127
|
+
apiKey: string;
|
|
128
|
+
orgId: string;
|
|
129
|
+
}): Promise<ConsumerBalance>;
|
|
130
|
+
createConsumerTopUp(options: {
|
|
131
|
+
baseUrl: string;
|
|
132
|
+
apiKey: string;
|
|
133
|
+
orgId: string;
|
|
134
|
+
amountUsd: number;
|
|
135
|
+
}): Promise<TopUpSession>;
|
|
112
136
|
}
|
|
113
137
|
export interface SendTaskParams {
|
|
114
138
|
agentName: string;
|
|
@@ -131,7 +155,7 @@ export interface CancelTaskParams {
|
|
|
131
155
|
taskId: string;
|
|
132
156
|
}
|
|
133
157
|
export interface ListAgentsParams {
|
|
134
|
-
|
|
158
|
+
tag?: string;
|
|
135
159
|
listing?: 'public' | 'private';
|
|
136
160
|
limit?: number;
|
|
137
161
|
}
|
|
@@ -142,6 +166,20 @@ export interface ConnectTaskParams {
|
|
|
142
166
|
taskId: string;
|
|
143
167
|
timeoutMs?: number;
|
|
144
168
|
}
|
|
169
|
+
export interface DownloadArtifactParams {
|
|
170
|
+
taskId: string;
|
|
171
|
+
fileName: string;
|
|
172
|
+
savePath?: string;
|
|
173
|
+
}
|
|
174
|
+
export interface TaskIdParams {
|
|
175
|
+
taskId: string;
|
|
176
|
+
}
|
|
177
|
+
export interface GetAgentStatusParams {
|
|
178
|
+
agentNames: string[];
|
|
179
|
+
}
|
|
180
|
+
export interface RequestTopUpParams {
|
|
181
|
+
amountUsd: number;
|
|
182
|
+
}
|
|
145
183
|
export declare function sendTask(params: SendTaskParams, deps: ToolDeps): Promise<ToolResult>;
|
|
146
184
|
export declare function getTask(params: GetTaskParams, deps: ToolDeps): Promise<ToolResult>;
|
|
147
185
|
export declare function listTasks(params: ListTasksParams, deps: ToolDeps): Promise<ToolResult>;
|
|
@@ -149,4 +187,11 @@ export declare function cancelTask(params: CancelTaskParams, deps: ToolDeps): Pr
|
|
|
149
187
|
export declare function listAgents(params: ListAgentsParams, deps: ToolDeps): Promise<ToolResult>;
|
|
150
188
|
export declare function getAgentCard(params: GetAgentCardParams, deps: ToolDeps): Promise<ToolResult>;
|
|
151
189
|
export declare function connectTask(params: ConnectTaskParams, deps: ToolDeps): Promise<ToolResult>;
|
|
190
|
+
export declare function downloadArtifact(params: DownloadArtifactParams, deps: ToolDeps): Promise<ToolResult>;
|
|
191
|
+
export declare function pauseTask(params: TaskIdParams, deps: ToolDeps): Promise<ToolResult>;
|
|
192
|
+
export declare function resumeTask(params: TaskIdParams, deps: ToolDeps): Promise<ToolResult>;
|
|
193
|
+
export declare function retryTask(params: TaskIdParams, deps: ToolDeps): Promise<ToolResult>;
|
|
194
|
+
export declare function getAgentStatus(params: GetAgentStatusParams, deps: ToolDeps): Promise<ToolResult>;
|
|
195
|
+
export declare function checkBalance(_params: Record<string, never>, deps: ToolDeps): Promise<ToolResult>;
|
|
196
|
+
export declare function requestTopup(params: RequestTopUpParams, deps: ToolDeps): Promise<ToolResult>;
|
|
152
197
|
export declare function defaultFileSize(path: string): number;
|