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.
- package/index.js +1 -1
- package/package.json +2 -2
- package/tools.cjs +53 -25
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tascan-mcp",
|
|
3
|
-
"version": "3.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
|
312
|
-
worker_id: args.worker_id
|
|
340
|
+
model: args.model,
|
|
341
|
+
worker_id: args.worker_id,
|
|
313
342
|
inbox_id: args.inbox_id,
|
|
314
|
-
capabilities: args.capabilities
|
|
315
|
-
location: args.location
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
|
536
|
-
text += ` -> ${
|
|
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
|
|
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}: ${
|
|
566
|
-
if (
|
|
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
|
|
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) => {
|