@hapticpaper/mcp-server 1.0.46 → 1.0.48
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/dist/client/hapticPaperClient.js +59 -0
- package/dist/tools/capabilities.js +246 -0
- package/dist/tools/crypto.js +209 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/tasks.js +19 -2
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -211,4 +211,63 @@ export class HapticPaperClient {
|
|
|
211
211
|
const response = await this.client.post('/gpt/reviews', data, { headers: await this.authHeaders(accessToken) });
|
|
212
212
|
return response.data;
|
|
213
213
|
}
|
|
214
|
+
// Capabilities Methods
|
|
215
|
+
async searchCapabilities(params, accessToken) {
|
|
216
|
+
const response = await this.client.get('/gpt/capabilities/search', { params, headers: await this.authHeaders(accessToken) });
|
|
217
|
+
return response.data;
|
|
218
|
+
}
|
|
219
|
+
async registerCapability(data, accessToken) {
|
|
220
|
+
const response = await this.client.post('/gpt/capabilities', data, { headers: await this.authHeaders(accessToken) });
|
|
221
|
+
return response.data;
|
|
222
|
+
}
|
|
223
|
+
async getCapability(capabilityId, accessToken) {
|
|
224
|
+
const response = await this.client.get(`/gpt/capabilities/${capabilityId}`, { headers: await this.authHeaders(accessToken) });
|
|
225
|
+
return response.data;
|
|
226
|
+
}
|
|
227
|
+
async requestCapability(data, accessToken) {
|
|
228
|
+
const { capabilityId, ...payload } = data;
|
|
229
|
+
const response = await this.client.post(`/gpt/capabilities/${capabilityId}/request`, payload, { headers: await this.authHeaders(accessToken) });
|
|
230
|
+
return response.data;
|
|
231
|
+
}
|
|
232
|
+
async listCapabilityJobs(params, accessToken) {
|
|
233
|
+
const response = await this.client.get('/gpt/capabilities/jobs', { params, headers: await this.authHeaders(accessToken) });
|
|
234
|
+
return response.data;
|
|
235
|
+
}
|
|
236
|
+
async claimCapabilityJob(jobId, accessToken) {
|
|
237
|
+
const response = await this.client.post(`/gpt/capabilities/jobs/${jobId}/claim`, {}, { headers: await this.authHeaders(accessToken) });
|
|
238
|
+
return response.data;
|
|
239
|
+
}
|
|
240
|
+
async submitCapabilityJob(jobId, data, accessToken) {
|
|
241
|
+
const response = await this.client.post(`/gpt/capabilities/jobs/${jobId}/submit`, data, { headers: await this.authHeaders(accessToken) });
|
|
242
|
+
return response.data;
|
|
243
|
+
}
|
|
244
|
+
// Crypto Wallet Methods
|
|
245
|
+
async addCryptoWallet(data, accessToken) {
|
|
246
|
+
const response = await this.client.post('/gpt/wallets', data, { headers: await this.authHeaders(accessToken) });
|
|
247
|
+
return response.data;
|
|
248
|
+
}
|
|
249
|
+
async verifyCryptoWallet(walletId, signature, accessToken) {
|
|
250
|
+
const response = await this.client.post(`/gpt/wallets/${walletId}/verify`, { signature }, { headers: await this.authHeaders(accessToken) });
|
|
251
|
+
return response.data;
|
|
252
|
+
}
|
|
253
|
+
async listCryptoWallets(accessToken) {
|
|
254
|
+
const response = await this.client.get('/gpt/wallets', { headers: await this.authHeaders(accessToken) });
|
|
255
|
+
return response.data;
|
|
256
|
+
}
|
|
257
|
+
async deleteCryptoWallet(walletId, accessToken) {
|
|
258
|
+
const response = await this.client.delete(`/gpt/wallets/${walletId}`, { headers: await this.authHeaders(accessToken) });
|
|
259
|
+
return response.data;
|
|
260
|
+
}
|
|
261
|
+
async sendCryptoPayment(data, accessToken) {
|
|
262
|
+
const response = await this.client.post('/gpt/payments/crypto', data, { headers: await this.authHeaders(accessToken) });
|
|
263
|
+
return response.data;
|
|
264
|
+
}
|
|
265
|
+
async getCryptoPayment(transactionId, accessToken) {
|
|
266
|
+
const response = await this.client.get(`/gpt/payments/crypto/${transactionId}`, { headers: await this.authHeaders(accessToken) });
|
|
267
|
+
return response.data;
|
|
268
|
+
}
|
|
269
|
+
async getCryptoBalance(chain, token, accessToken) {
|
|
270
|
+
const response = await this.client.get(`/gpt/wallets/balance/${chain}/${token}`, { headers: await this.authHeaders(accessToken) });
|
|
271
|
+
return response.data;
|
|
272
|
+
}
|
|
214
273
|
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { requireScopes } from "../auth/access.js";
|
|
3
|
+
import { HAPTICPAPER_WIDGET_URI } from "../constants/widget.js";
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
export const CAPABILITIES_TOOL_SCOPES = ['capabilities:read', 'capabilities:write'];
|
|
6
|
+
// --- Shared Helpers ---
|
|
7
|
+
function stableSessionId(prefix, value) {
|
|
8
|
+
const raw = value ?? 'unknown';
|
|
9
|
+
const hash = crypto.createHash('sha256').update(raw).digest('hex').slice(0, 16);
|
|
10
|
+
return `${prefix}:${hash}`;
|
|
11
|
+
}
|
|
12
|
+
function oauthSecuritySchemes(scopes) {
|
|
13
|
+
return [
|
|
14
|
+
{ type: 'oauth2', scopes },
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
function toolDescriptorMeta(invoking, invoked, scopes) {
|
|
18
|
+
return {
|
|
19
|
+
'openai/outputTemplate': HAPTICPAPER_WIDGET_URI,
|
|
20
|
+
'openai/toolInvocation/invoking': invoking,
|
|
21
|
+
'openai/toolInvocation/invoked': invoked,
|
|
22
|
+
'openai/widgetAccessible': true,
|
|
23
|
+
securitySchemes: oauthSecuritySchemes(scopes),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function toolInvocationMeta(invoking, invoked, widgetSessionId) {
|
|
27
|
+
return {
|
|
28
|
+
'openai/outputTemplate': HAPTICPAPER_WIDGET_URI,
|
|
29
|
+
'openai/toolInvocation/invoking': invoking,
|
|
30
|
+
'openai/toolInvocation/invoked': invoked,
|
|
31
|
+
'openai/widgetSessionId': widgetSessionId,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// --- Schema Definitions ---
|
|
35
|
+
const RegisterCapabilitySchema = z.object({
|
|
36
|
+
name: z.string().min(5).max(255).describe("Name of the capability (e.g., 'Competitive Analysis Report')"),
|
|
37
|
+
description: z.string().min(20).max(5000).describe("Detailed description of what this capability provides"),
|
|
38
|
+
category: z.string().optional().describe("Category (e.g., 'research', 'data-processing', 'analysis')"),
|
|
39
|
+
inputSchema: z.record(z.any()).describe("JSON schema defining required input structure"),
|
|
40
|
+
outputSchema: z.record(z.any()).describe("JSON schema defining output structure"),
|
|
41
|
+
priceModel: z.enum(['fixed', 'per_unit', 'bid']).describe("Pricing model"),
|
|
42
|
+
priceCents: z.number().min(0).optional().describe("Price in cents for fixed pricing"),
|
|
43
|
+
currency: z.string().default("USD").describe("Currency code (USD, ETH, USDC, etc.)"),
|
|
44
|
+
defaultValidationTier: z.enum(['none', 'llm', 'code']).optional().describe("Default validation tier"),
|
|
45
|
+
estimatedSeconds: z.number().positive().optional().describe("Estimated time to complete in seconds"),
|
|
46
|
+
});
|
|
47
|
+
const CapabilitiesToolSchema = z.object({
|
|
48
|
+
action: z.enum(['search', 'register', 'get', 'request', 'list_jobs', 'claim', 'submit']).describe("Action to perform on the capabilities resource"),
|
|
49
|
+
// Search params
|
|
50
|
+
query: z.string().optional().describe("Search query for capability name/description"),
|
|
51
|
+
category: z.string().optional().describe("Filter by category"),
|
|
52
|
+
maxPriceCents: z.number().optional().describe("Maximum price in cents"),
|
|
53
|
+
// Register params
|
|
54
|
+
name: RegisterCapabilitySchema.shape.name.optional(),
|
|
55
|
+
description: RegisterCapabilitySchema.shape.description.optional(),
|
|
56
|
+
inputSchema: RegisterCapabilitySchema.shape.inputSchema.optional(),
|
|
57
|
+
outputSchema: RegisterCapabilitySchema.shape.outputSchema.optional(),
|
|
58
|
+
priceModel: RegisterCapabilitySchema.shape.priceModel.optional(),
|
|
59
|
+
priceCents: RegisterCapabilitySchema.shape.priceCents.optional(),
|
|
60
|
+
currency: RegisterCapabilitySchema.shape.currency.optional(),
|
|
61
|
+
defaultValidationTier: RegisterCapabilitySchema.shape.defaultValidationTier.optional(),
|
|
62
|
+
estimatedSeconds: RegisterCapabilitySchema.shape.estimatedSeconds.optional(),
|
|
63
|
+
// Get/Request params
|
|
64
|
+
capabilityId: z.string().uuid().optional().describe("Capability ID for get/request actions"),
|
|
65
|
+
// Request params
|
|
66
|
+
inputData: z.record(z.any()).optional().describe("Input data matching capability's input schema"),
|
|
67
|
+
validationTier: z.enum(['none', 'llm', 'code']).optional().describe("Validation tier for this job"),
|
|
68
|
+
validationConfig: z.record(z.any()).optional().describe("Validation configuration (prompt for llm, function for code)"),
|
|
69
|
+
deadline: z.string().datetime().optional().describe("Deadline for job completion (ISO string)"),
|
|
70
|
+
paymentMethod: z.enum(['credits', 'crypto:eth', 'crypto:btc', 'crypto:usdc', 'crypto:usdt']).optional().describe("Payment method"),
|
|
71
|
+
// Claim/Submit params
|
|
72
|
+
jobId: z.string().uuid().optional().describe("Job ID for claim/submit actions"),
|
|
73
|
+
// Submit params
|
|
74
|
+
outputData: z.record(z.any()).optional().describe("Output data matching capability's output schema"),
|
|
75
|
+
// List params
|
|
76
|
+
status: z.enum(['pending', 'claimed', 'processing', 'submitted', 'validating', 'completed', 'failed']).optional().describe("Filter jobs by status"),
|
|
77
|
+
limit: z.number().min(1).max(50).optional().describe("Limit number of results returned"),
|
|
78
|
+
}).describe("Discover and use capabilities from other agents, or offer your own.\n\nUSE TO FIND HELP:\n- Search for agents who can do what you can't\n- Request work with structured input/output contracts\n- Pay in USD or crypto (ETH, BTC, USDC)\n\nUSE TO EARN:\n- Register capabilities your owner's workflows can provide\n- Claim jobs matching your capabilities\n- Get paid automatically on successful validation\n\nThis enables the AI-to-AI economy.\n\nActions: search, register, get, request, list_jobs, claim, submit");
|
|
79
|
+
export function registerCapabilitiesTools(server, client) {
|
|
80
|
+
// Helper for structured dispatch
|
|
81
|
+
const capabilitiesHandler = async (args, extra) => {
|
|
82
|
+
try {
|
|
83
|
+
switch (args.action) {
|
|
84
|
+
case 'search': {
|
|
85
|
+
const auth = requireScopes(extra, ['capabilities:read']);
|
|
86
|
+
const capabilities = await client.searchCapabilities({
|
|
87
|
+
query: args.query,
|
|
88
|
+
category: args.category,
|
|
89
|
+
maxPriceCents: args.maxPriceCents,
|
|
90
|
+
limit: args.limit
|
|
91
|
+
}, auth?.token);
|
|
92
|
+
const items = Array.isArray(capabilities) ? capabilities : capabilities?.capabilities ?? [];
|
|
93
|
+
const widgetSessionId = stableSessionId('capabilities', auth?.userId ?? auth?.clientId);
|
|
94
|
+
const summary = items.length
|
|
95
|
+
? items.map((c) => `- ${c.name} ($${(c.price_cents / 100).toFixed(2)}) by ${c.provider?.name ?? 'Unknown'} - ${c.description.slice(0, 100)}...`).join('\n')
|
|
96
|
+
: "No capabilities found.";
|
|
97
|
+
return {
|
|
98
|
+
structuredContent: { capabilities: items },
|
|
99
|
+
content: [{ type: 'text', text: `Capabilities search results:\n${summary}` }],
|
|
100
|
+
_meta: { ...toolInvocationMeta('Searching capabilities', 'Capabilities loaded', widgetSessionId), capabilities: items }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
case 'register': {
|
|
104
|
+
const auth = requireScopes(extra, ['capabilities:write']);
|
|
105
|
+
if (!args.name || !args.description || !args.inputSchema || !args.outputSchema || !args.priceModel) {
|
|
106
|
+
throw new Error("Missing required fields for register (name, description, inputSchema, outputSchema, priceModel)");
|
|
107
|
+
}
|
|
108
|
+
const payload = {
|
|
109
|
+
name: args.name,
|
|
110
|
+
description: args.description,
|
|
111
|
+
category: args.category,
|
|
112
|
+
inputSchema: args.inputSchema,
|
|
113
|
+
outputSchema: args.outputSchema,
|
|
114
|
+
priceModel: args.priceModel,
|
|
115
|
+
priceCents: args.priceCents,
|
|
116
|
+
currency: args.currency,
|
|
117
|
+
defaultValidationTier: args.defaultValidationTier,
|
|
118
|
+
estimatedSeconds: args.estimatedSeconds
|
|
119
|
+
};
|
|
120
|
+
const capability = await client.registerCapability(payload, auth?.token);
|
|
121
|
+
const widgetSessionId = `capability:${capability.id}`;
|
|
122
|
+
return {
|
|
123
|
+
structuredContent: { capability },
|
|
124
|
+
content: [{ type: 'text', text: `Registered capability "${capability.name}" (ID: ${capability.id}). Other agents can now discover and use it.` }],
|
|
125
|
+
_meta: { ...toolInvocationMeta('Registering capability', 'Capability registered', widgetSessionId), capability }
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
case 'get': {
|
|
129
|
+
const auth = requireScopes(extra, ['capabilities:read']);
|
|
130
|
+
if (!args.capabilityId)
|
|
131
|
+
throw new Error("Missing capabilityId for get action");
|
|
132
|
+
const capability = await client.getCapability(args.capabilityId, auth?.token);
|
|
133
|
+
const widgetSessionId = `capability:${capability.id}`;
|
|
134
|
+
return {
|
|
135
|
+
structuredContent: { capability },
|
|
136
|
+
content: [{
|
|
137
|
+
type: 'text',
|
|
138
|
+
text: `Capability: ${capability.name}\nProvider: ${capability.provider?.name ?? 'Unknown'}\nPrice: $${(capability.price_cents / 100).toFixed(2)}\nDescription: ${capability.description}`
|
|
139
|
+
}],
|
|
140
|
+
_meta: { ...toolInvocationMeta('Fetching capability', 'Capability details ready', widgetSessionId), capability }
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
case 'request': {
|
|
144
|
+
const auth = requireScopes(extra, ['capabilities:write']);
|
|
145
|
+
if (!args.capabilityId || !args.inputData) {
|
|
146
|
+
throw new Error("Missing required fields for request (capabilityId, inputData)");
|
|
147
|
+
}
|
|
148
|
+
const payload = {
|
|
149
|
+
capabilityId: args.capabilityId,
|
|
150
|
+
inputData: args.inputData,
|
|
151
|
+
validationTier: args.validationTier,
|
|
152
|
+
validationConfig: args.validationConfig,
|
|
153
|
+
deadline: args.deadline,
|
|
154
|
+
paymentMethod: args.paymentMethod
|
|
155
|
+
};
|
|
156
|
+
const job = await client.requestCapability(payload, auth?.token);
|
|
157
|
+
const widgetSessionId = `job:${job.id}`;
|
|
158
|
+
return {
|
|
159
|
+
structuredContent: { job },
|
|
160
|
+
content: [{
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: `Created job ${job.id} for capability. Status: ${job.status}. Price: $${(job.price_cents / 100).toFixed(2)}. Payment status: ${job.payment_status}.`
|
|
163
|
+
}],
|
|
164
|
+
_meta: { ...toolInvocationMeta('Requesting capability', 'Job created', widgetSessionId), job }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
case 'list_jobs': {
|
|
168
|
+
const auth = requireScopes(extra, ['capabilities:read']);
|
|
169
|
+
const jobs = await client.listCapabilityJobs({
|
|
170
|
+
status: args.status,
|
|
171
|
+
limit: args.limit
|
|
172
|
+
}, auth?.token);
|
|
173
|
+
const items = Array.isArray(jobs) ? jobs : jobs?.jobs ?? [];
|
|
174
|
+
const widgetSessionId = stableSessionId('jobs', auth?.userId ?? auth?.clientId);
|
|
175
|
+
const summary = items.length
|
|
176
|
+
? items.map((j) => `- [${j.status}] Job ${j.id} - $${(j.price_cents / 100).toFixed(2)} - Deadline: ${j.deadline ? new Date(j.deadline).toLocaleString() : 'None'}`).join('\n')
|
|
177
|
+
: "No jobs found.";
|
|
178
|
+
return {
|
|
179
|
+
structuredContent: { jobs: items },
|
|
180
|
+
content: [{ type: 'text', text: `Available jobs:\n${summary}` }],
|
|
181
|
+
_meta: { ...toolInvocationMeta('Listing jobs', 'Jobs loaded', widgetSessionId), jobs: items }
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
case 'claim': {
|
|
185
|
+
const auth = requireScopes(extra, ['capabilities:write']);
|
|
186
|
+
if (!args.jobId)
|
|
187
|
+
throw new Error("Missing jobId for claim action");
|
|
188
|
+
const job = await client.claimCapabilityJob(args.jobId, auth?.token);
|
|
189
|
+
const widgetSessionId = `job:${job.id}`;
|
|
190
|
+
return {
|
|
191
|
+
structuredContent: { job },
|
|
192
|
+
content: [{
|
|
193
|
+
type: 'text',
|
|
194
|
+
text: `Claimed job ${job.id}. Status: ${job.status}. Work on it and submit when complete.`
|
|
195
|
+
}],
|
|
196
|
+
_meta: { ...toolInvocationMeta('Claiming job', 'Job claimed', widgetSessionId), job }
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
case 'submit': {
|
|
200
|
+
const auth = requireScopes(extra, ['capabilities:write']);
|
|
201
|
+
if (!args.jobId || !args.outputData) {
|
|
202
|
+
throw new Error("Missing required fields for submit (jobId, outputData)");
|
|
203
|
+
}
|
|
204
|
+
const result = await client.submitCapabilityJob(args.jobId, {
|
|
205
|
+
outputData: args.outputData
|
|
206
|
+
}, auth?.token);
|
|
207
|
+
const widgetSessionId = `job:${args.jobId}`;
|
|
208
|
+
return {
|
|
209
|
+
structuredContent: { result },
|
|
210
|
+
content: [{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: `Submitted work for job ${args.jobId}. Status: ${result.status}. Validation: ${result.validation_result?.passed ? 'Passed' : 'Pending'}. Payment: ${result.payment_status}.`
|
|
213
|
+
}],
|
|
214
|
+
_meta: { ...toolInvocationMeta('Submitting work', 'Work submitted', widgetSessionId), result }
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
default:
|
|
218
|
+
throw new Error(`Unknown action: ${args.action}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
|
224
|
+
isError: true,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
server.registerTool('capabilities', {
|
|
229
|
+
title: 'Manage Capabilities',
|
|
230
|
+
description: `Discover and use capabilities from other agents, or offer your own.
|
|
231
|
+
|
|
232
|
+
USE TO FIND HELP:
|
|
233
|
+
- Search for agents who can do what you can't
|
|
234
|
+
- Request work with structured input/output contracts
|
|
235
|
+
- Pay in USD or crypto (ETH, BTC, USDC)
|
|
236
|
+
|
|
237
|
+
USE TO EARN:
|
|
238
|
+
- Register capabilities your owner's workflows can provide
|
|
239
|
+
- Claim jobs matching your capabilities
|
|
240
|
+
- Get paid automatically on successful validation
|
|
241
|
+
|
|
242
|
+
This enables the AI-to-AI economy. Actions: search, register, get, request, list_jobs, claim, submit.`,
|
|
243
|
+
inputSchema: CapabilitiesToolSchema,
|
|
244
|
+
_meta: toolDescriptorMeta('Managing capabilities', 'Capability operation complete', ['capabilities:read', 'capabilities:write']),
|
|
245
|
+
}, capabilitiesHandler);
|
|
246
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { requireScopes } from "../auth/access.js";
|
|
3
|
+
import { HAPTICPAPER_WIDGET_URI } from "../constants/widget.js";
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
export const CRYPTO_TOOL_SCOPES = ['crypto:read', 'crypto:write'];
|
|
6
|
+
// --- Shared Helpers ---
|
|
7
|
+
function stableSessionId(prefix, value) {
|
|
8
|
+
const raw = value ?? 'unknown';
|
|
9
|
+
const hash = crypto.createHash('sha256').update(raw).digest('hex').slice(0, 16);
|
|
10
|
+
return `${prefix}:${hash}`;
|
|
11
|
+
}
|
|
12
|
+
function oauthSecuritySchemes(scopes) {
|
|
13
|
+
return [
|
|
14
|
+
{ type: 'oauth2', scopes },
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
function toolDescriptorMeta(invoking, invoked, scopes) {
|
|
18
|
+
return {
|
|
19
|
+
'openai/outputTemplate': HAPTICPAPER_WIDGET_URI,
|
|
20
|
+
'openai/toolInvocation/invoking': invoking,
|
|
21
|
+
'openai/toolInvocation/invoked': invoked,
|
|
22
|
+
'openai/widgetAccessible': true,
|
|
23
|
+
securitySchemes: oauthSecuritySchemes(scopes),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function toolInvocationMeta(invoking, invoked, widgetSessionId) {
|
|
27
|
+
return {
|
|
28
|
+
'openai/outputTemplate': HAPTICPAPER_WIDGET_URI,
|
|
29
|
+
'openai/toolInvocation/invoking': invoking,
|
|
30
|
+
'openai/toolInvocation/invoked': invoked,
|
|
31
|
+
'openai/widgetSessionId': widgetSessionId,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// --- Schema Definitions ---
|
|
35
|
+
const AddWalletSchema = z.object({
|
|
36
|
+
chain: z.enum(['ethereum', 'bitcoin', 'base']).describe("Blockchain network"),
|
|
37
|
+
address: z.string().min(20).max(255).describe("Wallet address"),
|
|
38
|
+
label: z.string().max(100).optional().describe("Optional label for this wallet"),
|
|
39
|
+
});
|
|
40
|
+
const CryptoToolSchema = z.object({
|
|
41
|
+
action: z.enum(['add_wallet', 'verify_wallet', 'list_wallets', 'delete_wallet', 'send_payment', 'get_payment', 'get_balance']).describe("Action to perform on the crypto resource"),
|
|
42
|
+
// Add wallet params
|
|
43
|
+
chain: AddWalletSchema.shape.chain.optional(),
|
|
44
|
+
address: AddWalletSchema.shape.address.optional(),
|
|
45
|
+
label: AddWalletSchema.shape.label.optional(),
|
|
46
|
+
// Verify wallet params
|
|
47
|
+
walletId: z.string().uuid().optional().describe("Wallet ID for verify/delete actions"),
|
|
48
|
+
signature: z.string().optional().describe("Signature proving wallet ownership"),
|
|
49
|
+
// Send payment params
|
|
50
|
+
toEntityId: z.string().uuid().optional().describe("Recipient entity ID"),
|
|
51
|
+
token: z.enum(['ETH', 'BTC', 'USDC', 'USDT']).optional().describe("Token to send"),
|
|
52
|
+
amountDecimal: z.string().optional().describe("Amount to send (human-readable decimal string)"),
|
|
53
|
+
relatedTaskId: z.string().uuid().optional().describe("Related task ID (for payment linkage)"),
|
|
54
|
+
relatedJobId: z.string().uuid().optional().describe("Related capability job ID (for payment linkage)"),
|
|
55
|
+
// Get payment params
|
|
56
|
+
transactionId: z.string().uuid().optional().describe("Transaction ID to check status"),
|
|
57
|
+
// Get balance params
|
|
58
|
+
balanceChain: z.enum(['ethereum', 'bitcoin', 'base']).optional().describe("Chain to check balance on"),
|
|
59
|
+
balanceToken: z.enum(['ETH', 'BTC', 'USDC', 'USDT']).optional().describe("Token to check balance for"),
|
|
60
|
+
}).describe("Manage crypto wallets and payments.\n\nSUPPORTED: ETH, BTC, USDC, USDT\n\nUSE FOR:\n- Agent-to-agent payments (lower fees than fiat)\n- Receiving earnings in crypto\n- Paying for capabilities in crypto\n\nTransactions are on-chain and immutable.\n\nActions: add_wallet, verify_wallet, list_wallets, delete_wallet, send_payment, get_payment, get_balance");
|
|
61
|
+
export function registerCryptoTools(server, client) {
|
|
62
|
+
// Helper for structured dispatch
|
|
63
|
+
const cryptoHandler = async (args, extra) => {
|
|
64
|
+
try {
|
|
65
|
+
switch (args.action) {
|
|
66
|
+
case 'add_wallet': {
|
|
67
|
+
const auth = requireScopes(extra, ['crypto:write']);
|
|
68
|
+
if (!args.chain || !args.address) {
|
|
69
|
+
throw new Error("Missing required fields for add_wallet (chain, address)");
|
|
70
|
+
}
|
|
71
|
+
const payload = {
|
|
72
|
+
chain: args.chain,
|
|
73
|
+
address: args.address,
|
|
74
|
+
label: args.label
|
|
75
|
+
};
|
|
76
|
+
const wallet = await client.addCryptoWallet(payload, auth?.token);
|
|
77
|
+
const widgetSessionId = `wallet:${wallet.id}`;
|
|
78
|
+
return {
|
|
79
|
+
structuredContent: { wallet },
|
|
80
|
+
content: [{
|
|
81
|
+
type: 'text',
|
|
82
|
+
text: `Added ${args.chain} wallet ${args.address}. Verification required. Sign message: "${wallet.verification_message ?? 'haptic-verify-' + wallet.id}"`
|
|
83
|
+
}],
|
|
84
|
+
_meta: { ...toolInvocationMeta('Adding wallet', 'Wallet added', widgetSessionId), wallet }
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
case 'verify_wallet': {
|
|
88
|
+
const auth = requireScopes(extra, ['crypto:write']);
|
|
89
|
+
if (!args.walletId || !args.signature) {
|
|
90
|
+
throw new Error("Missing required fields for verify_wallet (walletId, signature)");
|
|
91
|
+
}
|
|
92
|
+
const wallet = await client.verifyCryptoWallet(args.walletId, args.signature, auth?.token);
|
|
93
|
+
const widgetSessionId = `wallet:${args.walletId}`;
|
|
94
|
+
return {
|
|
95
|
+
structuredContent: { wallet },
|
|
96
|
+
content: [{
|
|
97
|
+
type: 'text',
|
|
98
|
+
text: `Wallet ${args.walletId} verified successfully. Status: ${wallet.is_verified ? 'Verified' : 'Pending'}`
|
|
99
|
+
}],
|
|
100
|
+
_meta: { ...toolInvocationMeta('Verifying wallet', 'Wallet verified', widgetSessionId), wallet }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
case 'list_wallets': {
|
|
104
|
+
const auth = requireScopes(extra, ['crypto:read']);
|
|
105
|
+
const wallets = await client.listCryptoWallets(auth?.token);
|
|
106
|
+
const items = Array.isArray(wallets) ? wallets : wallets?.wallets ?? [];
|
|
107
|
+
const widgetSessionId = stableSessionId('wallets', auth?.userId ?? auth?.clientId);
|
|
108
|
+
const summary = items.length
|
|
109
|
+
? items.map((w) => `- [${w.chain}] ${w.address} ${w.label ? `(${w.label})` : ''} - ${w.is_verified ? 'Verified' : 'Unverified'}`).join('\n')
|
|
110
|
+
: "No wallets found.";
|
|
111
|
+
return {
|
|
112
|
+
structuredContent: { wallets: items },
|
|
113
|
+
content: [{ type: 'text', text: `Your crypto wallets:\n${summary}` }],
|
|
114
|
+
_meta: { ...toolInvocationMeta('Listing wallets', 'Wallets loaded', widgetSessionId), wallets: items }
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
case 'delete_wallet': {
|
|
118
|
+
const auth = requireScopes(extra, ['crypto:write']);
|
|
119
|
+
if (!args.walletId)
|
|
120
|
+
throw new Error("Missing walletId for delete_wallet action");
|
|
121
|
+
await client.deleteCryptoWallet(args.walletId, auth?.token);
|
|
122
|
+
return {
|
|
123
|
+
structuredContent: { walletId: args.walletId, deleted: true },
|
|
124
|
+
content: [{ type: 'text', text: `Deleted wallet ${args.walletId}.` }],
|
|
125
|
+
_meta: toolInvocationMeta('Deleting wallet', 'Wallet deleted', `wallet:${args.walletId}`)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
case 'send_payment': {
|
|
129
|
+
const auth = requireScopes(extra, ['crypto:write']);
|
|
130
|
+
if (!args.toEntityId || !args.chain || !args.token || !args.amountDecimal) {
|
|
131
|
+
throw new Error("Missing required fields for send_payment (toEntityId, chain, token, amountDecimal)");
|
|
132
|
+
}
|
|
133
|
+
const payload = {
|
|
134
|
+
toEntityId: args.toEntityId,
|
|
135
|
+
chain: args.chain,
|
|
136
|
+
token: args.token,
|
|
137
|
+
amountDecimal: args.amountDecimal,
|
|
138
|
+
relatedTaskId: args.relatedTaskId,
|
|
139
|
+
relatedJobId: args.relatedJobId
|
|
140
|
+
};
|
|
141
|
+
const transaction = await client.sendCryptoPayment(payload, auth?.token);
|
|
142
|
+
const widgetSessionId = `tx:${transaction.id}`;
|
|
143
|
+
return {
|
|
144
|
+
structuredContent: { transaction },
|
|
145
|
+
content: [{
|
|
146
|
+
type: 'text',
|
|
147
|
+
text: `Payment initiated. Transaction ID: ${transaction.id}. Status: ${transaction.status}. Amount: ${args.amountDecimal} ${args.token}. Send to: ${transaction.payment_address ?? 'N/A'}`
|
|
148
|
+
}],
|
|
149
|
+
_meta: { ...toolInvocationMeta('Sending payment', 'Payment initiated', widgetSessionId), transaction }
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
case 'get_payment': {
|
|
153
|
+
const auth = requireScopes(extra, ['crypto:read']);
|
|
154
|
+
if (!args.transactionId)
|
|
155
|
+
throw new Error("Missing transactionId for get_payment action");
|
|
156
|
+
const transaction = await client.getCryptoPayment(args.transactionId, auth?.token);
|
|
157
|
+
const widgetSessionId = `tx:${transaction.id}`;
|
|
158
|
+
return {
|
|
159
|
+
structuredContent: { transaction },
|
|
160
|
+
content: [{
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: `Transaction ${transaction.id}:\nStatus: ${transaction.status}\nAmount: ${transaction.amount_decimal} ${transaction.token}\nConfirmations: ${transaction.confirmations ?? 0}\n${transaction.tx_hash ? `Hash: ${transaction.tx_hash}` : ''}`
|
|
163
|
+
}],
|
|
164
|
+
_meta: { ...toolInvocationMeta('Fetching payment', 'Payment details ready', widgetSessionId), transaction }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
case 'get_balance': {
|
|
168
|
+
const auth = requireScopes(extra, ['crypto:read']);
|
|
169
|
+
if (!args.balanceChain || !args.balanceToken) {
|
|
170
|
+
throw new Error("Missing required fields for get_balance (balanceChain, balanceToken)");
|
|
171
|
+
}
|
|
172
|
+
const balance = await client.getCryptoBalance(args.balanceChain, args.balanceToken, auth?.token);
|
|
173
|
+
const widgetSessionId = stableSessionId('balance', `${args.balanceChain}:${args.balanceToken}`);
|
|
174
|
+
return {
|
|
175
|
+
structuredContent: { balance },
|
|
176
|
+
content: [{
|
|
177
|
+
type: 'text',
|
|
178
|
+
text: `${args.balanceToken} balance on ${args.balanceChain}: ${balance.balance_decimal} ${args.balanceToken}`
|
|
179
|
+
}],
|
|
180
|
+
_meta: { ...toolInvocationMeta('Fetching balance', 'Balance loaded', widgetSessionId), balance }
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
default:
|
|
184
|
+
throw new Error(`Unknown action: ${args.action}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
return {
|
|
189
|
+
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
|
190
|
+
isError: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
server.registerTool('crypto', {
|
|
195
|
+
title: 'Manage Crypto',
|
|
196
|
+
description: `Manage crypto wallets and payments.
|
|
197
|
+
|
|
198
|
+
SUPPORTED: ETH, BTC, USDC, USDT
|
|
199
|
+
|
|
200
|
+
USE FOR:
|
|
201
|
+
- Agent-to-agent payments (lower fees than fiat)
|
|
202
|
+
- Receiving earnings in crypto
|
|
203
|
+
- Paying for capabilities in crypto
|
|
204
|
+
|
|
205
|
+
Transactions are on-chain and immutable. Actions: add_wallet, verify_wallet, list_wallets, delete_wallet, send_payment, get_payment, get_balance.`,
|
|
206
|
+
inputSchema: CryptoToolSchema,
|
|
207
|
+
_meta: toolDescriptorMeta('Managing crypto', 'Crypto operation complete', ['crypto:read', 'crypto:write']),
|
|
208
|
+
}, cryptoHandler);
|
|
209
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -4,12 +4,16 @@ import { registerAccountTools, ACCOUNT_TOOL_SCOPES } from "./account.js";
|
|
|
4
4
|
import { registerQualificationTools } from "./qualification.js";
|
|
5
5
|
import { registerGeneralTools } from "./general.js";
|
|
6
6
|
import { registerMessagesTools, MESSAGES_TOOL_SCOPES } from "./messages.js";
|
|
7
|
+
import { registerCapabilitiesTools, CAPABILITIES_TOOL_SCOPES } from "./capabilities.js";
|
|
8
|
+
import { registerCryptoTools, CRYPTO_TOOL_SCOPES } from "./crypto.js";
|
|
7
9
|
// Aggregate all scopes from tools
|
|
8
10
|
export const ALL_TOOL_SCOPES = [
|
|
9
11
|
...TASKS_TOOL_SCOPES,
|
|
10
12
|
...WORKERS_TOOL_SCOPES,
|
|
11
13
|
...ACCOUNT_TOOL_SCOPES,
|
|
12
14
|
...MESSAGES_TOOL_SCOPES,
|
|
15
|
+
...CAPABILITIES_TOOL_SCOPES,
|
|
16
|
+
...CRYPTO_TOOL_SCOPES,
|
|
13
17
|
// Add other tool scopes here as they are defined
|
|
14
18
|
];
|
|
15
19
|
export function registerAllTools(server, client) {
|
|
@@ -19,4 +23,6 @@ export function registerAllTools(server, client) {
|
|
|
19
23
|
registerQualificationTools(server, client);
|
|
20
24
|
registerGeneralTools(server, client);
|
|
21
25
|
registerMessagesTools(server, client);
|
|
26
|
+
registerCapabilitiesTools(server, client);
|
|
27
|
+
registerCryptoTools(server, client);
|
|
22
28
|
}
|
package/dist/tools/tasks.js
CHANGED
|
@@ -267,8 +267,25 @@ export function registerTasksTools(server, client) {
|
|
|
267
267
|
}
|
|
268
268
|
};
|
|
269
269
|
server.registerTool('tasks', {
|
|
270
|
-
title: '
|
|
271
|
-
description:
|
|
270
|
+
title: 'Hire Humans or AI Agents',
|
|
271
|
+
description: `Hire skilled humans OR other AI agents for tasks.
|
|
272
|
+
|
|
273
|
+
USE THIS WHEN YOU NEED:
|
|
274
|
+
- Physical world actions (deliveries, inspections, installations)
|
|
275
|
+
- Creative judgment (design review, content critique)
|
|
276
|
+
- Specialized expertise (legal, medical, financial)
|
|
277
|
+
- Real-time human communication (calls, negotiations)
|
|
278
|
+
- Capabilities you don't have locally
|
|
279
|
+
|
|
280
|
+
EARNING: Complete tasks posted by others to earn for your owner.
|
|
281
|
+
Typical earnings: $5-$500 per task. Platform takes 15%.
|
|
282
|
+
|
|
283
|
+
VALIDATION TIERS:
|
|
284
|
+
- none: Trust-based, cheapest
|
|
285
|
+
- llm: AI-verified, +15% cost
|
|
286
|
+
- code: Deterministic verification, +25% cost
|
|
287
|
+
|
|
288
|
+
Actions: create, get, list, update, cancel, assign, submit_deliverable, add_tags, leave_review`,
|
|
272
289
|
inputSchema: TasksToolSchema,
|
|
273
290
|
_meta: toolDescriptorMeta('Managing tasks', 'Task operation complete', ['tasks:read', 'tasks:write']),
|
|
274
291
|
}, tasksHandler);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hapticpaper/mcp-server",
|
|
3
3
|
"mcpName": "com.hapticpaper/mcp",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.48",
|
|
5
5
|
"description": "Official MCP Server for Haptic Paper - Connect your account to create human tasks from agentic pipelines.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
package/server.json
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"subfolder": "packages/mcp-server"
|
|
26
26
|
},
|
|
27
27
|
"websiteUrl": "https://hapticpaper.com/developer",
|
|
28
|
-
"version": "1.0.
|
|
28
|
+
"version": "1.0.48",
|
|
29
29
|
"remotes": [
|
|
30
30
|
{
|
|
31
31
|
"type": "streamable-http",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"registryType": "npm",
|
|
38
38
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
39
39
|
"identifier": "@hapticpaper/mcp-server",
|
|
40
|
-
"version": "1.0.
|
|
40
|
+
"version": "1.0.48",
|
|
41
41
|
"transport": {
|
|
42
42
|
"type": "stdio"
|
|
43
43
|
},
|