@hapticpaper/mcp-server 1.0.26 → 1.0.29
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 +61 -0
- package/dist/tools/account.js +36 -4
- package/dist/tools/index.js +2 -0
- package/dist/tools/messages.js +83 -0
- package/dist/tools/tasks.js +116 -19
- package/dist/tools/workers.js +88 -12
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -126,4 +126,65 @@ export class HapticPaperClient {
|
|
|
126
126
|
const response = await this.client.post(`/gpt/qualification/${sessionId}/complete`, {});
|
|
127
127
|
return response.data;
|
|
128
128
|
}
|
|
129
|
+
// Message Methods
|
|
130
|
+
async sendMessage(data, accessToken) {
|
|
131
|
+
const response = await this.client.post('/gpt/messages', data, { headers: await this.authHeaders(accessToken) });
|
|
132
|
+
return response.data;
|
|
133
|
+
}
|
|
134
|
+
async getTaskMessages(taskId, accessToken) {
|
|
135
|
+
const response = await this.client.get(`/gpt/messages/task/${taskId}`, { headers: await this.authHeaders(accessToken) });
|
|
136
|
+
return response.data;
|
|
137
|
+
}
|
|
138
|
+
// Extended Task Methods
|
|
139
|
+
async assignTask(taskId, workerId, accessToken) {
|
|
140
|
+
const response = await this.client.post(`/gpt/tasks/${taskId}/assign`, { workerId }, { headers: await this.authHeaders(accessToken) });
|
|
141
|
+
return response.data;
|
|
142
|
+
}
|
|
143
|
+
async submitDeliverable(taskId, data, accessToken) {
|
|
144
|
+
const response = await this.client.post(`/gpt/tasks/${taskId}/deliverables`, data, { headers: await this.authHeaders(accessToken) });
|
|
145
|
+
return response.data;
|
|
146
|
+
}
|
|
147
|
+
async updateTaskTags(taskId, tags, accessToken) {
|
|
148
|
+
const response = await this.client.patch(`/gpt/tasks/${taskId}/tags`, { tags }, { headers: await this.authHeaders(accessToken) });
|
|
149
|
+
return response.data;
|
|
150
|
+
}
|
|
151
|
+
// Extended Worker Methods
|
|
152
|
+
async updateWorkerProfile(data, accessToken) {
|
|
153
|
+
const response = await this.client.put('/gpt/workers/profile', data, { headers: await this.authHeaders(accessToken) });
|
|
154
|
+
return response.data;
|
|
155
|
+
}
|
|
156
|
+
async addSkill(data, accessToken) {
|
|
157
|
+
const response = await this.client.post('/gpt/workers/skills', data, { headers: await this.authHeaders(accessToken) });
|
|
158
|
+
return response.data;
|
|
159
|
+
}
|
|
160
|
+
async deleteSkill(id, accessToken) {
|
|
161
|
+
const response = await this.client.delete(`/gpt/workers/skills/${id}`, { headers: await this.authHeaders(accessToken) });
|
|
162
|
+
return response.data;
|
|
163
|
+
}
|
|
164
|
+
async addCertification(data, accessToken) {
|
|
165
|
+
const response = await this.client.post('/gpt/workers/certifications', data, { headers: await this.authHeaders(accessToken) });
|
|
166
|
+
return response.data;
|
|
167
|
+
}
|
|
168
|
+
async deleteCertification(id, accessToken) {
|
|
169
|
+
const response = await this.client.delete(`/gpt/workers/certifications/${id}`, { headers: await this.authHeaders(accessToken) });
|
|
170
|
+
return response.data;
|
|
171
|
+
}
|
|
172
|
+
// Extended Account/Billing Methods
|
|
173
|
+
async purchaseCredits(entityId, amountCents, paymentMethodId, accessToken) {
|
|
174
|
+
const response = await this.client.post(`/gpt/billing/${entityId}/purchase`, { amountCents, paymentMethodId }, { headers: await this.authHeaders(accessToken) });
|
|
175
|
+
return response.data;
|
|
176
|
+
}
|
|
177
|
+
async getTransactions(entityId, accessToken) {
|
|
178
|
+
const response = await this.client.get(`/gpt/billing/${entityId}/transactions`, { headers: await this.authHeaders(accessToken) });
|
|
179
|
+
return response.data;
|
|
180
|
+
}
|
|
181
|
+
async getPaymentMethods(entityId, accessToken) {
|
|
182
|
+
const response = await this.client.get(`/gpt/payment-methods/${entityId}`, { headers: await this.authHeaders(accessToken) });
|
|
183
|
+
return response.data;
|
|
184
|
+
}
|
|
185
|
+
// Reviews
|
|
186
|
+
async leaveReview(data, accessToken) {
|
|
187
|
+
const response = await this.client.post('/gpt/reviews', data, { headers: await this.authHeaders(accessToken) });
|
|
188
|
+
return response.data;
|
|
189
|
+
}
|
|
129
190
|
}
|
package/dist/tools/account.js
CHANGED
|
@@ -23,8 +23,12 @@ function toolInvocationMeta(invoking, invoked, widgetSessionId) {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
const AccountToolSchema = z.object({
|
|
26
|
-
action: z.enum(['get_my_profile', 'get_balance', 'transaction_history']).describe("Action to perform on the account resource"),
|
|
27
|
-
|
|
26
|
+
action: z.enum(['get_my_profile', 'get_balance', 'transaction_history', 'purchase_credits', 'list_payment_methods']).describe("Action to perform on the account resource"),
|
|
27
|
+
// Purchase/Payment params
|
|
28
|
+
amountCents: z.number().min(100).optional().describe("Amount to purchase in cents (min 100)"),
|
|
29
|
+
paymentMethodId: z.string().optional().describe("Stripe Payment Method ID"),
|
|
30
|
+
entityId: z.string().uuid().optional().describe("Entity ID context (for billing and transactions)"),
|
|
31
|
+
}).describe("Manage user account and credits. Actions: get_my_profile (no params), get_balance (no params), transaction_history (requires entityId), list_payment_methods (requires entityId), purchase_credits (requires entityId, amountCents, optional paymentMethodId).");
|
|
28
32
|
export function registerAccountTools(server, client) {
|
|
29
33
|
const accountHandler = async (args, extra) => {
|
|
30
34
|
try {
|
|
@@ -63,12 +67,40 @@ export function registerAccountTools(server, client) {
|
|
|
63
67
|
};
|
|
64
68
|
}
|
|
65
69
|
case 'transaction_history': {
|
|
66
|
-
|
|
70
|
+
if (!args.entityId)
|
|
71
|
+
throw new Error("Missing entityId for transaction_history");
|
|
72
|
+
const history = await client.getTransactions(args.entityId, auth?.token);
|
|
73
|
+
const summary = history.data?.length
|
|
74
|
+
? history.data.map((t) => `- ${t.date}: ${t.description} (${t.amount} credits)`).join('\n')
|
|
75
|
+
: "No transactions found.";
|
|
67
76
|
return {
|
|
68
|
-
|
|
77
|
+
structuredContent: { transactions: history.data },
|
|
78
|
+
content: [{ type: 'text', text: `Transaction History:\n${summary}` }],
|
|
69
79
|
isError: false
|
|
70
80
|
};
|
|
71
81
|
}
|
|
82
|
+
case 'list_payment_methods': {
|
|
83
|
+
if (!args.entityId)
|
|
84
|
+
throw new Error("Missing entityId for list_payment_methods");
|
|
85
|
+
const methods = await client.getPaymentMethods(args.entityId, auth?.token);
|
|
86
|
+
const summary = methods.data?.length
|
|
87
|
+
? methods.data.map((m) => `- [${m.brand} *${m.last4}] Exp: ${m.expMonth}/${m.expYear} (ID: ${m.id}) ${m.isDefault ? '(Default)' : ''}`).join('\n')
|
|
88
|
+
: "No payment methods found.";
|
|
89
|
+
return {
|
|
90
|
+
structuredContent: { paymentMethods: methods.data },
|
|
91
|
+
content: [{ type: 'text', text: `Payment Methods:\n${summary}` }]
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
case 'purchase_credits': {
|
|
95
|
+
const writeAuth = requireScopes(extra, ['account:write']);
|
|
96
|
+
if (!args.entityId || !args.amountCents)
|
|
97
|
+
throw new Error("Missing entityId or amountCents for purchase_credits");
|
|
98
|
+
const res = await client.purchaseCredits(args.entityId, args.amountCents, args.paymentMethodId, writeAuth?.token);
|
|
99
|
+
return {
|
|
100
|
+
structuredContent: { result: res.data },
|
|
101
|
+
content: [{ type: 'text', text: `Successfully purchased credits. New balance available.` }]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
72
104
|
default:
|
|
73
105
|
throw new Error(`Unknown action: ${args.action}`);
|
|
74
106
|
}
|
package/dist/tools/index.js
CHANGED
|
@@ -3,10 +3,12 @@ import { registerWorkerTools } from "./workers.js";
|
|
|
3
3
|
import { registerAccountTools } from "./account.js";
|
|
4
4
|
import { registerQualificationTools } from "./qualification.js";
|
|
5
5
|
import { registerGeneralTools } from "./general.js";
|
|
6
|
+
import { registerMessagesTools } from "./messages.js";
|
|
6
7
|
export function registerAllTools(server, client) {
|
|
7
8
|
registerTasksTools(server, client);
|
|
8
9
|
registerWorkerTools(server, client);
|
|
9
10
|
registerAccountTools(server, client);
|
|
10
11
|
registerQualificationTools(server, client);
|
|
11
12
|
registerGeneralTools(server, client);
|
|
13
|
+
registerMessagesTools(server, client);
|
|
12
14
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { requireScopes } from "../auth/access.js";
|
|
3
|
+
import { HAPTICPAPER_WIDGET_URI } from "../constants/widget.js";
|
|
4
|
+
function oauthSecuritySchemes(scopes) {
|
|
5
|
+
return [
|
|
6
|
+
{ type: 'oauth2', scopes },
|
|
7
|
+
];
|
|
8
|
+
}
|
|
9
|
+
function toolDescriptorMeta(invoking, invoked, scopes) {
|
|
10
|
+
return {
|
|
11
|
+
'openai/outputTemplate': HAPTICPAPER_WIDGET_URI,
|
|
12
|
+
'openai/toolInvocation/invoking': invoking,
|
|
13
|
+
'openai/toolInvocation/invoked': invoked,
|
|
14
|
+
'openai/widgetAccessible': true,
|
|
15
|
+
securitySchemes: oauthSecuritySchemes(scopes),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function toolInvocationMeta(invoking, invoked, widgetSessionId) {
|
|
19
|
+
return {
|
|
20
|
+
'openai/toolInvocation/invoking': invoking,
|
|
21
|
+
'openai/toolInvocation/invoked': invoked,
|
|
22
|
+
'openai/widgetSessionId': widgetSessionId,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const MessagesToolSchema = z.object({
|
|
26
|
+
action: z.enum(['send', 'list']).describe("Action to perform on messages"),
|
|
27
|
+
taskId: z.string().uuid().describe("Task ID context for the message"),
|
|
28
|
+
// Send params
|
|
29
|
+
recipientId: z.string().uuid().optional().describe("User ID to send message to (required for send)"),
|
|
30
|
+
content: z.string().min(1).optional().describe("Message content (required for send)"),
|
|
31
|
+
}).describe("Communicate with workers or agents. Actions: send (requires taskId, recipientId, content), list (requires taskId).");
|
|
32
|
+
export function registerMessagesTools(server, client) {
|
|
33
|
+
const messagesHandler = async (args, extra) => {
|
|
34
|
+
try {
|
|
35
|
+
switch (args.action) {
|
|
36
|
+
case 'send': {
|
|
37
|
+
const auth = requireScopes(extra, ['messages:write']);
|
|
38
|
+
if (!args.recipientId || !args.content)
|
|
39
|
+
throw new Error("Missing recipientId or content for send action");
|
|
40
|
+
const message = await client.sendMessage({
|
|
41
|
+
taskId: args.taskId,
|
|
42
|
+
recipientId: args.recipientId,
|
|
43
|
+
content: args.content
|
|
44
|
+
}, auth?.token);
|
|
45
|
+
const widgetSessionId = `task:${args.taskId}`;
|
|
46
|
+
return {
|
|
47
|
+
structuredContent: { message },
|
|
48
|
+
content: [{ type: 'text', text: `Message sent to ${args.recipientId}` }],
|
|
49
|
+
_meta: { ...toolInvocationMeta('Sending message', 'Message sent', widgetSessionId), message }
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
case 'list': {
|
|
53
|
+
const auth = requireScopes(extra, ['messages:read']);
|
|
54
|
+
const result = await client.getTaskMessages(args.taskId, auth?.token);
|
|
55
|
+
const messages = result.messages || [];
|
|
56
|
+
const widgetSessionId = `task:${args.taskId}`;
|
|
57
|
+
const summary = messages.length
|
|
58
|
+
? messages.map((m) => `[${m.sender_name || 'User'}]: ${m.content}`).join('\n')
|
|
59
|
+
: "No messages yet.";
|
|
60
|
+
return {
|
|
61
|
+
structuredContent: { messages },
|
|
62
|
+
content: [{ type: 'text', text: `Messages for task ${args.taskId}:\n${summary}` }],
|
|
63
|
+
_meta: { ...toolInvocationMeta('Listing messages', 'Messages loaded', widgetSessionId), messages }
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(`Unknown action: ${args.action}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
|
73
|
+
isError: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
server.registerTool('messages', {
|
|
78
|
+
title: 'Messaging',
|
|
79
|
+
description: 'Communicate with workers or clients. Actions: send, list.',
|
|
80
|
+
inputSchema: MessagesToolSchema,
|
|
81
|
+
_meta: toolDescriptorMeta('Messaging', 'Message sent', ['messages:read', 'messages:write']),
|
|
82
|
+
}, messagesHandler);
|
|
83
|
+
}
|
package/dist/tools/tasks.js
CHANGED
|
@@ -34,13 +34,18 @@ const CreateTaskSchema = z.object({
|
|
|
34
34
|
title: z.string().min(5).max(200).describe("Short title for the task"),
|
|
35
35
|
description: z.string().min(20).max(5000).describe("Detailed description of what needs to be done"),
|
|
36
36
|
budget: z.number().min(5).max(500).describe("Payment to worker in dollars"),
|
|
37
|
+
taskType: z.string().optional().describe("Type of task (e.g., 'research', 'writing')"),
|
|
38
|
+
priority: z.enum(['low', 'normal', 'high']).optional().describe("Task priority level"),
|
|
39
|
+
estimatedHours: z.number().positive().optional().describe("Estimated hours to complete"),
|
|
40
|
+
deadline: z.string().datetime().optional().describe("When the task must be completed by (ISO string)"),
|
|
41
|
+
requiredSkills: z.array(z.string()).optional().describe("List of skills required"),
|
|
42
|
+
tags: z.record(z.string()).optional().describe("Key-value tags for categorization"),
|
|
37
43
|
location: z.object({
|
|
38
44
|
address: z.string().describe("Street address where task will be performed"),
|
|
39
45
|
city: z.string().optional(),
|
|
40
46
|
state: z.string().optional(),
|
|
41
47
|
zip: z.string().optional(),
|
|
42
48
|
}).optional().describe("Location for physical tasks"),
|
|
43
|
-
deadline: z.string().datetime().optional().describe("When the task must be completed by"),
|
|
44
49
|
requirements: z.object({
|
|
45
50
|
proofOfWork: z.boolean().default(true),
|
|
46
51
|
gpsRequired: z.boolean().default(true),
|
|
@@ -48,26 +53,57 @@ const CreateTaskSchema = z.object({
|
|
|
48
53
|
}).optional(),
|
|
49
54
|
entityId: z.string().uuid().optional().describe("Entity ID to creating/billing this task"),
|
|
50
55
|
});
|
|
51
|
-
const UpdateTaskSchema = CreateTaskSchema.partial().extend({
|
|
52
|
-
taskId: z.string().uuid().describe("ID of the task to update"),
|
|
53
|
-
});
|
|
54
56
|
const TasksToolSchema = z.object({
|
|
55
|
-
action: z.enum(['create', 'get', 'list', 'update', 'cancel']).describe("Action to perform on the tasks resource"),
|
|
56
|
-
// Create params
|
|
57
|
+
action: z.enum(['create', 'get', 'list', 'update', 'cancel', 'assign', 'submit_deliverable', 'add_tags', 'leave_review']).describe("Action to perform on the tasks resource"),
|
|
58
|
+
// Create/Update params
|
|
57
59
|
title: CreateTaskSchema.shape.title.optional(),
|
|
58
60
|
description: CreateTaskSchema.shape.description.optional(),
|
|
59
61
|
budget: CreateTaskSchema.shape.budget.optional(),
|
|
60
|
-
|
|
62
|
+
taskType: CreateTaskSchema.shape.taskType.optional(),
|
|
63
|
+
priority: CreateTaskSchema.shape.priority.optional(),
|
|
64
|
+
estimatedHours: CreateTaskSchema.shape.estimatedHours.optional(),
|
|
61
65
|
deadline: CreateTaskSchema.shape.deadline.optional(),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
requiredSkills: CreateTaskSchema.shape.requiredSkills.optional(),
|
|
67
|
+
tags: CreateTaskSchema.shape.tags.optional(),
|
|
68
|
+
location: CreateTaskSchema.shape.location.optional(),
|
|
69
|
+
// Get/Update/Cancel/Assign/Deliverable params
|
|
70
|
+
taskId: z.string().uuid().optional().describe("Task ID context"),
|
|
66
71
|
reason: z.string().optional().describe("Reason for cancellation"),
|
|
72
|
+
workerId: z.string().uuid().optional().describe("Worker ID to assign (for 'assign' action)"),
|
|
73
|
+
// Deliverable params
|
|
74
|
+
content: z.string().optional().describe("Content of the deliverable (for 'submit_deliverable')"),
|
|
75
|
+
deliverableType: z.string().optional().describe("Type of deliverable (for 'submit_deliverable')"),
|
|
76
|
+
// Detailed Requirements
|
|
77
|
+
requirements: z.object({
|
|
78
|
+
files: z.array(z.object({
|
|
79
|
+
name: z.string().describe("Name of the required file"),
|
|
80
|
+
description: z.string().describe("Description of what the file should contain"),
|
|
81
|
+
required: z.boolean().default(true).describe("Is this file mandatory?")
|
|
82
|
+
})).optional(),
|
|
83
|
+
questions: z.array(z.object({
|
|
84
|
+
text: z.string().describe("Question text"),
|
|
85
|
+
type: z.enum(['text', 'select', 'boolean']).describe("Answer type"),
|
|
86
|
+
options: z.array(z.string()).optional().describe("Options for select type"),
|
|
87
|
+
required: z.boolean().default(true)
|
|
88
|
+
})).optional()
|
|
89
|
+
}).optional().describe("Structured requirements for the task (files, questions)"),
|
|
90
|
+
// Pricing Model (passed via metadata)
|
|
91
|
+
pricing: z.object({
|
|
92
|
+
model: z.enum(['fixed_price', 'per_unit', 'hourly']),
|
|
93
|
+
unitName: z.string().optional().describe("Name of unit (e.g. 'bug', 'page')"),
|
|
94
|
+
rateCents: z.number().optional().describe("Price per unit in cents"),
|
|
95
|
+
maxUnits: z.number().optional().describe("Max units allowed"),
|
|
96
|
+
}).optional().describe("Advanced pricing model (e.g. per-bug bounty)"),
|
|
97
|
+
entityId: z.string().uuid().optional().describe("Entity ID to bill/attribute task to"),
|
|
67
98
|
// List params
|
|
68
|
-
status: z.enum(['open', 'assigned', 'completed', 'cancelled']).optional().describe("Filter by status for list action"),
|
|
99
|
+
status: z.enum(['open', 'pending', 'assigned', 'in_progress', 'completed', 'cancelled']).optional().describe("Filter by status for list action"),
|
|
100
|
+
role: z.enum(['client', 'worker']).optional().describe("Filter by role (client or worker)"),
|
|
69
101
|
limit: z.number().min(1).max(50).optional().describe("Limit number of tasks returned"),
|
|
70
|
-
|
|
102
|
+
// Review params
|
|
103
|
+
revieweeId: z.string().uuid().optional().describe("User ID of the person being reviewed (for 'leave_review')"),
|
|
104
|
+
rating: z.number().min(1).max(5).optional().describe("Rating from 1 to 5 (for 'leave_review')"),
|
|
105
|
+
comment: z.string().optional().describe("Review comment (for 'leave_review')"),
|
|
106
|
+
}).describe("Manage tasks lifecycle. Actions: create (requires title, description, budget; supports optional requirements, pricing), get (requires taskId), list (optional status, role), update (requires taskId), cancel (requires taskId), assign (requires taskId, workerId), submit_deliverable (requires taskId, content, deliverableType), add_tags (requires taskId, tags), leave_review (requires taskId, revieweeId, rating).");
|
|
71
107
|
export function registerTasksTools(server, client) {
|
|
72
108
|
// Helper for structured dispatch
|
|
73
109
|
const tasksHandler = async (args, extra) => {
|
|
@@ -75,7 +111,6 @@ export function registerTasksTools(server, client) {
|
|
|
75
111
|
switch (args.action) {
|
|
76
112
|
case 'create': {
|
|
77
113
|
const auth = requireScopes(extra, ['tasks:write']);
|
|
78
|
-
// Validate create params manually or rely on backend validation
|
|
79
114
|
if (!args.title || !args.description || !args.budget) {
|
|
80
115
|
throw new Error("Missing required fields for create (title, description, budget)");
|
|
81
116
|
}
|
|
@@ -83,8 +118,13 @@ export function registerTasksTools(server, client) {
|
|
|
83
118
|
title: args.title,
|
|
84
119
|
description: args.description,
|
|
85
120
|
budget: args.budget,
|
|
86
|
-
|
|
121
|
+
taskType: args.taskType,
|
|
122
|
+
priority: args.priority,
|
|
123
|
+
estimatedHours: args.estimatedHours,
|
|
87
124
|
deadline: args.deadline,
|
|
125
|
+
requiredSkills: args.requiredSkills,
|
|
126
|
+
tags: args.tags,
|
|
127
|
+
location: args.location,
|
|
88
128
|
requirements: args.requirements,
|
|
89
129
|
entityId: args.entityId
|
|
90
130
|
};
|
|
@@ -104,7 +144,7 @@ export function registerTasksTools(server, client) {
|
|
|
104
144
|
const widgetSessionId = `task:${task.id}`;
|
|
105
145
|
return {
|
|
106
146
|
structuredContent: { task },
|
|
107
|
-
content: [{ type: 'text', text: `Task ${task.id}: ${task.status}` }],
|
|
147
|
+
content: [{ type: 'text', text: `Task ${task.id}: ${task.status}\nTitle: ${task.title}\nDescription: ${task.description}` }],
|
|
108
148
|
_meta: { ...toolInvocationMeta('Fetching task', 'Task details ready', widgetSessionId), task }
|
|
109
149
|
};
|
|
110
150
|
}
|
|
@@ -116,7 +156,7 @@ export function registerTasksTools(server, client) {
|
|
|
116
156
|
const summary = items.length ? items.map((t) => `- [${t.status}] ${t.title} ($${t.budget}) (ID: ${t.id})`).join('\n') : "No tasks found.";
|
|
117
157
|
return {
|
|
118
158
|
structuredContent: { tasks: items },
|
|
119
|
-
content: [{ type: 'text', text: `
|
|
159
|
+
content: [{ type: 'text', text: `Tasks list:\n${summary}` }],
|
|
120
160
|
_meta: { ...toolInvocationMeta('Listing tasks', 'Tasks loaded', widgetSessionId), tasks: items }
|
|
121
161
|
};
|
|
122
162
|
}
|
|
@@ -124,7 +164,6 @@ export function registerTasksTools(server, client) {
|
|
|
124
164
|
const auth = requireScopes(extra, ['tasks:write']);
|
|
125
165
|
if (!args.taskId)
|
|
126
166
|
throw new Error("Missing taskId for update action");
|
|
127
|
-
// Construct update payload from optional args
|
|
128
167
|
const updatePayload = {};
|
|
129
168
|
if (args.title)
|
|
130
169
|
updatePayload.title = args.title;
|
|
@@ -134,6 +173,10 @@ export function registerTasksTools(server, client) {
|
|
|
134
173
|
updatePayload.budget = args.budget;
|
|
135
174
|
if (args.deadline)
|
|
136
175
|
updatePayload.deadline = args.deadline;
|
|
176
|
+
if (args.priority)
|
|
177
|
+
updatePayload.priority = args.priority; // New
|
|
178
|
+
if (args.status)
|
|
179
|
+
updatePayload.status = args.status; // New
|
|
137
180
|
if (args.requirements)
|
|
138
181
|
updatePayload.requirements = args.requirements;
|
|
139
182
|
const task = await client.updateTask(args.taskId, updatePayload, auth?.token);
|
|
@@ -156,6 +199,60 @@ export function registerTasksTools(server, client) {
|
|
|
156
199
|
_meta: { ...toolInvocationMeta('Cancelling task', 'Task cancelled', widgetSessionId), result: res }
|
|
157
200
|
};
|
|
158
201
|
}
|
|
202
|
+
case 'assign': { // New
|
|
203
|
+
const auth = requireScopes(extra, ['tasks:write']);
|
|
204
|
+
if (!args.taskId || !args.workerId)
|
|
205
|
+
throw new Error("Missing taskId or workerId for assign action");
|
|
206
|
+
const task = await client.assignTask(args.taskId, args.workerId, auth?.token);
|
|
207
|
+
const widgetSessionId = `task:${args.taskId}`;
|
|
208
|
+
return {
|
|
209
|
+
structuredContent: { task },
|
|
210
|
+
content: [{ type: 'text', text: `Assigned task ${args.taskId} to worker ${args.workerId}.` }],
|
|
211
|
+
_meta: { ...toolInvocationMeta('Assigning task', 'Task assigned', widgetSessionId), task }
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
case 'submit_deliverable': { // New
|
|
215
|
+
const auth = requireScopes(extra, ['tasks:write']);
|
|
216
|
+
if (!args.taskId || !args.content || !args.deliverableType)
|
|
217
|
+
throw new Error("Missing taskId, content, or deliverableType for submit_deliverable");
|
|
218
|
+
const res = await client.submitDeliverable(args.taskId, {
|
|
219
|
+
content: args.content,
|
|
220
|
+
deliverableType: args.deliverableType
|
|
221
|
+
}, auth?.token);
|
|
222
|
+
const widgetSessionId = `task:${args.taskId}`;
|
|
223
|
+
return {
|
|
224
|
+
structuredContent: { deliverable: res.deliverable },
|
|
225
|
+
content: [{ type: 'text', text: `Deliverable submitted for task ${args.taskId}.` }],
|
|
226
|
+
_meta: { ...toolInvocationMeta('Submitting deliverable', 'Deliverable submitted', widgetSessionId), deliverable: res }
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
case 'add_tags': { // New
|
|
230
|
+
const auth = requireScopes(extra, ['tasks:write']);
|
|
231
|
+
if (!args.taskId || !args.tags)
|
|
232
|
+
throw new Error("Missing taskId or tags for add_tags action");
|
|
233
|
+
const task = await client.updateTaskTags(args.taskId, args.tags, auth?.token);
|
|
234
|
+
const widgetSessionId = `task:${args.taskId}`;
|
|
235
|
+
return {
|
|
236
|
+
structuredContent: { task },
|
|
237
|
+
content: [{ type: 'text', text: `Tags updated for task ${args.taskId}.` }],
|
|
238
|
+
_meta: { ...toolInvocationMeta('Updating tags', 'Tags updated', widgetSessionId), task }
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
case 'leave_review': { // New
|
|
242
|
+
const auth = requireScopes(extra, ['tasks:write']);
|
|
243
|
+
if (!args.taskId || !args.revieweeId || !args.rating)
|
|
244
|
+
throw new Error("Missing taskId, revieweeId, or rating for leave_review");
|
|
245
|
+
const res = await client.leaveReview({
|
|
246
|
+
taskId: args.taskId,
|
|
247
|
+
revieweeId: args.revieweeId,
|
|
248
|
+
rating: args.rating,
|
|
249
|
+
comment: args.comment
|
|
250
|
+
}, auth?.token);
|
|
251
|
+
return {
|
|
252
|
+
structuredContent: { review: res.review },
|
|
253
|
+
content: [{ type: 'text', text: `Review submitted for task ${args.taskId}.` }]
|
|
254
|
+
};
|
|
255
|
+
}
|
|
159
256
|
default:
|
|
160
257
|
throw new Error(`Unknown action: ${args.action}`);
|
|
161
258
|
}
|
|
@@ -169,7 +266,7 @@ export function registerTasksTools(server, client) {
|
|
|
169
266
|
};
|
|
170
267
|
server.registerTool('tasks', {
|
|
171
268
|
title: 'Manage Tasks',
|
|
172
|
-
description: 'Manage tasks lifecycle. Actions: create, get, list, update, cancel. Use this tool for all task-related operations.',
|
|
269
|
+
description: 'Manage tasks lifecycle. Actions: create, get, list, update, cancel, assign, submit_deliverable, add_tags. Use this tool for all task-related operations.',
|
|
173
270
|
inputSchema: TasksToolSchema,
|
|
174
271
|
_meta: toolDescriptorMeta('Managing tasks', 'Task operation complete', ['tasks:read', 'tasks:write']),
|
|
175
272
|
}, tasksHandler);
|
package/dist/tools/workers.js
CHANGED
|
@@ -39,30 +39,50 @@ const SearchWorkersSchema = z.object({
|
|
|
39
39
|
skills: z.array(z.string()).optional()
|
|
40
40
|
});
|
|
41
41
|
const WorkersToolSchema = z.object({
|
|
42
|
-
action: z.enum(['search', 'get_profile']).describe("Action to perform on the workers resource"),
|
|
42
|
+
action: z.enum(['search', 'get_profile', 'update_profile', 'manage_skills', 'manage_certifications']).describe("Action to perform on the workers resource"),
|
|
43
43
|
// Search params
|
|
44
44
|
taskDescription: SearchWorkersSchema.shape.taskDescription.optional(),
|
|
45
45
|
location: SearchWorkersSchema.shape.location.optional(),
|
|
46
46
|
skills: SearchWorkersSchema.shape.skills.optional(),
|
|
47
|
-
|
|
47
|
+
minRating: z.number().min(0).max(5).optional().describe("Minimum star rating"),
|
|
48
|
+
maxRate: z.number().min(0).optional().describe("Maximum hourly rate"),
|
|
49
|
+
limit: z.number().min(1).max(50).optional().describe("Max number of records"),
|
|
50
|
+
// Get/Profile params
|
|
48
51
|
workerId: z.string().uuid().optional().describe("Worker ID to retrieve profile"),
|
|
49
|
-
|
|
52
|
+
// Update Profile params
|
|
53
|
+
bio: z.string().optional().describe("Worker bio"),
|
|
54
|
+
hourlyRate: z.number().optional().describe("Hourly rate in USD"),
|
|
55
|
+
availabilityStatus: z.enum(['available', 'busy', 'offline']).optional().describe("Current availability"),
|
|
56
|
+
// Skill/Cert params
|
|
57
|
+
subAction: z.enum(['add', 'delete']).optional().describe("Add or delete sub-item"),
|
|
58
|
+
skillName: z.string().optional(),
|
|
59
|
+
proficiencyLevel: z.string().optional(),
|
|
60
|
+
yearsExperience: z.number().optional(),
|
|
61
|
+
itemId: z.string().uuid().optional().describe("ID of skill/cert/license to delete"),
|
|
62
|
+
certName: z.string().optional(),
|
|
63
|
+
certIssuer: z.string().optional(),
|
|
64
|
+
certIssuedAt: z.string().optional(),
|
|
65
|
+
certExpiresAt: z.string().optional(),
|
|
66
|
+
}).describe("Find and view worker profiles. Actions: search (requires taskDescription), get_profile (requires workerId), update_profile, manage_skills, manage_certifications.");
|
|
50
67
|
export function registerWorkerTools(server, client) {
|
|
51
68
|
const workersHandler = async (args, extra) => {
|
|
52
69
|
try {
|
|
53
70
|
switch (args.action) {
|
|
54
71
|
case 'search': {
|
|
55
72
|
const auth = requireScopes(extra, ['workers:read']);
|
|
56
|
-
if (!args.taskDescription)
|
|
57
|
-
throw new Error("Missing taskDescription for search action");
|
|
73
|
+
// if (!args.taskDescription) throw new Error("Missing taskDescription for search action");
|
|
58
74
|
const searchArgs = {
|
|
59
|
-
taskDescription: args.taskDescription,
|
|
75
|
+
taskDescription: args.taskDescription, // This might be optional?
|
|
60
76
|
location: args.location,
|
|
61
|
-
skills: args.skills
|
|
77
|
+
skills: args.skills,
|
|
78
|
+
minRating: args.minRating,
|
|
79
|
+
maxRate: args.maxRate,
|
|
80
|
+
limit: args.limit
|
|
62
81
|
};
|
|
63
82
|
const result = await client.searchWorkers(searchArgs, auth?.token);
|
|
64
83
|
const widgetSessionId = stableSessionId('workers', JSON.stringify({ userId: auth?.userId ?? auth?.clientId, args: searchArgs }));
|
|
65
|
-
|
|
84
|
+
const workers = result.workers || [];
|
|
85
|
+
if (workers.length === 0) {
|
|
66
86
|
return {
|
|
67
87
|
structuredContent: { workers: [], suggestedBudget: result?.suggestedBudget },
|
|
68
88
|
content: [{ type: 'text', text: 'No workers found matching criteria.' }],
|
|
@@ -71,12 +91,12 @@ export function registerWorkerTools(server, client) {
|
|
|
71
91
|
}
|
|
72
92
|
return {
|
|
73
93
|
structuredContent: {
|
|
74
|
-
workers:
|
|
75
|
-
id: w.id, name: w.
|
|
94
|
+
workers: workers.map((w) => ({
|
|
95
|
+
id: w.id, name: w.display_name, rating: w.average_rating, rate: w.hourly_rate, skills: w.skills
|
|
76
96
|
})),
|
|
77
97
|
suggestedBudget: result.suggestedBudget,
|
|
78
98
|
},
|
|
79
|
-
content: [{ type: 'text', text: `Found ${
|
|
99
|
+
content: [{ type: 'text', text: `Found ${workers.length} workers.` }],
|
|
80
100
|
_meta: { ...toolInvocationMeta('Searching workers', 'Worker results ready', widgetSessionId), result }
|
|
81
101
|
};
|
|
82
102
|
}
|
|
@@ -88,10 +108,66 @@ export function registerWorkerTools(server, client) {
|
|
|
88
108
|
const widgetSessionId = `worker:${args.workerId}`;
|
|
89
109
|
return {
|
|
90
110
|
structuredContent: { worker },
|
|
91
|
-
content: [{ type: 'text', text: `Worker profile loaded: ${worker?.
|
|
111
|
+
content: [{ type: 'text', text: `Worker profile loaded: ${worker?.displayName ?? args.workerId}` }],
|
|
92
112
|
_meta: { ...toolInvocationMeta('Loading profile', 'Worker profile ready', widgetSessionId), worker }
|
|
93
113
|
};
|
|
94
114
|
}
|
|
115
|
+
case 'update_profile': {
|
|
116
|
+
const auth = requireScopes(extra, ['workers:write']);
|
|
117
|
+
const updateData = {};
|
|
118
|
+
if (args.bio)
|
|
119
|
+
updateData.bio = args.bio;
|
|
120
|
+
if (args.hourlyRate)
|
|
121
|
+
updateData.hourlyRate = args.hourlyRate;
|
|
122
|
+
if (args.availabilityStatus)
|
|
123
|
+
updateData.availabilityStatus = args.availabilityStatus;
|
|
124
|
+
const res = await client.updateWorkerProfile(updateData, auth?.token);
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: 'text', text: "Profile updated successfully." }],
|
|
127
|
+
structuredContent: { profile: res.profile }
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
case 'manage_skills': {
|
|
131
|
+
const auth = requireScopes(extra, ['workers:write']);
|
|
132
|
+
if (args.subAction === 'add') {
|
|
133
|
+
if (!args.skillName || !args.proficiencyLevel)
|
|
134
|
+
throw new Error("Missing skillName or proficiencyLevel");
|
|
135
|
+
const res = await client.addSkill({
|
|
136
|
+
name: args.skillName,
|
|
137
|
+
proficiencyLevel: args.proficiencyLevel,
|
|
138
|
+
yearsExperience: args.yearsExperience || 0
|
|
139
|
+
}, auth?.token);
|
|
140
|
+
return { content: [{ type: 'text', text: "Skill added." }], structuredContent: { skill: res.skill } };
|
|
141
|
+
}
|
|
142
|
+
else if (args.subAction === 'delete') {
|
|
143
|
+
if (!args.itemId)
|
|
144
|
+
throw new Error("Missing itemId (skill ID) to delete");
|
|
145
|
+
await client.deleteSkill(args.itemId, auth?.token);
|
|
146
|
+
return { content: [{ type: 'text', text: "Skill deleted." }] };
|
|
147
|
+
}
|
|
148
|
+
throw new Error("Invalid subAction for manage_skills");
|
|
149
|
+
}
|
|
150
|
+
case 'manage_certifications': {
|
|
151
|
+
const auth = requireScopes(extra, ['workers:write']);
|
|
152
|
+
if (args.subAction === 'add') {
|
|
153
|
+
if (!args.certName || !args.certIssuer || !args.certIssuedAt)
|
|
154
|
+
throw new Error("Missing cert details");
|
|
155
|
+
const res = await client.addCertification({
|
|
156
|
+
name: args.certName,
|
|
157
|
+
issuer: args.certIssuer,
|
|
158
|
+
issuedAt: args.certIssuedAt,
|
|
159
|
+
expiresAt: args.certExpiresAt
|
|
160
|
+
}, auth?.token);
|
|
161
|
+
return { content: [{ type: 'text', text: "Certification added." }], structuredContent: { certification: res.certification } };
|
|
162
|
+
}
|
|
163
|
+
else if (args.subAction === 'delete') {
|
|
164
|
+
if (!args.itemId)
|
|
165
|
+
throw new Error("Missing itemId (cert ID) to delete");
|
|
166
|
+
await client.deleteCertification(args.itemId, auth?.token);
|
|
167
|
+
return { content: [{ type: 'text', text: "Certification deleted." }] };
|
|
168
|
+
}
|
|
169
|
+
throw new Error("Invalid subAction for manage_certifications");
|
|
170
|
+
}
|
|
95
171
|
default:
|
|
96
172
|
throw new Error(`Unknown action: ${args.action}`);
|
|
97
173
|
}
|
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.29",
|
|
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.29",
|
|
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.29",
|
|
41
41
|
"transport": {
|
|
42
42
|
"type": "stdio"
|
|
43
43
|
},
|