@projectsolo/solo-mission-mcp 0.21.9 → 0.21.10

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/dist/index.js CHANGED
@@ -19,25 +19,25 @@ import {
19
19
  var RESPONSE_SCHEMA_PROPERTY = {
20
20
  type: "array",
21
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).",
22
+ description: 'Structured completion questions a human answers to complete the mission. Question ids must be unique within one schema \u2014 a duplicate id is rejected at creation/update, not merged or silently overwritten. 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 two-question schema when this is omitted at creation \u2014 literally { id: "rating", kind: "stars", required: true } and { id: "comment", kind: "long", required: false } \u2014 so read/append against those exact ids if you rely on the default rather than supplying your own schema. Every other type has no default (stays fully manual/chat-based unless you set this).',
23
23
  items: {
24
24
  type: "object",
25
25
  properties: {
26
- id: { type: "string", description: "Unique id within this schema \u2014 the key answers are stored under." },
26
+ id: { type: "string", description: "Unique id within this schema \u2014 the key answers are stored under. Duplicates are rejected, not merged." },
27
27
  kind: {
28
28
  type: "string",
29
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."
30
+ description: 'single=one choice, multi=several choices, likert=a scale rendered as a row of buttons (NOT fixed at 5 points \u2014 the number of options you supply IS the number of points, so give exactly as many option labels as the scale should have; a common convention is 5 labels like "1 Strongly disagree".."5 Strongly agree" but nothing enforces that), stars=1-5 rating (fixed, no options), 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 \u2014 the checkbox\'s own clickable text is `label` itself (there is no separate heading rendered above it, unlike every other kind); use `help` for any additional wording you want shown near it.'
31
31
  },
32
- label: { type: "string", description: "Question text shown to the human (max 200 chars)." },
32
+ label: { type: "string", description: "Question text shown to the human (max 200 chars). For the consent kind this is the checkbox's own clickable text, not a separate heading." },
33
33
  help: { type: "string", description: "Optional helper text shown under the label (max 500 chars)." },
34
34
  required: { type: "boolean", description: "Whether this question must be answered to complete the mission." },
35
35
  options: {
36
36
  type: "array",
37
37
  items: { type: "string" },
38
- description: "Required for single/multi/dropdown/likert (max 20 options, 50 chars each). Not accepted by other kinds."
38
+ description: "Required (non-empty) for single/multi/dropdown/likert (max 20 options, 50 chars each). Not accepted by other kinds."
39
39
  },
40
- min_length: { type: "number", description: "Minimum answer length, only meaningful for the 'long' kind." }
40
+ min_length: { type: "number", description: "Minimum answer length. Only read for the 'long' kind \u2014 silently ignored (not rejected) if set on any other kind." }
41
41
  },
42
42
  required: ["id", "kind", "label", "required"]
43
43
  }
@@ -90,7 +90,7 @@ var missionTools = [
90
90
  },
91
91
  {
92
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.",
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). This window is what makes a schema replacement safe: a human can only submit answers once hired, and the schema can no longer change once anyone has been hired \u2014 there is no state where a human has answered under one schema and the mission later presents a different one. Replaces the entire schema, not a merge.",
94
94
  inputSchema: {
95
95
  type: "object",
96
96
  properties: {
@@ -1155,7 +1155,14 @@ async function handleSolanaTool(name, args) {
1155
1155
  const { loadSolanaWallet, associatedTokenAddress, signTransaction } = await import("./wallet-IUQWBW6F.js");
1156
1156
  const wallet = await loadSolanaWallet();
1157
1157
  const cfg = await apiGet2("/agent/solana/config");
1158
- const mint = cfg.mints.TEST_USDC ?? Object.values(cfg.mints)[0];
1158
+ const mission = await apiGet2(`/agent/missions/${args.mission_id}`);
1159
+ const mint = mission.mission?.token_address;
1160
+ if (!mint) {
1161
+ return {
1162
+ refunded: false,
1163
+ error: `Could not resolve mission ${args.mission_id}'s token_address \u2014 call get_mission to check it exists and is funded.`
1164
+ };
1165
+ }
1159
1166
  const tokenAccount = await associatedTokenAddress(mint, wallet.publicKey);
1160
1167
  if (!args.action) {
1161
1168
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectsolo/solo-mission-mcp",
3
- "version": "0.21.9",
3
+ "version": "0.21.10",
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",
@@ -11,33 +11,42 @@ const RESPONSE_SCHEMA_PROPERTY = {
11
11
  type: 'array' as const,
12
12
  maxItems: 20,
13
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).',
14
+ 'Structured completion questions a human answers to complete the mission. Question ids must ' +
15
+ 'be unique within one schema — a duplicate id is rejected at creation/update, not merged or ' +
16
+ 'silently overwritten. When set, finalize_qualification auto-derives qualified UIDs from ' +
17
+ 'completion instead of requiring an explicit qualified_human_uids list — same mechanism ' +
18
+ 'media_review has always used for track ratings, generalized to any mission type. ' +
19
+ 'media_review defaults to a canonical two-question schema when this is omitted at creation — ' +
20
+ 'literally { id: "rating", kind: "stars", required: true } and { id: "comment", kind: "long", ' +
21
+ 'required: false } — so read/append against those exact ids if you rely on the default rather ' +
22
+ 'than supplying your own schema. Every other type has no default (stays fully manual/chat-based ' +
23
+ 'unless you set this).',
20
24
  items: {
21
25
  type: 'object',
22
26
  properties: {
23
- id: { type: 'string', description: 'Unique id within this schema — the key answers are stored under.' },
27
+ id: { type: 'string', description: 'Unique id within this schema — the key answers are stored under. Duplicates are rejected, not merged.' },
24
28
  kind: {
25
29
  type: 'string',
26
30
  enum: ['single', 'multi', 'likert', 'stars', 'short', 'long', 'dropdown', 'consent'],
27
31
  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.',
32
+ 'single=one choice, multi=several choices, likert=a scale rendered as a row of buttons (NOT ' +
33
+ 'fixed at 5 points — the number of options you supply IS the number of points, so give exactly ' +
34
+ 'as many option labels as the scale should have; a common convention is 5 labels like "1 ' +
35
+ 'Strongly disagree".."5 Strongly agree" but nothing enforces that), stars=1-5 rating (fixed, ' +
36
+ 'no options), short=one line of text, long=paragraph text (see min_length), dropdown=one ' +
37
+ 'choice from a long list, consent=a checkbox that must be explicitly true — the checkbox\'s ' +
38
+ 'own clickable text is `label` itself (there is no separate heading rendered above it, unlike ' +
39
+ 'every other kind); use `help` for any additional wording you want shown near it.',
31
40
  },
32
- label: { type: 'string', description: 'Question text shown to the human (max 200 chars).' },
41
+ label: { type: 'string', description: 'Question text shown to the human (max 200 chars). For the consent kind this is the checkbox\'s own clickable text, not a separate heading.' },
33
42
  help: { type: 'string', description: 'Optional helper text shown under the label (max 500 chars).' },
34
43
  required: { type: 'boolean', description: 'Whether this question must be answered to complete the mission.' },
35
44
  options: {
36
45
  type: 'array',
37
46
  items: { type: 'string' },
38
- description: 'Required for single/multi/dropdown/likert (max 20 options, 50 chars each). Not accepted by other kinds.',
47
+ description: 'Required (non-empty) for single/multi/dropdown/likert (max 20 options, 50 chars each). Not accepted by other kinds.',
39
48
  },
40
- min_length: { type: 'number', description: "Minimum answer length, only meaningful for the 'long' kind." },
49
+ min_length: { type: 'number', description: "Minimum answer length. Only read for the 'long' kind — silently ignored (not rejected) if set on any other kind." },
41
50
  },
42
51
  required: ['id', 'kind', 'label', 'required'],
43
52
  },
@@ -94,7 +103,10 @@ export const missionTools: Tool[] = [
94
103
  description:
95
104
  "Set or replace a mission's response_schema after creation. Locked once hiring starts — " +
96
105
  '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.',
106
+ 'active with zero hired participants yet). This window is what makes a schema replacement ' +
107
+ 'safe: a human can only submit answers once hired, and the schema can no longer change once ' +
108
+ 'anyone has been hired — there is no state where a human has answered under one schema and ' +
109
+ 'the mission later presents a different one. Replaces the entire schema, not a merge.',
98
110
  inputSchema: {
99
111
  type: 'object',
100
112
  properties: {
@@ -282,7 +282,22 @@ export async function handleSolanaTool(
282
282
  program_id: string;
283
283
  mints: Record<string, string>;
284
284
  };
285
- const mint = cfg.mints.TEST_USDC ?? Object.values(cfg.mints)[0];
285
+ // The mission's ACTUAL mint, not a guessed default — get_solana_config's mints always
286
+ // includes both the production payout mint (USDC) and TEST_USDC, and a mission is funded
287
+ // in exactly one of them. Guessing (the previous code preferred TEST_USDC whenever present)
288
+ // computes the wrong associated-token-account for any mission funded in the other one, and
289
+ // the refund/cancel/emergency-refund transaction then fails on-chain with
290
+ // AccountNotInitialized — confirmed live against a real USDC-funded mission.
291
+ const mission = (await apiGet(`/agent/missions/${args.mission_id}`)) as {
292
+ mission?: { token_address?: string };
293
+ };
294
+ const mint = mission.mission?.token_address;
295
+ if (!mint) {
296
+ return {
297
+ refunded: false,
298
+ error: `Could not resolve mission ${args.mission_id}'s token_address — call get_mission to check it exists and is funded.`,
299
+ };
300
+ }
286
301
  const tokenAccount = await associatedTokenAddress(mint, wallet.publicKey);
287
302
 
288
303
  // No action: ask what is legal rather than guessing and getting a 409. Uses a deliberately