@hapticpaper/mcp-server 1.0.0

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.
@@ -0,0 +1,273 @@
1
+ import { z } from "zod";
2
+ import crypto from 'node:crypto';
3
+ const OUTPUT_TEMPLATE = 'ui://widget/hirehuman.html';
4
+ function stableSessionId(prefix, value) {
5
+ const raw = value ?? 'unknown';
6
+ const hash = crypto.createHash('sha256').update(raw).digest('hex').slice(0, 16);
7
+ return `${prefix}:${hash}`;
8
+ }
9
+ function toolDescriptorMeta(invoking, invoked, scopes = []) {
10
+ return {
11
+ 'openai/outputTemplate': OUTPUT_TEMPLATE,
12
+ 'openai/toolInvocation/invoking': invoking,
13
+ 'openai/toolInvocation/invoked': invoked,
14
+ 'openai/widgetAccessible': true,
15
+ ...(scopes.length > 0 ? {
16
+ securitySchemes: [{ type: 'oauth2', scopes }],
17
+ } : {}),
18
+ };
19
+ }
20
+ function toolInvocationMeta(invoking, invoked, widgetSessionId) {
21
+ return {
22
+ 'openai/toolInvocation/invoking': invoking,
23
+ 'openai/toolInvocation/invoked': invoked,
24
+ 'openai/widgetSessionId': widgetSessionId,
25
+ };
26
+ }
27
+ // Input schema for discover_earning_opportunity
28
+ const DiscoverEarningSchema = z.object({
29
+ userMessage: z.string().describe("The user's message expressing interest in earning"),
30
+ conversationContext: z.array(z.object({
31
+ role: z.enum(['user', 'assistant']),
32
+ content: z.string(),
33
+ })).optional().describe("Previous conversation for context"),
34
+ });
35
+ // Input schema for continue_qualification
36
+ const ContinueQualificationSchema = z.object({
37
+ sessionId: z.string().uuid().describe("The qualification session ID"),
38
+ userResponse: z.string().describe("The user's response to the qualification question"),
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
+ });
48
+ 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) => {
55
+ 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!`,
125
+ },
126
+ ],
127
+ _meta: {
128
+ ...toolInvocationMeta(continueInvoking, continueInvoked, widgetSessionId),
129
+ result,
130
+ },
131
+ };
132
+ }
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
+ }
253
+ catch (err) {
254
+ return {
255
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
256
+ isError: true,
257
+ };
258
+ }
259
+ };
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
+ }
273
+ }
@@ -0,0 +1,246 @@
1
+ import { z } from "zod";
2
+ import { requireScopes } from "../auth/access.js";
3
+ import crypto from 'node:crypto';
4
+ const OUTPUT_TEMPLATE = 'ui://widget/hirehuman.html';
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 oauthSecuritySchemes(scopes) {
11
+ return [
12
+ {
13
+ type: 'oauth2',
14
+ scopes,
15
+ },
16
+ ];
17
+ }
18
+ function toolDescriptorMeta(invoking, invoked, scopes) {
19
+ return {
20
+ 'openai/outputTemplate': OUTPUT_TEMPLATE,
21
+ 'openai/toolInvocation/invoking': invoking,
22
+ 'openai/toolInvocation/invoked': invoked,
23
+ 'openai/widgetAccessible': true,
24
+ securitySchemes: oauthSecuritySchemes(scopes),
25
+ };
26
+ }
27
+ function toolInvocationMeta(invoking, invoked, widgetSessionId) {
28
+ return {
29
+ 'openai/toolInvocation/invoking': invoking,
30
+ 'openai/toolInvocation/invoked': invoked,
31
+ 'openai/widgetSessionId': widgetSessionId,
32
+ };
33
+ }
34
+ const CreateTaskSchema = z.object({
35
+ title: z.string().min(5).max(200).describe("Short title for the task"),
36
+ description: z.string().min(20).max(5000).describe("Detailed description of what needs to be done"),
37
+ budget: z.number().min(5).max(500).describe("Payment to worker in dollars"),
38
+ location: z.object({
39
+ address: z.string().describe("Street address where task will be performed"),
40
+ city: z.string().optional(),
41
+ state: z.string().optional(),
42
+ zip: z.string().optional(),
43
+ }).optional().describe("Location for physical tasks"),
44
+ deadline: z.string().datetime().optional().describe("When the task must be completed by"),
45
+ requirements: z.object({
46
+ proofOfWork: z.boolean().default(true),
47
+ gpsRequired: z.boolean().default(true)
48
+ }).optional(),
49
+ });
50
+ export function registerTaskTools(server, client) {
51
+ const createTaskInvoking = 'Creating your task';
52
+ const createTaskInvoked = 'Task created';
53
+ const createTaskHandler = async (args, extra) => {
54
+ try {
55
+ const auth = requireScopes(extra, ['tasks:write']);
56
+ const task = await client.createTask(args, auth.token);
57
+ const widgetSessionId = `task:${task.id}`;
58
+ return {
59
+ structuredContent: {
60
+ task: {
61
+ id: task.id,
62
+ title: task.title,
63
+ status: task.status,
64
+ budget: task.budget,
65
+ },
66
+ },
67
+ content: [{ type: 'text', text: `Created task “${task.title}” (ID: ${task.id}).` }],
68
+ _meta: {
69
+ ...toolInvocationMeta(createTaskInvoking, createTaskInvoked, widgetSessionId),
70
+ task,
71
+ },
72
+ };
73
+ }
74
+ catch (err) {
75
+ return {
76
+ content: [
77
+ {
78
+ type: 'text',
79
+ text: `Error creating task: ${err.response?.data?.error?.message || err.message}`,
80
+ },
81
+ ],
82
+ isError: true,
83
+ };
84
+ }
85
+ };
86
+ for (const toolName of ['create_task', 'tasks_create']) {
87
+ server.registerTool(toolName, {
88
+ title: 'Create a task',
89
+ description: 'Create a new task for a human worker. This is a write action and may charge the user.',
90
+ inputSchema: CreateTaskSchema,
91
+ annotations: {
92
+ readOnlyHint: false,
93
+ openWorldHint: false,
94
+ destructiveHint: false,
95
+ },
96
+ _meta: toolDescriptorMeta(createTaskInvoking, createTaskInvoked, ['tasks:write']),
97
+ }, createTaskHandler);
98
+ }
99
+ const getTaskInvoking = 'Fetching task details';
100
+ const getTaskInvoked = 'Task details ready';
101
+ const getTaskHandler = async (args, extra) => {
102
+ try {
103
+ const auth = requireScopes(extra, ['tasks:read']);
104
+ const task = await client.getTask(args.taskId, auth.token);
105
+ const widgetSessionId = `task:${args.taskId}`;
106
+ return {
107
+ structuredContent: {
108
+ task: {
109
+ id: task.id,
110
+ title: task.title,
111
+ status: task.status,
112
+ budget: task.budget,
113
+ },
114
+ },
115
+ content: [{ type: 'text', text: `Task ${task.id}: ${task.status}` }],
116
+ _meta: {
117
+ ...toolInvocationMeta(getTaskInvoking, getTaskInvoked, widgetSessionId),
118
+ task,
119
+ },
120
+ };
121
+ }
122
+ catch (err) {
123
+ return {
124
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
125
+ isError: true,
126
+ };
127
+ }
128
+ };
129
+ for (const toolName of ['get_task', 'tasks_get']) {
130
+ server.registerTool(toolName, {
131
+ title: 'Get task',
132
+ description: 'Get details of a task by ID.',
133
+ inputSchema: z.object({ taskId: z.string().uuid() }),
134
+ annotations: {
135
+ readOnlyHint: true,
136
+ openWorldHint: false,
137
+ destructiveHint: false,
138
+ },
139
+ _meta: toolDescriptorMeta(getTaskInvoking, getTaskInvoked, ['tasks:read']),
140
+ }, getTaskHandler);
141
+ }
142
+ const listTasksInvoking = 'Loading your tasks';
143
+ const listTasksInvoked = 'Tasks loaded';
144
+ const listTasksHandler = async (args, extra) => {
145
+ try {
146
+ const auth = requireScopes(extra, ['tasks:read']);
147
+ const tasks = await client.listTasks(args, auth.token);
148
+ const items = Array.isArray(tasks) ? tasks : tasks?.tasks ?? [];
149
+ const widgetSessionId = stableSessionId('tasks', auth.userId ?? auth.clientId);
150
+ if (items.length === 0) {
151
+ return {
152
+ structuredContent: { tasks: [] },
153
+ content: [{ type: 'text', text: 'No tasks found.' }],
154
+ _meta: {
155
+ ...toolInvocationMeta(listTasksInvoking, listTasksInvoked, widgetSessionId),
156
+ tasks: items,
157
+ },
158
+ };
159
+ }
160
+ const summary = items
161
+ .map((t) => `- [${t.status}] ${t.title} ($${t.budget}) (ID: ${t.id})`)
162
+ .join('\n');
163
+ return {
164
+ structuredContent: {
165
+ tasks: items.map((t) => ({
166
+ id: t.id,
167
+ title: t.title,
168
+ status: t.status,
169
+ budget: t.budget,
170
+ })),
171
+ },
172
+ content: [{ type: 'text', text: `My tasks:\n${summary}` }],
173
+ _meta: {
174
+ ...toolInvocationMeta(listTasksInvoking, listTasksInvoked, widgetSessionId),
175
+ tasks: items,
176
+ },
177
+ };
178
+ }
179
+ catch (err) {
180
+ return {
181
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
182
+ isError: true,
183
+ };
184
+ }
185
+ };
186
+ for (const toolName of ['list_my_tasks', 'tasks_list']) {
187
+ server.registerTool(toolName, {
188
+ title: 'List my tasks',
189
+ description: 'List tasks created by the current user.',
190
+ inputSchema: z.object({
191
+ status: z.enum(['open', 'assigned', 'completed', 'cancelled']).optional(),
192
+ limit: z.number().min(1).max(50).optional(),
193
+ }),
194
+ annotations: {
195
+ readOnlyHint: true,
196
+ openWorldHint: false,
197
+ destructiveHint: false,
198
+ },
199
+ _meta: toolDescriptorMeta(listTasksInvoking, listTasksInvoked, ['tasks:read']),
200
+ }, listTasksHandler);
201
+ }
202
+ const cancelTaskInvoking = 'Cancelling task';
203
+ const cancelTaskInvoked = 'Task cancelled';
204
+ const cancelTaskHandler = async (args, extra) => {
205
+ try {
206
+ const auth = requireScopes(extra, ['tasks:write']);
207
+ const res = await client.cancelTask(args.taskId, args.reason, auth.token);
208
+ const widgetSessionId = `task:${args.taskId}`;
209
+ return {
210
+ structuredContent: {
211
+ taskId: args.taskId,
212
+ refunded: Boolean(res?.refunded),
213
+ },
214
+ content: [
215
+ {
216
+ type: 'text',
217
+ text: `Cancelled task ${args.taskId}. Refunded: ${res?.refunded ? 'Yes' : 'No'}`,
218
+ },
219
+ ],
220
+ _meta: {
221
+ ...toolInvocationMeta(cancelTaskInvoking, cancelTaskInvoked, widgetSessionId),
222
+ result: res,
223
+ },
224
+ };
225
+ }
226
+ catch (err) {
227
+ return {
228
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
229
+ isError: true,
230
+ };
231
+ }
232
+ };
233
+ for (const toolName of ['cancel_task', 'tasks_cancel']) {
234
+ server.registerTool(toolName, {
235
+ title: 'Cancel task',
236
+ description: 'Cancel a pending task. This is a write action and may have irreversible side effects.',
237
+ inputSchema: z.object({ taskId: z.string().uuid(), reason: z.string().optional() }),
238
+ annotations: {
239
+ readOnlyHint: false,
240
+ openWorldHint: false,
241
+ destructiveHint: true,
242
+ },
243
+ _meta: toolDescriptorMeta(cancelTaskInvoking, cancelTaskInvoked, ['tasks:write']),
244
+ }, cancelTaskHandler);
245
+ }
246
+ }
@@ -0,0 +1,139 @@
1
+ import { z } from "zod";
2
+ import { requireScopes } from "../auth/access.js";
3
+ import crypto from 'node:crypto';
4
+ const OUTPUT_TEMPLATE = 'ui://widget/hirehuman.html';
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 oauthSecuritySchemes(scopes) {
11
+ return [
12
+ {
13
+ type: 'oauth2',
14
+ scopes,
15
+ },
16
+ ];
17
+ }
18
+ function toolDescriptorMeta(invoking, invoked, scopes) {
19
+ return {
20
+ 'openai/outputTemplate': OUTPUT_TEMPLATE,
21
+ 'openai/toolInvocation/invoking': invoking,
22
+ 'openai/toolInvocation/invoked': invoked,
23
+ 'openai/widgetAccessible': true,
24
+ securitySchemes: oauthSecuritySchemes(scopes),
25
+ };
26
+ }
27
+ function toolInvocationMeta(invoking, invoked, widgetSessionId) {
28
+ return {
29
+ 'openai/toolInvocation/invoking': invoking,
30
+ 'openai/toolInvocation/invoked': invoked,
31
+ 'openai/widgetSessionId': widgetSessionId,
32
+ };
33
+ }
34
+ const SearchWorkersSchema = z.object({
35
+ taskDescription: z.string().describe("Description of what needs to be done"),
36
+ location: z.object({
37
+ address: z.string(),
38
+ radiusMiles: z.number().default(10)
39
+ }).optional(),
40
+ skills: z.array(z.string()).optional()
41
+ });
42
+ export function registerWorkerTools(server, client) {
43
+ const searchWorkersInvoking = 'Searching nearby workers';
44
+ const searchWorkersInvoked = 'Worker results ready';
45
+ const searchWorkersHandler = async (args, extra) => {
46
+ try {
47
+ const auth = requireScopes(extra, ['workers:read']);
48
+ const result = await client.searchWorkers(args, auth.token);
49
+ const widgetSessionId = stableSessionId('workers', JSON.stringify({ userId: auth.userId ?? auth.clientId, args }));
50
+ if (!result.workers || result.workers.length === 0) {
51
+ return {
52
+ structuredContent: { workers: [], suggestedBudget: result?.suggestedBudget },
53
+ content: [{ type: 'text', text: 'No workers found matching criteria.' }],
54
+ _meta: {
55
+ ...toolInvocationMeta(searchWorkersInvoking, searchWorkersInvoked, widgetSessionId),
56
+ result,
57
+ },
58
+ };
59
+ }
60
+ return {
61
+ structuredContent: {
62
+ workers: result.workers.map((w) => ({
63
+ id: w.id,
64
+ name: w.name,
65
+ rating: w.rating,
66
+ distanceMiles: w.distanceMiles,
67
+ estimatedArrival: w.estimatedArrival,
68
+ })),
69
+ suggestedBudget: result.suggestedBudget,
70
+ },
71
+ content: [
72
+ {
73
+ type: 'text',
74
+ text: `Found ${result.workers.length} workers. Suggested budget: $${result.suggestedBudget}`,
75
+ },
76
+ ],
77
+ _meta: {
78
+ ...toolInvocationMeta(searchWorkersInvoking, searchWorkersInvoked, widgetSessionId),
79
+ result,
80
+ },
81
+ };
82
+ }
83
+ catch (err) {
84
+ return {
85
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
86
+ isError: true,
87
+ };
88
+ }
89
+ };
90
+ for (const toolName of ['search_workers', 'workers_search']) {
91
+ server.registerTool(toolName, {
92
+ title: 'Search workers',
93
+ description: 'Search for available workers. Returns anonymized profiles.',
94
+ inputSchema: SearchWorkersSchema,
95
+ annotations: {
96
+ readOnlyHint: true,
97
+ openWorldHint: false,
98
+ destructiveHint: false,
99
+ },
100
+ _meta: toolDescriptorMeta(searchWorkersInvoking, searchWorkersInvoked, ['workers:read']),
101
+ }, searchWorkersHandler);
102
+ }
103
+ const getWorkerInvoking = 'Loading worker profile';
104
+ const getWorkerInvoked = 'Worker profile ready';
105
+ const getWorkerProfileHandler = async (args, extra) => {
106
+ try {
107
+ const auth = requireScopes(extra, ['workers:read']);
108
+ const worker = await client.getWorkerProfile(args.workerId, auth.token);
109
+ const widgetSessionId = `worker:${args.workerId}`;
110
+ return {
111
+ structuredContent: { worker },
112
+ content: [{ type: 'text', text: `Worker profile loaded: ${worker?.id ?? args.workerId}` }],
113
+ _meta: {
114
+ ...toolInvocationMeta(getWorkerInvoking, getWorkerInvoked, widgetSessionId),
115
+ worker,
116
+ },
117
+ };
118
+ }
119
+ catch (err) {
120
+ return {
121
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
122
+ isError: true,
123
+ };
124
+ }
125
+ };
126
+ for (const toolName of ['get_worker_profile', 'workers_get_profile']) {
127
+ server.registerTool(toolName, {
128
+ title: 'Get worker profile',
129
+ description: 'Get a detailed worker profile by ID.',
130
+ inputSchema: z.object({ workerId: z.string().uuid() }),
131
+ annotations: {
132
+ readOnlyHint: true,
133
+ openWorldHint: false,
134
+ destructiveHint: false,
135
+ },
136
+ _meta: toolDescriptorMeta(getWorkerInvoking, getWorkerInvoked, ['workers:read']),
137
+ }, getWorkerProfileHandler);
138
+ }
139
+ }