@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +33 -0
- package/README.md +47 -21
- package/docs/catalog.md +4 -3
- package/docs/contracts/file-ownership-matrix.json +27 -0
- package/docs/contracts/mcp-discovery-phase-notice.md +56 -0
- package/docs/contracts/mcp-tool-stub-envelope.md +78 -0
- package/docs/getting-started.md +1 -1
- package/docs/setup/mcp-client-config.md +94 -13
- package/docs/setup/mcp-cloud-setup.md +32 -1
- package/docs/setup/per-ide/claude-desktop.md +32 -7
- package/package.json +1 -1
- package/scripts/_lib/script_output.py +15 -11
- package/scripts/ai_council/session.py +14 -8
- package/scripts/chat_history.py +29 -53
- package/scripts/command_suggester/settings.py +15 -13
- package/scripts/compile_router.py +13 -9
- package/scripts/compress.py +22 -19
- package/scripts/council_cli.py +9 -3
- package/scripts/mcp_parity_smoke.py +20 -2
- package/scripts/mcp_server/catalog.py +125 -0
- package/scripts/mcp_server/consumer_tool_catalog.json +275 -0
- package/scripts/mcp_server/telemetry.py +128 -0
- package/scripts/mcp_server/tools.py +474 -15
- package/scripts/mcp_telemetry_health.py +214 -0
- package/scripts/mcp_telemetry_query.py +203 -0
- package/scripts/mcp_telemetry_store.py +211 -0
- package/scripts/memory_signal.py +12 -10
- package/scripts/pack_mcp_content.py +18 -4
- package/templates/claude_desktop_config.json.template +4 -3
|
@@ -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
|