@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 +7 -9
- package/dist/tools/account.js +54 -52
- package/dist/tools/general.js +98 -0
- package/dist/tools/index.js +10 -2
- package/dist/tools/qualification.js +80 -223
- package/dist/tools/tasks.js +121 -234
- package/dist/tools/verbs.js +75 -15
- package/dist/tools/workers.js +63 -90
- package/package.json +1 -1
- package/server.json +2 -2
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
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
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
|
|
package/dist/tools/account.js
CHANGED
|
@@ -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
|
-
|
|
29
|
-
|
|
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
|
|
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
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
sessionId: z.string().uuid().describe("
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
}
|