@three-ws/mcp-server 1.1.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 +180 -0
- package/README.md +304 -0
- package/package.json +79 -0
- package/server.json +54 -0
- package/src/index.js +201 -0
- package/src/lib/evm-rpc.js +130 -0
- package/src/lib/pose-presets.js +421 -0
- package/src/lib/pump-vanity.js +124 -0
- package/src/lib/resilient-fetch.js +194 -0
- package/src/lib/solana-rpc.js +130 -0
- package/src/payments.js +319 -0
- package/src/tools/_shared.js +41 -0
- package/src/tools/agenc-client.js +136 -0
- package/src/tools/agenc-get-agent.js +145 -0
- package/src/tools/agenc-get-task.js +187 -0
- package/src/tools/agenc-list-tasks.js +110 -0
- package/src/tools/agent-delegate-action.js +113 -0
- package/src/tools/agent-reputation.js +284 -0
- package/src/tools/aixbt-intel.js +108 -0
- package/src/tools/aixbt-projects.js +116 -0
- package/src/tools/ens-sns-resolve.js +209 -0
- package/src/tools/mesh-forge.js +379 -0
- package/src/tools/pose-seed.js +169 -0
- package/src/tools/pump-snapshot.js +262 -0
- package/src/tools/rig-mesh.js +207 -0
- package/src/tools/sentiment-pulse.js +118 -0
- package/src/tools/text-to-avatar.js +289 -0
- package/src/tools/vanity-grinder.js +178 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Shared AgenC read-only client used by the agenc-* MCP tools.
|
|
2
|
+
//
|
|
3
|
+
// Constructs an Anchor `Program` bound to the AgenC coordination protocol
|
|
4
|
+
// (agenc.tech, Tetsuo Corp) so the read-only helpers in @tetsuo-ai/sdk can be
|
|
5
|
+
// invoked without forcing every tool to know about Anchor's wallet plumbing.
|
|
6
|
+
//
|
|
7
|
+
// Cluster + RPC are resolved at call time so a single MCP server process can
|
|
8
|
+
// service both mainnet and devnet tools.
|
|
9
|
+
|
|
10
|
+
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
|
|
11
|
+
import { AnchorProvider, Program } from '@coral-xyz/anchor';
|
|
12
|
+
import { AGENC_COORDINATION_IDL } from '@tetsuo-ai/protocol';
|
|
13
|
+
import { DEVNET_RPC, MAINNET_RPC, PROGRAM_ID } from '@tetsuo-ai/sdk';
|
|
14
|
+
|
|
15
|
+
import { resilientFetch } from '../lib/resilient-fetch.js';
|
|
16
|
+
|
|
17
|
+
// Devnet program ID validated by the AgenC team on 2026-03-22.
|
|
18
|
+
// Source: https://docs.agenc.tech/docs/runtime/api/.
|
|
19
|
+
export const AGENC_DEVNET_PROGRAM_ID = new PublicKey(
|
|
20
|
+
'6UcJzbTEemBz3aY5wK5qKHGMD7bdRsmR4smND29gB2ab',
|
|
21
|
+
);
|
|
22
|
+
export const AGENC_MAINNET_PROGRAM_ID = PROGRAM_ID;
|
|
23
|
+
|
|
24
|
+
function pickRpc(cluster) {
|
|
25
|
+
const override = (process.env.AGENC_RPC_URL || '').trim();
|
|
26
|
+
if (override) return override;
|
|
27
|
+
if (cluster === 'devnet') {
|
|
28
|
+
return (process.env.SOLANA_DEVNET_RPC_URL || '').trim() || DEVNET_RPC;
|
|
29
|
+
}
|
|
30
|
+
return (process.env.SOLANA_RPC_URL || '').trim() || MAINNET_RPC;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildReadOnlyWallet() {
|
|
34
|
+
// Anchor's Wallet interface needs a payer slot + publicKey to construct a
|
|
35
|
+
// provider; we hold an ephemeral keypair purely for type satisfaction and
|
|
36
|
+
// refuse to sign anything with it. The MCP tools only expose read paths.
|
|
37
|
+
const ephemeral = Keypair.generate();
|
|
38
|
+
const refuse = () => {
|
|
39
|
+
throw new Error(
|
|
40
|
+
'agenc-client read-only wallet refuses to sign — this tool exposes read paths only.',
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
payer: ephemeral,
|
|
45
|
+
publicKey: ephemeral.publicKey,
|
|
46
|
+
async signTransaction(_tx) {
|
|
47
|
+
refuse();
|
|
48
|
+
},
|
|
49
|
+
async signAllTransactions(_txs) {
|
|
50
|
+
refuse();
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build a read-only AgenC client for `cluster` ("mainnet" | "devnet").
|
|
57
|
+
* Returns { connection, program, programId, cluster, rpcUrl }.
|
|
58
|
+
*/
|
|
59
|
+
export function getAgenCClient(cluster = 'mainnet') {
|
|
60
|
+
const c = cluster === 'devnet' ? 'devnet' : 'mainnet';
|
|
61
|
+
const rpcUrl = pickRpc(c);
|
|
62
|
+
const connection = new Connection(rpcUrl, {
|
|
63
|
+
commitment: 'confirmed',
|
|
64
|
+
// Bound every RPC call (web3.js otherwise inherits Node's unbounded
|
|
65
|
+
// default — the source of indefinite hangs) and replay a single
|
|
66
|
+
// transient blip. AgenC reads are idempotent queries, so retry is safe.
|
|
67
|
+
fetch: (url, init) =>
|
|
68
|
+
resilientFetch(url, init, {
|
|
69
|
+
timeoutMs: 12_000,
|
|
70
|
+
retries: 1,
|
|
71
|
+
retryNonIdempotent: true,
|
|
72
|
+
label: `agenc-rpc ${rpcUrl}`,
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
const provider = new AnchorProvider(connection, buildReadOnlyWallet(), {
|
|
76
|
+
commitment: 'confirmed',
|
|
77
|
+
preflightCommitment: 'confirmed',
|
|
78
|
+
});
|
|
79
|
+
const programId = c === 'devnet' ? AGENC_DEVNET_PROGRAM_ID : AGENC_MAINNET_PROGRAM_ID;
|
|
80
|
+
const program = new Program(AGENC_COORDINATION_IDL, provider);
|
|
81
|
+
return { connection, program, programId, cluster: c, rpcUrl };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Normalize a base58 pubkey string. Returns a PublicKey or throws. */
|
|
85
|
+
export function parsePubkey(s, label = 'pubkey') {
|
|
86
|
+
if (!s || typeof s !== 'string') {
|
|
87
|
+
throw new Error(`${label} must be a base58 string`);
|
|
88
|
+
}
|
|
89
|
+
return new PublicKey(s.trim());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Convert bigint values inside an object to strings so JSON.stringify works. */
|
|
93
|
+
export function serializeBigInts(value) {
|
|
94
|
+
if (value === null || value === undefined) return value;
|
|
95
|
+
if (typeof value === 'bigint') return value.toString();
|
|
96
|
+
if (value instanceof Uint8Array) return Buffer.from(value).toString('hex');
|
|
97
|
+
if (value instanceof PublicKey) return value.toBase58();
|
|
98
|
+
if (Array.isArray(value)) return value.map(serializeBigInts);
|
|
99
|
+
if (typeof value === 'object') {
|
|
100
|
+
const out = {};
|
|
101
|
+
for (const [k, v] of Object.entries(value)) {
|
|
102
|
+
out[k] = serializeBigInts(v);
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Translate a TaskState enum value to a human label. Matches @tetsuo-ai/sdk. */
|
|
110
|
+
export function taskStateLabel(state) {
|
|
111
|
+
const map = {
|
|
112
|
+
0: 'Open',
|
|
113
|
+
1: 'Claimed',
|
|
114
|
+
2: 'Completed',
|
|
115
|
+
3: 'Cancelled',
|
|
116
|
+
4: 'Disputed',
|
|
117
|
+
5: 'Expired',
|
|
118
|
+
};
|
|
119
|
+
if (typeof state === 'number') return map[state] ?? `Unknown(${state})`;
|
|
120
|
+
if (state && typeof state === 'object') {
|
|
121
|
+
const key = Object.keys(state)[0];
|
|
122
|
+
return key ? key[0].toUpperCase() + key.slice(1) : 'Unknown';
|
|
123
|
+
}
|
|
124
|
+
return String(state);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Translate an AgentStatus enum value to a label. */
|
|
128
|
+
export function agentStatusLabel(status) {
|
|
129
|
+
const map = { 0: 'Inactive', 1: 'Active', 2: 'Busy', 3: 'Suspended' };
|
|
130
|
+
if (typeof status === 'number') return map[status] ?? `Unknown(${status})`;
|
|
131
|
+
if (status && typeof status === 'object') {
|
|
132
|
+
const key = Object.keys(status)[0];
|
|
133
|
+
return key ? key[0].toUpperCase() + key.slice(1) : 'Unknown';
|
|
134
|
+
}
|
|
135
|
+
return String(status);
|
|
136
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// `agenc_get_agent` — paid MCP tool that looks up an AgenC agent's on-chain
|
|
2
|
+
// registration. Returns capability bitmask, status, endpoint, reputation,
|
|
3
|
+
// stake, and active-task count.
|
|
4
|
+
//
|
|
5
|
+
// Pricing: $0.001 USDC, settled `exact` in USDC on Solana mainnet.
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { PublicKey } from '@solana/web3.js';
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import { deriveAgentPda, getAgent } from '@tetsuo-ai/sdk';
|
|
11
|
+
|
|
12
|
+
import { paid, toolError } from '../payments.js';
|
|
13
|
+
import { jsonSchemaFromZod } from './_shared.js';
|
|
14
|
+
import {
|
|
15
|
+
getAgenCClient,
|
|
16
|
+
parsePubkey,
|
|
17
|
+
serializeBigInts,
|
|
18
|
+
agentStatusLabel,
|
|
19
|
+
} from './agenc-client.js';
|
|
20
|
+
|
|
21
|
+
const TOOL_NAME = 'agenc_get_agent';
|
|
22
|
+
const TOOL_DESCRIPTION =
|
|
23
|
+
'Look up an AgenC agent\'s on-chain registration. Pass either agentPda (the derived account address) OR agentId (32-byte hex, "0x"-prefixed hex, or any UTF-8 label which is hashed via SHA-256). Returns the agent\'s authority wallet, capability bitmask, endpoint URL, status, reputation, stake, and active task count. AgenC = agenc.tech (Tetsuo Corp). Paid: $0.001 USDC.';
|
|
24
|
+
|
|
25
|
+
function resolveAgentId(input) {
|
|
26
|
+
const s = String(input).trim();
|
|
27
|
+
if (s.startsWith('0x') || s.startsWith('0X')) {
|
|
28
|
+
const hex = s.slice(2);
|
|
29
|
+
if (hex.length !== 64) throw new Error('hex agentId must be 32 bytes');
|
|
30
|
+
return Uint8Array.from(Buffer.from(hex, 'hex'));
|
|
31
|
+
}
|
|
32
|
+
if (/^[0-9a-fA-F]{64}$/.test(s)) {
|
|
33
|
+
return Uint8Array.from(Buffer.from(s, 'hex'));
|
|
34
|
+
}
|
|
35
|
+
return Uint8Array.from(createHash('sha256').update(s, 'utf8').digest());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Single source of truth: Zod shape carries descriptions + bounds + cluster
|
|
39
|
+
// enum; JSON Schema derived. (No required fields — the handler enforces
|
|
40
|
+
// "agentPda OR agentId".)
|
|
41
|
+
const inputZodShape = {
|
|
42
|
+
agentPda: z
|
|
43
|
+
.string()
|
|
44
|
+
.min(32)
|
|
45
|
+
.max(44)
|
|
46
|
+
.describe('Base58 agent account PDA. Mutually exclusive with agentId.')
|
|
47
|
+
.optional(),
|
|
48
|
+
agentId: z
|
|
49
|
+
.string()
|
|
50
|
+
.min(1)
|
|
51
|
+
.max(256)
|
|
52
|
+
.describe('32-byte agent id as 64-char hex, "0x"-prefixed hex, or any UTF-8 label (SHA-256 hashed).')
|
|
53
|
+
.optional(),
|
|
54
|
+
cluster: z.enum(['mainnet', 'devnet']).describe('Solana cluster. Defaults to mainnet.').optional(),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const inputJsonSchema = jsonSchemaFromZod(inputZodShape);
|
|
58
|
+
|
|
59
|
+
export async function buildAgenCGetAgentTool() {
|
|
60
|
+
const handler = await paid(
|
|
61
|
+
{
|
|
62
|
+
toolName: TOOL_NAME,
|
|
63
|
+
description: TOOL_DESCRIPTION,
|
|
64
|
+
scheme: 'exact',
|
|
65
|
+
priceUsd: '$0.001',
|
|
66
|
+
inputSchema: inputJsonSchema,
|
|
67
|
+
example: { agentId: 'my-three-ws-bot', cluster: 'devnet' },
|
|
68
|
+
outputExample: {
|
|
69
|
+
ok: true,
|
|
70
|
+
cluster: 'devnet',
|
|
71
|
+
agentPda: '7p…',
|
|
72
|
+
agent: {
|
|
73
|
+
agentId: 'a3…',
|
|
74
|
+
authority: '5y…',
|
|
75
|
+
capabilities: '1',
|
|
76
|
+
status: 'Active',
|
|
77
|
+
endpoint: 'https://three.ws/agents/my-bot',
|
|
78
|
+
reputation: 0,
|
|
79
|
+
stakeAmount: '1000000',
|
|
80
|
+
activeTasks: 0,
|
|
81
|
+
registeredAt: 1716000000,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
async ({ agentPda, agentId, cluster }) => {
|
|
86
|
+
const client = getAgenCClient(cluster);
|
|
87
|
+
let pda;
|
|
88
|
+
if (agentPda) {
|
|
89
|
+
pda = parsePubkey(agentPda, 'agentPda');
|
|
90
|
+
} else if (agentId) {
|
|
91
|
+
pda = deriveAgentPda(resolveAgentId(agentId), client.programId);
|
|
92
|
+
} else {
|
|
93
|
+
return toolError('missing_input', 'Provide either agentPda or agentId.');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const agent = await getAgent(client.program, pda);
|
|
97
|
+
if (!agent) {
|
|
98
|
+
return serializeBigInts({
|
|
99
|
+
ok: false,
|
|
100
|
+
error: 'not_found',
|
|
101
|
+
cluster: client.cluster,
|
|
102
|
+
programId: client.programId.toBase58(),
|
|
103
|
+
agentPda: pda.toBase58(),
|
|
104
|
+
message: 'no agent account at that PDA on this cluster',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return serializeBigInts({
|
|
109
|
+
ok: true,
|
|
110
|
+
cluster: client.cluster,
|
|
111
|
+
rpcUrl: client.rpcUrl,
|
|
112
|
+
programId: client.programId.toBase58(),
|
|
113
|
+
agentPda: pda.toBase58(),
|
|
114
|
+
agent: {
|
|
115
|
+
agentId: Buffer.from(agent.agentId).toString('hex'),
|
|
116
|
+
authority: agent.authority.toBase58(),
|
|
117
|
+
capabilities: agent.capabilities.toString(),
|
|
118
|
+
status: agentStatusLabel(agent.status),
|
|
119
|
+
statusRaw: typeof agent.status === 'number' ? agent.status : null,
|
|
120
|
+
endpoint: agent.endpoint,
|
|
121
|
+
metadataUri: agent.metadataUri,
|
|
122
|
+
stakeAmount: agent.stakeAmount.toString(),
|
|
123
|
+
activeTasks: agent.activeTasks,
|
|
124
|
+
reputation: agent.reputation,
|
|
125
|
+
registeredAt: agent.registeredAt,
|
|
126
|
+
},
|
|
127
|
+
fetchedAt: new Date().toISOString(),
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
return {
|
|
132
|
+
name: TOOL_NAME,
|
|
133
|
+
title: 'AgenC get agent ($0.001)',
|
|
134
|
+
description: TOOL_DESCRIPTION,
|
|
135
|
+
inputSchema: inputZodShape,
|
|
136
|
+
// Read-only on-chain registry lookup — agent stats (tasks, reputation)
|
|
137
|
+
// change between calls, so not idempotent.
|
|
138
|
+
annotations: {
|
|
139
|
+
readOnlyHint: true,
|
|
140
|
+
idempotentHint: false,
|
|
141
|
+
openWorldHint: true,
|
|
142
|
+
},
|
|
143
|
+
handler,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// `agenc_get_task` — paid MCP tool that fetches the current state and lifecycle
|
|
2
|
+
// timeline of a single AgenC task. Useful for any agent that wants to decide
|
|
3
|
+
// whether to bid/claim, or for monitoring an in-flight job.
|
|
4
|
+
//
|
|
5
|
+
// Pricing: $0.001 USDC, settled `exact` in USDC on Solana mainnet.
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { PublicKey } from '@solana/web3.js';
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import {
|
|
11
|
+
deriveTaskPda,
|
|
12
|
+
getTask,
|
|
13
|
+
getTaskLifecycleSummary,
|
|
14
|
+
} from '@tetsuo-ai/sdk';
|
|
15
|
+
|
|
16
|
+
import { paid, toolError } from '../payments.js';
|
|
17
|
+
import { jsonSchemaFromZod } from './_shared.js';
|
|
18
|
+
import {
|
|
19
|
+
getAgenCClient,
|
|
20
|
+
parsePubkey,
|
|
21
|
+
serializeBigInts,
|
|
22
|
+
taskStateLabel,
|
|
23
|
+
} from './agenc-client.js';
|
|
24
|
+
|
|
25
|
+
const TOOL_NAME = 'agenc_get_task';
|
|
26
|
+
const TOOL_DESCRIPTION =
|
|
27
|
+
'Fetch the on-chain state and lifecycle timeline of a single AgenC task. Pass either taskPda (the derived account address) OR the {creator, taskId} pair, where taskId may be a 64-char hex string or any UTF-8 label (hashed to 32 bytes). Returns state, reward, deadline, worker counts, lifecycle events, and reward mint. AgenC = agenc.tech (Tetsuo Corp). Paid: $0.001 USDC.';
|
|
28
|
+
|
|
29
|
+
function resolveTaskId(input) {
|
|
30
|
+
const s = String(input).trim();
|
|
31
|
+
if (s.startsWith('0x') || s.startsWith('0X')) {
|
|
32
|
+
const hex = s.slice(2);
|
|
33
|
+
if (hex.length !== 64) throw new Error('hex taskId must be 32 bytes');
|
|
34
|
+
return Uint8Array.from(Buffer.from(hex, 'hex'));
|
|
35
|
+
}
|
|
36
|
+
if (/^[0-9a-fA-F]{64}$/.test(s)) {
|
|
37
|
+
return Uint8Array.from(Buffer.from(s, 'hex'));
|
|
38
|
+
}
|
|
39
|
+
return Uint8Array.from(createHash('sha256').update(s, 'utf8').digest());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Single source of truth: Zod shape carries descriptions + bounds + cluster
|
|
43
|
+
// enum; JSON Schema derived. (No required fields — the handler enforces
|
|
44
|
+
// "taskPda OR {creator, taskId}".)
|
|
45
|
+
const inputZodShape = {
|
|
46
|
+
taskPda: z
|
|
47
|
+
.string()
|
|
48
|
+
.min(32)
|
|
49
|
+
.max(44)
|
|
50
|
+
.describe('Base58 task account PDA. If omitted, supply creator + taskId.')
|
|
51
|
+
.optional(),
|
|
52
|
+
creator: z
|
|
53
|
+
.string()
|
|
54
|
+
.min(32)
|
|
55
|
+
.max(44)
|
|
56
|
+
.describe('Base58 task creator wallet (required if taskPda is omitted).')
|
|
57
|
+
.optional(),
|
|
58
|
+
taskId: z
|
|
59
|
+
.string()
|
|
60
|
+
.min(1)
|
|
61
|
+
.max(256)
|
|
62
|
+
.describe('32-byte task id as 64-char hex, "0x"-prefixed hex, or any UTF-8 label (hashed via SHA-256).')
|
|
63
|
+
.optional(),
|
|
64
|
+
cluster: z.enum(['mainnet', 'devnet']).describe('Solana cluster. Defaults to mainnet.').optional(),
|
|
65
|
+
includeLifecycle: z
|
|
66
|
+
.boolean()
|
|
67
|
+
.describe('When true (default), include the lifecycle event timeline. Set false for a cheaper read.')
|
|
68
|
+
.optional(),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const inputJsonSchema = jsonSchemaFromZod(inputZodShape);
|
|
72
|
+
|
|
73
|
+
export async function buildAgenCGetTaskTool() {
|
|
74
|
+
const handler = await paid(
|
|
75
|
+
{
|
|
76
|
+
toolName: TOOL_NAME,
|
|
77
|
+
description: TOOL_DESCRIPTION,
|
|
78
|
+
scheme: 'exact',
|
|
79
|
+
priceUsd: '$0.001',
|
|
80
|
+
inputSchema: inputJsonSchema,
|
|
81
|
+
example: { taskPda: '8xQ…', cluster: 'devnet' },
|
|
82
|
+
outputExample: {
|
|
83
|
+
ok: true,
|
|
84
|
+
cluster: 'devnet',
|
|
85
|
+
taskPda: '8xQ…',
|
|
86
|
+
task: {
|
|
87
|
+
taskId: 'a1b2…',
|
|
88
|
+
state: 'Claimed',
|
|
89
|
+
rewardAmount: '50000000',
|
|
90
|
+
currentWorkers: 1,
|
|
91
|
+
maxWorkers: 1,
|
|
92
|
+
},
|
|
93
|
+
lifecycle: {
|
|
94
|
+
timeline: [{ eventName: 'created', timestamp: 1716000000 }],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
async ({ taskPda, creator, taskId, cluster, includeLifecycle }) => {
|
|
99
|
+
const client = getAgenCClient(cluster);
|
|
100
|
+
let pda;
|
|
101
|
+
if (taskPda) {
|
|
102
|
+
pda = parsePubkey(taskPda, 'taskPda');
|
|
103
|
+
} else {
|
|
104
|
+
if (!creator || !taskId) {
|
|
105
|
+
return toolError('missing_input', 'Provide either taskPda OR both creator and taskId.');
|
|
106
|
+
}
|
|
107
|
+
pda = deriveTaskPda(
|
|
108
|
+
parsePubkey(creator, 'creator'),
|
|
109
|
+
resolveTaskId(taskId),
|
|
110
|
+
client.programId,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const task = await getTask(client.program, pda);
|
|
115
|
+
if (!task) {
|
|
116
|
+
return serializeBigInts({
|
|
117
|
+
ok: false,
|
|
118
|
+
error: 'not_found',
|
|
119
|
+
cluster: client.cluster,
|
|
120
|
+
programId: client.programId.toBase58(),
|
|
121
|
+
taskPda: pda.toBase58(),
|
|
122
|
+
message: 'no task account at that PDA on this cluster',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const taskOut = {
|
|
126
|
+
taskId: Buffer.from(task.taskId).toString('hex'),
|
|
127
|
+
state: taskStateLabel(task.state),
|
|
128
|
+
stateRaw: typeof task.state === 'number' ? task.state : null,
|
|
129
|
+
creator: task.creator.toBase58(),
|
|
130
|
+
rewardAmount: task.rewardAmount.toString(),
|
|
131
|
+
rewardMint: task.rewardMint ? task.rewardMint.toBase58() : null,
|
|
132
|
+
deadline: task.deadline,
|
|
133
|
+
currentWorkers: task.currentWorkers,
|
|
134
|
+
maxWorkers: task.maxWorkers,
|
|
135
|
+
completedAt: task.completedAt,
|
|
136
|
+
constraintHash: task.constraintHash
|
|
137
|
+
? Buffer.from(task.constraintHash).toString('hex')
|
|
138
|
+
: null,
|
|
139
|
+
private: task.constraintHash != null,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
let lifecycleOut = null;
|
|
143
|
+
if (includeLifecycle !== false) {
|
|
144
|
+
const lifecycle = await getTaskLifecycleSummary(client.program, pda);
|
|
145
|
+
if (lifecycle) {
|
|
146
|
+
lifecycleOut = {
|
|
147
|
+
currentState: taskStateLabel(lifecycle.currentState),
|
|
148
|
+
createdAt: lifecycle.createdAt,
|
|
149
|
+
currentWorkers: lifecycle.currentWorkers,
|
|
150
|
+
maxWorkers: lifecycle.maxWorkers,
|
|
151
|
+
timeline: lifecycle.timeline.map((e) => ({
|
|
152
|
+
eventName: e.eventName,
|
|
153
|
+
timestamp: e.timestamp,
|
|
154
|
+
txSignature: e.txSignature ?? null,
|
|
155
|
+
actor: e.actor ? e.actor.toBase58() : null,
|
|
156
|
+
})),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return serializeBigInts({
|
|
162
|
+
ok: true,
|
|
163
|
+
cluster: client.cluster,
|
|
164
|
+
rpcUrl: client.rpcUrl,
|
|
165
|
+
programId: client.programId.toBase58(),
|
|
166
|
+
taskPda: pda.toBase58(),
|
|
167
|
+
task: taskOut,
|
|
168
|
+
lifecycle: lifecycleOut,
|
|
169
|
+
fetchedAt: new Date().toISOString(),
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
return {
|
|
174
|
+
name: TOOL_NAME,
|
|
175
|
+
title: 'AgenC get task ($0.001)',
|
|
176
|
+
description: TOOL_DESCRIPTION,
|
|
177
|
+
inputSchema: inputZodShape,
|
|
178
|
+
// Read-only on-chain lookup — task status moves through its lifecycle
|
|
179
|
+
// between calls, so not idempotent.
|
|
180
|
+
annotations: {
|
|
181
|
+
readOnlyHint: true,
|
|
182
|
+
idempotentHint: false,
|
|
183
|
+
openWorldHint: true,
|
|
184
|
+
},
|
|
185
|
+
handler,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// `agenc_list_tasks` — paid MCP tool that lists every public AgenC task
|
|
2
|
+
// created by a given Solana wallet on the AgenC coordination protocol.
|
|
3
|
+
//
|
|
4
|
+
// Pricing: $0.001 USDC, settled `exact` in USDC on Solana mainnet.
|
|
5
|
+
//
|
|
6
|
+
// AgenC = agenc.tech (Tetsuo Corp). Coordination protocol on Solana. This tool
|
|
7
|
+
// surfaces the on-chain task marketplace so any MCP client (Claude Desktop,
|
|
8
|
+
// Cursor, three.ws agents) can discover open jobs without standing up Anchor.
|
|
9
|
+
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { getTasksByCreator } from '@tetsuo-ai/sdk';
|
|
12
|
+
|
|
13
|
+
import { paid } from '../payments.js';
|
|
14
|
+
import { jsonSchemaFromZod } from './_shared.js';
|
|
15
|
+
import {
|
|
16
|
+
getAgenCClient,
|
|
17
|
+
parsePubkey,
|
|
18
|
+
serializeBigInts,
|
|
19
|
+
taskStateLabel,
|
|
20
|
+
} from './agenc-client.js';
|
|
21
|
+
|
|
22
|
+
const TOOL_NAME = 'agenc_list_tasks';
|
|
23
|
+
const TOOL_DESCRIPTION =
|
|
24
|
+
'List every public AgenC task created by a given Solana wallet. AgenC (agenc.tech, by Tetsuo Corp) is a Solana coordination protocol where agents bid on, claim, and complete tasks with SOL/SPL escrow and optional zero-knowledge settlement. Returns task PDA, state, reward, deadline, worker counts, and reward mint for each task. Specify cluster="devnet" for the dev cluster (program 6UcJzbT...), otherwise mainnet. Paid: $0.001 USDC.';
|
|
25
|
+
|
|
26
|
+
// Single source of truth: Zod shape carries descriptions + bounds + cluster
|
|
27
|
+
// enum; JSON Schema derived.
|
|
28
|
+
const inputZodShape = {
|
|
29
|
+
creator: z.string().min(32).max(44).describe('Base58 Solana pubkey of the task creator wallet.'),
|
|
30
|
+
cluster: z.enum(['mainnet', 'devnet']).describe('Solana cluster to query. Defaults to mainnet.').optional(),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const inputJsonSchema = jsonSchemaFromZod(inputZodShape);
|
|
34
|
+
|
|
35
|
+
export async function buildAgenCListTasksTool() {
|
|
36
|
+
const handler = await paid(
|
|
37
|
+
{
|
|
38
|
+
toolName: TOOL_NAME,
|
|
39
|
+
description: TOOL_DESCRIPTION,
|
|
40
|
+
scheme: 'exact',
|
|
41
|
+
priceUsd: '$0.001',
|
|
42
|
+
inputSchema: inputJsonSchema,
|
|
43
|
+
example: {
|
|
44
|
+
creator: '5yC9BM8KUsJTPbWPLfA2N8qH1s9V8DQ3Vcw1G6Jdpump',
|
|
45
|
+
cluster: 'devnet',
|
|
46
|
+
},
|
|
47
|
+
outputExample: {
|
|
48
|
+
ok: true,
|
|
49
|
+
cluster: 'devnet',
|
|
50
|
+
programId: '6UcJzbTEemBz3aY5wK5qKHGMD7bdRsmR4smND29gB2ab',
|
|
51
|
+
creator: '5yC9BM8KUsJTPbWPLfA2N8qH1s9V8DQ3Vcw1G6Jdpump',
|
|
52
|
+
count: 1,
|
|
53
|
+
tasks: [
|
|
54
|
+
{
|
|
55
|
+
taskId: '11d3...',
|
|
56
|
+
state: 'Open',
|
|
57
|
+
rewardAmount: '50000000',
|
|
58
|
+
rewardMint: null,
|
|
59
|
+
deadline: 1716508800,
|
|
60
|
+
currentWorkers: 0,
|
|
61
|
+
maxWorkers: 1,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
async ({ creator, cluster }) => {
|
|
67
|
+
const creatorPk = parsePubkey(creator, 'creator');
|
|
68
|
+
const client = getAgenCClient(cluster);
|
|
69
|
+
const tasks = await getTasksByCreator(client.program, creatorPk);
|
|
70
|
+
const rows = tasks.map((t) => ({
|
|
71
|
+
taskId: Buffer.from(t.taskId).toString('hex'),
|
|
72
|
+
state: taskStateLabel(t.state),
|
|
73
|
+
stateRaw: typeof t.state === 'number' ? t.state : null,
|
|
74
|
+
rewardAmount: t.rewardAmount.toString(),
|
|
75
|
+
rewardMint: t.rewardMint ? t.rewardMint.toBase58() : null,
|
|
76
|
+
deadline: t.deadline,
|
|
77
|
+
currentWorkers: t.currentWorkers,
|
|
78
|
+
maxWorkers: t.maxWorkers,
|
|
79
|
+
completedAt: t.completedAt,
|
|
80
|
+
constraintHash: t.constraintHash
|
|
81
|
+
? Buffer.from(t.constraintHash).toString('hex')
|
|
82
|
+
: null,
|
|
83
|
+
}));
|
|
84
|
+
return serializeBigInts({
|
|
85
|
+
ok: true,
|
|
86
|
+
cluster: client.cluster,
|
|
87
|
+
rpcUrl: client.rpcUrl,
|
|
88
|
+
programId: client.programId.toBase58(),
|
|
89
|
+
creator: creatorPk.toBase58(),
|
|
90
|
+
count: rows.length,
|
|
91
|
+
tasks: rows,
|
|
92
|
+
fetchedAt: new Date().toISOString(),
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
return {
|
|
97
|
+
name: TOOL_NAME,
|
|
98
|
+
title: 'AgenC list tasks ($0.001)',
|
|
99
|
+
description: TOOL_DESCRIPTION,
|
|
100
|
+
inputSchema: inputZodShape,
|
|
101
|
+
// Read-only on-chain program scan — the task set changes as agents
|
|
102
|
+
// post/claim work, so not idempotent.
|
|
103
|
+
annotations: {
|
|
104
|
+
readOnlyHint: true,
|
|
105
|
+
idempotentHint: false,
|
|
106
|
+
openWorldHint: true,
|
|
107
|
+
},
|
|
108
|
+
handler,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// `agent_delegate_action` — paid MCP tool that lets an external agent
|
|
2
|
+
// send a message to any three.ws-registered agent and get its reply.
|
|
3
|
+
//
|
|
4
|
+
// Pricing: $0.01 USDC, settled `exact` in USDC on Solana mainnet.
|
|
5
|
+
//
|
|
6
|
+
// Implementation: calls POST /api/agents/talk with the target agentId
|
|
7
|
+
// and message. The target's brain is driven by its embed_policy.brain
|
|
8
|
+
// settings on three.ws (which model, system prompt). Agents whose owner
|
|
9
|
+
// has set surfaces.mcp = false in their embed policy are refused.
|
|
10
|
+
//
|
|
11
|
+
// Recursion is prevented server-side via the x-delegate-depth header.
|
|
12
|
+
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
|
|
15
|
+
import { paid, toolError } from '../payments.js';
|
|
16
|
+
import { jsonSchemaFromZod } from './_shared.js';
|
|
17
|
+
import { resilientFetch } from '../lib/resilient-fetch.js';
|
|
18
|
+
|
|
19
|
+
const TOOL_NAME = 'agent_delegate_action';
|
|
20
|
+
const TOOL_DESCRIPTION =
|
|
21
|
+
'Send a message to a three.ws-registered agent and receive its response. The target agent uses its configured brain (Claude model and system prompt set via its embed policy). Agents that have opted out of MCP delegation are refused. Useful for agent-to-agent collaboration and tool composition. Paid: $0.01 USDC.';
|
|
22
|
+
|
|
23
|
+
function env(k, def) {
|
|
24
|
+
const v = process.env[k];
|
|
25
|
+
return v && String(v).trim() ? String(v).trim() : def;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Single source of truth: Zod shape carries descriptions + bounds; JSON Schema
|
|
29
|
+
// derived. The prior hand-written JSON Schema left `model` with no bounds; the
|
|
30
|
+
// Zod (min 1, max 100) is stricter and now wins, surfacing those bounds in the
|
|
31
|
+
// advertised schema too.
|
|
32
|
+
const inputZodShape = {
|
|
33
|
+
agentId: z.string().min(1).max(120).describe('three.ws agent id (UUID).'),
|
|
34
|
+
message: z.string().min(1).max(4000),
|
|
35
|
+
model: z
|
|
36
|
+
.string()
|
|
37
|
+
.min(1)
|
|
38
|
+
.max(100)
|
|
39
|
+
.describe(
|
|
40
|
+
'Optional Claude model override (e.g. claude-sonnet-4-6). Must be in the allowlist.',
|
|
41
|
+
)
|
|
42
|
+
.optional(),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const inputJsonSchema = jsonSchemaFromZod(inputZodShape);
|
|
46
|
+
|
|
47
|
+
export async function buildAgentDelegateActionTool() {
|
|
48
|
+
const handler = await paid(
|
|
49
|
+
{
|
|
50
|
+
toolName: TOOL_NAME,
|
|
51
|
+
description: TOOL_DESCRIPTION,
|
|
52
|
+
scheme: 'exact',
|
|
53
|
+
priceUsd: '$0.01',
|
|
54
|
+
inputSchema: inputJsonSchema,
|
|
55
|
+
example: {
|
|
56
|
+
agentId: '5a4b3c2d-1234-5678-90ab-cdef01234567',
|
|
57
|
+
message: 'Summarise the latest pump.fun graduations in 3 bullets.',
|
|
58
|
+
},
|
|
59
|
+
outputExample: {
|
|
60
|
+
ok: true,
|
|
61
|
+
agentId: '5a4b3c2d-1234-5678-90ab-cdef01234567',
|
|
62
|
+
agentName: 'Pump Sage',
|
|
63
|
+
response: '...',
|
|
64
|
+
model: 'claude-haiku-4-5-20251001',
|
|
65
|
+
durationMs: 1840,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
async ({ agentId, message, model }) => {
|
|
69
|
+
const endpoint = env('MCP_AGENT_TALK_ENDPOINT', 'https://three.ws/api/agents/talk');
|
|
70
|
+
let res;
|
|
71
|
+
try {
|
|
72
|
+
// Bounded timeout but NO retry: delivering a message to an agent is
|
|
73
|
+
// not idempotent, so a replay could double-send / double-bill the
|
|
74
|
+
// target. A long brain response is expected, so the timeout is
|
|
75
|
+
// generous.
|
|
76
|
+
res = await resilientFetch(
|
|
77
|
+
endpoint,
|
|
78
|
+
{
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: { 'content-type': 'application/json' },
|
|
81
|
+
body: JSON.stringify({ agentId, message, model }),
|
|
82
|
+
},
|
|
83
|
+
{ timeoutMs: 60_000, retries: 0, label: 'agent-delegate' },
|
|
84
|
+
);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return toolError('upstream_unreachable', err?.message || 'fetch failed');
|
|
87
|
+
}
|
|
88
|
+
const data = await res.json().catch(() => null);
|
|
89
|
+
if (!res.ok || !data || data.ok === false) {
|
|
90
|
+
return toolError(
|
|
91
|
+
data?.code || data?.error || 'agent_delegate_failed',
|
|
92
|
+
data?.message || `endpoint returned ${res.status}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return data;
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
return {
|
|
99
|
+
name: TOOL_NAME,
|
|
100
|
+
title: 'Agent delegate action ($0.01)',
|
|
101
|
+
description: TOOL_DESCRIPTION,
|
|
102
|
+
inputSchema: inputZodShape,
|
|
103
|
+
// Dispatches a delegated action to an external agent — a write with
|
|
104
|
+
// side effects, but it never destroys existing state.
|
|
105
|
+
annotations: {
|
|
106
|
+
readOnlyHint: false,
|
|
107
|
+
destructiveHint: false,
|
|
108
|
+
idempotentHint: false,
|
|
109
|
+
openWorldHint: true,
|
|
110
|
+
},
|
|
111
|
+
handler,
|
|
112
|
+
};
|
|
113
|
+
}
|