qyl-mcp-server 7.1.1 → 7.2.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 (55) hide show
  1. package/README.md +71 -11
  2. package/dist/ci.d.ts +5 -5
  3. package/dist/ci.js +23 -18
  4. package/dist/ci.js.map +1 -1
  5. package/dist/collector-access.d.ts +17 -0
  6. package/dist/collector-access.js +61 -0
  7. package/dist/collector-access.js.map +1 -0
  8. package/dist/collector.d.ts +4 -1
  9. package/dist/collector.js +6 -2
  10. package/dist/collector.js.map +1 -1
  11. package/dist/config.d.ts +4 -3
  12. package/dist/config.js +5 -5
  13. package/dist/config.js.map +1 -1
  14. package/dist/contract-operations.d.ts +1 -1
  15. package/dist/contract-operations.js +1 -1
  16. package/dist/contract-operations.js.map +1 -1
  17. package/dist/contract-validation.d.ts +1 -1
  18. package/dist/contract-validation.js +45 -1
  19. package/dist/contract-validation.js.map +1 -1
  20. package/dist/events-authorization.js +1 -1
  21. package/dist/events-authorization.js.map +1 -1
  22. package/dist/events.d.ts +5 -4
  23. package/dist/events.js +88 -37
  24. package/dist/events.js.map +1 -1
  25. package/dist/main.d.ts +3 -6
  26. package/dist/main.js +18 -8
  27. package/dist/main.js.map +1 -1
  28. package/dist/mcp-app.html +1 -1
  29. package/dist/mcp-dashboard.html +1 -1
  30. package/dist/mcp-home.html +3 -3
  31. package/dist/metrics-tools.js +12 -12
  32. package/dist/metrics-tools.js.map +1 -1
  33. package/dist/native-execution.d.ts +8 -52
  34. package/dist/native-execution.js +51 -222
  35. package/dist/native-execution.js.map +1 -1
  36. package/dist/request-scope.d.ts +4 -12
  37. package/dist/request-scope.js +9 -22
  38. package/dist/request-scope.js.map +1 -1
  39. package/dist/server.d.ts +4 -3
  40. package/dist/server.js +36 -25
  41. package/dist/server.js.map +1 -1
  42. package/dist/summaries.d.ts +1 -1
  43. package/dist/summaries.js +2 -2
  44. package/dist/summaries.js.map +1 -1
  45. package/dist/telemetry-redaction.js +13 -0
  46. package/dist/telemetry-redaction.js.map +1 -1
  47. package/dist/tools.js +65 -18
  48. package/dist/tools.js.map +1 -1
  49. package/dist/trace-query.d.ts +5 -0
  50. package/dist/trace-query.js +8 -0
  51. package/dist/trace-query.js.map +1 -0
  52. package/dist/ui-domain.d.ts +4 -0
  53. package/dist/ui-domain.js +28 -0
  54. package/dist/ui-domain.js.map +1 -0
  55. package/package.json +5 -4
@@ -6,7 +6,7 @@
6
6
  <meta name="color-scheme" content="dark">
7
7
  <meta name="description" content="Bring qyl traces, logs, and MCP telemetry into your coding agent.">
8
8
  <title>qyl · MCP telemetry for coding agents</title>
9
- <script type="module" crossorigin>(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=`7.1.1`,t=`https://mcp.qyl.at/mcp`,n={codex:{title:`terminal`,value:`codex mcp add qyl --url ${t}`},claude:{title:`terminal`,value:`claude mcp add --transport http qyl ${t}`},cursor:{title:`mcp.json`,value:`{
9
+ <script type="module" crossorigin>(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=`7.2.0`,t=`https://mcp.qyl.at/mcp`,n={codex:{title:`terminal`,value:`codex mcp add qyl --url ${t}`},claude:{title:`terminal`,value:`claude mcp add --transport http qyl ${t}`},cursor:{title:`mcp.json`,value:`{
10
10
  "mcpServers": {
11
11
  "qyl": {
12
12
  "url": "${t}"
@@ -117,12 +117,12 @@
117
117
  <p class="section-index">02 / REQUEST SCOPE</p>
118
118
  <div class="protocol-heading">
119
119
  <h2 id="scope-title">Every call runs inside its request.</h2>
120
- <p>One frame around all eleven tools. A tool registered outside it fails the build, so none of the three can go missing.</p>
120
+ <p>One frame around all eleven tools. A tool registered outside it fails the build, so cancellation and requested progress stay in the common frame.</p>
121
121
  </div>
122
122
  <div class="protocol-grid scope-grid">
123
123
  <article><span>Cancel</span><h3>Stop means stop</h3><p>A <code>notifications/cancelled</code>, or a dropped connection, aborts the in-flight collector fetch at once instead of waiting out its timeout.</p></article>
124
124
  <article><span>Progress</span><h3>Only when you ask</h3><p>A request that carries a <code>progressToken</code> gets one <code>notifications/progress</code> per collector round trip and a final <em>Result ready</em>. No token, no noise.</p></article>
125
- <article><span>Log</span><h3>One line per call</h3><p>A request that names a log level in its <code>_meta</code> envelope receives one <code>notifications/message</code>: <em>info</em> on success, <em>warning</em> on failure, logger <code>qyl.mcp</code>.</p></article>
125
+ <article><span>Observe</span><h3>Operation telemetry</h3><p>Native tool telemetry records timing, status and error type through OpenTelemetry. The server sends no deprecated protocol log messages.</p></article>
126
126
  </div>
127
127
  </section>
128
128
 
@@ -34,13 +34,13 @@ export function registerMetricsTools(server) {
34
34
  title: "List Metrics",
35
35
  description: "List the metric instruments recorded for this project: name, kind " +
36
36
  "(gauge/sum/histogram), unit, how many attribute streams exist under each " +
37
- "name, and when it was last written. Start here — query_metric needs an " +
38
- "exact instrument name, and this is where the names come from.",
37
+ "name, and when it was last written. Useful when the user wants to discover " +
38
+ "instrument names before querying a metric by its exact name.",
39
39
  inputSchema: operationInputSchema(METRICS_PATH),
40
40
  outputSchema: compactOutputSchema(MetricsListResponseSchema),
41
41
  annotations: READ_ONLY_TELEMETRY_TOOL_ANNOTATIONS,
42
42
  _meta: TELEMETRY_TOOL_AUTH_META,
43
- }, (args, ctx) => runTool(ctx, "list_metrics", 1, async (scope) => {
43
+ }, (args, ctx) => runTool(ctx, 1, async (scope) => {
44
44
  const { metrics, mode } = await listMetrics(args, scope.collector);
45
45
  await scope.step(`Fetched ${metrics.length} metric instrument(s)`);
46
46
  return telemetryToolResult(summarizeMetricCatalog(metrics, mode), {
@@ -51,15 +51,14 @@ export function registerMetricsTools(server) {
51
51
  server.registerTool("get_metric_series", {
52
52
  title: "Get Metric Series",
53
53
  description: "List the distinct attribute streams recorded under one metric name, with " +
54
- "each stream's attributes, service, and first/last seen. Use it to discover " +
55
- "which attribute keys are worth passing to query_metric as group_by or attr " +
56
- "filters, before running a range query that would otherwise collapse or " +
57
- "explode the result.",
54
+ "each stream's attributes, service, and first/last seen. Useful when the user " +
55
+ "wants to discover group_by keys or attr filters for query_metric before " +
56
+ "choosing the groups in a range query.",
58
57
  inputSchema: operationInputSchema(SERIES_PATH),
59
58
  outputSchema: compactOutputSchema(MetricSeriesListResponseSchema),
60
59
  annotations: READ_ONLY_TELEMETRY_TOOL_ANNOTATIONS,
61
60
  _meta: TELEMETRY_TOOL_AUTH_META,
62
- }, (args, ctx) => runTool(ctx, "get_metric_series", 1, async (scope) => {
61
+ }, (args, ctx) => runTool(ctx, 1, async (scope) => {
63
62
  const { series, mode } = await listMetricSeries(args, scope.collector);
64
63
  await scope.step(`Fetched ${series.length} series of ${args.metric_name}`);
65
64
  return telemetryToolResult(summarizeMetricSeries(series, mode), {
@@ -69,17 +68,18 @@ export function registerMetricsTools(server) {
69
68
  }));
70
69
  server.registerTool("query_metric", {
71
70
  title: "Query Metric",
72
- description: "Run a time-bucketed range query over one metric: a window (start_time, " +
71
+ description: "Run a time-bucketed range query when the user wants to compare a metric " +
72
+ "over time or across groups: a window (start_time, " +
73
73
  "end_time), a bucket width (step_ms), a reducer (aggregation: avg, min, max, " +
74
74
  "sum, count, last, p50, p90, p95, p99), optional group_by attribute keys, and " +
75
75
  "optional attr/attr_prefix matchers written 'key=value'. Returns one stream " +
76
- "per grouping with its buckets. One bucket spanning the whole window collapses " +
77
- "the answer to a single number.",
76
+ "per grouping with its buckets. With step_ms equal to the window duration, " +
77
+ "each grouping yields one bucket: one number per series.",
78
78
  inputSchema: operationInputSchema(QUERY_PATH),
79
79
  outputSchema: compactOutputSchema(MetricQueryResultSchema),
80
80
  annotations: READ_ONLY_TELEMETRY_TOOL_ANNOTATIONS,
81
81
  _meta: TELEMETRY_TOOL_AUTH_META,
82
- }, (args, ctx) => runTool(ctx, "query_metric", 1, async (scope) => {
82
+ }, (args, ctx) => runTool(ctx, 1, async (scope) => {
83
83
  const { result, mode } = await queryMetric(args, scope.collector);
84
84
  await scope.step(`Queried ${args.metric_name}: ${result.series.length} series, ` +
85
85
  `${result.series[0]?.buckets.length ?? 0} bucket(s) each`);
@@ -1 +1 @@
1
- {"version":3,"file":"metrics-tools.js","sourceRoot":"","sources":["../src/metrics-tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"metrics-tools.js","sourceRoot":"","sources":["../src/metrics-tools.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,11 @@
1
- import type { WorkbenchExecutionCost, WorkbenchExecutionTokenUsage } from "@ancplua/qyl-api-schema/types";
2
1
  import type { McpServer, CallToolRequest, CallToolResult, ServerContext } from "@modelcontextprotocol/server";
3
- import { z } from "zod";
2
+ import * as z from "zod/v4";
4
3
  import type { McpTelemetryTransport } from "./mcp-semconv.js";
5
4
  import { SecretRedactor } from "./secret-redactor.js";
6
5
  import { McpTelemetry } from "./telemetry.js";
7
6
  declare const NativeExecutionRecordSchema: z.ZodObject<{
8
7
  id: z.ZodString;
9
- serverId: z.ZodLiteral<"qyl.mcp/native">;
8
+ toolName: z.ZodString;
10
9
  status: z.ZodEnum<{
11
10
  failed: "failed";
12
11
  running: "running";
@@ -16,51 +15,7 @@ declare const NativeExecutionRecordSchema: z.ZodObject<{
16
15
  startedAt: z.ZodISODateTime;
17
16
  completedAt: z.ZodOptional<z.ZodISODateTime>;
18
17
  durationMs: z.ZodOptional<z.ZodNumber>;
19
- attemptCount: z.ZodLiteral<1>;
20
- request: z.ZodObject<{
21
- requestId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
22
- toolName: z.ZodString;
23
- arguments: z.ZodRecord<z.ZodString, z.ZodUnknown>;
24
- transport: z.ZodEnum<{
25
- builtin: "builtin";
26
- http: "http";
27
- inproc: "inproc";
28
- sse: "sse";
29
- stdio: "stdio";
30
- "streamable-http": "streamable-http";
31
- streamable_http: "streamable_http";
32
- }>;
33
- meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
34
- }, z.core.$strict>;
35
- result: z.ZodOptional<z.ZodUnknown>;
36
- error: z.ZodOptional<z.ZodObject<{
37
- code: z.ZodString;
38
- message: z.ZodString;
39
- }, z.core.$strict>>;
40
- tokenUsage: z.ZodOptional<z.ZodType<WorkbenchExecutionTokenUsage, unknown, z.core.$ZodTypeInternals<WorkbenchExecutionTokenUsage, unknown>>>;
41
- cost: z.ZodOptional<z.ZodType<WorkbenchExecutionCost, unknown, z.core.$ZodTypeInternals<WorkbenchExecutionCost, unknown>>>;
42
- protocolEvents: z.ZodArray<z.ZodObject<{
43
- sequence: z.ZodNumber;
44
- timestamp: z.ZodISODateTime;
45
- direction: z.ZodEnum<{
46
- inbound: "inbound";
47
- outbound: "outbound";
48
- }>;
49
- messageKind: z.ZodEnum<{
50
- error: "error";
51
- request: "request";
52
- result: "result";
53
- }>;
54
- jsonrpc: z.ZodLiteral<"2.0">;
55
- requestId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
56
- method: z.ZodLiteral<"tools/call">;
57
- payload: z.ZodUnknown;
58
- }, z.core.$strict>>;
59
- telemetryCorrelation: z.ZodObject<{
60
- executionId: z.ZodString;
61
- traceIds: z.ZodArray<z.ZodString>;
62
- spanIds: z.ZodArray<z.ZodString>;
63
- }, z.core.$strict>;
18
+ errorType: z.ZodOptional<z.ZodString>;
64
19
  }, z.core.$strict>;
65
20
  export type NativeExecutionRecord = z.infer<typeof NativeExecutionRecordSchema>;
66
21
  export interface NativeExecutionRepository {
@@ -73,7 +28,7 @@ export interface FileNativeExecutionRepositoryOptions {
73
28
  redactor?: SecretRedactor;
74
29
  environment?: Readonly<Record<string, string | undefined>>;
75
30
  }
76
- /** Durable, redacted native-server evidence kept separately from workbench state. */
31
+ /** Bounded operation metadata kept separately from Workbench execution state. */
77
32
  export declare class FileNativeExecutionRepository implements NativeExecutionRepository {
78
33
  private store;
79
34
  private readonly openStore;
@@ -81,17 +36,18 @@ export declare class FileNativeExecutionRepository implements NativeExecutionRep
81
36
  private readonly maxExecutions;
82
37
  private readonly now;
83
38
  private initialization;
39
+ private needsMigration;
84
40
  constructor(options?: FileNativeExecutionRepositoryOptions);
85
41
  /**
86
42
  * Move a state file this build cannot read out of the way, and say so.
87
43
  *
88
- * The schema deliberately refuses to half-parse an older layout, but refusing
44
+ * The schema deliberately refuses to guess an unknown layout, but refusing
89
45
  * forever is not the same as refusing to guess: a state file written before a
90
46
  * version bump would otherwise fail every single tool call for the life of the
91
47
  * installation, because the store caches its failed load and evidence is
92
48
  * written before the tool runs. Renaming preserves the old records for anyone
93
- * who wants them and lets this process start a fresh log. Nothing is deleted
94
- * and nothing is migrated.
49
+ * who wants them and lets this process start a fresh log. Known version-1/2
50
+ * files follow the payload-removing migration instead of this recovery path.
95
51
  */
96
52
  private archiveUnreadableState;
97
53
  save(record: NativeExecutionRecord): Promise<void>;
@@ -1,84 +1,46 @@
1
1
  import { CallToolRequestSchema, CallToolResultSchema } from "@modelcontextprotocol/core";
2
- import { ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
2
+ import { ProtocolError } from "@modelcontextprotocol/server";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { rename } from "node:fs/promises";
5
5
  import { homedir } from "node:os";
6
6
  import { join } from "node:path";
7
- import { z } from "zod";
7
+ import * as z from "zod/v4";
8
8
  import { AtomicJsonStore, PersistenceError } from "./atomic-json-store.js";
9
- import { WorkbenchExecutionCostSchema, WorkbenchExecutionTokenUsageSchema, } from "./contract-validation.js";
10
- import { extractExecutionEvidence } from "./execution-evidence.js";
11
- import { MAX_PERSISTED_RESULT_CHARACTERS, sanitizePersistedToolResult, } from "./execution-result.js";
12
9
  import { SecretRedactor } from "./secret-redactor.js";
13
10
  import { McpTelemetry, } from "./telemetry.js";
14
- // 2: usage and cost evidence follows the published contract's snake_case names,
15
- // so a version-1 file must never half-parse. It is set aside rather than read —
16
- // see archiveUnreadableState.
17
- const NATIVE_STATE_VERSION = 2;
11
+ // 3: operation metadata only. Known older layouts are projected into this
12
+ // schema and replaced atomically, without keeping an archive of their payloads.
13
+ const NATIVE_STATE_VERSION = 3;
18
14
  const DEFAULT_MAX_EXECUTIONS = 1_000;
19
15
  const NATIVE_SERVER_ID = "qyl.mcp/native";
20
- const MAX_PROTOCOL_PAYLOAD_CHARACTERS = 64_000;
21
16
  const IdentifierSchema = z.string().min(1).max(256);
22
17
  const IsoDateSchema = z.iso.datetime({ offset: true });
23
- const JsonRpcRequestIdSchema = z.union([z.string().max(2_048), z.number().finite()]);
24
- // Usage and cost evidence is the published contract's own shape, so the
25
- // persisted record validates against the generated schema rather than a local
26
- // copy that can drift from it.
27
- const TokenUsageSchema = WorkbenchExecutionTokenUsageSchema;
28
- const CostSchema = WorkbenchExecutionCostSchema;
29
- const ProtocolEventSchema = z.object({
30
- sequence: z.number().int().positive(),
31
- timestamp: IsoDateSchema,
32
- direction: z.enum(["inbound", "outbound"]),
33
- messageKind: z.enum(["request", "result", "error"]),
34
- jsonrpc: z.literal("2.0"),
35
- requestId: JsonRpcRequestIdSchema,
36
- method: z.literal("tools/call"),
37
- payload: z.unknown(),
38
- }).strict();
39
18
  const NativeExecutionRecordSchema = z.object({
19
+ // Generated by this server; never a client-provided JSON-RPC request id.
40
20
  id: IdentifierSchema,
41
- serverId: z.literal(NATIVE_SERVER_ID),
21
+ toolName: z.string().min(1).max(1_024),
42
22
  status: z.enum(["running", "succeeded", "failed"]),
43
23
  createdAt: IsoDateSchema,
44
24
  startedAt: IsoDateSchema,
45
25
  completedAt: IsoDateSchema.optional(),
46
26
  durationMs: z.number().finite().nonnegative().optional(),
47
- attemptCount: z.literal(1),
48
- request: z.object({
49
- requestId: JsonRpcRequestIdSchema,
50
- toolName: z.string().min(1).max(1_024),
51
- arguments: z.record(z.string(), z.unknown()),
52
- transport: z.enum([
53
- "stdio",
54
- "http",
55
- "streamable_http",
56
- "streamable-http",
57
- "sse",
58
- "inproc",
59
- "builtin",
60
- ]),
61
- meta: z.record(z.string(), z.unknown()).optional(),
62
- }).strict(),
63
- result: z.unknown().optional(),
64
- error: z.object({
65
- code: z.string().min(1).max(128),
66
- message: z.string().min(1).max(4_096),
67
- }).strict().optional(),
68
- tokenUsage: TokenUsageSchema.optional(),
69
- cost: CostSchema.optional(),
70
- protocolEvents: z.array(ProtocolEventSchema).min(1).max(2),
71
- telemetryCorrelation: z.object({
72
- executionId: IdentifierSchema,
73
- traceIds: z.array(z.string().regex(/^[0-9a-f]{32}$/u)).max(2),
74
- spanIds: z.array(z.string().regex(/^[0-9a-f]{16}$/u)).max(2),
75
- }).strict(),
27
+ errorType: z.string().min(1).max(128).optional(),
76
28
  }).strict();
77
29
  const NativeExecutionStateSchema = z.object({
78
30
  version: z.literal(NATIVE_STATE_VERSION),
79
31
  executions: z.array(NativeExecutionRecordSchema),
80
32
  }).strict();
81
- /** Durable, redacted native-server evidence kept separately from workbench state. */
33
+ // Strip unknown fields before migration: legacy request/result contents must
34
+ // never reach a new write, even when an old file contains unrecognized fields.
35
+ const LegacyNativeExecutionStateSchema = z.object({
36
+ version: z.union([z.literal(1), z.literal(2)]),
37
+ executions: z.array(NativeExecutionRecordSchema.omit({ toolName: true, errorType: true })
38
+ .extend({
39
+ request: z.object({ toolName: NativeExecutionRecordSchema.shape.toolName }),
40
+ error: z.object({ code: NativeExecutionRecordSchema.shape.errorType }).optional(),
41
+ }).strip()),
42
+ }).strict();
43
+ /** Bounded operation metadata kept separately from Workbench execution state. */
82
44
  export class FileNativeExecutionRepository {
83
45
  store;
84
46
  openStore;
@@ -86,18 +48,31 @@ export class FileNativeExecutionRepository {
86
48
  maxExecutions;
87
49
  now;
88
50
  initialization;
51
+ needsMigration = false;
89
52
  constructor(options = {}) {
90
53
  const environment = options.environment ?? process.env;
91
54
  const redactor = options.redactor ?? new SecretRedactor({
92
55
  environment,
93
- maxStringLength: MAX_PERSISTED_RESULT_CHARACTERS + 1,
94
56
  });
95
57
  this.maxExecutions = positiveInteger(options.maxExecutions ?? DEFAULT_MAX_EXECUTIONS, "maxExecutions");
96
58
  this.now = options.now ?? Date.now;
97
59
  this.filePath = options.filePath ?? nativeStatePath(environment);
98
60
  this.openStore = () => new AtomicJsonStore(this.filePath, {
99
61
  initial: () => ({ version: NATIVE_STATE_VERSION, executions: [] }),
100
- parse: (value) => NativeExecutionStateSchema.parse(value),
62
+ parse: (value) => {
63
+ const legacy = LegacyNativeExecutionStateSchema.safeParse(value);
64
+ if (!legacy.success)
65
+ return NativeExecutionStateSchema.parse(value);
66
+ this.needsMigration = true;
67
+ return NativeExecutionStateSchema.parse({
68
+ version: NATIVE_STATE_VERSION,
69
+ executions: legacy.data.executions.map(({ request, error, ...record }) => ({
70
+ ...record,
71
+ toolName: request.toolName,
72
+ ...(error?.code === undefined ? {} : { errorType: error.code }),
73
+ })),
74
+ });
75
+ },
101
76
  prepareForWrite: (value) => NativeExecutionStateSchema.parse(redactor.redact(value)),
102
77
  });
103
78
  this.store = this.openStore();
@@ -105,13 +80,13 @@ export class FileNativeExecutionRepository {
105
80
  /**
106
81
  * Move a state file this build cannot read out of the way, and say so.
107
82
  *
108
- * The schema deliberately refuses to half-parse an older layout, but refusing
83
+ * The schema deliberately refuses to guess an unknown layout, but refusing
109
84
  * forever is not the same as refusing to guess: a state file written before a
110
85
  * version bump would otherwise fail every single tool call for the life of the
111
86
  * installation, because the store caches its failed load and evidence is
112
87
  * written before the tool runs. Renaming preserves the old records for anyone
113
- * who wants them and lets this process start a fresh log. Nothing is deleted
114
- * and nothing is migrated.
88
+ * who wants them and lets this process start a fresh log. Known version-1/2
89
+ * files follow the payload-removing migration instead of this recovery path.
115
90
  */
116
91
  async archiveUnreadableState(reason) {
117
92
  const archived = `${this.filePath}.unreadable-${new Date(this.now()).toISOString().replaceAll(":", "-")}`;
@@ -155,6 +130,10 @@ export class FileNativeExecutionRepository {
155
130
  this.store = this.openStore();
156
131
  await this.store.initialize();
157
132
  }
133
+ if (this.needsMigration) {
134
+ await this.store.replace(await this.store.read());
135
+ this.needsMigration = false;
136
+ }
158
137
  const completedMs = this.now();
159
138
  const completedAt = timestamp(completedMs);
160
139
  const current = await this.store.read();
@@ -167,10 +146,7 @@ export class FileNativeExecutionRepository {
167
146
  record.status = "failed";
168
147
  record.completedAt = completedAt;
169
148
  record.durationMs = Math.max(0, completedMs - Date.parse(record.startedAt));
170
- record.error = {
171
- code: "process_interrupted",
172
- message: "The qyl.mcp process stopped before this native tool call completed.",
173
- };
149
+ record.errorType = "process_interrupted";
174
150
  }
175
151
  });
176
152
  }
@@ -187,7 +163,6 @@ export class NativeExecutionRuntime {
187
163
  this.telemetry = options.telemetry;
188
164
  this.redactor = options.redactor ?? new SecretRedactor({
189
165
  environment: process.env,
190
- maxStringLength: MAX_PERSISTED_RESULT_CHARACTERS + 1,
191
166
  });
192
167
  this.now = options.now ?? Date.now;
193
168
  this.id = options.id ?? randomUUID;
@@ -196,28 +171,14 @@ export class NativeExecutionRuntime {
196
171
  const startedMs = this.now();
197
172
  const executionId = this.id();
198
173
  const startedAt = timestamp(startedMs);
199
- const requestId = input.ctx.mcpReq.id;
200
- const durableRequestId = sanitizeRequestId(requestId, this.redactor);
201
174
  const toolName = input.request.params.name;
202
175
  const operation = this.startTelemetry(input, executionId, startedMs);
203
176
  const running = NativeExecutionRecordSchema.parse({
204
177
  id: executionId,
205
- serverId: NATIVE_SERVER_ID,
178
+ toolName: this.redactor.redactText(toolName).slice(0, 1_024),
206
179
  status: "running",
207
180
  createdAt: startedAt,
208
181
  startedAt,
209
- attemptCount: 1,
210
- request: {
211
- requestId: durableRequestId,
212
- toolName: this.redactor.redactText(toolName).slice(0, 1_024),
213
- arguments: sanitizeArguments(input.request.params.arguments, this.redactor),
214
- transport: input.transport,
215
- ...(input.request.params._meta === undefined
216
- ? {}
217
- : { meta: sanitizeRecord(input.request.params._meta, this.redactor) }),
218
- },
219
- protocolEvents: [requestEvent(input, durableRequestId, startedAt, this.redactor)],
220
- telemetryCorrelation: correlation(executionId, operation?.correlation),
221
182
  });
222
183
  try {
223
184
  await this.repository.save(running);
@@ -225,7 +186,6 @@ export class NativeExecutionRuntime {
225
186
  catch (error) {
226
187
  operation?.end({
227
188
  endTimeMs: this.now(),
228
- jsonRpcRequestId: requestId,
229
189
  errorType: "evidence_persistence_failed",
230
190
  });
231
191
  throw new EvidenceRecordingError(error);
@@ -243,12 +203,7 @@ export class NativeExecutionRuntime {
243
203
  status: "failed",
244
204
  completedAt,
245
205
  durationMs: duration(startedMs, completedMs),
246
- error: errorEvidence(error, this.redactor),
247
- protocolEvents: [
248
- ...running.protocolEvents,
249
- errorEvent(durableRequestId, toolName, completedAt, error, this.redactor),
250
- ],
251
- telemetryCorrelation: correlation(executionId, operation?.correlation),
206
+ errorType: errorType(error),
252
207
  });
253
208
  try {
254
209
  await this.repository.save(failedRecord);
@@ -256,14 +211,12 @@ export class NativeExecutionRuntime {
256
211
  catch (persistenceError) {
257
212
  operation?.end({
258
213
  endTimeMs: this.now(),
259
- jsonRpcRequestId: requestId,
260
214
  errorType: "evidence_persistence_failed",
261
215
  });
262
216
  throw new EvidenceRecordingError(persistenceError);
263
217
  }
264
218
  operation?.end({
265
219
  endTimeMs: completedMs,
266
- jsonRpcRequestId: requestId,
267
220
  errorType: errorType(error),
268
221
  ...(error instanceof ProtocolError
269
222
  ? { rpcResponseStatusCode: String(error.code) }
@@ -273,29 +226,13 @@ export class NativeExecutionRuntime {
273
226
  }
274
227
  const completedMs = this.now();
275
228
  const completedAt = timestamp(completedMs);
276
- const durableResult = sanitizePersistedToolResult(result, this.redactor);
277
- const evidence = extractExecutionEvidence(result);
278
229
  const toolFailed = result.isError === true;
279
230
  const completedRecord = NativeExecutionRecordSchema.parse({
280
231
  ...running,
281
232
  status: toolFailed ? "failed" : "succeeded",
282
233
  completedAt,
283
234
  durationMs: duration(startedMs, completedMs),
284
- result: durableResult,
285
- ...(toolFailed
286
- ? {
287
- error: {
288
- code: "tool_result_error",
289
- message: "The MCP tool returned an error result.",
290
- },
291
- }
292
- : {}),
293
- ...evidenceFields(evidence, this.redactor),
294
- protocolEvents: [
295
- ...running.protocolEvents,
296
- resultEvent(durableRequestId, completedAt, durableResult),
297
- ],
298
- telemetryCorrelation: correlation(executionId, operation?.correlation),
235
+ ...(toolFailed ? { errorType: "tool_result_error" } : {}),
299
236
  });
300
237
  try {
301
238
  await this.repository.save(completedRecord);
@@ -303,16 +240,13 @@ export class NativeExecutionRuntime {
303
240
  catch (error) {
304
241
  operation?.end({
305
242
  endTimeMs: this.now(),
306
- jsonRpcRequestId: requestId,
307
243
  errorType: "evidence_persistence_failed",
308
244
  });
309
245
  throw new EvidenceRecordingError(error);
310
246
  }
311
247
  operation?.end({
312
248
  endTimeMs: completedMs,
313
- jsonRpcRequestId: requestId,
314
249
  ...(toolFailed ? { errorType: "tool_error" } : {}),
315
- responseBody: this.redactor.redact(result),
316
250
  });
317
251
  return result;
318
252
  }
@@ -332,7 +266,6 @@ export class NativeExecutionRuntime {
332
266
  transport: input.transport,
333
267
  jsonRpcProtocolVersion: "2.0",
334
268
  executionId,
335
- requestBody: this.redactor.redact(input.request),
336
269
  remotePropagation: propagationCarrier(input.request.params._meta),
337
270
  startTimeMs: startedMs,
338
271
  });
@@ -444,7 +377,6 @@ export async function closeDefaultNativeExecutionRuntime() {
444
377
  function createDefaultRuntime(environment) {
445
378
  const redactor = new SecretRedactor({
446
379
  environment,
447
- maxStringLength: MAX_PERSISTED_RESULT_CHARACTERS + 1,
448
380
  });
449
381
  const repository = new FileNativeExecutionRepository({ environment, redactor });
450
382
  let telemetry;
@@ -460,63 +392,6 @@ function nativeStatePath(environment) {
460
392
  return environment.QYL_MCP_NATIVE_STATE_PATH
461
393
  ?? join(homedir(), ".qyl", "mcp-native-executions.json");
462
394
  }
463
- function requestEvent(input, requestId, at, redactor) {
464
- return {
465
- sequence: 1,
466
- timestamp: at,
467
- direction: "inbound",
468
- messageKind: "request",
469
- jsonrpc: "2.0",
470
- requestId,
471
- method: "tools/call",
472
- payload: boundPayload(redactor.redact({
473
- jsonrpc: "2.0",
474
- id: requestId,
475
- method: "tools/call",
476
- params: input.request.params,
477
- })),
478
- };
479
- }
480
- function resultEvent(requestId, at, result) {
481
- return {
482
- sequence: 2,
483
- timestamp: at,
484
- direction: "outbound",
485
- messageKind: "result",
486
- jsonrpc: "2.0",
487
- requestId,
488
- method: "tools/call",
489
- payload: boundPayload({ jsonrpc: "2.0", id: requestId, result }),
490
- };
491
- }
492
- function errorEvent(requestId, toolName, at, error, redactor) {
493
- const evidence = errorEvidence(error, redactor);
494
- return {
495
- sequence: 2,
496
- timestamp: at,
497
- direction: "outbound",
498
- messageKind: "error",
499
- jsonrpc: "2.0",
500
- requestId,
501
- method: "tools/call",
502
- payload: boundPayload({
503
- jsonrpc: "2.0",
504
- id: requestId,
505
- error: {
506
- code: error instanceof ProtocolError ? error.code : ProtocolErrorCode.InternalError,
507
- message: evidence.message,
508
- data: { toolName: redactor.redactText(toolName).slice(0, 1_024) },
509
- },
510
- }),
511
- };
512
- }
513
- function errorEvidence(error, redactor) {
514
- const message = redactor.redactText(error instanceof Error ? error.message : String(error));
515
- return {
516
- code: errorType(error),
517
- message: message.trim().length === 0 ? "Native MCP tool execution failed." : message.slice(0, 4_096),
518
- };
519
- }
520
395
  function errorType(error) {
521
396
  if (error instanceof ProtocolError)
522
397
  return `mcp_${error.code}`;
@@ -525,61 +400,15 @@ function errorType(error) {
525
400
  }
526
401
  return "native_tool_failure";
527
402
  }
528
- function correlation(executionId, span) {
529
- return {
530
- executionId,
531
- traceIds: span === undefined ? [] : [span.traceId],
532
- spanIds: span === undefined ? [] : [span.spanId],
533
- };
534
- }
535
403
  function propagationCarrier(value) {
536
404
  if (!isRecord(value))
537
405
  return undefined;
538
- const entries = Object.entries(value)
539
- .filter((entry) => typeof entry[1] === "string");
540
- return entries.length === 0 ? undefined : Object.fromEntries(entries);
541
- }
542
- function sanitizeArguments(value, redactor) {
543
- return sanitizeRecord(value ?? {}, redactor);
544
- }
545
- function sanitizeRecord(value, redactor) {
546
- const sanitized = redactor.redact(value);
547
- if (!isRecord(sanitized))
548
- return {};
549
- const bounded = boundPayload(sanitized);
550
- return isRecord(bounded) ? bounded : {};
551
- }
552
- function boundPayload(value) {
553
- const serialized = JSON.stringify(value);
554
- if (serialized === undefined || serialized.length <= MAX_PROTOCOL_PAYLOAD_CHARACTERS) {
555
- return value;
556
- }
557
- return {
558
- truncated: true,
559
- originalCharacters: serialized.length,
560
- preview: `${serialized.slice(0, MAX_PROTOCOL_PAYLOAD_CHARACTERS - 1)}…`,
561
- };
562
- }
563
- function evidenceFields(evidence, redactor) {
564
- const source = evidence.cost?.source === undefined
565
- ? undefined
566
- : redactor.redactText(evidence.cost.source).trim().slice(0, 256);
567
- const cost = evidence.cost === undefined
568
- ? undefined
569
- : {
570
- amount_usd: evidence.cost.amount_usd,
571
- estimated: evidence.cost.estimated,
572
- ...(source === undefined || source.length === 0 ? {} : { source }),
573
- };
574
- return {
575
- ...(evidence.tokenUsage === undefined ? {} : { tokenUsage: evidence.tokenUsage }),
576
- ...(cost === undefined ? {} : { cost }),
577
- };
578
- }
579
- function sanitizeRequestId(value, redactor) {
580
- return typeof value === "string"
581
- ? redactor.redactText(value).slice(0, 2_048)
582
- : value;
406
+ // Preserve trace parenting without passing arbitrary metadata or baggage to
407
+ // logging/exporters. OpenTelemetry validates the trace/span IDs on extraction.
408
+ const traceparent = value.traceparent;
409
+ return typeof traceparent === "string"
410
+ ? { traceparent }
411
+ : undefined;
583
412
  }
584
413
  function duration(startedMs, completedMs) {
585
414
  return Math.max(0, completedMs - startedMs);