@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.
@@ -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
  }
@@ -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
- }).describe("Manage user account and credits. Actions: get_my_profile, get_balance.");
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
- // Placeholder for future implementation
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
- content: [{ type: 'text', text: "Transaction history is not yet available via MCP." }],
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
  }
@@ -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
+ }
@@ -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
- location: CreateTaskSchema.shape.location.optional(),
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
- requirements: CreateTaskSchema.shape.requirements.optional(),
63
- entityId: CreateTaskSchema.shape.entityId.optional(),
64
- // Get/Update/Cancel params
65
- taskId: z.string().uuid().optional().describe("Task ID for get, update, or cancel actions"),
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
- }).describe("Manage tasks lifecycle: create, read, update, cancel. Actions determine required fields.");
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
- location: args.location,
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: `My tasks:\n${summary}` }],
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);
@@ -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
- // Get params
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
- }).describe("Find and view worker profiles. Actions: search, get_profile.");
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
- if (!result.workers || result.workers.length === 0) {
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: result.workers.map((w) => ({
75
- id: w.id, name: w.name, rating: w.rating, distanceMiles: w.distanceMiles, estimatedArrival: w.estimatedArrival,
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 ${result.workers.length} workers. Suggested budget: $${result.suggestedBudget}` }],
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?.id ?? args.workerId}` }],
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.26",
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.26",
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.26",
40
+ "version": "1.0.29",
41
41
  "transport": {
42
42
  "type": "stdio"
43
43
  },