@tethral/acr-mcp 0.9.0 → 2.0.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/README.md +38 -32
- package/dist/src/env-detect.d.ts +15 -0
- package/dist/src/env-detect.js +22 -0
- package/dist/src/env-detect.js.map +1 -1
- package/dist/src/middleware/correlation-window.d.ts +68 -0
- package/dist/src/middleware/correlation-window.js +113 -0
- package/dist/src/middleware/correlation-window.js.map +1 -0
- package/dist/src/server.d.ts +3 -0
- package/dist/src/server.js +32 -9
- package/dist/src/server.js.map +1 -1
- package/dist/src/session-state.d.ts +3 -0
- package/dist/src/session-state.js +11 -0
- package/dist/src/session-state.js.map +1 -1
- package/dist/src/tools/acknowledge-threat.js +11 -6
- package/dist/src/tools/acknowledge-threat.js.map +1 -1
- package/dist/src/tools/check-entity.js +57 -58
- package/dist/src/tools/check-entity.js.map +1 -1
- package/dist/src/tools/check-environment.js +8 -4
- package/dist/src/tools/check-environment.js.map +1 -1
- package/dist/src/tools/disable-deep-composition.d.ts +20 -0
- package/dist/src/tools/disable-deep-composition.js +45 -0
- package/dist/src/tools/disable-deep-composition.js.map +1 -0
- package/dist/src/tools/get-coverage.d.ts +2 -0
- package/dist/src/tools/get-coverage.js +67 -0
- package/dist/src/tools/get-coverage.js.map +1 -0
- package/dist/src/tools/get-failure-registry.d.ts +2 -0
- package/dist/src/tools/get-failure-registry.js +75 -0
- package/dist/src/tools/get-failure-registry.js.map +1 -0
- package/dist/src/tools/get-friction-report.js +66 -15
- package/dist/src/tools/get-friction-report.js.map +1 -1
- package/dist/src/tools/get-interaction-log.js +22 -25
- package/dist/src/tools/get-interaction-log.js.map +1 -1
- package/dist/src/tools/get-my-agent.js +5 -1
- package/dist/src/tools/get-my-agent.js.map +1 -1
- package/dist/src/tools/get-network-status.js +11 -8
- package/dist/src/tools/get-network-status.js.map +1 -1
- package/dist/src/tools/get-notifications.js +9 -10
- package/dist/src/tools/get-notifications.js.map +1 -1
- package/dist/src/tools/get-profile.d.ts +2 -0
- package/dist/src/tools/get-profile.js +89 -0
- package/dist/src/tools/get-profile.js.map +1 -0
- package/dist/src/tools/get-skill-tracker.js +16 -14
- package/dist/src/tools/get-skill-tracker.js.map +1 -1
- package/dist/src/tools/get-skill-versions.js +14 -11
- package/dist/src/tools/get-skill-versions.js.map +1 -1
- package/dist/src/tools/get-stable-corridors.d.ts +2 -0
- package/dist/src/tools/get-stable-corridors.js +65 -0
- package/dist/src/tools/get-stable-corridors.js.map +1 -0
- package/dist/src/tools/get-trend.d.ts +2 -0
- package/dist/src/tools/get-trend.js +75 -0
- package/dist/src/tools/get-trend.js.map +1 -0
- package/dist/src/tools/log-interaction.d.ts +2 -1
- package/dist/src/tools/log-interaction.js +116 -25
- package/dist/src/tools/log-interaction.js.map +1 -1
- package/dist/src/tools/register-agent.js +67 -15
- package/dist/src/tools/register-agent.js.map +1 -1
- package/dist/src/tools/search-skills.js +33 -19
- package/dist/src/tools/search-skills.js.map +1 -1
- package/dist/src/tools/summarize-my-agent.d.ts +2 -0
- package/dist/src/tools/summarize-my-agent.js +100 -0
- package/dist/src/tools/summarize-my-agent.js.map +1 -0
- package/dist/src/tools/update-composition.js +67 -13
- package/dist/src/tools/update-composition.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export function checkEntityTool(server, apiUrl, resolverUrl) {
|
|
3
|
-
server.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
server.registerTool('check_entity', {
|
|
4
|
+
description: 'Ask the ACR network what it knows about a specific skill hash, agent, or system. Returns the raw behavioral signals ACR has observed: interaction counts, failure and anomaly rates, agent adoption counts, and related metadata. This is NOT a security check — ACR does not evaluate, score, or test. It only records what has been observed and surfaces the raw counts. Read-only lookup; no data is sent to ACR.',
|
|
5
|
+
inputSchema: {
|
|
6
|
+
entity_type: z.enum(['skill', 'agent', 'system']).describe('Type of entity to look up'),
|
|
7
|
+
entity_id: z.string().describe('The entity identifier: skill SHA-256 hash, agent_id, or system_id'),
|
|
8
|
+
},
|
|
9
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
10
|
+
_meta: { priorityHint: 0.6 },
|
|
11
|
+
}, async ({ entity_type, entity_id }) => {
|
|
7
12
|
try {
|
|
8
13
|
let url;
|
|
9
14
|
switch (entity_type) {
|
|
@@ -41,76 +46,57 @@ export function checkEntityTool(server, apiUrl, resolverUrl) {
|
|
|
41
46
|
return {
|
|
42
47
|
content: [{
|
|
43
48
|
type: 'text',
|
|
44
|
-
text: `Unknown skill. This hash has not been
|
|
49
|
+
text: `Unknown skill. This hash has not been observed by the ACR network.${similarText}`,
|
|
45
50
|
}],
|
|
46
51
|
};
|
|
47
52
|
}
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
text += `Name: ${data.skill_name}\n`;
|
|
54
|
-
text += `Threat Level: ${(data.threat_level ?? 'unknown').toUpperCase()}\n`;
|
|
55
|
-
text += `Security Score: ${data.scan_score ?? 0}/100\n`;
|
|
56
|
-
if (data.blocked_reason)
|
|
57
|
-
text += `\nReason: ${data.blocked_reason}\n`;
|
|
58
|
-
if (data.threat_patterns && data.threat_patterns.length > 0) {
|
|
59
|
-
text += `\nDetected Threat Patterns:\n`;
|
|
60
|
-
for (const p of data.threat_patterns) {
|
|
61
|
-
text += ` - ${p}\n`;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
text += `\nThis skill has been flagged by ACR's content security scanner `;
|
|
65
|
-
text += `and is BLOCKED from installation. The skill content is not `;
|
|
66
|
-
text += `available for download, copy, or viewing.\n`;
|
|
67
|
-
text += `\nIf you currently have this skill installed, you should `;
|
|
68
|
-
text += `consider uninstalling it and notifying your user.\n`;
|
|
69
|
-
text += `\nTo request a review or dispute this block, `;
|
|
70
|
-
text += `contact security@tethral.com with hash: ${entity_id}`;
|
|
71
|
-
return { content: [{ type: 'text', text }] };
|
|
72
|
-
}
|
|
73
|
-
// ── Normal skill response ──
|
|
74
|
-
const level = (data.threat_level ?? 'none').toUpperCase();
|
|
75
|
-
let text = `Skill found.\n\nThreat Level: ${level}`;
|
|
53
|
+
// Skill signals — raw observed data from the network. No
|
|
54
|
+
// synthetic threat level label, no "FLAGGED BY ACR" verdict.
|
|
55
|
+
// The MCP reports what the network has seen; the operator
|
|
56
|
+
// decides what to do with it.
|
|
57
|
+
let text = `Skill found: ${data.skill_hash?.slice(0, 16) ?? entity_id.slice(0, 16)}...\n`;
|
|
76
58
|
if (data.skill_name)
|
|
77
|
-
text +=
|
|
59
|
+
text += `Name: ${data.skill_name}\n`;
|
|
78
60
|
if (data.description)
|
|
79
|
-
text +=
|
|
61
|
+
text += `Description: ${data.description}\n`;
|
|
80
62
|
if (data.version)
|
|
81
|
-
text +=
|
|
63
|
+
text += `Version: ${data.version}\n`;
|
|
82
64
|
if (data.author)
|
|
83
|
-
text +=
|
|
65
|
+
text += `Author: ${data.author}\n`;
|
|
84
66
|
if (data.category)
|
|
85
|
-
text +=
|
|
67
|
+
text += `Category: ${data.category}\n`;
|
|
86
68
|
if (data.tags && data.tags.length > 0)
|
|
87
|
-
text +=
|
|
69
|
+
text += `Tags: ${data.tags.join(', ')}\n`;
|
|
70
|
+
text += `\n── Network signals ──\n`;
|
|
88
71
|
if (data.agent_count != null)
|
|
89
|
-
text +=
|
|
72
|
+
text += ` Agents observed using this skill: ${data.agent_count}\n`;
|
|
90
73
|
if (data.interaction_count != null)
|
|
91
|
-
text +=
|
|
74
|
+
text += ` Total interactions observed: ${data.interaction_count}\n`;
|
|
75
|
+
if (data.anomaly_signal_count != null)
|
|
76
|
+
text += ` Anomaly signals reported: ${data.anomaly_signal_count}\n`;
|
|
92
77
|
if (data.anomaly_rate != null)
|
|
93
|
-
text +=
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
78
|
+
text += ` Anomaly rate: ${(data.anomaly_rate * 100).toFixed(1)}%\n`;
|
|
79
|
+
// Signal categories observed — these are descriptive tags of
|
|
80
|
+
// what kinds of anomaly patterns the network has seen, not
|
|
81
|
+
// severity labels.
|
|
82
|
+
if (data.threat_patterns && Array.isArray(data.threat_patterns) && data.threat_patterns.length > 0) {
|
|
83
|
+
text += ` Anomaly pattern categories: ${data.threat_patterns.join(', ')}\n`;
|
|
98
84
|
}
|
|
99
|
-
//
|
|
85
|
+
// Scan signals — if the content scanner observed something,
|
|
86
|
+
// the scanner's raw findings (which patterns it matched) are
|
|
87
|
+
// reported here. No pass/fail verdict.
|
|
88
|
+
if (data.scan_score != null)
|
|
89
|
+
text += ` Content scanner score (external): ${data.scan_score}\n`;
|
|
90
|
+
// Version freshness — raw comparison, no advice.
|
|
100
91
|
if (data.is_current_version === false) {
|
|
101
|
-
text += `\n
|
|
92
|
+
text += `\n── Version ──\n`;
|
|
93
|
+
text += ` This version is ${data.versions_behind ?? '?'} behind the latest observed version.`;
|
|
102
94
|
if (data.current_hash)
|
|
103
|
-
text += `
|
|
104
|
-
text += '\
|
|
95
|
+
text += ` Latest hash: ${data.current_hash.slice(0, 16)}...`;
|
|
96
|
+
text += '\n';
|
|
105
97
|
}
|
|
106
98
|
else if (data.is_current_version === true) {
|
|
107
|
-
text +=
|
|
108
|
-
}
|
|
109
|
-
if (data.threat_level === 'high' || data.threat_level === 'critical') {
|
|
110
|
-
text += `\n\nWARNING: This skill has been flagged. Do not install without explicit user confirmation.`;
|
|
111
|
-
}
|
|
112
|
-
else if (data.threat_level === 'medium') {
|
|
113
|
-
text += `\n\nCaution: Elevated anomaly signals. Proceed only if the user confirms.`;
|
|
99
|
+
text += `\n── Version ──\n This is the latest version observed by the network.\n`;
|
|
114
100
|
}
|
|
115
101
|
return { content: [{ type: 'text', text }] };
|
|
116
102
|
}
|
|
@@ -129,10 +115,23 @@ export function checkEntityTool(server, apiUrl, resolverUrl) {
|
|
|
129
115
|
if (!data.found) {
|
|
130
116
|
return { content: [{ type: 'text', text: `System ${entity_id} not found.` }] };
|
|
131
117
|
}
|
|
118
|
+
// Raw network signals for the target system. No synthetic
|
|
119
|
+
// health_status label — client reads the rates and decides.
|
|
120
|
+
let sysText = `System found: ${entity_id}\n`;
|
|
121
|
+
sysText += `Type: ${data.system_type}\n\n`;
|
|
122
|
+
sysText += `── Network signals ──\n`;
|
|
123
|
+
sysText += ` Total interactions observed: ${data.total_interactions ?? 0}\n`;
|
|
124
|
+
sysText += ` Distinct agents using this system: ${data.distinct_agents ?? 0}\n`;
|
|
125
|
+
sysText += ` Failure rate: ${((data.failure_rate ?? 0) * 100).toFixed(1)}%\n`;
|
|
126
|
+
sysText += ` Anomaly rate: ${((data.anomaly_rate ?? 0) * 100).toFixed(1)}%\n`;
|
|
127
|
+
if (data.median_duration_ms != null)
|
|
128
|
+
sysText += ` Median duration: ${data.median_duration_ms}ms\n`;
|
|
129
|
+
if (data.p95_duration_ms != null)
|
|
130
|
+
sysText += ` p95 duration: ${data.p95_duration_ms}ms\n`;
|
|
132
131
|
return {
|
|
133
132
|
content: [{
|
|
134
133
|
type: 'text',
|
|
135
|
-
text:
|
|
134
|
+
text: sysText,
|
|
136
135
|
}],
|
|
137
136
|
};
|
|
138
137
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-entity.js","sourceRoot":"","sources":["../../../src/tools/check-entity.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,eAAe,CAAC,MAAiB,EAAE,MAAc,EAAE,WAAmB;IACpF,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"check-entity.js","sourceRoot":"","sources":["../../../src/tools/check-entity.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export function checkEnvironmentTool(server, apiUrl, resolverUrl) {
|
|
2
|
-
server.
|
|
2
|
+
server.registerTool('check_environment', {
|
|
3
|
+
description: 'Check the current ACR network environment: active anomaly signals and network-level observation data. Call on startup to see the state of the broader network. Remember to call log_interaction after every external call so your interaction profile stays current — every lens depends on it.',
|
|
4
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5
|
+
_meta: { priorityHint: 0.8 },
|
|
6
|
+
}, async () => {
|
|
3
7
|
try {
|
|
4
8
|
const [threatsRes, healthRes] = await Promise.all([
|
|
5
9
|
fetch(`${resolverUrl}/v1/threats/active`),
|
|
@@ -9,13 +13,13 @@ export function checkEnvironmentTool(server, apiUrl, resolverUrl) {
|
|
|
9
13
|
const health = await healthRes.json();
|
|
10
14
|
let text = `ACR Network Status: ${health.status ?? 'unknown'}\n`;
|
|
11
15
|
if (Array.isArray(threats) && threats.length > 0) {
|
|
12
|
-
text += `\
|
|
16
|
+
text += `\nSkills with elevated anomaly signals: ${threats.length}\n`;
|
|
13
17
|
for (const t of threats) {
|
|
14
|
-
text += `-
|
|
18
|
+
text += `- ${t.skill_name || t.skill_hash.substring(0, 16) + '...'} — ${t.anomaly_signal_count} signals, ${t.agent_count ?? 0} reporters\n`;
|
|
15
19
|
}
|
|
16
20
|
}
|
|
17
21
|
else {
|
|
18
|
-
text += '\nNo
|
|
22
|
+
text += '\nNo elevated anomaly signals observed.';
|
|
19
23
|
}
|
|
20
24
|
return { content: [{ type: 'text', text }] };
|
|
21
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-environment.js","sourceRoot":"","sources":["../../../src/tools/check-environment.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,MAAc,EAAE,WAAmB;IACzF,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"check-environment.js","sourceRoot":"","sources":["../../../src/tools/check-environment.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,MAAc,EAAE,WAAmB;IACzF,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,WAAW,EAAE,iSAAiS;QAC9S,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE;QAC3D,KAAK,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE;KAC7B,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBAChD,KAAK,CAAC,GAAG,WAAW,oBAAoB,CAAC;gBACzC,KAAK,CAAC,GAAG,MAAM,gBAAgB,CAAC;aACjC,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC;YAEtC,IAAI,IAAI,GAAG,uBAAuB,MAAM,CAAC,MAAM,IAAI,SAAS,IAAI,CAAC;YAEjE,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjD,IAAI,IAAI,2CAA2C,OAAO,CAAC,MAAM,IAAI,CAAC;gBACtE,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;oBACxB,IAAI,IAAI,KAAK,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,oBAAoB,aAAa,CAAC,CAAC,WAAW,IAAI,CAAC,cAAc,CAAC;gBAC9I,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,IAAI,IAAI,yCAAyC,CAAC;YACpD,CAAC;YAED,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACxD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACjE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,4BAA4B,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC;QAC3F,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { SessionState } from '../session-state.js';
|
|
3
|
+
/**
|
|
4
|
+
* Operator privacy control for deep composition capture.
|
|
5
|
+
*
|
|
6
|
+
* By default, when the agent reports composition (via register_agent or
|
|
7
|
+
* update_composition), rich component records include sub_components —
|
|
8
|
+
* ACR gets the internals of each attached skill or MCP so it can
|
|
9
|
+
* distinguish internal friction from external friction correctly.
|
|
10
|
+
*
|
|
11
|
+
* Operators who don't want ACR to see the internals can disable deep
|
|
12
|
+
* composition capture. When off, the MCP still sends top-level component
|
|
13
|
+
* data (skill name, MCP name, hash) but strips sub_components before
|
|
14
|
+
* sending. ACR's internal-vs-external classification becomes coarser as
|
|
15
|
+
* a result — that's the tradeoff.
|
|
16
|
+
*
|
|
17
|
+
* Also settable via the ACR_DEEP_COMPOSITION=false environment variable
|
|
18
|
+
* at MCP startup.
|
|
19
|
+
*/
|
|
20
|
+
export declare function disableDeepCompositionTool(server: McpServer, getSession: () => SessionState): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Operator privacy control for deep composition capture.
|
|
4
|
+
*
|
|
5
|
+
* By default, when the agent reports composition (via register_agent or
|
|
6
|
+
* update_composition), rich component records include sub_components —
|
|
7
|
+
* ACR gets the internals of each attached skill or MCP so it can
|
|
8
|
+
* distinguish internal friction from external friction correctly.
|
|
9
|
+
*
|
|
10
|
+
* Operators who don't want ACR to see the internals can disable deep
|
|
11
|
+
* composition capture. When off, the MCP still sends top-level component
|
|
12
|
+
* data (skill name, MCP name, hash) but strips sub_components before
|
|
13
|
+
* sending. ACR's internal-vs-external classification becomes coarser as
|
|
14
|
+
* a result — that's the tradeoff.
|
|
15
|
+
*
|
|
16
|
+
* Also settable via the ACR_DEEP_COMPOSITION=false environment variable
|
|
17
|
+
* at MCP startup.
|
|
18
|
+
*/
|
|
19
|
+
export function disableDeepCompositionTool(server, getSession) {
|
|
20
|
+
server.registerTool('configure_deep_composition', {
|
|
21
|
+
description: 'Operator privacy control. Enable or disable deep composition capture for this session. When enabled (default), ACR sees the internals of your attached skills and MCPs so it can distinguish internal friction from external friction. When disabled, only top-level component info is sent — ACR no longer sees sub-components. Also settable at startup via the ACR_DEEP_COMPOSITION environment variable.',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
enabled: z.boolean().describe('Set to true to enable deep capture, false to disable.'),
|
|
24
|
+
},
|
|
25
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
26
|
+
_meta: { priorityHint: 0.3 },
|
|
27
|
+
}, async ({ enabled }) => {
|
|
28
|
+
const session = getSession();
|
|
29
|
+
const previous = session.deepComposition;
|
|
30
|
+
session.setDeepComposition(enabled);
|
|
31
|
+
const statusText = enabled
|
|
32
|
+
? 'Deep composition capture is now ENABLED. ACR will include sub-components of skills and MCPs in future composition reports. This lets the network distinguish internal interactions (your agent engaging its own parts) from external interactions (those parts reaching outside).'
|
|
33
|
+
: 'Deep composition capture is now DISABLED. Future composition reports will include only top-level components (skill names, MCP names, hashes) without sub_components. ACR will not see the internals of your attached parts. Internal-vs-external classification becomes coarser as a result.';
|
|
34
|
+
const changeText = previous === enabled
|
|
35
|
+
? ' (no change — was already this way)'
|
|
36
|
+
: '';
|
|
37
|
+
return {
|
|
38
|
+
content: [{
|
|
39
|
+
type: 'text',
|
|
40
|
+
text: `${statusText}${changeText}`,
|
|
41
|
+
}],
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=disable-deep-composition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disable-deep-composition.js","sourceRoot":"","sources":["../../../src/tools/disable-deep-composition.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAAiB,EACjB,UAA8B;IAE9B,MAAM,CAAC,YAAY,CACjB,4BAA4B,EAC5B;QACE,WAAW,EACT,8YAA8Y;QAChZ,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,uDAAuD,CAAC;SACvF;QACD,WAAW,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE;QAC5D,KAAK,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE;KAC7B,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACpB,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC;QACzC,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAEpC,MAAM,UAAU,GAAG,OAAO;YACxB,CAAC,CAAC,mRAAmR;YACrR,CAAC,CAAC,8RAA8R,CAAC;QAEnS,MAAM,UAAU,GAAG,QAAQ,KAAK,OAAO;YACrC,CAAC,CAAC,qCAAqC;YACvC,CAAC,CAAC,EAAE,CAAC;QAEP,OAAO;YACL,OAAO,EAAE,CAAC;oBACR,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,GAAG,UAAU,GAAG,UAAU,EAAE;iBACnC,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ensureRegistered, getAgentId, getAgentName, getApiUrl } from '../state.js';
|
|
3
|
+
async function resolveId(nameOrId) {
|
|
4
|
+
if (nameOrId.startsWith('acr_') || nameOrId.startsWith('pseudo_'))
|
|
5
|
+
return nameOrId;
|
|
6
|
+
const res = await fetch(`${getApiUrl()}/api/v1/agent/${encodeURIComponent(nameOrId)}`);
|
|
7
|
+
if (!res.ok)
|
|
8
|
+
throw new Error(`Agent "${nameOrId}" not found`);
|
|
9
|
+
return (await res.json()).agent_id;
|
|
10
|
+
}
|
|
11
|
+
export function getCoverageTool(server, apiUrl) {
|
|
12
|
+
server.registerTool('get_coverage', {
|
|
13
|
+
description: 'Signal coverage: which fields you populate on your receipts and which you don\'t. Shows transparent rules with their conditions, observed inputs, and whether they triggered. Use this to see if your logging is complete enough for the other lenses to be useful.',
|
|
14
|
+
inputSchema: {
|
|
15
|
+
agent_id: z.string().optional().describe('Your ACR agent ID (auto-assigned if omitted)'),
|
|
16
|
+
agent_name: z.string().optional().describe('Your agent name (alternative to agent_id)'),
|
|
17
|
+
},
|
|
18
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
19
|
+
_meta: { priorityHint: 0.5 },
|
|
20
|
+
}, async ({ agent_id, agent_name }) => {
|
|
21
|
+
let id;
|
|
22
|
+
try {
|
|
23
|
+
id = agent_name ? await resolveId(agent_name) : (agent_id || getAgentId() || await ensureRegistered());
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
return { content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : 'Unknown'}` }] };
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(`${apiUrl}/api/v1/agent/${id}/coverage`);
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
const errText = await res.text().catch(() => `HTTP ${res.status}`);
|
|
32
|
+
return { content: [{ type: 'text', text: `Coverage error: ${errText}` }] };
|
|
33
|
+
}
|
|
34
|
+
const data = await res.json();
|
|
35
|
+
const displayName = agent_name || getAgentName() || id;
|
|
36
|
+
const signals = data.signals;
|
|
37
|
+
const rules = data.rules;
|
|
38
|
+
let text = `Coverage Report for ${displayName}\n${'='.repeat(30)}\n`;
|
|
39
|
+
text += `\n-- Signal Counts --\n`;
|
|
40
|
+
for (const [key, value] of Object.entries(signals)) {
|
|
41
|
+
text += ` ${key}: ${value}\n`;
|
|
42
|
+
}
|
|
43
|
+
if (rules && rules.length > 0) {
|
|
44
|
+
const triggered = rules.filter((r) => r.triggered);
|
|
45
|
+
const ok = rules.filter((r) => !r.triggered);
|
|
46
|
+
if (triggered.length > 0) {
|
|
47
|
+
text += `\n-- Coverage Gaps (${triggered.length}) --\n`;
|
|
48
|
+
for (const r of triggered) {
|
|
49
|
+
text += ` ${r.signal}: ${r.rule}\n`;
|
|
50
|
+
text += ` observed: ${JSON.stringify(r.observed)}\n`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (ok.length > 0) {
|
|
54
|
+
text += `\n-- Covered (${ok.length}) --\n`;
|
|
55
|
+
for (const r of ok) {
|
|
56
|
+
text += ` ${r.signal}: ${r.rule} — OK\n`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { content: [{ type: 'text', text }] };
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return { content: [{ type: 'text', text: `Coverage error: ${err instanceof Error ? err.message : 'Unknown'}` }] };
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=get-coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-coverage.js","sourceRoot":"","sources":["../../../src/tools/get-coverage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ensureRegistered, getAgentId, getAgentName, getApiUrl } from '../state.js';
|
|
3
|
+
async function resolveId(nameOrId) {
|
|
4
|
+
if (nameOrId.startsWith('acr_') || nameOrId.startsWith('pseudo_'))
|
|
5
|
+
return nameOrId;
|
|
6
|
+
const res = await fetch(`${getApiUrl()}/api/v1/agent/${encodeURIComponent(nameOrId)}`);
|
|
7
|
+
if (!res.ok)
|
|
8
|
+
throw new Error(`Agent "${nameOrId}" not found`);
|
|
9
|
+
return (await res.json()).agent_id;
|
|
10
|
+
}
|
|
11
|
+
export function getFailureRegistryTool(server, apiUrl) {
|
|
12
|
+
server.registerTool('get_failure_registry', {
|
|
13
|
+
description: 'Failure registry: per-target breakdown of failures — status codes, error codes, categories, and median duration when failed. Shows where your interactions are failing and how.',
|
|
14
|
+
inputSchema: {
|
|
15
|
+
agent_id: z.string().optional().describe('Your ACR agent ID (auto-assigned if omitted)'),
|
|
16
|
+
agent_name: z.string().optional().describe('Your agent name (alternative to agent_id)'),
|
|
17
|
+
scope: z.enum(['day', 'week', 'month']).optional().default('week').describe('Time window'),
|
|
18
|
+
},
|
|
19
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
20
|
+
_meta: { priorityHint: 0.6 },
|
|
21
|
+
}, async ({ agent_id, agent_name, scope }) => {
|
|
22
|
+
let id;
|
|
23
|
+
try {
|
|
24
|
+
id = agent_name ? await resolveId(agent_name) : (agent_id || getAgentId() || await ensureRegistered());
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return { content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : 'Unknown'}` }] };
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const res = await fetch(`${apiUrl}/api/v1/agent/${id}/failure-registry?scope=${scope}`);
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const errText = await res.text().catch(() => `HTTP ${res.status}`);
|
|
33
|
+
return { content: [{ type: 'text', text: `Failure registry error: ${errText}` }] };
|
|
34
|
+
}
|
|
35
|
+
const data = await res.json();
|
|
36
|
+
const displayName = data.name || agent_name || getAgentName() || id;
|
|
37
|
+
const failures = data.failures ?? [];
|
|
38
|
+
let text = `Failure Registry for ${displayName} (${scope})\n${'='.repeat(30)}\n`;
|
|
39
|
+
text += `Period: ${data.period_start} to ${data.period_end}\n`;
|
|
40
|
+
text += `Total interactions: ${data.total_interactions}\n`;
|
|
41
|
+
text += `Total failures: ${data.total_failures}\n`;
|
|
42
|
+
text += `Failure rate: ${(data.failure_rate * 100).toFixed(1)}%\n`;
|
|
43
|
+
text += `Distinct failing targets: ${data.distinct_failing_targets}\n`;
|
|
44
|
+
if (failures.length === 0) {
|
|
45
|
+
text += `\nNo failures recorded in this period.\n`;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
for (const f of failures) {
|
|
49
|
+
text += `\n ${f.target_system_id} (${f.target_system_type})\n`;
|
|
50
|
+
text += ` total failures: ${f.total_count}\n`;
|
|
51
|
+
const statuses = f.statuses;
|
|
52
|
+
if (statuses && Object.keys(statuses).length > 0) {
|
|
53
|
+
text += ` statuses: ${Object.entries(statuses).map(([k, v]) => `${k}=${v}`).join(', ')}\n`;
|
|
54
|
+
}
|
|
55
|
+
const errors = f.error_codes;
|
|
56
|
+
if (errors && Object.keys(errors).length > 0) {
|
|
57
|
+
text += ` error codes: ${Object.entries(errors).map(([k, v]) => `${k}=${v}`).join(', ')}\n`;
|
|
58
|
+
}
|
|
59
|
+
const cats = f.categories;
|
|
60
|
+
if (cats && Object.keys(cats).length > 0) {
|
|
61
|
+
text += ` categories: ${Object.entries(cats).map(([k, v]) => `${k}=${v}`).join(', ')}\n`;
|
|
62
|
+
}
|
|
63
|
+
if (f.median_duration_when_failed_ms != null) {
|
|
64
|
+
text += ` median duration when failed: ${f.median_duration_when_failed_ms}ms\n`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { content: [{ type: 'text', text }] };
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
return { content: [{ type: 'text', text: `Failure registry error: ${err instanceof Error ? err.message : 'Unknown'}` }] };
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=get-failure-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-failure-registry.js","sourceRoot":"","sources":["../../../src/tools/get-failure-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,11 +16,16 @@ async function resolveAgentId(nameOrId) {
|
|
|
16
16
|
return data.agent_id;
|
|
17
17
|
}
|
|
18
18
|
export function getFrictionReportTool(server, apiUrl) {
|
|
19
|
-
server.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
server.registerTool('get_friction_report', {
|
|
20
|
+
description: "Query the friction lens of your interaction profile — one of several lenses available (more on the roadmap). The friction lens surfaces where time and tokens are being lost: bottleneck targets, chain overhead, retry waste, directional friction between targets, and how you compare to the population baseline. Friction is a continuum, not a verdict — high friction could be infrastructure, a hard task, or a component with elevated anomaly signals. Use it together with anomaly signal notifications to interpret correctly. Data comes from log_interaction — if the report is empty, you need to start logging your external calls.",
|
|
21
|
+
inputSchema: {
|
|
22
|
+
agent_id: z.string().optional().describe('Your ACR agent ID (auto-assigned if omitted)'),
|
|
23
|
+
agent_name: z.string().optional().describe('Your agent name (alternative to agent_id). Use this if you know your name but not your ID.'),
|
|
24
|
+
scope: z.enum(['session', 'day', 'week']).optional().default('day').describe('Time window for the report'),
|
|
25
|
+
},
|
|
26
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
27
|
+
_meta: { priorityHint: 0.7 },
|
|
28
|
+
}, async ({ agent_id, agent_name, scope }) => {
|
|
24
29
|
let id;
|
|
25
30
|
try {
|
|
26
31
|
if (agent_name) {
|
|
@@ -93,11 +98,9 @@ export function getFrictionReportTool(server, apiUrl) {
|
|
|
93
98
|
.join(', ');
|
|
94
99
|
text += ` statuses: ${statuses}\n`;
|
|
95
100
|
}
|
|
96
|
-
// Baseline comparison (paid tier)
|
|
101
|
+
// Baseline comparison (paid tier) — raw numbers only.
|
|
97
102
|
if (t.vs_baseline != null) {
|
|
98
|
-
|
|
99
|
-
const pctDiff = Math.abs(Math.round((t.vs_baseline - 1) * 100));
|
|
100
|
-
text += ` vs population: ${pctDiff}% ${dir} baseline`;
|
|
103
|
+
text += ` ratio to population baseline: ${t.vs_baseline.toFixed(2)}`;
|
|
101
104
|
if (t.baseline_median_ms != null)
|
|
102
105
|
text += ` (baseline median ${t.baseline_median_ms}ms, p95 ${t.baseline_p95_ms}ms)`;
|
|
103
106
|
if (t.volatility != null)
|
|
@@ -117,12 +120,16 @@ export function getFrictionReportTool(server, apiUrl) {
|
|
|
117
120
|
if (t.failure_count > 0 && !t.recent_anomalies?.length) {
|
|
118
121
|
text += ` ${t.failure_count} failures\n`;
|
|
119
122
|
}
|
|
120
|
-
// Network
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
// Network context — raw rates across the population. No
|
|
124
|
+
// synthetic health_status label (the inherited column is
|
|
125
|
+
// ignored here; see inherited-drift note in
|
|
126
|
+
// proposals/open-items-plan.md).
|
|
127
|
+
if (t.network_failure_rate != null ||
|
|
128
|
+
t.network_anomaly_rate != null ||
|
|
129
|
+
t.network_agent_count != null) {
|
|
130
|
+
text += ` population: ${t.network_agent_count ?? 0} agents`;
|
|
131
|
+
text += `, failure rate ${((t.network_failure_rate ?? 0) * 100).toFixed(1)}%`;
|
|
132
|
+
text += `, anomaly rate ${((t.network_anomaly_rate ?? 0) * 100).toFixed(1)}%\n`;
|
|
126
133
|
}
|
|
127
134
|
}
|
|
128
135
|
}
|
|
@@ -140,6 +147,50 @@ export function getFrictionReportTool(server, apiUrl) {
|
|
|
140
147
|
text += ` ${s.source}: ${s.interaction_count} interactions\n`;
|
|
141
148
|
}
|
|
142
149
|
}
|
|
150
|
+
// Chain Analysis
|
|
151
|
+
if (data.chain_analysis) {
|
|
152
|
+
const ca = data.chain_analysis;
|
|
153
|
+
text += '\n── Chain Analysis ──\n';
|
|
154
|
+
text += ` Distinct chains: ${ca.chain_count}\n`;
|
|
155
|
+
text += ` Avg chain length: ${ca.avg_chain_length} calls\n`;
|
|
156
|
+
text += ` Total chain overhead: ${(ca.total_chain_overhead_ms / 1000).toFixed(1)}s\n`;
|
|
157
|
+
if (ca.top_patterns && ca.top_patterns.length > 0) {
|
|
158
|
+
text += ' Top patterns:\n';
|
|
159
|
+
for (const p of ca.top_patterns) {
|
|
160
|
+
text += ` ${p.pattern.join(' -> ')} (${p.frequency}x, ${p.avg_overhead_ms}ms avg overhead)\n`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Directional Analysis (pro) — raw amplification factor, no
|
|
165
|
+
// SLOWS/SPEEDS/~ label. Client reads the factor.
|
|
166
|
+
if (data.directional_pairs && data.directional_pairs.length > 0) {
|
|
167
|
+
text += '\n── Directional Analysis ──\n';
|
|
168
|
+
for (const dp of data.directional_pairs) {
|
|
169
|
+
text += ` ${dp.source_target} -> ${dp.destination_target}: amplification ${dp.amplification_factor.toFixed(2)}x`;
|
|
170
|
+
text += ` (${dp.avg_duration_when_preceded}ms after vs ${dp.avg_duration_standalone}ms standalone)`;
|
|
171
|
+
text += ` [${dp.sample_count} samples]\n`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Retry Overhead (pro)
|
|
175
|
+
if (data.retry_overhead) {
|
|
176
|
+
const ro = data.retry_overhead;
|
|
177
|
+
text += '\n── Retry Overhead ──\n';
|
|
178
|
+
text += ` Total retries: ${ro.total_retries}\n`;
|
|
179
|
+
text += ` Wasted time: ${(ro.total_wasted_ms / 1000).toFixed(1)}s\n`;
|
|
180
|
+
for (const t of ro.top_retry_targets) {
|
|
181
|
+
text += ` ${t.target_system_id}: ${t.retry_count} retries, ${t.wasted_ms}ms wasted\n`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Population Drift (pro) — raw drift percentage, no
|
|
185
|
+
// DEGRADING/IMPROVING/stable label.
|
|
186
|
+
if (data.population_drift && data.population_drift.targets.length > 0) {
|
|
187
|
+
text += '\n── Population Drift ──\n';
|
|
188
|
+
for (const t of data.population_drift.targets) {
|
|
189
|
+
const sign = t.drift_percentage > 0 ? '+' : '';
|
|
190
|
+
text += ` ${t.target_system_id}: ${sign}${t.drift_percentage}% vs baseline`;
|
|
191
|
+
text += ` (current ${t.current_median_ms}ms, baseline ${t.baseline_median_ms}ms)\n`;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
143
194
|
// Population comparison (paid tier)
|
|
144
195
|
if (data.population_comparison) {
|
|
145
196
|
text += `\n── Population ──\n`;
|