@projectsolo/solo-mission-mcp 0.21.7 → 0.21.9

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.
@@ -51,6 +51,16 @@ async function apiPost(path, body) {
51
51
  if (!response.ok) return parseErrorResponse(response);
52
52
  return response.json();
53
53
  }
54
+ async function apiPatch(path, body) {
55
+ const response = await fetch(`${config.apiUrl}${path}`, {
56
+ method: "PATCH",
57
+ headers: DEFAULT_HEADERS,
58
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
59
+ signal: AbortSignal.timeout(3e4)
60
+ });
61
+ if (!response.ok) return parseErrorResponse(response);
62
+ return response.json();
63
+ }
54
64
  async function apiDelete(path) {
55
65
  const response = await fetch(`${config.apiUrl}${path}`, {
56
66
  method: "DELETE",
@@ -74,6 +84,7 @@ async function publicApiPost(path, body) {
74
84
  export {
75
85
  apiGet,
76
86
  apiPost,
87
+ apiPatch,
77
88
  apiDelete,
78
89
  publicApiPost
79
90
  };
@@ -1,12 +1,14 @@
1
1
  import {
2
2
  apiDelete,
3
3
  apiGet,
4
+ apiPatch,
4
5
  apiPost,
5
6
  publicApiPost
6
- } from "./chunk-NXOOPOSF.js";
7
+ } from "./chunk-TKRT2V2W.js";
7
8
  export {
8
9
  apiDelete,
9
10
  apiGet,
11
+ apiPatch,
10
12
  apiPost,
11
13
  publicApiPost
12
14
  };
package/dist/index.js CHANGED
@@ -2,9 +2,10 @@
2
2
  import {
3
3
  apiDelete,
4
4
  apiGet,
5
+ apiPatch,
5
6
  apiPost,
6
7
  publicApiPost
7
- } from "./chunk-NXOOPOSF.js";
8
+ } from "./chunk-TKRT2V2W.js";
8
9
 
9
10
  // src/index.ts
10
11
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -15,6 +16,32 @@ import {
15
16
  } from "@modelcontextprotocol/sdk/types.js";
16
17
 
17
18
  // src/tools/missions.ts
19
+ var RESPONSE_SCHEMA_PROPERTY = {
20
+ type: "array",
21
+ maxItems: 20,
22
+ description: "Structured completion questions a human answers to complete the mission. When set, finalize_qualification auto-derives qualified UIDs from completion instead of requiring an explicit qualified_human_uids list \u2014 same mechanism media_review has always used for track ratings, generalized to any mission type. media_review defaults to a canonical {rating (stars), comment (long, optional)} schema when this is omitted at creation; every other type has no default (stays fully manual/chat-based unless you set this).",
23
+ items: {
24
+ type: "object",
25
+ properties: {
26
+ id: { type: "string", description: "Unique id within this schema \u2014 the key answers are stored under." },
27
+ kind: {
28
+ type: "string",
29
+ enum: ["single", "multi", "likert", "stars", "short", "long", "dropdown", "consent"],
30
+ description: "single=one choice, multi=several choices, likert=5-point agreement scale, stars=1-5 rating, short=one line of text, long=paragraph text (see min_length), dropdown=one choice from a long list, consent=a checkbox that must be explicitly true."
31
+ },
32
+ label: { type: "string", description: "Question text shown to the human (max 200 chars)." },
33
+ help: { type: "string", description: "Optional helper text shown under the label (max 500 chars)." },
34
+ required: { type: "boolean", description: "Whether this question must be answered to complete the mission." },
35
+ options: {
36
+ type: "array",
37
+ items: { type: "string" },
38
+ description: "Required for single/multi/dropdown/likert (max 20 options, 50 chars each). Not accepted by other kinds."
39
+ },
40
+ min_length: { type: "number", description: "Minimum answer length, only meaningful for the 'long' kind." }
41
+ },
42
+ required: ["id", "kind", "label", "required"]
43
+ }
44
+ };
18
45
  var missionTools = [
19
46
  {
20
47
  name: "create_mission",
@@ -53,13 +80,26 @@ var missionTools = [
53
80
  reward_per_human: { type: "number", minimum: 0, description: "Deprecated \u2014 use base_reward instead." },
54
81
  lottery_winner_count: { type: "integer", minimum: 1, description: "Number of winners randomly selected from all qualified participants. Must be <= max_humans. Must be paired with lottery_prize_per_winner. Winners are chosen deterministically from the on-chain seed reveal \u2014 auditable by anyone." },
55
82
  lottery_prize_per_winner: { type: "number", minimum: 0, description: "Additional prize in USDC paid to each lottery winner on top of base_reward. Must be paired with lottery_winner_count." },
56
- hiring_duration_hours: { type: "number", minimum: 1, description: "How long (hours) the mission accepts applications and the agent hires/rejects. The hiring window closes at now + hiring_duration_hours. Finalize-qualification cannot be called before this." },
57
- work_duration_hours: { type: "number", minimum: 2, description: "How long (hours) hired participants have to complete the work. Agent must call settle_mission before this period ends. Minimum is 2, not 1 \u2014 the contract requires at least 1 hour of settlement window remaining when finalize_qualification is called, and finalize can only happen after the hiring window already closes, so a value of exactly 1 leaves no reachable window at all." },
58
- auto_accept_applicants: { type: "boolean", description: "When true, applicants are automatically hired when they apply \u2014 no manual hire_participant call needed. First-come first-served up to max_humans. Face verification is still required. Ideal for open media_review missions." }
83
+ hiring_duration_hours: { type: "number", minimum: 0.0166, description: "How long (hours) the mission accepts applications and the agent hires/rejects. The hiring window closes at now + hiring_duration_hours. Finalize-qualification cannot be called before this. Backend floor is 60s (0.0166h), same for every chain \u2014 see work_duration_hours for the reasoning." },
84
+ work_duration_hours: { type: "number", minimum: 0.0166, description: "How long (hours) hired participants have to complete the work. Agent must call settle_mission before this period ends. Backend floor is 60s (0.0166h) for every chain \u2014 this is a flat sanity check against a near-zero window, not a guarantee of a legal settlement window on every chain: Base's own EscrowVault contract separately enforces its own fixed 1-hour minimum regardless of what this floor allows through, so a too-short Base mission still fails downstream (at finalize_qualification, or on-chain) instead of being caught at creation." },
85
+ auto_accept_applicants: { type: "boolean", description: "When true, applicants are automatically hired when they apply \u2014 no manual hire_participant call needed. First-come first-served up to max_humans. Face verification is still required. Ideal for open media_review missions." },
86
+ response_schema: RESPONSE_SCHEMA_PROPERTY
59
87
  },
60
88
  required: ["type", "title", "description"]
61
89
  }
62
90
  },
91
+ {
92
+ name: "update_mission_questions",
93
+ description: "Set or replace a mission's response_schema after creation. Locked once hiring starts \u2014 same window as add_mission_track (on-chain: only before funding; off-chain: only while active with zero hired participants yet). Replaces the entire schema, not a merge.",
94
+ inputSchema: {
95
+ type: "object",
96
+ properties: {
97
+ mission_id: { type: "string" },
98
+ response_schema: RESPONSE_SCHEMA_PROPERTY
99
+ },
100
+ required: ["mission_id", "response_schema"]
101
+ }
102
+ },
63
103
  {
64
104
  name: "list_missions",
65
105
  description: "List missions created by this agent. Filter by status to manage your mission portfolio.",
@@ -125,7 +165,7 @@ var missionTools = [
125
165
  },
126
166
  {
127
167
  name: "finalize_qualification",
128
- description: "Lock in the qualified participants after reviewing their work. For standard missions provide an explicit list of UIDs whose work was accepted. For media_review missions pass an empty body \u2014 qualified_human_uids is ignored and the backend auto-qualifies anyone who rated every track. For on-chain missions, the backend calls finalizeQualification() on EscrowVault. Mission transitions to qualifying. If you never call this, the platform does it for you shortly after the hiring window closes, qualifying every hired participant you did not explicitly reject_participant (silence = go) \u2014 call reject_participant first for anyone whose work should NOT be paid.",
168
+ description: "Lock in the qualified participants after reviewing their work. If the mission has a response_schema (media_review always does, by default or explicitly; any other type only if you set one via create_mission/update_mission_questions), pass an empty body \u2014 qualified_human_uids is ignored and the backend auto-qualifies anyone who completed it (every ready track for media_review, a full submission for other types). Otherwise provide an explicit list of UIDs whose work was accepted. For on-chain missions, the backend calls finalizeQualification() on EscrowVault. Mission transitions to qualifying. If you never call this, the platform does it for you shortly after the hiring window closes, qualifying every hired participant you did not explicitly reject_participant (silence = go) \u2014 call reject_participant first for anyone whose work should NOT be paid.",
129
169
  inputSchema: {
130
170
  type: "object",
131
171
  properties: {
@@ -133,7 +173,7 @@ var missionTools = [
133
173
  qualified_human_uids: {
134
174
  type: "array",
135
175
  items: { type: "string" },
136
- description: "Firebase UIDs of humans whose work was accepted. Ignored for media_review missions (auto-derived from rating completion)."
176
+ description: "Firebase UIDs of humans whose work was accepted. Ignored for any mission with a response_schema (auto-derived from completion instead)."
137
177
  }
138
178
  },
139
179
  required: ["mission_id"]
@@ -258,6 +298,8 @@ async function handleMissionTool(name, args) {
258
298
  }
259
299
  case "get_mission":
260
300
  return apiGet(`/agent/missions/${args.mission_id}`);
301
+ case "update_mission_questions":
302
+ return apiPatch(`/agent/missions/${args.mission_id}/questions`, { response_schema: args.response_schema });
261
303
  case "confirm_funding":
262
304
  return apiPost(`/agent/missions/${args.mission_id}/confirm-funding`, { tx_hash: args.tx_hash });
263
305
  case "hire_participant":
@@ -999,7 +1041,7 @@ function toRawAmount(amount, decimals) {
999
1041
  }
1000
1042
  var SOLANA_TOOL_NAMES = new Set(solanaTools.map((t) => t.name));
1001
1043
  async function handleSolanaTool(name, args) {
1002
- const { apiGet: apiGet2, apiPost: apiPost2 } = await import("./client-2NLDPRAH.js");
1044
+ const { apiGet: apiGet2, apiPost: apiPost2 } = await import("./client-LDWK5HLP.js");
1003
1045
  switch (name) {
1004
1046
  case "get_solana_config":
1005
1047
  return apiGet2("/agent/solana/config");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectsolo/solo-mission-mcp",
3
- "version": "0.21.7",
3
+ "version": "0.21.9",
4
4
  "description": "MCP server for Solo Mission Platform — lets AI agents create missions, browse humans, and chat.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/api/client.ts CHANGED
@@ -48,6 +48,17 @@ export async function apiPost<T>(path: string, body?: unknown): Promise<T> {
48
48
  return response.json() as Promise<T>;
49
49
  }
50
50
 
51
+ export async function apiPatch<T>(path: string, body?: unknown): Promise<T> {
52
+ const response = await fetch(`${config.apiUrl}${path}`, {
53
+ method: 'PATCH',
54
+ headers: DEFAULT_HEADERS,
55
+ body: body !== undefined ? JSON.stringify(body) : undefined,
56
+ signal: AbortSignal.timeout(30_000),
57
+ });
58
+ if (!response.ok) return parseErrorResponse(response);
59
+ return response.json() as Promise<T>;
60
+ }
61
+
51
62
  export async function apiDelete<T>(path: string): Promise<T> {
52
63
  const response = await fetch(`${config.apiUrl}${path}`, {
53
64
  method: 'DELETE',
@@ -1,5 +1,47 @@
1
1
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
- import { apiGet, apiPost } from '../api/client.js';
2
+ import { apiGet, apiPatch, apiPost } from '../api/client.js';
3
+
4
+ /**
5
+ * Shared JSON-schema for Mission.response_schema, used by both create_mission (set at creation)
6
+ * and update_mission_questions (set/replace afterward, pre-hiring). v1 kinds only — text/choice/
7
+ * rating input; kinds needing video-stimulus/gating infra (ab/slider/rank/tags/timestamps/upload)
8
+ * aren't supported yet.
9
+ */
10
+ const RESPONSE_SCHEMA_PROPERTY = {
11
+ type: 'array' as const,
12
+ maxItems: 20,
13
+ description:
14
+ 'Structured completion questions a human answers to complete the mission. When set, ' +
15
+ 'finalize_qualification auto-derives qualified UIDs from completion instead of requiring an ' +
16
+ 'explicit qualified_human_uids list — same mechanism media_review has always used for track ' +
17
+ 'ratings, generalized to any mission type. media_review defaults to a canonical ' +
18
+ '{rating (stars), comment (long, optional)} schema when this is omitted at creation; every ' +
19
+ 'other type has no default (stays fully manual/chat-based unless you set this).',
20
+ items: {
21
+ type: 'object',
22
+ properties: {
23
+ id: { type: 'string', description: 'Unique id within this schema — the key answers are stored under.' },
24
+ kind: {
25
+ type: 'string',
26
+ enum: ['single', 'multi', 'likert', 'stars', 'short', 'long', 'dropdown', 'consent'],
27
+ description:
28
+ 'single=one choice, multi=several choices, likert=5-point agreement scale, stars=1-5 rating, ' +
29
+ 'short=one line of text, long=paragraph text (see min_length), dropdown=one choice from a long ' +
30
+ 'list, consent=a checkbox that must be explicitly true.',
31
+ },
32
+ label: { type: 'string', description: 'Question text shown to the human (max 200 chars).' },
33
+ help: { type: 'string', description: 'Optional helper text shown under the label (max 500 chars).' },
34
+ required: { type: 'boolean', description: 'Whether this question must be answered to complete the mission.' },
35
+ options: {
36
+ type: 'array',
37
+ items: { type: 'string' },
38
+ description: 'Required for single/multi/dropdown/likert (max 20 options, 50 chars each). Not accepted by other kinds.',
39
+ },
40
+ min_length: { type: 'number', description: "Minimum answer length, only meaningful for the 'long' kind." },
41
+ },
42
+ required: ['id', 'kind', 'label', 'required'],
43
+ },
44
+ };
3
45
 
4
46
  export const missionTools: Tool[] = [
5
47
  {
@@ -39,13 +81,29 @@ export const missionTools: Tool[] = [
39
81
  reward_per_human: { type: 'number', minimum: 0, description: 'Deprecated — use base_reward instead.' },
40
82
  lottery_winner_count: { type: 'integer', minimum: 1, description: 'Number of winners randomly selected from all qualified participants. Must be <= max_humans. Must be paired with lottery_prize_per_winner. Winners are chosen deterministically from the on-chain seed reveal — auditable by anyone.' },
41
83
  lottery_prize_per_winner: { type: 'number', minimum: 0, description: 'Additional prize in USDC paid to each lottery winner on top of base_reward. Must be paired with lottery_winner_count.' },
42
- hiring_duration_hours: { type: 'number', minimum: 1, description: 'How long (hours) the mission accepts applications and the agent hires/rejects. The hiring window closes at now + hiring_duration_hours. Finalize-qualification cannot be called before this.' },
43
- work_duration_hours: { type: 'number', minimum: 2, description: 'How long (hours) hired participants have to complete the work. Agent must call settle_mission before this period ends. Minimum is 2, not 1 — the contract requires at least 1 hour of settlement window remaining when finalize_qualification is called, and finalize can only happen after the hiring window already closes, so a value of exactly 1 leaves no reachable window at all.' },
84
+ hiring_duration_hours: { type: 'number', minimum: 0.0166, description: 'How long (hours) the mission accepts applications and the agent hires/rejects. The hiring window closes at now + hiring_duration_hours. Finalize-qualification cannot be called before this. Backend floor is 60s (0.0166h), same for every chain — see work_duration_hours for the reasoning.' },
85
+ work_duration_hours: { type: 'number', minimum: 0.0166, description: 'How long (hours) hired participants have to complete the work. Agent must call settle_mission before this period ends. Backend floor is 60s (0.0166h) for every chain — this is a flat sanity check against a near-zero window, not a guarantee of a legal settlement window on every chain: Base\'s own EscrowVault contract separately enforces its own fixed 1-hour minimum regardless of what this floor allows through, so a too-short Base mission still fails downstream (at finalize_qualification, or on-chain) instead of being caught at creation.' },
44
86
  auto_accept_applicants: { type: 'boolean', description: 'When true, applicants are automatically hired when they apply — no manual hire_participant call needed. First-come first-served up to max_humans. Face verification is still required. Ideal for open media_review missions.' },
87
+ response_schema: RESPONSE_SCHEMA_PROPERTY,
45
88
  },
46
89
  required: ['type', 'title', 'description'],
47
90
  },
48
91
  },
92
+ {
93
+ name: 'update_mission_questions',
94
+ description:
95
+ "Set or replace a mission's response_schema after creation. Locked once hiring starts — " +
96
+ 'same window as add_mission_track (on-chain: only before funding; off-chain: only while ' +
97
+ 'active with zero hired participants yet). Replaces the entire schema, not a merge.',
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: {
101
+ mission_id: { type: 'string' },
102
+ response_schema: RESPONSE_SCHEMA_PROPERTY,
103
+ },
104
+ required: ['mission_id', 'response_schema'],
105
+ },
106
+ },
49
107
  {
50
108
  name: 'list_missions',
51
109
  description: 'List missions created by this agent. Filter by status to manage your mission portfolio.',
@@ -111,7 +169,7 @@ export const missionTools: Tool[] = [
111
169
  },
112
170
  {
113
171
  name: 'finalize_qualification',
114
- description: 'Lock in the qualified participants after reviewing their work. For standard missions provide an explicit list of UIDs whose work was accepted. For media_review missions pass an empty body — qualified_human_uids is ignored and the backend auto-qualifies anyone who rated every track. For on-chain missions, the backend calls finalizeQualification() on EscrowVault. Mission transitions to qualifying. If you never call this, the platform does it for you shortly after the hiring window closes, qualifying every hired participant you did not explicitly reject_participant (silence = go) — call reject_participant first for anyone whose work should NOT be paid.',
172
+ description: 'Lock in the qualified participants after reviewing their work. If the mission has a response_schema (media_review always does, by default or explicitly; any other type only if you set one via create_mission/update_mission_questions), pass an empty body — qualified_human_uids is ignored and the backend auto-qualifies anyone who completed it (every ready track for media_review, a full submission for other types). Otherwise provide an explicit list of UIDs whose work was accepted. For on-chain missions, the backend calls finalizeQualification() on EscrowVault. Mission transitions to qualifying. If you never call this, the platform does it for you shortly after the hiring window closes, qualifying every hired participant you did not explicitly reject_participant (silence = go) — call reject_participant first for anyone whose work should NOT be paid.',
115
173
  inputSchema: {
116
174
  type: 'object',
117
175
  properties: {
@@ -119,7 +177,7 @@ export const missionTools: Tool[] = [
119
177
  qualified_human_uids: {
120
178
  type: 'array',
121
179
  items: { type: 'string' },
122
- description: 'Firebase UIDs of humans whose work was accepted. Ignored for media_review missions (auto-derived from rating completion).',
180
+ description: 'Firebase UIDs of humans whose work was accepted. Ignored for any mission with a response_schema (auto-derived from completion instead).',
123
181
  },
124
182
  },
125
183
  required: ['mission_id'],
@@ -248,6 +306,9 @@ export async function handleMissionTool(name: string, args: Record<string, any>)
248
306
  case 'get_mission':
249
307
  return apiGet(`/agent/missions/${args.mission_id}`);
250
308
 
309
+ case 'update_mission_questions':
310
+ return apiPatch(`/agent/missions/${args.mission_id}/questions`, { response_schema: args.response_schema });
311
+
251
312
  case 'confirm_funding':
252
313
  return apiPost(`/agent/missions/${args.mission_id}/confirm-funding`, { tx_hash: args.tx_hash });
253
314