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.
- package/README.md +71 -11
- package/dist/ci.d.ts +5 -5
- package/dist/ci.js +23 -18
- package/dist/ci.js.map +1 -1
- package/dist/collector-access.d.ts +17 -0
- package/dist/collector-access.js +61 -0
- package/dist/collector-access.js.map +1 -0
- package/dist/collector.d.ts +4 -1
- package/dist/collector.js +6 -2
- package/dist/collector.js.map +1 -1
- package/dist/config.d.ts +4 -3
- package/dist/config.js +5 -5
- package/dist/config.js.map +1 -1
- package/dist/contract-operations.d.ts +1 -1
- package/dist/contract-operations.js +1 -1
- package/dist/contract-operations.js.map +1 -1
- package/dist/contract-validation.d.ts +1 -1
- package/dist/contract-validation.js +45 -1
- package/dist/contract-validation.js.map +1 -1
- package/dist/events-authorization.js +1 -1
- package/dist/events-authorization.js.map +1 -1
- package/dist/events.d.ts +5 -4
- package/dist/events.js +88 -37
- package/dist/events.js.map +1 -1
- package/dist/main.d.ts +3 -6
- package/dist/main.js +18 -8
- package/dist/main.js.map +1 -1
- package/dist/mcp-app.html +1 -1
- package/dist/mcp-dashboard.html +1 -1
- package/dist/mcp-home.html +3 -3
- package/dist/metrics-tools.js +12 -12
- package/dist/metrics-tools.js.map +1 -1
- package/dist/native-execution.d.ts +8 -52
- package/dist/native-execution.js +51 -222
- package/dist/native-execution.js.map +1 -1
- package/dist/request-scope.d.ts +4 -12
- package/dist/request-scope.js +9 -22
- package/dist/request-scope.js.map +1 -1
- package/dist/server.d.ts +4 -3
- package/dist/server.js +36 -25
- package/dist/server.js.map +1 -1
- package/dist/summaries.d.ts +1 -1
- package/dist/summaries.js +2 -2
- package/dist/summaries.js.map +1 -1
- package/dist/telemetry-redaction.js +13 -0
- package/dist/telemetry-redaction.js.map +1 -1
- package/dist/tools.js +65 -18
- package/dist/tools.js.map +1 -1
- package/dist/trace-query.d.ts +5 -0
- package/dist/trace-query.js +8 -0
- package/dist/trace-query.js.map +1 -0
- package/dist/ui-domain.d.ts +4 -0
- package/dist/ui-domain.js +28 -0
- package/dist/ui-domain.js.map +1 -0
- package/package.json +5 -4
package/dist/mcp-home.html
CHANGED
|
@@ -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.
|
|
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
|
|
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>
|
|
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
|
|
package/dist/metrics-tools.js
CHANGED
|
@@ -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.
|
|
38
|
-
"
|
|
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,
|
|
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.
|
|
55
|
-
"
|
|
56
|
-
"
|
|
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,
|
|
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
|
|
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.
|
|
77
|
-
"
|
|
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,
|
|
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":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,8BAA8B,EAC9B,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,WAAW,GAIZ,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,oCAAoC,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE5F,MAAM,YAAY,GAAG,iBAAiB,CAAC;AACvC,MAAM,WAAW,GAAG,sCAAsC,CAAC;AAC3D,MAAM,UAAU,GAAG,qCAAqC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,oEAAoE;YACpE,2EAA2E;YAC3E,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
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.
|
|
94
|
-
*
|
|
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>;
|
package/dist/native-execution.js
CHANGED
|
@@ -1,84 +1,46 @@
|
|
|
1
1
|
import { CallToolRequestSchema, CallToolResultSchema } from "@modelcontextprotocol/core";
|
|
2
|
-
import { ProtocolError
|
|
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
|
|
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
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
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.
|
|
114
|
-
*
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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);
|