@hapticpaper/mcp-server 1.0.17 → 1.0.19

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/README.md CHANGED
@@ -32,15 +32,13 @@ Your tokens are stored securely in `~/.hapticpaper/tokens.json`.
32
32
 
33
33
  ## Available Tools
34
34
 
35
- | Tool | Description |
36
- |------|-------------|
37
- | `create_task` | Create a new task for a human worker |
38
- | `get_task` | Get details of a specific task |
39
- | `list_my_tasks` | List your tasks |
40
- | `cancel_task` | Cancel a pending task |
41
- | `search_workers` | Find available workers |
42
- | `get_estimate` | Get price/time estimates |
43
- | `get_account` | View your account info |
35
+ | Tool | Description | Actions |
36
+ |------|-------------|---------|
37
+ | `tasks` | Manage task lifecycle | `create`, `get`, `list`, `update`, `cancel` |
38
+ | `workers` | Browse and view workers | `search`, `get_profile` |
39
+ | `account` | Manage your account | `get_my_profile`, `get_balance` |
40
+ | `qualifications` | Onboarding & Interviews | `start`, `continue`, `check_status` |
41
+ | `haptic` | Discovery & Helpers | `intent`, `search_docs` |
44
42
 
45
43
  ## Manual Authentication
46
44
 
@@ -3,10 +3,7 @@ import { requireScopes } from "../auth/access.js";
3
3
  import { HAPTICPAPER_WIDGET_URI } from "../constants/widget.js";
4
4
  function oauthSecuritySchemes(scopes) {
5
5
  return [
6
- {
7
- type: 'oauth2',
8
- scopes,
9
- },
6
+ { type: 'oauth2', scopes },
10
7
  ];
11
8
  }
12
9
  function toolDescriptorMeta(invoking, invoked, scopes) {
@@ -25,63 +22,68 @@ function toolInvocationMeta(invoking, invoked, widgetSessionId) {
25
22
  'openai/widgetSessionId': widgetSessionId,
26
23
  };
27
24
  }
28
- // No input needed - just returns current user's account info
29
- const GetAccountSchema = z.object({}).describe("No parameters required");
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.");
30
28
  export function registerAccountTools(server, client) {
31
- const getAccountInvoking = 'Fetching your account details';
32
- const getAccountInvoked = 'Account details ready';
33
- const getAccountHandler = async (_args, extra) => {
29
+ const accountHandler = async (args, extra) => {
34
30
  try {
35
- // For HTTP transport, auth comes from extra.authInfo. For stdio, auth is handled by tokenProvider.
36
31
  const auth = requireScopes(extra, ['account:read']);
37
- const result = await client.getAccount(auth?.token);
38
- const account = result.data;
39
- const widgetSessionId = `account:${account.userId}`;
40
- return {
41
- structuredContent: {
42
- account: {
43
- displayName: account.displayName,
44
- email: account.email,
45
- balanceCredits: account.balanceCredits,
46
- hasPaymentMethod: account.hasPaymentMethod,
47
- },
48
- },
49
- content: [
50
- {
51
- type: 'text',
52
- text: `Welcome, ${account.displayName}! Your Haptic Paper balance is ${account.balanceCredits} credits.${account.hasPaymentMethod ? '' : ' Add a payment method to create paid tasks.'}`,
53
- },
54
- ],
55
- _meta: {
56
- ...toolInvocationMeta(getAccountInvoking, getAccountInvoked, widgetSessionId),
57
- account,
58
- },
32
+ // Common logic for fetching account data (used by profile and balance actions)
33
+ const fetchAccount = async () => {
34
+ const result = await client.getAccount(auth?.token);
35
+ return result.data;
59
36
  };
37
+ switch (args.action) {
38
+ case 'get_my_profile': {
39
+ const account = await fetchAccount();
40
+ const widgetSessionId = `account:${account.userId}`;
41
+ return {
42
+ structuredContent: {
43
+ account: {
44
+ userId: account.userId,
45
+ displayName: account.displayName,
46
+ email: account.email
47
+ }
48
+ },
49
+ content: [{ type: 'text', text: `Logged in as ${account.displayName} (${account.email})` }],
50
+ _meta: { ...toolInvocationMeta('Fetching profile', 'Profile loaded', widgetSessionId), account }
51
+ };
52
+ }
53
+ case 'get_balance': {
54
+ const account = await fetchAccount();
55
+ const widgetSessionId = `balance:${account.userId}`;
56
+ return {
57
+ structuredContent: {
58
+ balanceCredits: account.balanceCredits,
59
+ hasPaymentMethod: account.hasPaymentMethod
60
+ },
61
+ content: [{ type: 'text', text: `Current Balance: ${account.balanceCredits} credits.${account.hasPaymentMethod ? '' : ' (No payment method added)'}` }],
62
+ _meta: { ...toolInvocationMeta('Checking balance', 'Balance loaded', widgetSessionId), account }
63
+ };
64
+ }
65
+ case 'transaction_history': {
66
+ // Placeholder for future implementation
67
+ return {
68
+ content: [{ type: 'text', text: "Transaction history is not yet available via MCP." }],
69
+ isError: false
70
+ };
71
+ }
72
+ default:
73
+ throw new Error(`Unknown action: ${args.action}`);
74
+ }
60
75
  }
61
76
  catch (err) {
62
77
  return {
63
- content: [
64
- {
65
- type: 'text',
66
- text: `Error fetching account: ${err.response?.data?.error?.message || err.message || 'Unknown error'}`,
67
- },
68
- ],
78
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
69
79
  isError: true,
70
80
  };
71
81
  }
72
82
  };
73
- // Register as both naming conventions for compatibility
74
- for (const toolName of ['get_account', 'account_get']) {
75
- server.registerTool(toolName, {
76
- title: 'Get account details',
77
- description: 'Get your Haptic Paper account information including display name, email, and credit balance. Use this to check how much credit is available for creating tasks.',
78
- inputSchema: GetAccountSchema,
79
- annotations: {
80
- readOnlyHint: true,
81
- openWorldHint: false,
82
- destructiveHint: false,
83
- },
84
- _meta: toolDescriptorMeta(getAccountInvoking, getAccountInvoked, ['account:read']),
85
- }, getAccountHandler);
86
- }
83
+ server.registerTool('account', {
84
+ title: 'Manage Account',
85
+ description: 'Manage user account, profile, and credits. Actions: get_my_profile, get_balance. Use this to check who is logged in or their credit balance.',
86
+ inputSchema: AccountToolSchema,
87
+ _meta: toolDescriptorMeta('Accessing account', 'Account operation complete', ['account:read']),
88
+ }, accountHandler);
87
89
  }
@@ -0,0 +1,98 @@
1
+ import { z } from "zod";
2
+ import { HAPTICPAPER_WIDGET_URI } from "../constants/widget.js";
3
+ import crypto from 'node:crypto';
4
+ // --- Shared Helpers ---
5
+ function stableSessionId(prefix, value) {
6
+ const raw = value ?? 'unknown';
7
+ const hash = crypto.createHash('sha256').update(raw).digest('hex').slice(0, 16);
8
+ return `${prefix}:${hash}`;
9
+ }
10
+ function toolDescriptorMeta(invoking, invoked, scopes = []) {
11
+ return {
12
+ 'openai/outputTemplate': HAPTICPAPER_WIDGET_URI,
13
+ 'openai/toolInvocation/invoking': invoking,
14
+ 'openai/toolInvocation/invoked': invoked,
15
+ 'openai/widgetAccessible': true,
16
+ ...(scopes.length > 0 ? {
17
+ securitySchemes: [{ type: 'oauth2', scopes }],
18
+ } : {}),
19
+ };
20
+ }
21
+ function toolInvocationMeta(invoking, invoked, widgetSessionId) {
22
+ return {
23
+ 'openai/toolInvocation/invoking': invoking,
24
+ 'openai/toolInvocation/invoked': invoked,
25
+ 'openai/widgetSessionId': widgetSessionId,
26
+ };
27
+ }
28
+ // --- Schema Definitions ---
29
+ const IntentSchema = z.object({
30
+ message: z.string().describe("The user's message expressing interest, a skill, or a potential need."),
31
+ context: z.array(z.object({
32
+ role: z.enum(['user', 'assistant']),
33
+ content: z.string(),
34
+ })).optional().describe("Previous conversation context to help understand the intent."),
35
+ sessionId: z.string().uuid().optional().describe("If continuing an existing qualification/intent session."),
36
+ });
37
+ const HapticToolSchema = z.object({
38
+ action: z.enum(['intent', 'search_docs']).describe("Action to perform."),
39
+ // Intent params
40
+ message: IntentSchema.shape.message.optional(),
41
+ context: IntentSchema.shape.context.optional(),
42
+ sessionId: IntentSchema.shape.sessionId.optional(),
43
+ // Search docs params
44
+ query: z.string().optional().describe("Query for searching documentation (not yet implemented)"),
45
+ }).describe("General purpose helper for Haptic Paper. Actions: intent (Analyze user intent/start work), search_docs (Find help).");
46
+ export function registerGeneralTools(server, client) {
47
+ const hapticHandler = async (args, _extra) => {
48
+ try {
49
+ switch (args.action) {
50
+ case 'intent': {
51
+ if (!args.message)
52
+ throw new Error("Missing message for intent action");
53
+ // If we have a sessionId, it's likely a continue
54
+ if (args.sessionId) {
55
+ const result = await client.continueQualification(args.sessionId, args.message);
56
+ const widgetSessionId = stableSessionId('qual', args.sessionId);
57
+ return {
58
+ content: [{ type: 'text', text: result.nextPrompt?.text || "Processed." }],
59
+ structuredContent: result, // Pass full result
60
+ _meta: { ...toolInvocationMeta('Processing...', 'Done', widgetSessionId), result }
61
+ };
62
+ }
63
+ // Otherwise it's a discovery/start
64
+ const result = await client.discoverEarningOpportunity({
65
+ userMessage: args.message,
66
+ conversationContext: args.context
67
+ });
68
+ const widgetSessionId = stableSessionId('qual', result.sessionId);
69
+ return {
70
+ content: [{ type: 'text', text: result.nextPrompt?.text || "Started qualification." }],
71
+ structuredContent: result,
72
+ _meta: { ...toolInvocationMeta('Starting...', 'Started', widgetSessionId), result }
73
+ };
74
+ }
75
+ case 'search_docs': {
76
+ return {
77
+ content: [{ type: 'text', text: "Documentation search is not yet implemented. Please refer to standard Haptic Paper formatting guides." }],
78
+ isError: false
79
+ };
80
+ }
81
+ default:
82
+ throw new Error(`Unknown action: ${args.action}`);
83
+ }
84
+ }
85
+ catch (err) {
86
+ return {
87
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
88
+ isError: true,
89
+ };
90
+ }
91
+ };
92
+ server.registerTool('haptic', {
93
+ title: 'Haptic Helper',
94
+ description: 'General purpose helper for Haptic Paper. Actions: intent, search_docs. Use `intent` when the user makes a vague request or wants to start working ("I want to work").',
95
+ inputSchema: HapticToolSchema,
96
+ _meta: toolDescriptorMeta('Analyzing...', 'Analysis complete'),
97
+ }, hapticHandler);
98
+ }
@@ -1,4 +1,12 @@
1
- import { registerVerbTools } from "./verbs.js";
1
+ import { registerTasksTools } from "./tasks.js";
2
+ import { registerWorkerTools } from "./workers.js";
3
+ import { registerAccountTools } from "./account.js";
4
+ import { registerQualificationTools } from "./qualification.js";
5
+ import { registerGeneralTools } from "./general.js";
2
6
  export function registerAllTools(server, client) {
3
- registerVerbTools(server, client);
7
+ registerTasksTools(server, client);
8
+ registerWorkerTools(server, client);
9
+ registerAccountTools(server, client);
10
+ registerQualificationTools(server, client);
11
+ registerGeneralTools(server, client);
4
12
  }
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { HAPTICPAPER_WIDGET_URI } from "../constants/widget.js";
3
3
  import crypto from 'node:crypto';
4
+ // --- Shared Helpers ---
4
5
  function stableSessionId(prefix, value) {
5
6
  const raw = value ?? 'unknown';
6
7
  const hash = crypto.createHash('sha256').update(raw).digest('hex').slice(0, 16);
@@ -24,7 +25,7 @@ function toolInvocationMeta(invoking, invoked, widgetSessionId) {
24
25
  'openai/widgetSessionId': widgetSessionId,
25
26
  };
26
27
  }
27
- // Input schema for discover_earning_opportunity
28
+ // --- Schema Definitions ---
28
29
  const DiscoverEarningSchema = z.object({
29
30
  userMessage: z.string().describe("The user's message expressing interest in earning"),
30
31
  conversationContext: z.array(z.object({
@@ -32,223 +33,86 @@ const DiscoverEarningSchema = z.object({
32
33
  content: z.string(),
33
34
  })).optional().describe("Previous conversation for context"),
34
35
  });
35
- // Input schema for continue_qualification
36
36
  const ContinueQualificationSchema = z.object({
37
37
  sessionId: z.string().uuid().describe("The qualification session ID"),
38
38
  userResponse: z.string().describe("The user's response to the qualification question"),
39
39
  });
40
- // Input schema for get_qualification_status
41
- const GetQualificationStatusSchema = z.object({
42
- sessionId: z.string().uuid().describe("The qualification session ID"),
43
- });
44
- // Input schema for complete_qualification
45
- const CompleteQualificationSchema = z.object({
46
- sessionId: z.string().uuid().describe("The qualification session ID"),
47
- });
40
+ const QualificationsToolSchema = z.object({
41
+ action: z.enum(['start', 'continue', 'check_status']).describe("Action to perform on the qualification session"),
42
+ // Start params
43
+ userMessage: DiscoverEarningSchema.shape.userMessage.optional(),
44
+ conversationContext: DiscoverEarningSchema.shape.conversationContext.optional(),
45
+ // Continue params
46
+ sessionId: z.string().uuid().optional().describe("Session ID for continue or check_status actions"),
47
+ userResponse: ContinueQualificationSchema.shape.userResponse.optional(),
48
+ }).describe("Handle the conversational onboarding/qualification flow. Actions: start, continue, check_status.");
48
49
  export function registerQualificationTools(server, client) {
49
- // ==========================================
50
- // discover_earning_opportunity
51
- // ==========================================
52
- const discoverInvoking = 'Setting up your earning profile...';
53
- const discoverInvoked = 'Profile setup ready!';
54
- const discoverHandler = async (args, _extra) => {
50
+ const qualificationsHandler = async (args, _extra) => {
55
51
  try {
56
- // Detect intent and start qualification session
57
- const result = await client.discoverEarningOpportunity({
58
- userMessage: args.userMessage,
59
- conversationContext: args.conversationContext,
60
- });
61
- const widgetSessionId = stableSessionId('qual', result.sessionId);
62
- return {
63
- structuredContent: {
64
- sessionId: result.sessionId,
65
- intentDetected: result.intentDetected,
66
- intentType: result.intentType,
67
- confidenceScore: result.confidenceScore,
68
- nextQuestion: result.nextPrompt?.text,
69
- },
70
- content: [
71
- {
72
- type: 'text',
73
- text: result.nextPrompt?.text ?? 'Let me help you get started with earning opportunities!',
74
- },
75
- ],
76
- _meta: {
77
- ...toolInvocationMeta(discoverInvoking, discoverInvoked, widgetSessionId),
78
- result,
79
- },
80
- };
81
- }
82
- catch (err) {
83
- return {
84
- content: [{ type: 'text', text: `Error: ${err.message}` }],
85
- isError: true,
86
- };
87
- }
88
- };
89
- // Register with multiple names for compatibility
90
- for (const toolName of ['discover_earning_opportunity', 'qualification_start']) {
91
- server.registerTool(toolName, {
92
- title: 'Discover earning opportunities',
93
- description: 'Detect when a user expresses interest in earning money and start a conversational qualification flow to build their profile. No OAuth required - works for new users.',
94
- inputSchema: DiscoverEarningSchema,
95
- annotations: {
96
- readOnlyHint: false,
97
- openWorldHint: false,
98
- destructiveHint: false,
99
- },
100
- _meta: toolDescriptorMeta(discoverInvoking, discoverInvoked),
101
- }, discoverHandler);
102
- }
103
- // ==========================================
104
- // continue_qualification
105
- // ==========================================
106
- const continueInvoking = 'Processing your response...';
107
- const continueInvoked = 'Ready for next step';
108
- const continueHandler = async (args, _extra) => {
109
- try {
110
- const result = await client.continueQualification(args.sessionId, args.userResponse);
111
- const widgetSessionId = stableSessionId('qual', args.sessionId);
112
- // Check if qualification is complete
113
- if (result.status === 'completed' || result.completionScore >= 0.75) {
114
- return {
115
- structuredContent: {
116
- sessionId: args.sessionId,
117
- status: 'completed',
118
- completionScore: result.completionScore,
119
- profileSummary: result.profileSummary,
120
- },
121
- content: [
122
- {
123
- type: 'text',
124
- text: result.nextPrompt?.text ?? `Great! Your profile is now ${Math.round(result.completionScore * 100)}% complete. You're all set to start receiving task opportunities!`,
52
+ switch (args.action) {
53
+ case 'start': {
54
+ if (!args.userMessage)
55
+ throw new Error("Missing userMessage for start action");
56
+ // Detect intent and start qualification session
57
+ const result = await client.discoverEarningOpportunity({
58
+ userMessage: args.userMessage,
59
+ conversationContext: args.conversationContext,
60
+ });
61
+ const widgetSessionId = stableSessionId('qual', result.sessionId);
62
+ return {
63
+ structuredContent: {
64
+ sessionId: result.sessionId,
65
+ intentDetected: result.intentDetected,
66
+ intentType: result.intentType,
67
+ confidenceScore: result.confidenceScore,
68
+ nextQuestion: result.nextPrompt?.text,
69
+ },
70
+ content: [{ type: 'text', text: result.nextPrompt?.text ?? 'Let me help you get started with earning opportunities!' }],
71
+ _meta: { ...toolInvocationMeta('Starting qualification', 'Profile setup ready!', widgetSessionId), result }
72
+ };
73
+ }
74
+ case 'continue': {
75
+ if (!args.sessionId || !args.userResponse)
76
+ throw new Error("Missing sessionId or userResponse for continue action");
77
+ const result = await client.continueQualification(args.sessionId, args.userResponse);
78
+ const widgetSessionId = stableSessionId('qual', args.sessionId);
79
+ // Check if qualification is complete
80
+ if (result.status === 'completed' || result.completionScore >= 0.75) {
81
+ return {
82
+ structuredContent: {
83
+ sessionId: args.sessionId, status: 'completed', completionScore: result.completionScore, profileSummary: result.profileSummary,
84
+ },
85
+ content: [{ type: 'text', text: result.nextPrompt?.text ?? `Great! Your profile is now ${Math.round(result.completionScore * 100)}% complete. You're all set to start receiving task opportunities!` }],
86
+ _meta: { ...toolInvocationMeta('Processing response', 'Ready for next step', widgetSessionId), result }
87
+ };
88
+ }
89
+ return {
90
+ structuredContent: {
91
+ sessionId: args.sessionId, status: result.status, currentStage: result.currentStage,
92
+ completionScore: result.completionScore, questionCount: result.questionCount, nextQuestion: result.nextPrompt?.text,
125
93
  },
126
- ],
127
- _meta: {
128
- ...toolInvocationMeta(continueInvoking, continueInvoked, widgetSessionId),
129
- result,
130
- },
131
- };
94
+ content: [{ type: 'text', text: result.nextPrompt?.text ?? 'Thanks! What else would you like to share?' }],
95
+ _meta: { ...toolInvocationMeta('Processing response', 'Ready for next step', widgetSessionId), result }
96
+ };
97
+ }
98
+ case 'check_status': {
99
+ if (!args.sessionId)
100
+ throw new Error("Missing sessionId for check_status action");
101
+ const result = await client.getQualificationStatus(args.sessionId);
102
+ const widgetSessionId = stableSessionId('qual', args.sessionId);
103
+ return {
104
+ structuredContent: {
105
+ sessionId: args.sessionId, status: result.status, currentStage: result.currentStage,
106
+ completionScore: result.completionScore, questionCount: result.questionCount,
107
+ extractedSkills: result.extractedSkills, extractedLocation: result.extractedLocation,
108
+ },
109
+ content: [{ type: 'text', text: `Profile is ${Math.round(result.completionScore * 100)}% complete. Current stage: ${result.currentStage}` }],
110
+ _meta: { ...toolInvocationMeta('Checking status', 'Status retrieved', widgetSessionId), result }
111
+ };
112
+ }
113
+ default:
114
+ throw new Error(`Unknown action: ${args.action}`);
132
115
  }
133
- return {
134
- structuredContent: {
135
- sessionId: args.sessionId,
136
- status: result.status,
137
- currentStage: result.currentStage,
138
- completionScore: result.completionScore,
139
- questionCount: result.questionCount,
140
- nextQuestion: result.nextPrompt?.text,
141
- },
142
- content: [
143
- {
144
- type: 'text',
145
- text: result.nextPrompt?.text ?? 'Thanks! What else would you like to share?',
146
- },
147
- ],
148
- _meta: {
149
- ...toolInvocationMeta(continueInvoking, continueInvoked, widgetSessionId),
150
- result,
151
- },
152
- };
153
- }
154
- catch (err) {
155
- return {
156
- content: [{ type: 'text', text: `Error: ${err.message}` }],
157
- isError: true,
158
- };
159
- }
160
- };
161
- for (const toolName of ['continue_qualification', 'qualification_continue']) {
162
- server.registerTool(toolName, {
163
- title: 'Continue qualification',
164
- description: 'Submit a user response and get the next qualification question. Use this to continue the conversational profile-building flow.',
165
- inputSchema: ContinueQualificationSchema,
166
- annotations: {
167
- readOnlyHint: false,
168
- openWorldHint: false,
169
- destructiveHint: false,
170
- },
171
- _meta: toolDescriptorMeta(continueInvoking, continueInvoked),
172
- }, continueHandler);
173
- }
174
- // ==========================================
175
- // get_qualification_status
176
- // ==========================================
177
- const statusInvoking = 'Checking qualification progress...';
178
- const statusInvoked = 'Status retrieved';
179
- const statusHandler = async (args, _extra) => {
180
- try {
181
- const result = await client.getQualificationStatus(args.sessionId);
182
- const widgetSessionId = stableSessionId('qual', args.sessionId);
183
- return {
184
- structuredContent: {
185
- sessionId: args.sessionId,
186
- status: result.status,
187
- currentStage: result.currentStage,
188
- completionScore: result.completionScore,
189
- questionCount: result.questionCount,
190
- extractedSkills: result.extractedSkills,
191
- extractedLocation: result.extractedLocation,
192
- },
193
- content: [
194
- {
195
- type: 'text',
196
- text: `Profile is ${Math.round(result.completionScore * 100)}% complete. Current stage: ${result.currentStage}`,
197
- },
198
- ],
199
- _meta: {
200
- ...toolInvocationMeta(statusInvoking, statusInvoked, widgetSessionId),
201
- result,
202
- },
203
- };
204
- }
205
- catch (err) {
206
- return {
207
- content: [{ type: 'text', text: `Error: ${err.message}` }],
208
- isError: true,
209
- };
210
- }
211
- };
212
- for (const toolName of ['get_qualification_status', 'qualification_status']) {
213
- server.registerTool(toolName, {
214
- title: 'Get qualification status',
215
- description: 'Check the progress of a qualification session including completion score and extracted profile data.',
216
- inputSchema: GetQualificationStatusSchema,
217
- annotations: {
218
- readOnlyHint: true,
219
- openWorldHint: false,
220
- destructiveHint: false,
221
- },
222
- _meta: toolDescriptorMeta(statusInvoking, statusInvoked),
223
- }, statusHandler);
224
- }
225
- // ==========================================
226
- // complete_qualification
227
- // ==========================================
228
- const completeInvoking = 'Finalizing your profile...';
229
- const completeInvoked = 'Profile complete!';
230
- const completeHandler = async (args, _extra) => {
231
- try {
232
- const result = await client.completeQualification(args.sessionId);
233
- const widgetSessionId = stableSessionId('qual', args.sessionId);
234
- return {
235
- structuredContent: {
236
- sessionId: args.sessionId,
237
- status: 'completed',
238
- profileId: result.workerProfileId,
239
- completionScore: result.completionScore,
240
- },
241
- content: [
242
- {
243
- type: 'text',
244
- text: `🎉 Your profile is complete! You'll now start seeing task opportunities that match your skills. Welcome to Haptic!`,
245
- },
246
- ],
247
- _meta: {
248
- ...toolInvocationMeta(completeInvoking, completeInvoked, widgetSessionId),
249
- result,
250
- },
251
- };
252
116
  }
253
117
  catch (err) {
254
118
  return {
@@ -257,17 +121,10 @@ export function registerQualificationTools(server, client) {
257
121
  };
258
122
  }
259
123
  };
260
- for (const toolName of ['complete_qualification', 'qualification_complete']) {
261
- server.registerTool(toolName, {
262
- title: 'Complete qualification',
263
- description: 'Finalize a qualification session and activate the worker profile.',
264
- inputSchema: CompleteQualificationSchema,
265
- annotations: {
266
- readOnlyHint: false,
267
- openWorldHint: false,
268
- destructiveHint: false,
269
- },
270
- _meta: toolDescriptorMeta(completeInvoking, completeInvoked),
271
- }, completeHandler);
272
- }
124
+ server.registerTool('qualifications', {
125
+ title: 'Qualification Flow',
126
+ description: 'Handle the conversational onboarding/qualification flow. Actions: start, continue, check_status. Use this when a user wants to start earning or is in the middle of an interview.',
127
+ inputSchema: QualificationsToolSchema,
128
+ _meta: toolDescriptorMeta('Qualification', 'Qualification step complete'),
129
+ }, qualificationsHandler);
273
130
  }