@event4u/agent-config 1.40.0 → 1.41.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.
@@ -0,0 +1,275 @@
1
+ {
2
+ "schema_version": 1,
3
+ "description": "Source-of-truth catalog of consumer-relevant MCP tools. Read by the stdio server (scripts/mcp_server/) and packed into the Cloud Worker bundle (workers/mcp/). Phase 1 of road-to-mcp-full-coverage: tools without 'implemented' transports return the 'not_implemented' envelope defined in docs/contracts/mcp-tool-stub-envelope.md. The 'implemented_on' field lists transports where the real handler is wired; everything else is a discovery stub. See agents/roadmaps/road-to-mcp-full-coverage.md.",
4
+ "install_hint_stdio": "pip install agent-config[mcp] && ./agent-config mcp:run",
5
+ "tools": [
6
+ {
7
+ "name": "lint_skills",
8
+ "description": "Lint skill / rule / command / guideline / persona markdown files. Returns the same JSON payload as `scripts/skill_linter.py --format json`. Read-only — never writes or spawns git. Pass `paths` to lint a subset, omit for a full tree scan.",
9
+ "side_effect": "ro",
10
+ "implemented_on": ["stdio"],
11
+ "input_schema": {
12
+ "type": "object",
13
+ "properties": {
14
+ "paths": {
15
+ "type": "array",
16
+ "items": {"type": "string"},
17
+ "description": "Repo-relative paths to lint. Empty / missing → full tree scan."
18
+ }
19
+ },
20
+ "additionalProperties": false
21
+ }
22
+ },
23
+ {
24
+ "name": "chat_history_append",
25
+ "description": "Append one entry to the consumer's chat-history JSONL (`agents/.agent-chat-history`). Path-scoped — writes outside the allowlist raise ValueError. Use `dry_run` to preview the payload without touching the filesystem.",
26
+ "side_effect": "fs-write",
27
+ "implemented_on": ["stdio"],
28
+ "input_schema": {
29
+ "type": "object",
30
+ "properties": {
31
+ "text": {"type": "string"},
32
+ "entry_type": {"type": "string", "description": "Short `t` tag (e.g. note, decision). Defaults to `note`."},
33
+ "path": {"type": "string", "description": "Optional path override. Must resolve to `agents/.agent-chat-history` or `.agent-chat-history` under consumer_root."},
34
+ "session": {"type": "string"},
35
+ "dry_run": {"type": "boolean", "default": false},
36
+ "min_schema_version": {"type": "integer"}
37
+ },
38
+ "required": ["text"],
39
+ "additionalProperties": false
40
+ }
41
+ },
42
+ {
43
+ "name": "chat_history_read",
44
+ "description": "Read recent chat-history entries from `agents/.agent-chat-history`. Filter by session, limit, or entry-type. Read-only.",
45
+ "side_effect": "ro",
46
+ "implemented_on": ["stdio"],
47
+ "input_schema": {
48
+ "type": "object",
49
+ "properties": {
50
+ "last": {"type": "integer", "description": "Return only the last N entries.", "minimum": 1},
51
+ "session": {"type": "string", "description": "Filter by 16-char session id."},
52
+ "entry_type": {"type": "string", "description": "Filter by `t` field."},
53
+ "path": {"type": "string"}
54
+ },
55
+ "additionalProperties": false
56
+ }
57
+ },
58
+ {
59
+ "name": "memory_lookup",
60
+ "description": "Hybrid memory retrieval over `agents/memory/<type>/*.yml` and `agents/memory/intake/*.jsonl`. Read-only.",
61
+ "side_effect": "ro",
62
+ "implemented_on": ["stdio"],
63
+ "input_schema": {
64
+ "type": "object",
65
+ "properties": {
66
+ "types": {"type": "array", "items": {"type": "string"}, "description": "Memory types to scan (e.g. ownership, historical-patterns)."},
67
+ "keys": {"type": "array", "items": {"type": "string"}, "description": "Path / glob keys to match."},
68
+ "limit": {"type": "integer", "minimum": 1, "default": 5}
69
+ },
70
+ "required": ["types"],
71
+ "additionalProperties": false
72
+ }
73
+ },
74
+ {
75
+ "name": "memory_signal",
76
+ "description": "Append an engineering-memory signal to `agents/memory/intake/signals-YYYY-MM.jsonl`. Rate-limited per `(type, path)` within a rolling window.",
77
+ "side_effect": "fs-write",
78
+ "implemented_on": [],
79
+ "input_schema": {
80
+ "type": "object",
81
+ "properties": {
82
+ "type": {"type": "string", "description": "Memory type (historical-patterns, incident-learnings, ownership, ...)."},
83
+ "path": {"type": "string", "description": "Anchor path the signal is about."},
84
+ "body": {"type": "string", "description": "Free-form signal body."}
85
+ },
86
+ "required": ["type", "path", "body"],
87
+ "additionalProperties": false
88
+ }
89
+ },
90
+ {
91
+ "name": "memory_status",
92
+ "description": "Report whether the optional `@event4u/agent-memory` package is reachable, and surface its routing metadata. Read-only.",
93
+ "side_effect": "ro",
94
+ "implemented_on": ["stdio"],
95
+ "input_schema": {"type": "object", "properties": {}, "additionalProperties": false}
96
+ },
97
+ {
98
+ "name": "skill_trigger_eval",
99
+ "description": "Score a user message against the compiled router and return the matching skills with their trigger reasons. Read-only.",
100
+ "side_effect": "ro",
101
+ "implemented_on": [],
102
+ "input_schema": {
103
+ "type": "object",
104
+ "properties": {
105
+ "message": {"type": "string"},
106
+ "context": {"type": "string", "description": "Optional recent-turn context."},
107
+ "limit": {"type": "integer", "minimum": 1, "default": 5}
108
+ },
109
+ "required": ["message"],
110
+ "additionalProperties": false
111
+ }
112
+ },
113
+ {
114
+ "name": "suggest_command",
115
+ "description": "Run the command-suggestion engine: score commands against a user message + context and return the numbered options block. Read-only, deterministic.",
116
+ "side_effect": "ro",
117
+ "implemented_on": [],
118
+ "input_schema": {
119
+ "type": "object",
120
+ "properties": {
121
+ "message": {"type": "string"},
122
+ "context": {"type": "string"}
123
+ },
124
+ "required": ["message"],
125
+ "additionalProperties": false
126
+ }
127
+ },
128
+ {
129
+ "name": "suggest_skill_for_task",
130
+ "description": "Match a free-form task description to the most relevant skills with frontmatter triggers. Read-only.",
131
+ "side_effect": "ro",
132
+ "implemented_on": [],
133
+ "input_schema": {
134
+ "type": "object",
135
+ "properties": {
136
+ "task": {"type": "string"},
137
+ "limit": {"type": "integer", "minimum": 1, "default": 5}
138
+ },
139
+ "required": ["task"],
140
+ "additionalProperties": false
141
+ }
142
+ },
143
+ {
144
+ "name": "mine_session",
145
+ "description": "Extract patterns, decisions, and learnings from a chat-history session JSONL. Read-only.",
146
+ "side_effect": "ro",
147
+ "implemented_on": [],
148
+ "input_schema": {
149
+ "type": "object",
150
+ "properties": {
151
+ "session": {"type": "string", "description": "Session id to mine."},
152
+ "path": {"type": "string", "description": "Optional path to a chat-history JSONL."}
153
+ },
154
+ "additionalProperties": false
155
+ }
156
+ },
157
+ {
158
+ "name": "update_form_request_messages",
159
+ "description": "Sync the `messages()` method of a Laravel FormRequest class — add missing entries, link to language keys, drop stale ones.",
160
+ "side_effect": "fs-write",
161
+ "implemented_on": [],
162
+ "input_schema": {
163
+ "type": "object",
164
+ "properties": {
165
+ "request_class": {"type": "string", "description": "Fully-qualified FormRequest class FQN or repo-relative file path."},
166
+ "dry_run": {"type": "boolean", "default": false}
167
+ },
168
+ "required": ["request_class"],
169
+ "additionalProperties": false
170
+ }
171
+ },
172
+ {
173
+ "name": "sync_gitignore",
174
+ "description": "Synchronise the `event4u/agent-config` block in the consumer project's `.gitignore` — adds missing entries, preserves user-added lines.",
175
+ "side_effect": "fs-write",
176
+ "implemented_on": [],
177
+ "input_schema": {
178
+ "type": "object",
179
+ "properties": {
180
+ "dry_run": {"type": "boolean", "default": false}
181
+ },
182
+ "additionalProperties": false
183
+ }
184
+ },
185
+ {
186
+ "name": "sync_agent_settings",
187
+ "description": "Synchronise `.agent-settings.yml` against the current template + profile — adds new sections/keys, preserves user values.",
188
+ "side_effect": "fs-write",
189
+ "implemented_on": [],
190
+ "input_schema": {
191
+ "type": "object",
192
+ "properties": {
193
+ "profile": {"type": "string", "description": "Override the profile pinned in the settings file."},
194
+ "dry_run": {"type": "boolean", "default": false}
195
+ },
196
+ "additionalProperties": false
197
+ }
198
+ },
199
+ {
200
+ "name": "run_tests",
201
+ "description": "Execute the consumer project's test suite via the project's standard test runner. Returns the exit code, runner name, and a structured tail of the output.",
202
+ "side_effect": "shell",
203
+ "implemented_on": [],
204
+ "input_schema": {
205
+ "type": "object",
206
+ "properties": {
207
+ "filter": {"type": "string", "description": "Restrict to tests matching this pattern."},
208
+ "path": {"type": "string", "description": "Restrict to tests under this directory."}
209
+ },
210
+ "additionalProperties": false
211
+ }
212
+ },
213
+ {
214
+ "name": "run_quality_checks",
215
+ "description": "Run the configured quality gate (PHPStan / Rector / ECS / equivalent) and return a structured per-tool result.",
216
+ "side_effect": "shell",
217
+ "implemented_on": [],
218
+ "input_schema": {
219
+ "type": "object",
220
+ "properties": {
221
+ "tool": {"type": "string", "description": "Restrict to a single tool name."}
222
+ },
223
+ "additionalProperties": false
224
+ }
225
+ },
226
+ {
227
+ "name": "list_skills",
228
+ "description": "Enumerate every skill currently exposed as a prompt, with name + description + source. Read-only manifest view.",
229
+ "side_effect": "ro",
230
+ "implemented_on": ["stdio"],
231
+ "input_schema": {"type": "object", "properties": {}, "additionalProperties": false}
232
+ },
233
+ {
234
+ "name": "list_commands",
235
+ "description": "Enumerate every slash command currently exposed as a prompt. Read-only manifest view.",
236
+ "side_effect": "ro",
237
+ "implemented_on": ["stdio"],
238
+ "input_schema": {"type": "object", "properties": {}, "additionalProperties": false}
239
+ },
240
+ {
241
+ "name": "list_rules",
242
+ "description": "Enumerate every rule currently exposed as a resource. Read-only manifest view.",
243
+ "side_effect": "ro",
244
+ "implemented_on": ["stdio"],
245
+ "input_schema": {"type": "object", "properties": {}, "additionalProperties": false}
246
+ },
247
+ {
248
+ "name": "compile_router",
249
+ "description": "Regenerate the compiled router (`router.compiled.json`) from the current rule / skill / command frontmatter. Shell-bound — wraps `scripts/compile_router.py`.",
250
+ "side_effect": "shell",
251
+ "implemented_on": [],
252
+ "input_schema": {
253
+ "type": "object",
254
+ "properties": {
255
+ "dry_run": {"type": "boolean", "default": false}
256
+ },
257
+ "additionalProperties": false
258
+ }
259
+ },
260
+ {
261
+ "name": "read_resource_body",
262
+ "description": "Fetch the rendered body of any resource URI (rule, guideline, context) without going through `resources/read`. Convenience for clients that want to inline content into a tool call result.",
263
+ "side_effect": "ro",
264
+ "implemented_on": ["stdio"],
265
+ "input_schema": {
266
+ "type": "object",
267
+ "properties": {
268
+ "uri": {"type": "string", "description": "Resource URI like `rule://commit-policy`."}
269
+ },
270
+ "required": ["uri"],
271
+ "additionalProperties": false
272
+ }
273
+ }
274
+ ]
275
+ }
@@ -0,0 +1,128 @@
1
+ """MCP telemetry sink — Phase 1 J4 instrumentation.
2
+
3
+ Per ``agents/roadmaps/road-to-mcp-full-coverage.md`` §Phase 1 J4 +
4
+ ``docs/contracts/mcp-tool-stub-envelope.md``, both transports log every
5
+ ``tools/call`` with ``{tool_name, client_id_hash, ts, transport,
6
+ outcome}``. Payload bodies are never logged; the client identifier is
7
+ hashed at the server boundary so the queryable store never sees raw
8
+ identity.
9
+
10
+ Outcomes:
11
+
12
+ - ``implemented`` — real handler ran (no envelope returned).
13
+ - ``stub`` — catalog entry missing this transport; ``not_implemented``
14
+ envelope returned.
15
+ - ``latent_demand`` — caller asked for a tool not in the catalog.
16
+
17
+ The sink writes JSONL to ``agents/.mcp-telemetry/calls.jsonl`` under the
18
+ consumer root. Failure to write must not break the wire surface: the
19
+ ``record_call`` helper swallows OSError + ValueError and emits a single
20
+ warning to stderr.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import hashlib
25
+ import json
26
+ import os
27
+ import sys
28
+ import time
29
+ from pathlib import Path
30
+ from typing import Literal
31
+
32
+ Outcome = Literal["implemented", "stub", "latent_demand"]
33
+
34
+ # Stable file location relative to consumer_root. Phase 2 K1 routes
35
+ # this into a queryable store; Phase 1 only needs the file to exist.
36
+ TELEMETRY_REL_DIR = "agents/.mcp-telemetry"
37
+ TELEMETRY_FILENAME = "calls.jsonl"
38
+
39
+ # Truncation length for the client_id hash. 12 hex chars = 48 bits of
40
+ # entropy — enough to distinguish hundreds of consumers without
41
+ # becoming a re-identification vector.
42
+ _HASH_LEN = 12
43
+
44
+
45
+ def _client_id_seed() -> str:
46
+ """Identity components that together pin a consumer install.
47
+
48
+ USER + machine hostname + repo path is a stable triple that survives
49
+ sessions without leaking PII into the log. The hash never reverses.
50
+ """
51
+ user = os.environ.get("USER") or os.environ.get("USERNAME") or "unknown"
52
+ host = os.environ.get("HOSTNAME")
53
+ if not host and hasattr(os, "uname"):
54
+ host = os.uname().nodename
55
+ host = host or "unknown"
56
+ cwd = str(Path.cwd().resolve())
57
+ return f"{user}|{host}|{cwd}"
58
+
59
+
60
+ def hash_client_id(seed: str | None = None) -> str:
61
+ """SHA-256(seed) truncated to 12 hex chars. Boundary-only call."""
62
+ raw = seed if seed is not None else _client_id_seed()
63
+ digest = hashlib.sha256(raw.encode("utf-8")).hexdigest()
64
+ return digest[:_HASH_LEN]
65
+
66
+
67
+ def _resolve_log_path(consumer_root: Path | None = None) -> Path:
68
+ """Pick the JSONL location. Defaults to CWD when no override given."""
69
+ root = (consumer_root or Path.cwd()).resolve()
70
+ return root / TELEMETRY_REL_DIR / TELEMETRY_FILENAME
71
+
72
+
73
+ def _now_iso() -> str:
74
+ """ISO-8601 UTC timestamp, seconds precision."""
75
+ return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
76
+
77
+
78
+ def build_record(
79
+ *,
80
+ tool_name: str,
81
+ outcome: Outcome,
82
+ transport: str,
83
+ client_id_hash_value: str | None = None,
84
+ ts: str | None = None,
85
+ ) -> dict[str, object]:
86
+ """Pure helper — assemble the record without touching the filesystem."""
87
+ return {
88
+ "tool_name": tool_name,
89
+ "client_id_hash": client_id_hash_value or hash_client_id(),
90
+ "ts": ts or _now_iso(),
91
+ "transport": transport,
92
+ "outcome": outcome,
93
+ }
94
+
95
+
96
+ def record_call(
97
+ *,
98
+ tool_name: str,
99
+ outcome: Outcome,
100
+ transport: str,
101
+ consumer_root: Path | None = None,
102
+ client_id_hash_value: str | None = None,
103
+ ) -> dict[str, object] | None:
104
+ """Append one JSONL record. Returns the record or None on failure.
105
+
106
+ Failures are swallowed: telemetry must never break the wire surface.
107
+ A single ``mcp-server: warn: telemetry`` line is emitted to stderr
108
+ so silent-failure windows show up in the boot log and the J6
109
+ healthcheck can detect them.
110
+ """
111
+ record = build_record(
112
+ tool_name=tool_name,
113
+ outcome=outcome,
114
+ transport=transport,
115
+ client_id_hash_value=client_id_hash_value,
116
+ )
117
+ target = _resolve_log_path(consumer_root)
118
+ try:
119
+ target.parent.mkdir(parents=True, exist_ok=True)
120
+ with target.open("a", encoding="utf-8") as fh:
121
+ fh.write(json.dumps(record, separators=(",", ":")) + "\n")
122
+ except (OSError, ValueError) as exc:
123
+ print(
124
+ f"mcp-server: warn: telemetry write failed: {exc}",
125
+ file=sys.stderr,
126
+ )
127
+ return None
128
+ return record