tascan-mcp 3.0.0 → 3.1.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.
Files changed (3) hide show
  1. package/index.js +1 -1
  2. package/package.json +2 -2
  3. package/tools.cjs +53 -25
package/index.js CHANGED
@@ -45,7 +45,7 @@ async function api(method, path, body) {
45
45
  }
46
46
 
47
47
  const server = new Server(
48
- { name: 'tascan', version: '3.0.0' },
48
+ { name: 'tascan', version: '3.0.1' },
49
49
  { capabilities: { tools: {} } }
50
50
  );
51
51
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "tascan-mcp",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "mcpName": "io.github.snowbikemike/tascan-mcp",
5
- "description": "TaScan MCP Server — Closed-loop autonomous operations protocol. 36 tools for projects, tasks, workers, QR codes, NFC tag registry, scan verification, AI issue analysis, and autonomous remediation dispatch. 12 patents filed (335+ claims). Task. Scan. Done.",
5
+ "description": "TaScan MCP Server — Closed-loop autonomous operations protocol. 36 tools for projects, tasks, workers, QR codes, NFC tag registry, scan verification, AI issue analysis, and autonomous remediation dispatch. 14 patents filed (435+ claims). Task. Scan. Done.",
6
6
  "type": "module",
7
7
  "main": "index.js",
8
8
  "bin": {
package/tools.cjs CHANGED
@@ -13,6 +13,34 @@
13
13
  // content blocks.
14
14
  // ============================================================
15
15
 
16
+ // ─── Response display normalization ─────────────────────
17
+ // The worker app stores the clean value in response_value AND a raw
18
+ // submission blob {"type","value","submitted_at"} in notes for typed
19
+ // tasks; for checkbox tasks the human text lives only in notes.
20
+ // Collapse to one display value, never print the blob.
21
+ function fmtResponse(responseValue, notes) {
22
+ let value = responseValue;
23
+ if (value && typeof value === 'object') {
24
+ value = ('value' in value) ? value.value : JSON.stringify(value);
25
+ }
26
+ let note = notes || null;
27
+ if (note && typeof note === 'string' && note.trim().startsWith('{')) {
28
+ try {
29
+ const parsed = JSON.parse(note);
30
+ if (parsed && typeof parsed === 'object' && 'value' in parsed) {
31
+ if (value == null || value === '') value = parsed.value;
32
+ note = null; // pure duplicate of response_value — drop the blob
33
+ }
34
+ } catch { /* plain text note that happens to start with { — keep it */ }
35
+ }
36
+ if ((value == null || value === '') && note) {
37
+ // checkbox task with a typed note — the note IS the data
38
+ value = note;
39
+ note = null;
40
+ }
41
+ return { value: value == null || value === '' ? null : String(value), note };
42
+ }
43
+
16
44
  const AGENT_REGISTRY = [
17
45
  {
18
46
  id: 'claude-code-local',
@@ -254,7 +282,9 @@ const TOOLS = [
254
282
  },
255
283
  annotations: { title: 'Dispatch to AI Agent', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
256
284
  handler: async (args, api) => {
257
- const allAgents = [...AGENT_REGISTRY, ...dynamicAgents];
285
+ let registered = [];
286
+ try { registered = (await api('GET', '/agents')).data || []; } catch (e) { /* registry unreachable — fall back to seeds */ }
287
+ const allAgents = [...AGENT_REGISTRY, ...dynamicAgents, ...registered.filter(r => !AGENT_REGISTRY.some(s => s.id === r.id))];
258
288
  const agentQuery = (args.agent || 'claude-code-local').toLowerCase();
259
289
  const agent = allAgents.find(a => a.id === agentQuery || a.name.toLowerCase() === agentQuery) || allAgents[0];
260
290
  const title = args.priority === 'urgent' ? `🔴 ${args.task}` : args.task;
@@ -271,8 +301,10 @@ const TOOLS = [
271
301
  required: []
272
302
  },
273
303
  annotations: { title: 'List AI Agents', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
274
- handler: async () => {
275
- const allAgents = [...AGENT_REGISTRY, ...dynamicAgents];
304
+ handler: async (args, api) => {
305
+ let registered = [];
306
+ try { registered = (await api('GET', '/agents')).data || []; } catch (e) { /* registry unreachable — fall back to seeds */ }
307
+ const allAgents = [...AGENT_REGISTRY, ...dynamicAgents, ...registered.filter(r => !AGENT_REGISTRY.some(s => s.id === r.id))];
276
308
  const summary = allAgents.map(a =>
277
309
  `${a.name} (${a.id})\n Type: ${a.type} | Model: ${a.model}\n Worker ID: ${a.worker_id}\n Inbox: ${a.inbox_id}\n Capabilities: ${a.capabilities.join(', ')}\n Location: ${a.location}\n Status: ${a.status}\n ${a.description}`
278
310
  ).join('\n\n');
@@ -298,26 +330,22 @@ const TOOLS = [
298
330
  required: ['id', 'name', 'type', 'inbox_id', 'capabilities']
299
331
  },
300
332
  annotations: { title: 'Register AI Agent', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
301
- handler: async (args) => {
302
- const existing = [...AGENT_REGISTRY, ...dynamicAgents].find(a => a.id === args.id);
303
- if (existing) {
304
- Object.assign(existing, args, { status: 'active' });
305
- return `Agent "${args.name}" (${args.id}) updated in registry.\n\n${JSON.stringify(existing, null, 2)}`;
306
- }
307
- const agent = {
333
+ handler: async (args, api) => {
334
+ // Durable registration (Patent 10 Cl. 2): persists to the agent_registry
335
+ // table via the API — survives cold starts and spans both transports
336
+ const result = await api('POST', '/agents', {
308
337
  id: args.id,
309
338
  name: args.name,
310
339
  type: args.type,
311
- model: args.model || 'unknown',
312
- worker_id: args.worker_id || 'unassigned',
340
+ model: args.model,
341
+ worker_id: args.worker_id,
313
342
  inbox_id: args.inbox_id,
314
- capabilities: args.capabilities || ['DEFAULT'],
315
- location: args.location || 'unknown',
316
- status: 'active',
317
- description: args.description || `${args.name} agent`
318
- };
319
- dynamicAgents.push(agent);
320
- return `Agent "${args.name}" registered!\n\nID: ${agent.id}\nInbox: ${agent.inbox_id}\nCapabilities: ${agent.capabilities.join(', ')}\nStatus: active\n\nUse tascan_dispatch_to_agent with agent="${agent.id}" to send tasks.\n\n${JSON.stringify(agent, null, 2)}`;
343
+ capabilities: args.capabilities,
344
+ location: args.location,
345
+ description: args.description
346
+ });
347
+ const agent = result.data;
348
+ return `Agent "${agent.name}" registered (durable)!\n\nID: ${agent.id}\nInbox: ${agent.inbox_id}\nCapabilities: ${agent.capabilities.join(', ')}\nStatus: ${agent.status}\n\nUse tascan_dispatch_to_agent with agent="${agent.id}" to send tasks.\n\n${JSON.stringify(agent, null, 2)}`;
321
349
  }
322
350
  },
323
351
  {
@@ -532,8 +560,8 @@ const TOOLS = [
532
560
  text += '\n';
533
561
  if (args.include_responses && task.responses && task.responses.length > 0) {
534
562
  for (const resp of task.responses) {
535
- const val = resp.response_value != null ? JSON.stringify(resp.response_value) : '(no value)';
536
- text += ` -> ${val}${resp.notes ? ' — ' + resp.notes : ''} (${resp.worker_name}, ${resp.completed_at})\n`;
563
+ const { value, note } = fmtResponse(resp.response_value, resp.notes);
564
+ text += ` -> ${value != null ? value : '(no value)'}${note ? ' — ' + note : ''} (${resp.worker_name}, ${resp.completed_at})\n`;
537
565
  }
538
566
  }
539
567
  }
@@ -560,10 +588,10 @@ const TOOLS = [
560
588
  const r = result.data;
561
589
  let text = `Responses for "${r.task}" in project "${r.project_name}" (${r.matched_tasks} matching task(s), ${r.responses.length} response(s), oldest first):\n\n`;
562
590
  for (const resp of r.responses) {
563
- const val = resp.response_value != null ? JSON.stringify(resp.response_value) : '(no value)';
591
+ const { value, note } = fmtResponse(resp.response_value, resp.notes);
564
592
  const when = (resp.completed_at || '').slice(0, 16).replace('T', ' ');
565
- text += `${when} · ${resp.list_name} · ${resp.task_title}: ${val}`;
566
- if (resp.notes) text += ` — ${resp.notes}`;
593
+ text += `${when} · ${resp.list_name} · ${resp.task_title}: ${value != null ? value : '(no value)'}`;
594
+ if (note) text += ` — ${note}`;
567
595
  text += ` (${resp.worker_name})\n`;
568
596
  }
569
597
  if (r.responses.length === 0) text += '(no responses found)\n';
@@ -748,7 +776,7 @@ const TOOLS = [
748
776
  text += `--- ANALYSIS ---\n`;
749
777
  text += `Summary: ${d.analysis?.summary || 'N/A'}\n`;
750
778
  text += `Classification: ${d.analysis?.classification?.type || 'N/A'} (${d.analysis?.classification?.severity || 'N/A'})\n`;
751
- text += `Urgency: ${d.analysis?.urgency_score || 'N/A'}/10\n`;
779
+ text += `Urgency: ${d.analysis?.urgency_score ?? 'N/A'}/100\n`;
752
780
  if (d.analysis?.root_cause_hypotheses?.length) {
753
781
  text += `Root Causes:\n`;
754
782
  d.analysis.root_cause_hypotheses.forEach((h, i) => {