@descryy/mcp 0.1.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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/dist/action-handshake.d.ts +86 -0
  3. package/dist/action-handshake.d.ts.map +1 -0
  4. package/dist/action-handshake.js +144 -0
  5. package/dist/action-handshake.js.map +1 -0
  6. package/dist/bin/descry-mcp.d.ts +18 -0
  7. package/dist/bin/descry-mcp.d.ts.map +1 -0
  8. package/dist/bin/descry-mcp.js +54 -0
  9. package/dist/bin/descry-mcp.js.map +1 -0
  10. package/dist/index.d.ts +32 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +15 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/language-guess.d.ts +17 -0
  15. package/dist/language-guess.d.ts.map +1 -0
  16. package/dist/language-guess.js +43 -0
  17. package/dist/language-guess.js.map +1 -0
  18. package/dist/protocol.d.ts +91 -0
  19. package/dist/protocol.d.ts.map +1 -0
  20. package/dist/protocol.js +127 -0
  21. package/dist/protocol.js.map +1 -0
  22. package/dist/registry.d.ts +72 -0
  23. package/dist/registry.d.ts.map +1 -0
  24. package/dist/registry.js +136 -0
  25. package/dist/registry.js.map +1 -0
  26. package/dist/render.d.ts +275 -0
  27. package/dist/render.d.ts.map +1 -0
  28. package/dist/render.js +333 -0
  29. package/dist/render.js.map +1 -0
  30. package/dist/server.d.ts +42 -0
  31. package/dist/server.d.ts.map +1 -0
  32. package/dist/server.js +263 -0
  33. package/dist/server.js.map +1 -0
  34. package/dist/session.d.ts +164 -0
  35. package/dist/session.d.ts.map +1 -0
  36. package/dist/session.js +302 -0
  37. package/dist/session.js.map +1 -0
  38. package/dist/tools/analyze.d.ts +124 -0
  39. package/dist/tools/analyze.d.ts.map +1 -0
  40. package/dist/tools/analyze.js +411 -0
  41. package/dist/tools/analyze.js.map +1 -0
  42. package/dist/tools/contracts.d.ts +105 -0
  43. package/dist/tools/contracts.d.ts.map +1 -0
  44. package/dist/tools/contracts.js +141 -0
  45. package/dist/tools/contracts.js.map +1 -0
  46. package/dist/tools/cross-pr.d.ts +137 -0
  47. package/dist/tools/cross-pr.d.ts.map +1 -0
  48. package/dist/tools/cross-pr.js +216 -0
  49. package/dist/tools/cross-pr.js.map +1 -0
  50. package/dist/tools/git-diff.d.ts +29 -0
  51. package/dist/tools/git-diff.d.ts.map +1 -0
  52. package/dist/tools/git-diff.js +117 -0
  53. package/dist/tools/git-diff.js.map +1 -0
  54. package/dist/tools/git-history.d.ts +27 -0
  55. package/dist/tools/git-history.d.ts.map +1 -0
  56. package/dist/tools/git-history.js +91 -0
  57. package/dist/tools/git-history.js.map +1 -0
  58. package/dist/tools/history.d.ts +63 -0
  59. package/dist/tools/history.d.ts.map +1 -0
  60. package/dist/tools/history.js +114 -0
  61. package/dist/tools/history.js.map +1 -0
  62. package/dist/tools/impact.d.ts +81 -0
  63. package/dist/tools/impact.d.ts.map +1 -0
  64. package/dist/tools/impact.js +229 -0
  65. package/dist/tools/impact.js.map +1 -0
  66. package/dist/tools/index.d.ts +33 -0
  67. package/dist/tools/index.d.ts.map +1 -0
  68. package/dist/tools/index.js +47 -0
  69. package/dist/tools/index.js.map +1 -0
  70. package/dist/tools/kit.d.ts +183 -0
  71. package/dist/tools/kit.d.ts.map +1 -0
  72. package/dist/tools/kit.js +117 -0
  73. package/dist/tools/kit.js.map +1 -0
  74. package/dist/tools/link-workspace.d.ts +62 -0
  75. package/dist/tools/link-workspace.d.ts.map +1 -0
  76. package/dist/tools/link-workspace.js +156 -0
  77. package/dist/tools/link-workspace.js.map +1 -0
  78. package/dist/tools/lookup.d.ts +29 -0
  79. package/dist/tools/lookup.d.ts.map +1 -0
  80. package/dist/tools/lookup.js +73 -0
  81. package/dist/tools/lookup.js.map +1 -0
  82. package/dist/tools/pr-analysis.d.ts +89 -0
  83. package/dist/tools/pr-analysis.d.ts.map +1 -0
  84. package/dist/tools/pr-analysis.js +169 -0
  85. package/dist/tools/pr-analysis.js.map +1 -0
  86. package/dist/tools/propagation.d.ts +74 -0
  87. package/dist/tools/propagation.d.ts.map +1 -0
  88. package/dist/tools/propagation.js +194 -0
  89. package/dist/tools/propagation.js.map +1 -0
  90. package/dist/tools/questions.d.ts +80 -0
  91. package/dist/tools/questions.d.ts.map +1 -0
  92. package/dist/tools/questions.js +213 -0
  93. package/dist/tools/questions.js.map +1 -0
  94. package/dist/tools/refusal-fetch.d.ts +71 -0
  95. package/dist/tools/refusal-fetch.d.ts.map +1 -0
  96. package/dist/tools/refusal-fetch.js +137 -0
  97. package/dist/tools/refusal-fetch.js.map +1 -0
  98. package/dist/tools/scope.d.ts +60 -0
  99. package/dist/tools/scope.d.ts.map +1 -0
  100. package/dist/tools/scope.js +160 -0
  101. package/dist/tools/scope.js.map +1 -0
  102. package/dist/tools/similar-incidents.d.ts +105 -0
  103. package/dist/tools/similar-incidents.d.ts.map +1 -0
  104. package/dist/tools/similar-incidents.js +228 -0
  105. package/dist/tools/similar-incidents.js.map +1 -0
  106. package/dist/tools/validate.d.ts +103 -0
  107. package/dist/tools/validate.d.ts.map +1 -0
  108. package/dist/tools/validate.js +176 -0
  109. package/dist/tools/validate.js.map +1 -0
  110. package/dist/tools/verification-status.d.ts +68 -0
  111. package/dist/tools/verification-status.d.ts.map +1 -0
  112. package/dist/tools/verification-status.js +179 -0
  113. package/dist/tools/verification-status.js.map +1 -0
  114. package/dist/transport.d.ts +58 -0
  115. package/dist/transport.d.ts.map +1 -0
  116. package/dist/transport.js +115 -0
  117. package/dist/transport.js.map +1 -0
  118. package/package.json +33 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * `verification_status` — §4 technique 6: *"Coverage is a separate axis and
3
+ * a separate call. Appending it everywhere pays on every call; a tool pays
4
+ * once, when the client is actually reporting."*
5
+ *
6
+ * Wraps `packages/core/src/query/verification-status.ts`'s
7
+ * `computeVerificationStatus` — the module's own header calls itself "the
8
+ * static half only" and says building the `ScopeItem[]` from a caller's refs
9
+ * is "the caller's job, done above this boundary." This tool is that caller.
10
+ *
11
+ * ## Real capability lookup, without ever naming a language up here
12
+ *
13
+ * `computeVerificationStatus`'s `ScopeItem.capabilities`, when supplied,
14
+ * lets a `notExercised` reason describe a real declared gap rather than a
15
+ * generic absence. The first design attempted here guessed a language from
16
+ * a ref's file extension and matched it against a `language` tag on a
17
+ * configured source — and `npm run lint` caught it immediately:
18
+ * `descry-boundary/no-language-vocabulary` and `no-language-branch` both
19
+ * fired, because `packages/*\/src/**` (this file included, not just
20
+ * `packages/core`) is bound by architecture principle 7 — nothing above the
21
+ * Canonical IR may name or dispatch on a language, full stop. A hardcoded
22
+ * extension table and a `.get(guessedLanguage)` lookup are exactly the
23
+ * "lookup dispatch" shape that rule exists to catch, not a false positive.
24
+ *
25
+ * The fix moves the decision below the boundary, where it belongs:
26
+ * `LanguageAdapter` (`@descryy/ir`) gained an optional
27
+ * `ownsPath(path): boolean` — the adapter decides, using whatever
28
+ * language-specific logic it wants, entirely inside `descry-adapters`,
29
+ * which is not bound by this repo's lint. This tool never sees or compares
30
+ * a language string; it just asks every loaded, configured adapter "do you
31
+ * own this path," and attaches whichever one says yes. An unresolved ref's
32
+ * `language` therefore stays `null` here always — this tool does not
33
+ * determine one, and does not need to: `computeVerificationStatus`'s reason
34
+ * text already reads correctly either way (see its own module header),
35
+ * naming an adapter as registered without ever naming which language it is
36
+ * for.
37
+ *
38
+ * A configured source that fails to import cannot be asked at all — this
39
+ * tool's own `notes` disclose that plainly, the same register `analyze.ts`
40
+ * already uses for a source that will not import.
41
+ *
42
+ * ## Resolving a ref: node id first, then file
43
+ *
44
+ * A ref that is a real node id resolves directly (`getNode`). Otherwise it
45
+ * is tried as a file path (`findNodes({ file: ref })`, the same reader
46
+ * `lookup.ts`'s `resolveOneNode` already uses for symbol/file matching): the
47
+ * file's own `FILE`-typed node stands in when one exists, else the node
48
+ * with the *weakest* resolution in that file — the same "report the
49
+ * weakest link" convention `resolutionFloor` uses elsewhere in this
50
+ * package, since a file is only as verified as its least-resolved member.
51
+ * A ref that resolves neither way is `notExercised`.
52
+ */
53
+ import { getNode, findNodes, computeVerificationStatus, resolutionOfExercised } from "@descryy/core";
54
+ import { answer, optionalStringArray, ToolInputError, } from "./kit.js";
55
+ import { loadSources } from "../registry.js";
56
+ const SCHEMA = {
57
+ type: "object",
58
+ properties: {
59
+ refs: {
60
+ type: "array",
61
+ items: { type: "string" },
62
+ minItems: 1,
63
+ description: "Node ids and/or repository-relative file paths to check coverage for — typically ids " +
64
+ "already returned by an earlier call (impact, scope, contracts) that you now want to know " +
65
+ "whether the graph actually reached.",
66
+ },
67
+ },
68
+ required: ["refs"],
69
+ additionalProperties: false,
70
+ };
71
+ /** The file's own `FILE` node when present, else the node with the weakest resolution — never picked at random. */
72
+ function representativeForFile(nodes) {
73
+ const fileNode = nodes.find((n) => n.type === "FILE");
74
+ if (fileNode !== undefined)
75
+ return fileNode;
76
+ return nodes.reduce((weakest, n) => (n.resolution < weakest.resolution ? n : weakest));
77
+ }
78
+ function resolveRef(driver, ref) {
79
+ const byId = getNode(driver, ref);
80
+ if (byId !== undefined)
81
+ return { ref, language: byId.language, node: byId };
82
+ const inFile = findNodes(driver, { file: ref });
83
+ if (inFile.length > 0) {
84
+ const node = representativeForFile(inFile);
85
+ return { ref, language: node.language, node };
86
+ }
87
+ return { ref, language: null, node: null };
88
+ }
89
+ /**
90
+ * Real capabilities for whichever configured, loaded adapter owns each
91
+ * unresolved ref — decided entirely by the adapter's own `ownsPath`, never
92
+ * by a language name compared up here. See the module header.
93
+ */
94
+ async function capabilitiesForUnresolvedRefs(sources, unresolvedRefs) {
95
+ const capabilitiesByRef = new Map();
96
+ const failedNotes = [];
97
+ if (unresolvedRefs.length === 0 || sources.length === 0)
98
+ return { capabilitiesByRef, failedNotes };
99
+ const { loaded, failed } = await loadSources(sources);
100
+ try {
101
+ for (const ref of unresolvedRefs) {
102
+ const owner = loaded.find((entry) => entry.adapter?.ownsPath?.(ref) === true);
103
+ if (owner?.adapter !== undefined && owner?.adapter !== null) {
104
+ capabilitiesByRef.set(ref, owner.adapter.capabilities());
105
+ }
106
+ }
107
+ }
108
+ finally {
109
+ await Promise.all(loaded.map((entry) => entry.adapter?.dispose()));
110
+ }
111
+ for (const failure of failed) {
112
+ failedNotes.push(`A configured source (${failure.spec.module}) did not import (${failure.reason}), so it could ` +
113
+ "not be asked whether it owns any of this call's unresolved refs.");
114
+ }
115
+ return { capabilitiesByRef, failedNotes };
116
+ }
117
+ async function run(args, ctx) {
118
+ const refs = optionalStringArray(args, "refs") ?? [];
119
+ if (refs.length === 0) {
120
+ throw new ToolInputError('"refs" must name at least one node id or file path — with none, there is nothing to report ' +
121
+ "coverage for, and an empty answer would look like a check that ran");
122
+ }
123
+ const driver = ctx.session.store().driver;
124
+ const base = ctx.session.provider().baseStamp();
125
+ ctx.progress(`Checking coverage for ${refs.length} ref(s)`);
126
+ const resolved = refs.map((ref) => resolveRef(driver, ref));
127
+ const unresolvedRefs = resolved.filter((r) => r.node === null).map((r) => r.ref);
128
+ const { capabilitiesByRef, failedNotes } = await capabilitiesForUnresolvedRefs(ctx.session.config.sources, unresolvedRefs);
129
+ const scope = resolved.map((r) => ({
130
+ ref: r.ref,
131
+ language: r.language,
132
+ node: r.node,
133
+ capabilities: r.node === null ? capabilitiesByRef.get(r.ref) : undefined,
134
+ }));
135
+ const status = computeVerificationStatus(scope);
136
+ const exercised = status.exercised.map((entry) => resolutionOfExercised(entry));
137
+ return answer({
138
+ headline: exercised.length === refs.length
139
+ ? `All ${refs.length} ref(s) were reached by the graph.`
140
+ : `${exercised.length} of ${refs.length} ref(s) were reached by the graph; ` +
141
+ `${status.notExercised.length} were not — see notExercised for why.`,
142
+ data: { checked: refs, exercised, notExercised: status.notExercised },
143
+ resolutionFloor: exercised.length === 0 ? 0 : Math.min(...exercised.map((e) => e.resolution)),
144
+ commitSha: base.commitSha,
145
+ graphBuiltAt: base.graphBuiltAt,
146
+ irSchemaVersion: base.irSchemaVersion,
147
+ // A presence/coverage claim ("was this ref reached, and at what resolution")
148
+ // is about identity, not shape — the same class impact/history/similar_incidents
149
+ // already use.
150
+ nameLevel: true,
151
+ notes: [
152
+ ...(status.notExercised.length === 0
153
+ ? []
154
+ : [
155
+ "This tool never names a language for an absent ref — it only asks each configured, " +
156
+ "loaded adapter whether it owns that path. See each notExercised reason for exactly " +
157
+ "what was determined.",
158
+ ]),
159
+ ...failedNotes,
160
+ ],
161
+ });
162
+ }
163
+ export const verificationStatusTool = {
164
+ name: "verification_status",
165
+ class: "read",
166
+ tier: "evidence",
167
+ version: "1.0.0",
168
+ title: "Was this actually checked, and at what resolution?",
169
+ description: "Given a list of node ids and/or file paths, report which were actually reached by the graph " +
170
+ "and at what resolution, and which were not — with why, where determinable. Coverage is a " +
171
+ "separate axis from any finding's confidence (§19/§20): use this after another call to learn " +
172
+ "what that call's answer does and does not actually cover, rather than trusting silence as " +
173
+ "coverage. When a session's configured adapters can say whether they own an unreached file, " +
174
+ "real declared capabilities are attached to explain the gap more precisely — see the tool's own " +
175
+ "notes when some refs are not exercised.",
176
+ inputSchema: SCHEMA,
177
+ run,
178
+ };
179
+ //# sourceMappingURL=verification-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification-status.js","sourceRoot":"","sources":["../../src/tools/verification-status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * stdio transport: newline-delimited JSON over stdin/stdout.
3
+ *
4
+ * ## The one rule that breaks everything when broken
5
+ *
6
+ * **stdout is the protocol channel and nothing else may write to it.** A single
7
+ * `console.log` anywhere in the process — in the engine, in a loaded adapter, in
8
+ * a dependency — emits a line the client tries to parse as a frame, and the
9
+ * session dies with an error that names the parser rather than the culprit.
10
+ *
11
+ * This is not a comment asking people to be careful. `guardStdout()` replaces
12
+ * `console.log` and friends with writers onto **stderr**, which the MCP host
13
+ * shows as server logs, so a stray print stays visible and stops being fatal.
14
+ * The transport keeps the one real handle to stdout for itself.
15
+ *
16
+ * ## Framing
17
+ *
18
+ * MCP's stdio transport is one JSON object per line, UTF-8, with no embedded
19
+ * newlines. `JSON.stringify` never emits a raw newline, so writing is a
20
+ * stringify plus `"\n"`. Reading needs a buffer because a chunk boundary can
21
+ * land anywhere, including mid-multibyte-character — hence `StringDecoder`
22
+ * rather than `chunk.toString()`, which would corrupt any non-ASCII payload
23
+ * split across two chunks.
24
+ */
25
+ import type { Readable, Writable } from "node:stream";
26
+ export interface Transport {
27
+ /** Write one frame. Must never be called with anything but a JSON-RPC message. */
28
+ send(frame: unknown): void;
29
+ }
30
+ /**
31
+ * Redirect every console channel to stderr and return the real stdout writer.
32
+ *
33
+ * Called once, by the stdio runner, before any handler can run. The returned
34
+ * function is the only sanctioned path to stdout in the process.
35
+ */
36
+ export declare function guardStdout(stdout: Writable, stderr: Writable): (line: string) => void;
37
+ /**
38
+ * Split a byte stream into lines, decoding correctly across chunk boundaries.
39
+ *
40
+ * Returned as a function rather than a class because the buffer is the entire
41
+ * state and there is nothing else to hang on an object.
42
+ */
43
+ export declare function createLineReader(onLine: (line: string) => void): (chunk: Buffer) => void;
44
+ export interface StdioOptions {
45
+ readonly stdin: Readable;
46
+ readonly stdout: Writable;
47
+ readonly stderr: Writable;
48
+ }
49
+ /**
50
+ * Run a dispatcher over stdio until stdin closes.
51
+ *
52
+ * Resolves on end-of-input, which is how an MCP host asks a stdio server to shut
53
+ * down — it closes the pipe. Anything the server holds open (a database handle,
54
+ * a worker pool) is disposed by the caller after this resolves, not here: this
55
+ * function knows about bytes and nothing else.
56
+ */
57
+ export declare function serveStdio(options: StdioOptions, dispatch: (line: string, transport: Transport) => Promise<void>): Promise<void>;
58
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,SAAS;IACxB,kFAAkF;IAClF,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,QAAQ,GACf,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAiBxB;AAWD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAcxF;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GAC9D,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * stdio transport: newline-delimited JSON over stdin/stdout.
3
+ *
4
+ * ## The one rule that breaks everything when broken
5
+ *
6
+ * **stdout is the protocol channel and nothing else may write to it.** A single
7
+ * `console.log` anywhere in the process — in the engine, in a loaded adapter, in
8
+ * a dependency — emits a line the client tries to parse as a frame, and the
9
+ * session dies with an error that names the parser rather than the culprit.
10
+ *
11
+ * This is not a comment asking people to be careful. `guardStdout()` replaces
12
+ * `console.log` and friends with writers onto **stderr**, which the MCP host
13
+ * shows as server logs, so a stray print stays visible and stops being fatal.
14
+ * The transport keeps the one real handle to stdout for itself.
15
+ *
16
+ * ## Framing
17
+ *
18
+ * MCP's stdio transport is one JSON object per line, UTF-8, with no embedded
19
+ * newlines. `JSON.stringify` never emits a raw newline, so writing is a
20
+ * stringify plus `"\n"`. Reading needs a buffer because a chunk boundary can
21
+ * land anywhere, including mid-multibyte-character — hence `StringDecoder`
22
+ * rather than `chunk.toString()`, which would corrupt any non-ASCII payload
23
+ * split across two chunks.
24
+ */
25
+ import { StringDecoder } from "node:string_decoder";
26
+ /**
27
+ * Redirect every console channel to stderr and return the real stdout writer.
28
+ *
29
+ * Called once, by the stdio runner, before any handler can run. The returned
30
+ * function is the only sanctioned path to stdout in the process.
31
+ */
32
+ export function guardStdout(stdout, stderr) {
33
+ const toStderr = (...args) => {
34
+ stderr.write(`${args
35
+ .map((a) => (typeof a === "string" ? a : inspectish(a)))
36
+ .join(" ")}\n`);
37
+ };
38
+ console.log = toStderr;
39
+ console.info = toStderr;
40
+ console.warn = toStderr;
41
+ console.debug = toStderr;
42
+ console.error = toStderr;
43
+ return (line) => {
44
+ stdout.write(line);
45
+ };
46
+ }
47
+ function inspectish(value) {
48
+ if (value instanceof Error)
49
+ return `${value.name}: ${value.message}`;
50
+ try {
51
+ return JSON.stringify(value) ?? String(value);
52
+ }
53
+ catch {
54
+ return String(value);
55
+ }
56
+ }
57
+ /**
58
+ * Split a byte stream into lines, decoding correctly across chunk boundaries.
59
+ *
60
+ * Returned as a function rather than a class because the buffer is the entire
61
+ * state and there is nothing else to hang on an object.
62
+ */
63
+ export function createLineReader(onLine) {
64
+ const decoder = new StringDecoder("utf8");
65
+ let buffer = "";
66
+ return (chunk) => {
67
+ buffer += decoder.write(chunk);
68
+ let index = buffer.indexOf("\n");
69
+ while (index !== -1) {
70
+ const line = buffer.slice(0, index).replace(/\r$/, "");
71
+ buffer = buffer.slice(index + 1);
72
+ if (line.trim() !== "")
73
+ onLine(line);
74
+ index = buffer.indexOf("\n");
75
+ }
76
+ };
77
+ }
78
+ /**
79
+ * Run a dispatcher over stdio until stdin closes.
80
+ *
81
+ * Resolves on end-of-input, which is how an MCP host asks a stdio server to shut
82
+ * down — it closes the pipe. Anything the server holds open (a database handle,
83
+ * a worker pool) is disposed by the caller after this resolves, not here: this
84
+ * function knows about bytes and nothing else.
85
+ */
86
+ export function serveStdio(options, dispatch) {
87
+ const write = guardStdout(options.stdout, options.stderr);
88
+ const transport = {
89
+ send: (frame) => {
90
+ write(`${JSON.stringify(frame)}\n`);
91
+ },
92
+ };
93
+ // Frames are handled strictly in arrival order. Concurrency here would let a
94
+ // fast `tools/list` overtake a slow `analyze`, and while JSON-RPC permits
95
+ // out-of-order responses, an `analyze` that has not finished writing the graph
96
+ // must not be overtaken by a `scope` that would then read a half-built one.
97
+ let chain = Promise.resolve();
98
+ return new Promise((resolve, reject) => {
99
+ const feed = createLineReader((line) => {
100
+ chain = chain.then(() => dispatch(line, transport)).catch((error) => {
101
+ // A dispatcher that throws is a bug in this package: `dispatch` is
102
+ // supposed to turn every failure into an error frame. Report it on
103
+ // stderr and keep the session alive rather than killing a developer's
104
+ // editor integration over one bad request.
105
+ options.stderr.write(`descry-mcp: dispatch failed: ${inspectish(error)}\n`);
106
+ });
107
+ });
108
+ options.stdin.on("data", feed);
109
+ options.stdin.on("error", reject);
110
+ options.stdin.on("end", () => {
111
+ chain.then(resolve, resolve);
112
+ });
113
+ });
114
+ }
115
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@descryy/mcp",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Descry's MCP server: the agent-facing front door over the query layer. Zero runtime dependencies — the stdio transport is JSON-RPC 2.0 by hand rather than seventeen packages of HTTP and OAuth.",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=22.5"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "bin": {
17
+ "descry-mcp": "dist/bin/descry-mcp.js"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "publishConfig": {
23
+ "registry": "https://registry.npmjs.org",
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc -b"
28
+ },
29
+ "dependencies": {
30
+ "@descryy/core": "0.1.0",
31
+ "@descryy/ir": "0.1.0"
32
+ }
33
+ }