humanlypossible 0.2.2 → 0.2.4

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.
@@ -1,851 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/mcp.ts
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { z } from "zod";
7
- var API_URL = process.env.HUMANLYPOSSIBLE_API_URL || "https://humanlypossible.ai";
8
- var API_KEY = process.env.HUMANLYPOSSIBLE_API_KEY;
9
- if (!API_KEY) {
10
- console.error(
11
- "HUMANLYPOSSIBLE_API_KEY not set. Starting in bootstrap mode \u2014 use the signup tool to authenticate."
12
- );
13
- }
14
- function mapHttpStatusToErrorCode(status) {
15
- switch (status) {
16
- case 401:
17
- return "UNAUTHORIZED";
18
- case 403:
19
- return "FORBIDDEN";
20
- case 404:
21
- return "NOT_FOUND";
22
- case 409:
23
- return "CONFLICT";
24
- case 400:
25
- case 422:
26
- return "VALIDATION_ERROR";
27
- default:
28
- return status >= 500 ? "INTERNAL_ERROR" : "VALIDATION_ERROR";
29
- }
30
- }
31
- function formatError(error) {
32
- return JSON.stringify({ error }, null, 2);
33
- }
34
- async function apiRequest(path, options = {}) {
35
- if (!API_KEY) {
36
- return {
37
- error: {
38
- code: "UNAUTHORIZED",
39
- message: "No API key configured. Call the signup tool first to authenticate."
40
- }
41
- };
42
- }
43
- const { method = "GET", body, params } = options;
44
- let url = `${API_URL}/api/v0${path}`;
45
- if (params) {
46
- const searchParams = new URLSearchParams();
47
- for (const [key, value] of Object.entries(params)) {
48
- if (value !== void 0) {
49
- searchParams.set(key, String(value));
50
- }
51
- }
52
- const queryString = searchParams.toString();
53
- if (queryString) {
54
- url += `?${queryString}`;
55
- }
56
- }
57
- try {
58
- const res = await fetch(url, {
59
- method,
60
- headers: {
61
- Authorization: `Bearer ${API_KEY}`,
62
- "Content-Type": "application/json"
63
- },
64
- body: body ? JSON.stringify(body) : void 0
65
- });
66
- const json = await res.json();
67
- if (!res.ok) {
68
- const errorMessage = json.error || `HTTP ${res.status}`;
69
- return {
70
- error: {
71
- code: mapHttpStatusToErrorCode(res.status),
72
- message: errorMessage,
73
- details: json.details
74
- }
75
- };
76
- }
77
- return { data: json };
78
- } catch (err) {
79
- return {
80
- error: {
81
- code: "NETWORK_ERROR",
82
- message: err instanceof Error ? err.message : "Request failed"
83
- }
84
- };
85
- }
86
- }
87
- var VerificationLevel = z.enum(["L0", "L1", "L2", "L3", "L4"]);
88
- var OracleType = z.enum(["hp", "requester", "executor", "third_party"]);
89
- var EvidenceKind = z.enum([
90
- "photo",
91
- "video",
92
- "audio",
93
- "receipt",
94
- "signature",
95
- "measurement",
96
- "json",
97
- "pdf",
98
- "log",
99
- "note"
100
- ]);
101
- var OutcomeState = z.enum([
102
- "queued",
103
- "matching",
104
- "running",
105
- "needs_input",
106
- "under_review",
107
- "completed",
108
- "failed",
109
- "expired",
110
- "disputed"
111
- ]);
112
- var EvidenceRequirement = z.object({
113
- kind: EvidenceKind.optional().describe("Type of evidence required"),
114
- name: z.string().optional().describe("Name/label for this evidence requirement"),
115
- description: z.string().optional().describe("Description of what is needed"),
116
- min: z.number().int().min(0).optional().describe("Minimum number of items required"),
117
- max: z.number().int().min(0).optional().describe("Maximum number of items allowed")
118
- });
119
- var Verification = z.object({
120
- level: VerificationLevel.optional().describe("Verification level (L0-L4)"),
121
- oracle: OracleType.optional().describe("Who verifies the outcome"),
122
- dispute_window_seconds: z.number().int().min(0).optional().describe("Time window for disputes")
123
- });
124
- var Budget = z.object({
125
- max_usd: z.number().min(0).optional().describe("Maximum budget in USD"),
126
- model: z.enum(["fixed", "not_to_exceed", "per_hour"]).optional().describe("Pricing model")
127
- });
128
- var Constraints = z.object({
129
- location: z.string().optional().describe("Location constraint (address or coordinates)"),
130
- tools_allowed: z.array(z.string()).optional().describe("List of allowed tools"),
131
- data_handling: z.enum(["no_pii", "redacted", "allowed"]).optional().describe("Data handling policy"),
132
- requires_license: z.boolean().optional().describe("Whether a license is required")
133
- });
134
- var Prefs = z.object({
135
- prefer: z.enum(["human", "agent", "either"]).optional().describe("Preferred executor type"),
136
- optimize_for: z.enum(["cost", "speed", "quality", "verification"]).optional().describe("Optimization priority")
137
- });
138
- var OutcomeInput = z.object({
139
- kind: EvidenceKind.optional().describe("Type of input"),
140
- name: z.string().optional().describe("Name/label for this input"),
141
- mime_type: z.string().optional().describe("MIME type of the content"),
142
- url: z.string().url().optional().describe("URL to the content"),
143
- text: z.string().optional().describe("Text content"),
144
- bytes_base64: z.string().optional().describe("Base64-encoded binary content")
145
- });
146
- var StatusProgress = z.object({
147
- pct: z.number().min(0).max(100).optional().describe("Completion percentage"),
148
- checkpoints: z.array(z.string()).optional().describe("Completed checkpoints")
149
- });
150
- var RequestedInputs = z.object({
151
- prompt: z.string().describe("Prompt explaining what input is needed"),
152
- evidence_kind: z.array(EvidenceKind).optional().describe("Types of evidence that can be provided")
153
- });
154
- var StatusUpdate = z.object({
155
- state: OutcomeState.optional().describe("New state"),
156
- message: z.string().optional().describe("Status message"),
157
- progress: StatusProgress.optional().describe("Progress information"),
158
- requested_inputs: RequestedInputs.optional().describe("Request for additional inputs")
159
- });
160
- var Artifact = z.object({
161
- id: z.string().describe("Artifact ID"),
162
- kind: EvidenceKind.describe("Type of artifact"),
163
- name: z.string().optional().describe("Name of the artifact"),
164
- mime_type: z.string().optional().describe("MIME type"),
165
- url: z.string().optional().describe("URL to access the artifact"),
166
- text_preview: z.string().optional().describe("Text preview of the content"),
167
- meta: z.record(z.unknown()).optional().describe("Additional metadata")
168
- });
169
- var Resolution = z.object({
170
- accepted: z.boolean().describe("Whether the outcome was accepted"),
171
- decided_at: z.string().datetime().describe("When the decision was made"),
172
- oracle: OracleType.describe("Who made the decision"),
173
- reason: z.string().optional().describe("Reason for the decision"),
174
- failures: z.array(z.string()).optional().describe("List of failures if not accepted")
175
- });
176
- var Receipt = z.object({
177
- verification_level: VerificationLevel.describe("Verification level achieved"),
178
- hashes: z.record(z.string()).optional().describe("Content hashes"),
179
- timestamps: z.object({
180
- started_at: z.string().datetime().optional(),
181
- completed_at: z.string().datetime().optional()
182
- }).optional().describe("Timing information"),
183
- location_proof: z.object({
184
- gps_points: z.array(
185
- z.object({
186
- lat: z.number(),
187
- lon: z.number(),
188
- ts: z.string().datetime()
189
- })
190
- ).optional(),
191
- radius_m: z.number().optional()
192
- }).optional().describe("Location verification data"),
193
- audit_log_url: z.string().url().optional().describe("URL to audit log"),
194
- executor_attestation: z.string().optional().describe("Executor attestation")
195
- });
196
- var ResultSubmission = z.object({
197
- resolution: Resolution.describe("Resolution details"),
198
- artifacts: z.array(Artifact).describe("Submitted artifacts"),
199
- receipt: Receipt.describe("Verification receipt")
200
- });
201
- var server = new McpServer({
202
- name: "humanlypossible",
203
- version: "0.4.0"
204
- });
205
- server.resource(
206
- "guide",
207
- "humanlypossible://guide",
208
- async () => ({
209
- contents: [
210
- {
211
- uri: "humanlypossible://guide",
212
- mimeType: "text/markdown",
213
- text: `# HumanlyPossible v0 Outcome Protocol
214
-
215
- ## Quick Start
216
-
217
- 1. Create an outcome request
218
- 2. Poll status
219
- 3. Provide extra inputs if requested
220
- 4. Fetch result when completed
221
-
222
- ## Requester Tools
223
-
224
- - \`request_outcome\` - Create a new outcome request
225
- - \`list_outcomes\` - List your outcome requests with filters
226
- - \`get_status\` - Check status of an outcome
227
- - \`send_inputs\` - Provide additional inputs if requested
228
- - \`get_result\` - Fetch result when completed
229
-
230
- ## Executor Tools
231
-
232
- - \`list_available_outcomes\` - List outcomes available for claiming
233
- - \`claim_outcome\` - Claim an outcome for execution
234
- - \`submit_result\` - Submit artifacts/result for an outcome
235
- - \`update_status\` - Update outcome status during execution
236
-
237
- ## Example Request
238
-
239
- \`\`\`json
240
- {
241
- "outcome": "6 exterior photos of 123 Main St are captured in daylight",
242
- "resolve_by": "2026-02-06T20:00:00Z",
243
- "evidence": [{ "kind": "photo", "min": 6 }],
244
- "verification": { "level": "L1", "oracle": "hp" },
245
- "constraints": { "location": "123 Main St, SF" }
246
- }
247
- \`\`\`
248
-
249
- ## Evidence Kinds
250
- photo, video, audio, receipt, signature, measurement, json, pdf, log, note
251
-
252
- ## Verification Levels
253
- - L0: No verification
254
- - L1: Basic verification
255
- - L2: Standard verification
256
- - L3: Enhanced verification
257
- - L4: Maximum verification
258
-
259
- ## Outcome States
260
- queued, matching, running, needs_input, under_review, completed, failed, expired, disputed
261
-
262
- ## Adapter Routing
263
-
264
- Outcomes are automatically routed to the best available service adapter:
265
-
266
- ### Available Adapters
267
-
268
- **Uber Direct** (delivery, courier, logistics)
269
- - Categories: food_delivery, courier, delivery, logistics
270
- - Params: pickup_address, pickup_name, pickup_phone, dropoff_address, dropoff_name, dropoff_phone, manifest_description
271
- - Example: \`{ adapter_hint: "delivery", params: { pickup_address: "123 Main St", dropoff_address: "456 Oak Ave" } }\`
272
-
273
- **Twilio** (communication, messaging)
274
- - Categories: communication, sms, voice, messaging, phone_call
275
- - Params: to (E.164 phone), from (optional), body, channel (sms|voice)
276
- - Example: \`{ adapter_hint: "sms", params: { to: "+15551234567", body: "Your order is ready" } }\`
277
-
278
- **Native Marketplace** (general, manual, human)
279
- - Default fallback for outcomes that don't match a service adapter
280
- - Outcomes stay in 'matching' for human/AI executors to claim
281
-
282
- ### Routing Cascade
283
- 1. If adapter_hint is provided, route to matching adapter
284
- 2. If no hint, use LLM to interpret the outcome and select an adapter
285
- 3. If no adapter matches, fall through to native marketplace
286
-
287
- ## Error Codes
288
- - UNAUTHORIZED: API key invalid or missing
289
- - FORBIDDEN: Actor lacks required permissions
290
- - NOT_FOUND: Outcome not found
291
- - CONFLICT: Outcome already claimed or invalid state transition
292
- - VALIDATION_ERROR: Invalid request parameters
293
- - INTERNAL_ERROR: Server error
294
- - NETWORK_ERROR: Network connectivity issue
295
- `
296
- }
297
- ]
298
- })
299
- );
300
- server.registerTool(
301
- "request_outcome",
302
- {
303
- title: "Request Outcome",
304
- description: `Create a new outcome request. Delegates a real-world task to humans or service adapters.
305
-
306
- The platform automatically routes outcomes to the best available adapter:
307
- - Use adapter_hint to target a specific service category (e.g. "delivery", "sms", "voice")
308
- - Use params to provide structured parameters matching the adapter's schema
309
- - Without hints, the platform uses LLM interpretation to route to the best adapter
310
- - Outcomes that don't match any adapter go to the native marketplace for human/AI claim
311
-
312
- Available adapter categories:
313
- - delivery/courier/logistics: Uber Direct (pickup_address, dropoff_address, etc.)
314
- - sms/voice/messaging: Twilio (to, body, channel)
315
- - general/manual: Native marketplace (any outcome)
316
-
317
- Returns:
318
- { "id": "<uuid>" } - The outcome request ID for tracking
319
-
320
- Error Codes:
321
- - VALIDATION_ERROR: Invalid parameters
322
- - UNAUTHORIZED: Invalid API key`,
323
- inputSchema: {
324
- outcome: z.string().min(1).describe("Clear description of the desired outcome"),
325
- resolve_by: z.string().describe("ISO 8601 deadline (e.g. 2026-02-06T20:00:00Z)"),
326
- params: z.record(z.unknown()).optional().describe("Structured params for adapter routing (e.g. { to: '+1234567890', body: 'Hello' } for SMS)"),
327
- adapter_hint: z.string().optional().describe("Hint for adapter selection (e.g. 'delivery', 'sms', 'voice', 'courier')"),
328
- evidence: z.array(EvidenceRequirement).optional().describe("Evidence requirements"),
329
- verification: Verification.optional().describe("Verification settings"),
330
- budget: Budget.optional().describe("Budget constraints"),
331
- constraints: Constraints.optional().describe("Execution constraints"),
332
- prefs: Prefs.optional().describe("Execution preferences")
333
- },
334
- annotations: {
335
- readOnlyHint: false,
336
- destructiveHint: false,
337
- idempotentHint: false,
338
- openWorldHint: true
339
- }
340
- },
341
- async (params) => {
342
- const body = {
343
- outcome: params.outcome,
344
- resolve_by: params.resolve_by
345
- };
346
- if (params.params) body.params = params.params;
347
- if (params.adapter_hint) body.adapter_hint = params.adapter_hint;
348
- if (params.evidence) body.evidence = params.evidence;
349
- if (params.verification) body.verification = params.verification;
350
- if (params.budget) body.budget = params.budget;
351
- if (params.constraints) body.constraints = params.constraints;
352
- if (params.prefs) body.prefs = params.prefs;
353
- const { data, error } = await apiRequest("/outcomes", {
354
- method: "POST",
355
- body
356
- });
357
- if (error) {
358
- return {
359
- content: [{ type: "text", text: formatError(error) }],
360
- isError: true
361
- };
362
- }
363
- return {
364
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
365
- };
366
- }
367
- );
368
- server.registerTool(
369
- "list_outcomes",
370
- {
371
- title: "List Outcomes",
372
- description: `List your outcome requests with optional filters.
373
-
374
- Returns:
375
- {
376
- "items": [...],
377
- "has_more": true|false,
378
- "next_cursor": "<ISO 8601 timestamp>"
379
- }
380
-
381
- Error Codes:
382
- - VALIDATION_ERROR: Invalid filter parameters
383
- - UNAUTHORIZED: Invalid API key`,
384
- inputSchema: {
385
- state: OutcomeState.optional().describe("Filter by state"),
386
- limit: z.number().int().min(1).max(100).optional().describe("Max results (default 20, max 100)"),
387
- created_before: z.string().optional().describe("Cursor for pagination (ISO 8601 timestamp)")
388
- },
389
- annotations: {
390
- readOnlyHint: true,
391
- destructiveHint: false,
392
- idempotentHint: true,
393
- openWorldHint: false
394
- }
395
- },
396
- async (params) => {
397
- const { data, error } = await apiRequest("/outcomes", {
398
- params: {
399
- state: params.state,
400
- limit: params.limit,
401
- created_before: params.created_before
402
- }
403
- });
404
- if (error) {
405
- return {
406
- content: [{ type: "text", text: formatError(error) }],
407
- isError: true
408
- };
409
- }
410
- return {
411
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
412
- };
413
- }
414
- );
415
- server.registerTool(
416
- "get_status",
417
- {
418
- title: "Get Outcome Status",
419
- description: `Get the current status of an outcome request.
420
-
421
- Returns:
422
- {
423
- "id": "<uuid>",
424
- "state": "queued|matching|running|needs_input|under_review|completed|failed|expired|disputed",
425
- "updated_at": "<ISO 8601 timestamp>",
426
- "message": "<optional status message>",
427
- "progress": { "pct": 0-100, "checkpoints": [...] },
428
- "requested_inputs": { "prompt": "...", "evidence_kind": [...] }
429
- }
430
-
431
- Error Codes:
432
- - NOT_FOUND: Outcome not found
433
- - FORBIDDEN: Not authorized to view this outcome
434
- - UNAUTHORIZED: Invalid API key`,
435
- inputSchema: {
436
- id: z.string().describe("Outcome request ID (UUID)")
437
- },
438
- annotations: {
439
- readOnlyHint: true,
440
- destructiveHint: false,
441
- idempotentHint: true,
442
- openWorldHint: false
443
- }
444
- },
445
- async (params) => {
446
- const { data, error } = await apiRequest(
447
- `/outcomes/${params.id}/status`
448
- );
449
- if (error) {
450
- return {
451
- content: [{ type: "text", text: formatError(error) }],
452
- isError: true
453
- };
454
- }
455
- return {
456
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
457
- };
458
- }
459
- );
460
- server.registerTool(
461
- "send_inputs",
462
- {
463
- title: "Send Inputs",
464
- description: `Provide additional inputs for an outcome request.
465
-
466
- Returns:
467
- { "ok": true }
468
-
469
- Error Codes:
470
- - NOT_FOUND: Outcome not found
471
- - FORBIDDEN: Not authorized to send inputs
472
- - VALIDATION_ERROR: Invalid input format
473
- - UNAUTHORIZED: Invalid API key`,
474
- inputSchema: {
475
- id: z.string().describe("Outcome request ID (UUID)"),
476
- inputs: z.array(OutcomeInput).describe("Array of input objects")
477
- },
478
- annotations: {
479
- readOnlyHint: false,
480
- destructiveHint: false,
481
- idempotentHint: false,
482
- openWorldHint: false
483
- }
484
- },
485
- async (params) => {
486
- const { data, error } = await apiRequest(
487
- `/outcomes/${params.id}/inputs`,
488
- {
489
- method: "POST",
490
- body: params.inputs
491
- }
492
- );
493
- if (error) {
494
- return {
495
- content: [{ type: "text", text: formatError(error) }],
496
- isError: true
497
- };
498
- }
499
- return {
500
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
501
- };
502
- }
503
- );
504
- server.registerTool(
505
- "get_result",
506
- {
507
- title: "Get Outcome Result",
508
- description: `Fetch the result for a completed outcome request.
509
-
510
- Returns:
511
- {
512
- "id": "<uuid>",
513
- "resolution": { "accepted": true|false, "decided_at": "...", "oracle": "..." },
514
- "artifacts": [...],
515
- "receipt": { "verification_level": "L0-L4", ... }
516
- }
517
-
518
- Error Codes:
519
- - NOT_FOUND: Outcome not found or not yet completed
520
- - FORBIDDEN: Not authorized to view this outcome
521
- - UNAUTHORIZED: Invalid API key`,
522
- inputSchema: {
523
- id: z.string().describe("Outcome request ID (UUID)")
524
- },
525
- annotations: {
526
- readOnlyHint: true,
527
- destructiveHint: false,
528
- idempotentHint: true,
529
- openWorldHint: false
530
- }
531
- },
532
- async (params) => {
533
- const { data, error } = await apiRequest(
534
- `/outcomes/${params.id}/result`
535
- );
536
- if (error) {
537
- return {
538
- content: [{ type: "text", text: formatError(error) }],
539
- isError: true
540
- };
541
- }
542
- return {
543
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
544
- };
545
- }
546
- );
547
- server.registerTool(
548
- "list_available_outcomes",
549
- {
550
- title: "List Available Outcomes",
551
- description: `List outcomes available for claiming. Requires executor permissions (can_execute=true).
552
-
553
- Returns:
554
- {
555
- "items": [
556
- {
557
- "id": "<uuid>",
558
- "state": "queued|matching",
559
- "resolve_by": "<ISO 8601>",
560
- "outcome": "<description>",
561
- "budget": { ... },
562
- "constraints": { ... },
563
- "created_at": "<ISO 8601>"
564
- },
565
- ...
566
- ],
567
- "limit": 20,
568
- "offset": 0
569
- }
570
-
571
- Error Codes:
572
- - FORBIDDEN: Actor lacks execute permissions
573
- - UNAUTHORIZED: Invalid API key`,
574
- inputSchema: {
575
- limit: z.number().int().min(1).max(100).optional().describe("Max results (default 20, max 100)"),
576
- offset: z.number().int().min(0).optional().describe("Offset for pagination (default 0)")
577
- },
578
- annotations: {
579
- readOnlyHint: true,
580
- destructiveHint: false,
581
- idempotentHint: true,
582
- openWorldHint: false
583
- }
584
- },
585
- async (params) => {
586
- const { data, error } = await apiRequest("/outcomes/available", {
587
- params: {
588
- limit: params.limit,
589
- offset: params.offset
590
- }
591
- });
592
- if (error) {
593
- return {
594
- content: [{ type: "text", text: formatError(error) }],
595
- isError: true
596
- };
597
- }
598
- return {
599
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
600
- };
601
- }
602
- );
603
- server.registerTool(
604
- "claim_outcome",
605
- {
606
- title: "Claim Outcome",
607
- description: `Claim an outcome for execution. Requires executor permissions (can_execute=true).
608
-
609
- Returns:
610
- {
611
- "id": "<uuid>",
612
- "executor_actor_id": "<uuid>",
613
- "state": "running",
614
- "status": { ... }
615
- }
616
-
617
- Error Codes:
618
- - NOT_FOUND: Outcome not found
619
- - FORBIDDEN: Actor lacks execute permissions
620
- - CONFLICT: Outcome already claimed by another executor
621
- - UNAUTHORIZED: Invalid API key`,
622
- inputSchema: {
623
- id: z.string().describe("Outcome request ID (UUID) to claim")
624
- },
625
- annotations: {
626
- readOnlyHint: false,
627
- destructiveHint: false,
628
- idempotentHint: false,
629
- openWorldHint: false
630
- }
631
- },
632
- async (params) => {
633
- const { data, error } = await apiRequest(`/outcomes/${params.id}/claim`, {
634
- method: "POST"
635
- });
636
- if (error) {
637
- return {
638
- content: [{ type: "text", text: formatError(error) }],
639
- isError: true
640
- };
641
- }
642
- return {
643
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
644
- };
645
- }
646
- );
647
- server.registerTool(
648
- "update_status",
649
- {
650
- title: "Update Outcome Status",
651
- description: `Update the status of an outcome you are executing. Use this to report progress, request inputs, or change state.
652
-
653
- Returns:
654
- {
655
- "id": "<uuid>",
656
- "state": "...",
657
- "status": { ... }
658
- }
659
-
660
- Error Codes:
661
- - NOT_FOUND: Outcome not found
662
- - FORBIDDEN: Not the executor of this outcome
663
- - CONFLICT: Outcome not claimed
664
- - VALIDATION_ERROR: Invalid status update
665
- - UNAUTHORIZED: Invalid API key`,
666
- inputSchema: {
667
- id: z.string().describe("Outcome request ID (UUID)"),
668
- state: OutcomeState.optional().describe("New state"),
669
- message: z.string().optional().describe("Status message"),
670
- progress: StatusProgress.optional().describe("Progress information"),
671
- requested_inputs: RequestedInputs.optional().describe("Request for additional inputs from requester")
672
- },
673
- annotations: {
674
- readOnlyHint: false,
675
- destructiveHint: false,
676
- idempotentHint: false,
677
- openWorldHint: false
678
- }
679
- },
680
- async (params) => {
681
- const { id, ...statusUpdate } = params;
682
- if (!statusUpdate.state && !statusUpdate.message && !statusUpdate.progress && !statusUpdate.requested_inputs) {
683
- return {
684
- content: [
685
- {
686
- type: "text",
687
- text: formatError({
688
- code: "VALIDATION_ERROR",
689
- message: "At least one status field (state, message, progress, or requested_inputs) is required"
690
- })
691
- }
692
- ],
693
- isError: true
694
- };
695
- }
696
- const { data, error } = await apiRequest(`/outcomes/${id}/submit`, {
697
- method: "POST",
698
- body: { status: statusUpdate }
699
- });
700
- if (error) {
701
- return {
702
- content: [{ type: "text", text: formatError(error) }],
703
- isError: true
704
- };
705
- }
706
- return {
707
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
708
- };
709
- }
710
- );
711
- server.registerTool(
712
- "submit_result",
713
- {
714
- title: "Submit Outcome Result",
715
- description: `Submit the final result for an outcome you are executing. This completes the outcome.
716
-
717
- The result must include:
718
- - resolution: Whether the outcome was achieved, when, and by whom
719
- - artifacts: Evidence/deliverables produced
720
- - receipt: Verification proof
721
-
722
- Returns:
723
- {
724
- "id": "<uuid>",
725
- "state": "completed|failed",
726
- "status": { ... },
727
- "result": { ... }
728
- }
729
-
730
- Error Codes:
731
- - NOT_FOUND: Outcome not found
732
- - FORBIDDEN: Not the executor of this outcome
733
- - CONFLICT: Outcome not claimed or already completed
734
- - VALIDATION_ERROR: Invalid result format
735
- - UNAUTHORIZED: Invalid API key`,
736
- inputSchema: {
737
- id: z.string().describe("Outcome request ID (UUID)"),
738
- resolution: Resolution.describe("Resolution details"),
739
- artifacts: z.array(Artifact).describe("Evidence/deliverables produced"),
740
- receipt: Receipt.describe("Verification proof")
741
- },
742
- annotations: {
743
- readOnlyHint: false,
744
- destructiveHint: false,
745
- idempotentHint: false,
746
- openWorldHint: false
747
- }
748
- },
749
- async (params) => {
750
- const { id, ...result } = params;
751
- const { data, error } = await apiRequest(`/outcomes/${id}/submit`, {
752
- method: "POST",
753
- body: { result }
754
- });
755
- if (error) {
756
- return {
757
- content: [{ type: "text", text: formatError(error) }],
758
- isError: true
759
- };
760
- }
761
- return {
762
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
763
- };
764
- }
765
- );
766
- server.registerTool(
767
- "signup",
768
- {
769
- title: "Sign Up / Log In",
770
- description: `Create an account or log into an existing one to get an API key. This is required before using any other tools if no API key was provided at startup.
771
-
772
- Returns:
773
- {
774
- "api_key": "hp_...",
775
- "actor_id": "<uuid>",
776
- "key_prefix": "hp_abc",
777
- "scopes": ["outcomes:read", "outcomes:write"],
778
- "is_new_account": true|false
779
- }
780
-
781
- Error Codes:
782
- - VALIDATION_ERROR: Missing fields or weak password
783
- - UNAUTHORIZED: Wrong password for existing account`,
784
- inputSchema: {
785
- email: z.string().describe("Email address"),
786
- password: z.string().describe("Password (min 8 characters)"),
787
- display_name: z.string().optional().describe("Display name for the account"),
788
- actor_kind: z.enum(["human", "ai", "org"]).optional().describe("Type of actor (default: ai)")
789
- },
790
- annotations: {
791
- readOnlyHint: false,
792
- destructiveHint: false,
793
- idempotentHint: false,
794
- openWorldHint: true
795
- }
796
- },
797
- async (params) => {
798
- try {
799
- const res = await fetch(`${API_URL}/api/v0/auth/signup`, {
800
- method: "POST",
801
- headers: { "Content-Type": "application/json" },
802
- body: JSON.stringify(params)
803
- });
804
- const json = await res.json();
805
- if (!res.ok) {
806
- return {
807
- content: [
808
- {
809
- type: "text",
810
- text: formatError({
811
- code: mapHttpStatusToErrorCode(res.status),
812
- message: json.error || `HTTP ${res.status}`
813
- })
814
- }
815
- ],
816
- isError: true
817
- };
818
- }
819
- API_KEY = json.api_key;
820
- return {
821
- content: [
822
- {
823
- type: "text",
824
- text: JSON.stringify(json, null, 2)
825
- }
826
- ]
827
- };
828
- } catch (err) {
829
- return {
830
- content: [
831
- {
832
- type: "text",
833
- text: formatError({
834
- code: "NETWORK_ERROR",
835
- message: err instanceof Error ? err.message : "Signup request failed"
836
- })
837
- }
838
- ],
839
- isError: true
840
- };
841
- }
842
- }
843
- );
844
- async function startMcpServer() {
845
- const transport = new StdioServerTransport();
846
- await server.connect(transport);
847
- }
848
- startMcpServer();
849
- export {
850
- startMcpServer
851
- };