arcane-os 0.63.0 → 0.65.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 (58) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +1 -1
  3. package/browser-runtime/core/client.mjs +69 -7
  4. package/docs/guides/native-whisper-openvino.md +334 -0
  5. package/docs/reference/README.md +6 -0
  6. package/docs/reference/arcane-ollama.md +1 -1
  7. package/docs/reference/codex-app-server.md +269 -0
  8. package/docs/reference/core-client.md +71 -4
  9. package/docs/reference/core-development.md +157 -0
  10. package/docs/reference/core-native-packaging.md +48 -6
  11. package/docs/reference/core-repositories.md +258 -0
  12. package/docs/reference/inventory/package-api.json +199 -3
  13. package/docs/reference/inventory/runtime-modules.json +3 -2
  14. package/docs/reference/local-ai.md +77 -4
  15. package/docs/reference/local-image-flux.md +1 -1
  16. package/docs/reference/local-image-generation.md +11 -0
  17. package/docs/reference/local-image-sdxl.md +139 -0
  18. package/docs/reference/local-whisper.md +219 -0
  19. package/docs/reference/mcp-stdio.md +263 -0
  20. package/docs/reference/native-speech.md +51 -2
  21. package/docs/reference/runtime-components.md +18 -1
  22. package/docs/reference/runtime-modules.md +26 -4
  23. package/docs/reference/sdk-api.md +412 -2
  24. package/package.json +9 -2
  25. package/runtime/arcane/components/speech.html +15 -3
  26. package/runtime/arcane/components/voice-transcription.html +19 -3
  27. package/runtime/arcane/modules/AI.js +406 -40
  28. package/runtime/arcane/modules/AIProviderRuntime.js +20 -3
  29. package/runtime/arcane/modules/AIRuntimeState.js +15 -2
  30. package/runtime/arcane/modules/ComponentContracts.js +3 -0
  31. package/runtime/arcane/modules/Ollama.js +1 -1
  32. package/runtime/arcane/modules/OllamaModelIdentifier.js +13 -0
  33. package/schemas/arcane-app.schema.json +60 -2
  34. package/schemas/arcane-package.schema.json +60 -2
  35. package/src/codex/app-server.mjs +845 -0
  36. package/src/core/development.mjs +457 -0
  37. package/src/core/repositories.mjs +375 -0
  38. package/src/core/services/local-ai.mjs +80 -10
  39. package/src/core/services/speech.mjs +1 -0
  40. package/src/dev-server.mjs +3 -2
  41. package/src/index.mjs +1 -0
  42. package/src/local-ai/config.mjs +7 -3
  43. package/src/local-ai/dev.mjs +85 -401
  44. package/src/local-ai/image-library.mjs +9 -0
  45. package/src/local-ai/image-models/sdxl.mjs +18 -0
  46. package/src/local-ai/install.mjs +26 -7
  47. package/src/local-ai/whisper/build.mjs +48 -0
  48. package/src/local-ai/whisper/config.mjs +89 -0
  49. package/src/local-ai/whisper/index.mjs +572 -0
  50. package/src/local-ai/whisper/install.mjs +332 -0
  51. package/src/local-ai/whisper/native/CMakeLists.txt +98 -0
  52. package/src/local-ai/whisper/native/NOTICE +18 -0
  53. package/src/local-ai/whisper/native/README.md +104 -0
  54. package/src/local-ai/whisper/native/main.cpp +520 -0
  55. package/src/local-ai/whisper/openvino-build.mjs +138 -0
  56. package/src/mcp/stdio-server.mjs +579 -0
  57. package/src/native/portable-layout.mjs +39 -7
  58. package/src/toolchain.mjs +19 -8
@@ -41,6 +41,17 @@ model's resource metadata; runtime installation does not independently download
41
41
  or bundle model files. The shared model asset owner prepares selected browser
42
42
  assets and supplies a Core working projection for native path-based loading.
43
43
 
44
+ `flux2-klein-4b` selects the three-resource distilled FLUX.2 Klein definition.
45
+ Its [family guide](local-image-flux.md) covers diffusion/VAE/Qwen preparation,
46
+ four-step defaults and the advertised `txt2img` operation. Keep each model's
47
+ supported operations distinct: selecting FLUX does not enable reference-image
48
+ editing or imply the same input path as SD1.4 img2img.
49
+
50
+ `sdxl-base-1.0` selects the complete SDXL Base checkpoint, including its text
51
+ encoders and original VAE. Its [family guide](local-image-sdxl.md) covers the
52
+ single model resource, 1024-by-1024 default canvas and native sampling defaults.
53
+ It advertises `txt2img`; selecting it does not enable an editing operation.
54
+
44
55
  The first published distribution path selects CPU on Windows and Linux.
45
56
  On macOS, `auto` selects the same upstream universal runtime's built-in Metal
46
57
  backend, with CPU fallback; explicit `cpu` remains available. A caller-supplied
@@ -0,0 +1,139 @@
1
+ # SDXL Base 1.0 through Core
2
+
3
+ The `sdxl-base-1.0` model definition selects Stability AI's complete SDXL Base
4
+ 1.0 checkpoint for text-to-image generation through the shared
5
+ [local image runtime](local-image-generation.md). Applications supply their
6
+ own complete prompts and own the resulting images and saved associations.
7
+ The model uses the shared retained native context, progress, cancellation
8
+ and complete PNG result contract.
9
+
10
+ ## Model selection
11
+
12
+ Select this runtime record under `native.localAI` in `arcane-app.json`, or
13
+ the corresponding package projection's `localAI` record:
14
+
15
+ ```json
16
+ {
17
+ "runtimes": [
18
+ {
19
+ "id": "stable-diffusion.cpp",
20
+ "version": "master-929-3f8527a",
21
+ "backend": "auto",
22
+ "models": ["sdxl-base-1.0"]
23
+ }
24
+ ]
25
+ }
26
+ ```
27
+
28
+ The built-in ID supplies the cohesive `SDXL_BASE_1_0_MODEL` definition from
29
+ `src/local-ai/image-models/sdxl.mjs`. The shared service exposes its resource
30
+ metadata through `image.status` and `images.inspect()`.
31
+
32
+ Backend discovery, accelerated-device selection and CPU fallback belong to
33
+ the shared runtime. This model definition leaves `context` empty and adds no
34
+ device or precision override. Available acceleration depends on the selected
35
+ runtime distribution; see the shared guide's platform availability. Model
36
+ selection alone does not establish actual execution on any device.
37
+
38
+ ## Prepare the checkpoint and generate an image
39
+
40
+ The single `model` resource is the official
41
+ [`sd_xl_base_1.0.safetensors` checkpoint](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/462165984030d82259a11f4367a4eed129e94a7b/sd_xl_base_1.0.safetensors)
42
+ at revision `462165984030d82259a11f4367a4eed129e94a7b`. It includes the text
43
+ encoders, denoiser and original VAE. Standalone base generation uses this one
44
+ resource; a refiner, separate VAE, LoRA or Python converter is unnecessary for
45
+ this selection. The upstream model carries the
46
+ [CreativeML Open RAIL++-M license](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/blob/462165984030d82259a11f4367a4eed129e94a7b/LICENSE.md).
47
+
48
+ The existing DBOPFS model store owns the authoritative download. The shared
49
+ [model-assets service](model-assets.md) prepares complete native working files
50
+ when the application loads the model. Installing the native runtime does not
51
+ download a second model copy.
52
+
53
+ This example extends the shared generation guide. It uses an existing `images`
54
+ accessor, DBOPFS-backed `modelStore`, application-selected `workingDirectory`
55
+ and request `signal`:
56
+
57
+ ```javascript
58
+ import {createBrowserModelSource} from 'arcane-os/ai/browser-wasm';
59
+ import {prepareCoreModelAssets} from 'arcane-os/ai/core-model-assets';
60
+
61
+ const selected = (await images.inspect({signal})).models.find(
62
+ function selectedSDXL(model) {
63
+ return model.id === 'sdxl-base-1.0';
64
+ }
65
+ );
66
+ const resource = selected.resources.model;
67
+ const source = createBrowserModelSource({
68
+ id: selected.id,
69
+ files: [{name: resource.filename, url: resource.url}]
70
+ });
71
+ const stored = await modelStore.ensure(source, {signal});
72
+ const projection = await prepareCoreModelAssets({
73
+ workingDirectory,
74
+ members: source.files.map(function workingMember(member, index) {
75
+ return {path: member.name, file: stored.files[index]};
76
+ }),
77
+ signal
78
+ });
79
+
80
+ try {
81
+ await images.load({
82
+ model: selected.id,
83
+ assetProjectionId: projection.id,
84
+ resourcePaths: {model: resource.filename},
85
+ signal
86
+ });
87
+ } finally {
88
+ await projection.release();
89
+ }
90
+
91
+ const result = await images.generate({
92
+ model: selected.id,
93
+ prompt: 'An octopus librarian shelving tiny books inside a glass submarine.',
94
+ signal
95
+ });
96
+ // Display result.images using the shared guide's Blob preview example.
97
+ ```
98
+
99
+ Change the prompt to request another scene. The model defaults to a
100
+ 1024-by-1024 canvas; pass `parameters: {width, height}` to select other native
101
+ dimensions. Display or save every returned image using its actual width and
102
+ height. Each result contains the complete PNG Blob.
103
+
104
+ Successful loading retains the working projection in the native model owner.
105
+ Releasing the caller's preparation handle preserves that native retain.
106
+ Repeated generation reuses the loaded context; unload it when the application
107
+ no longer needs it. Follow the shared lifecycle contract for request
108
+ cancellation, state subscriptions, unloading and accessor closure.
109
+
110
+ ## Defaults and operation scope
111
+
112
+ Only canvas dimensions are model-specific defaults here. Sampling parameters
113
+ remain owned by the selected native engine and can be supplied through
114
+ `parameters.sample_params`. At `master-929-3f8527a`, SDXL resolves the native
115
+ default sampler to Euler A and scheduler to discrete, with 20 steps and CFG 7.
116
+ These are native defaults; Stability AI's separate base-plus-refiner example
117
+ and Python pipeline have their own sampling configuration.
118
+
119
+ The selected engine has an explicit path for SDXL's embedded original VAE,
120
+ including its native convolution scaling. The model definition leaves that
121
+ treatment with the engine and supplies no guessed weight-type override or
122
+ separate VAE checkpoint. Output quality, speed and device behavior depend on
123
+ the actual selected runtime and generation parameters.
124
+
125
+ The SDK forwards the complete caller prompt to the native API. The engine
126
+ applies its own text conditioning and prompt-weighting syntax; see the shared
127
+ guide for native string-boundary limitations. The SDK adds no avatar wording,
128
+ style instructions or application-specific content.
129
+
130
+ This definition advertises `txt2img`. Whole-image editing, masked editing and
131
+ reference-image editing are separate operations with their own model and
132
+ runtime requirements. This base selection does not advertise those operations.
133
+
134
+ The model resource and standalone use follow the
135
+ [official model card](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/blob/462165984030d82259a11f4367a4eed129e94a7b/README.md).
136
+ Native defaults and embedded-VAE behavior follow the selected engine's
137
+ [request implementation](https://github.com/leejet/stable-diffusion.cpp/blob/master-929-3f8527a/src/pipeline/request.cpp),
138
+ [parameter initialization](https://github.com/leejet/stable-diffusion.cpp/blob/master-929-3f8527a/src/stable-diffusion.cpp)
139
+ and [model construction](https://github.com/leejet/stable-diffusion.cpp/blob/master-929-3f8527a/src/pipeline/model_builders.cpp).
@@ -0,0 +1,219 @@
1
+ # Native Whisper transcription
2
+
3
+ Arcane runs `whisper.cpp` through a persistent native helper. A selected model
4
+ stays loaded across recordings. The helper reports actual model readiness,
5
+ native progress and timestamped segments, and completes cancellation before
6
+ the model or recording files are reused. The browser's existing
7
+ `AI.fetchSTT(audioFile, signal)` and Core `speech.transcribe` routes retain the
8
+ application's selected model and cancellation signal.
9
+
10
+ ## Select the runtime and model
11
+
12
+ An application's `arcane-app.json` selects native speech alongside its other
13
+ independent local runtimes:
14
+
15
+ ```json
16
+ {
17
+ "native": {
18
+ "localAI": {
19
+ "runtimes": [
20
+ {
21
+ "id": "whisper.cpp",
22
+ "backend": "auto",
23
+ "models": ["whisper-small"]
24
+ }
25
+ ]
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ The equivalent package configuration is `arcane-package.json`'s `localAI`.
32
+ Existing unrelated descriptor fields remain part of the application's file.
33
+ `models` defaults to an empty array, so selecting the runtime alone acquires no
34
+ model. A single selected model becomes `modelId`; multiple models require an
35
+ explicit selected `modelId` before transcription. The built-in `whisper-small`
36
+ descriptor selects the official `ggerganov/whisper.cpp` `ggml-small.bin` model.
37
+ An explicit model descriptor is `{id, path}` or `{id, url, filename?}`.
38
+
39
+ Default Windows x64 preparation acquires the selected complete upstream
40
+ Whisper 1.9.4/b5130 CUDA 12.4 and CPU distributions, FFmpeg 9.0.2 essentials,
41
+ the selected model, and Arcane's helper asset from the matching SDK numeric
42
+ release. The Windows helper includes its selected Visual C++ release runtime
43
+ DLLs beside the executable. Preparation shares matching installation jobs, observes cancellation,
44
+ and preserves the complete selected runtime trees. Model-selection changes
45
+ can reuse the already prepared runtime and matching models.
46
+
47
+ With `backend: "auto"`, accelerated runtimes are attempted before CPU. Failure
48
+ to load an accelerated runtime falls back to the prepared CPU runtime. If
49
+ native accelerated inference fails, Arcane drains that context and retries the
50
+ complete decoded recording once on CPU with the same model. Cancellation,
51
+ input errors and observer errors do not trigger this retry. Explicit `cuda`,
52
+ `metal` or `cpu` selections retain that chosen backend.
53
+
54
+ During that accepted request's internal recovery, the engine reports
55
+ `state: "recovering"`, `loaded: false`, and `busy: true`, with the same selected
56
+ provider and model. It accepts no new transcription while the old context
57
+ drains and the CPU context loads. Only actual CPU readiness restores `ready`.
58
+ An explicitly unloaded, replaced, or closed model remains a cancellation
59
+ boundary; internal recovery preserves the original request and its signal.
60
+
61
+ `requestedBackend` identifies a preference. `observedBackend` and
62
+ `backendEvidence` report native initialization evidence when available; they
63
+ do not claim that every computation runs on the GPU. No driver or compiler is
64
+ installed on application startup.
65
+
66
+ ## Core and development integration
67
+
68
+ Development and generated native Core entries compose a
69
+ [`createSpeechService`](./native-speech.md) with the selected Whisper engine.
70
+ Speech preparation and model loading proceed independently of the page,
71
+ chat and image services. Core publishes retained `speech.state` and per-request
72
+ `speech.progress`; transcription availability follows the selected engine's
73
+ actual loaded state. The shared service remains usable when only STT is
74
+ selected, through `transcriptionAvailable` and `roles.stt`.
75
+
76
+ The native bundle includes the helper, runtime libraries, decoder and selected
77
+ model under its runtime directory beside the application executable. Those
78
+ resources stay in the application bundle. They are not embedded into one
79
+ Windows executable. The compiler and matching upstream headers are build-time
80
+ requirements for the first-party helper only.
81
+
82
+ Applications may continue using the existing Core call:
83
+
84
+ ```js
85
+ const transcript = await client.speech.transcribe({
86
+ audioBase64,
87
+ mimeType: 'audio/webm',
88
+ model: 'whisper-small'
89
+ }, {signal});
90
+ console.log(transcript.text);
91
+ ```
92
+
93
+ FFmpeg decodes the complete recorded media into mono 16 kHz PCM at the native
94
+ transport boundary. The original recording remains untouched during the
95
+ operation. Arcane imposes no recording-duration cap and does not trim the
96
+ transcript. The upstream inference API's signed-integer sample argument is
97
+ reported honestly if a recording cannot fit that native call; no audio is
98
+ silently shortened. Complete operation files are removed only after decoder
99
+ and native inference cleanup have completed.
100
+
101
+ ## Public engine API
102
+
103
+ ```js
104
+ import {createWhisperRuntime} from 'arcane-os/local-ai/whisper';
105
+
106
+ const stt = createWhisperRuntime({
107
+ runtime: preparedRuntime,
108
+ modelId: 'whisper-small',
109
+ temporaryDirectory: applicationSpeechDirectory,
110
+ onEvent: reportDiagnostic
111
+ });
112
+ const unsubscribe = stt.subscribe(renderModelState);
113
+ await stt.load({signal});
114
+ try {
115
+ const result = await stt.transcribe({audioBase64, mimeType, model: 'whisper-small'}, {
116
+ signal,
117
+ onProgress: renderTranscriptionProgress
118
+ });
119
+ console.log(result.text, result.segments);
120
+ } finally {
121
+ unsubscribe();
122
+ await stt.close();
123
+ }
124
+ ```
125
+
126
+ `createWhisperRuntime(options)` performs no model load. `options.runtime` is a
127
+ prepared descriptor returned by `ensureLocalAIRuntimes`; alternatively,
128
+ `options.prepare({signal, onEvent})` lazily returns that descriptor during
129
+ `load`. `temporaryDirectory` is required and belongs to the application.
130
+
131
+ | Member | Contract |
132
+ | --- | --- |
133
+ | `current()` | Returns `providerId`, selected `modelId`, lifecycle `state`, `loaded`, `busy`, correlated `requestId` or null, requested/observed backend, evidence and any error. |
134
+ | `subscribe(listener, {replay = true, signal} = {})` | Immediately replays current state by default and returns an unsubscribe function. |
135
+ | `load({modelId, signal} = {})` | Prepares the runtime if necessary, then waits for actual native model readiness. With no selected model it remains unloaded. |
136
+ | `transcribe(request, {signal, onProgress, requestId} = {})` | Consumes the complete `audioBase64` recording. Optional `language` defaults to native automatic detection; `translate: true` selects Whisper's translation operation. `model` must match the loaded model. Optional `requestId` carries the caller's existing operation identity through lifecycle snapshots as separate control information; it does not alter the recording or request payload. |
137
+ | `unload()` | Cancels and joins active load/transcription, releases the native context, and permits a later explicit load. |
138
+ | `close()` | Joins shutdown and disposes this engine permanently. Repeated calls share the same shutdown. |
139
+
140
+ One retained model processes one recording at a time; a simultaneous request
141
+ receives `WHISPER_BUSY`. Model changes use `await stt.unload()` followed by
142
+ `stt.load({modelId})`. The `ready` state remains `loaded: true` with `busy: true`
143
+ during ordinary inference. Progress records include the native event plus
144
+ `backend` and `attempt`, so a CPU retry remains distinguishable. Segment text
145
+ is preserved in order. The final result is
146
+ `{text, language, duration, segments: [{index, start, end, text}]}`; times are
147
+ seconds and are native segment timestamps, not invented word timestamps.
148
+
149
+ Full process diagnostics stay with `onEvent` and native process failures.
150
+ Applications should use their selected inspection surface for those details
151
+ and keep ordinary user status concise.
152
+ An error snapshot retains the failed operation's `requestId` after `busy`
153
+ becomes false, allowing the caller to correlate its actual pending response.
154
+ A new request or model load supersedes that identity; unload and close clear
155
+ it. No recording or request payload is retained by this correlation field.
156
+ Progress callbacks may cancel through the supplied controller and return;
157
+ they must not await the same transcription or its shutdown from inside its
158
+ own output callback. Await those operations in the initiating owner instead.
159
+
160
+ ## Explicit runtimes and helper builds
161
+
162
+ Windows, Linux and macOS host integration uses the same public engine and
163
+ portable helper source. Automatic archive selection currently provides the
164
+ Windows x64 distribution above. Other targets supply their complete selected
165
+ runtime, decoder and compiled helper paths; their native execution requires
166
+ verification on that actual platform. Android requires a host adapter for
167
+ its process and library lifecycle.
168
+
169
+ An explicit runtime requirement can supply:
170
+
171
+ ```js
172
+ {
173
+ id: 'whisper.cpp',
174
+ version: '1.9.4',
175
+ backend: 'auto',
176
+ variants: [
177
+ {backend: 'cuda', root: cudaRuntimeDirectory, libraryDirectory: cudaLibraryDirectory},
178
+ {backend: 'cpu', root: cpuRuntimeDirectory, libraryDirectory: cpuLibraryDirectory}
179
+ ],
180
+ helperExecutable,
181
+ helperRoot,
182
+ decoderExecutable,
183
+ decoderRoot,
184
+ models: [{id: 'whisper-small', path: selectedModelPath}],
185
+ modelId: 'whisper-small'
186
+ }
187
+ ```
188
+
189
+ `libraryDirectory` belongs to its complete variant root. `decoderExecutable`
190
+ belongs to `decoderRoot` (which defaults to its containing directory).
191
+ `helperExecutable` similarly belongs to `helperRoot`; its complete directory
192
+ tree is retained, including app-local native dependencies.
193
+ Preparation copies those selected resources into its owned installation, and
194
+ native packaging projects their paths relative to the application bundle.
195
+ `helperUrl`, `helperVersion`, and `decoderUrl` allow an explicitly selected
196
+ published distribution instead of local paths. Dependency permissions remain
197
+ the caller's responsibility.
198
+
199
+ ```js
200
+ import {buildWhisperHelper} from 'arcane-os/local-ai/whisper/build';
201
+
202
+ const helper = await buildWhisperHelper({
203
+ runtime: {directory: runtimeLibraryDirectory, sourceDirectory: matchingUpstreamSource},
204
+ outputRoot: helperBuildDirectory,
205
+ signal,
206
+ onEvent: reportDiagnostic
207
+ });
208
+ ```
209
+
210
+ The builder runs CMake configure/build/install against the selected matching
211
+ public upstream headers and runtime. Optional `cmake`, `generator`,
212
+ `cmakeArgs` and `env` select the existing toolchain. It returns the helper
213
+ `executable`, platform, architecture, runtime directory and complete build
214
+ diagnostics. The helper's documented process contract is in
215
+ [`native/README.md`](../../src/local-ai/whisper/native/README.md).
216
+ On MSVC, CMake's installed runtime-library module adds release CRT files to
217
+ the helper's `bin/` directory. `-DMSVC_REDIST_DIR=...` selects an existing
218
+ redistributable directory when needed. Windows provides the Universal CRT;
219
+ compiler tools and debug runtimes stay outside the application bundle.
@@ -0,0 +1,263 @@
1
+ # MCP STDIO server
2
+
3
+ Expose application-owned tools and static resources to an MCP client with
4
+ `arcane-os/mcp/stdio`. This optional Node entrypoint owns one JSON-RPC session,
5
+ UTF-8 newline framing, discovery, request correlation, cooperative cancellation,
6
+ and graceful drain. The application owns its launch command, tool definitions,
7
+ resource contents, business behavior, and underlying service lifetime.
8
+
9
+ Availability: Node on Windows, Linux, and macOS, using the package's supported
10
+ Node version. Android requires a host that supplies the same Node stream and
11
+ process environment. Importing the entrypoint starts no process or listener.
12
+ It is separate from the native Core `arcane/1` transport and its Content-Length
13
+ frames; the two stream formats are not interchangeable.
14
+
15
+ ## createMcpStdioServer
16
+
17
+ ```javascript
18
+ import {createMcpStdioServer} from 'arcane-os/mcp/stdio';
19
+
20
+ const menu = {
21
+ content: 'Moon soup\nExtra tentacles available.',
22
+ message: {chef: 'Octopus', open: true}
23
+ };
24
+
25
+ const server = createMcpStdioServer(
26
+ {
27
+ serverInfo: {name: 'moon-pantry', version: '1.0.0'},
28
+ instructions: 'Ask moon.menu for the complete lunar dinner menu.',
29
+ tools: [
30
+ {
31
+ name: 'moon.menu',
32
+ description: 'Read the complete lunar dinner menu.',
33
+ inputSchema: {type: 'object'},
34
+ annotations: {readOnlyHint: true},
35
+ handler: readMenu
36
+ }
37
+ ],
38
+ resources: [
39
+ {
40
+ uri: 'pantry://menu',
41
+ name: 'menu',
42
+ title: 'Lunar dinner menu',
43
+ mimeType: 'application/json',
44
+ handler: readMenuResource
45
+ }
46
+ ]
47
+ }
48
+ );
49
+
50
+ function readMenu(args, {signal}) {
51
+ signal.throwIfAborted();
52
+ return {
53
+ content: [{type: 'text', text: JSON.stringify(menu)}],
54
+ structuredContent: menu
55
+ };
56
+ }
57
+
58
+ function readMenuResource({uri}, {signal}) {
59
+ signal.throwIfAborted();
60
+ return {
61
+ contents: [{uri, mimeType: 'application/json', text: JSON.stringify(menu)}]
62
+ };
63
+ }
64
+
65
+ server.start();
66
+ try {
67
+ await server.closed;
68
+ } catch {
69
+ // The adapter already sent its diagnostic to stderr.
70
+ process.exitCode = 1;
71
+ }
72
+ ```
73
+
74
+ Save this in the consuming application and configure the MCP client's subprocess
75
+ command as `node` with that application's script path. Client configuration
76
+ file formats and process launch policy remain client-owned. Change `menu` to
77
+ change the complete result; no model, repository, or native host is needed for
78
+ this example.
79
+
80
+ The application explicitly converts its domain record into an MCP result.
81
+ The SDK does not guess from `content`, `message`, or other domain field names.
82
+ The serialized text in this example is an application-selected representation;
83
+ the SDK never adds it automatically.
84
+
85
+ ### Parameters
86
+
87
+ `createMcpStdioServer(options)` returns a server object synchronously.
88
+
89
+ | Option | Contract |
90
+ | --- | --- |
91
+ | `serverInfo` | Required MCP implementation object with `name` and `version` strings. Optional MCP implementation fields are passed through. |
92
+ | `instructions` | Optional complete instruction string returned by initialization. |
93
+ | `tools` | Optional static array; each entry is a complete MCP tool descriptor plus `handler(args, context)`. Defaults to `[]`. Tool names identify handlers exactly and must be unique. |
94
+ | `resources` | Optional static array; each entry is a complete MCP resource descriptor plus `handler(params, context)`. Defaults to `[]`. Each resource requires `uri` and `name`; URIs identify handlers exactly and must be unique. |
95
+ | `onDiagnostic` | Optional error observer. Omit it for complete error text on the selected stderr stream. It may return a promise, which participates in drain. Observer failures are reported to stderr and retained by `closed`. |
96
+
97
+ The factory captures catalog entries at construction. Discovery preserves
98
+ descriptor fields, including authored schemas, descriptions, annotations,
99
+ icons, and metadata; only the local `handler` function is omitted. No tool
100
+ arguments, descriptions, schemas, resources, or result content are injected,
101
+ trimmed, rewritten, or shortened. Supply JSON-serializable protocol values.
102
+
103
+ The SDK checks the request envelope and required result container. It does not
104
+ provide a general JSON Schema evaluator. The application supplies a valid
105
+ `inputSchema` object, owns the handler's input semantics, and ensures any
106
+ declared `outputSchema` matches its structured result. This adapter does not
107
+ implement task-augmented execution; descriptors must describe ordinary tool
108
+ execution.
109
+
110
+ ### Handler inputs and results
111
+
112
+ Tool handlers receive `params.arguments` unchanged. Omitted arguments become
113
+ an empty object as the protocol's no-argument case. Resource handlers receive
114
+ the complete `resources/read` params object, including its exact `uri` and
115
+ any metadata. A handler may return its result directly or return a promise.
116
+
117
+ Each handler receives a context with:
118
+
119
+ - `signal`: its own request's `AbortSignal`;
120
+ - `requestId`: the original string or integer ID, including numeric zero;
121
+ - `method`: `tools/call` or `resources/read`;
122
+ - `params`: the complete request params object;
123
+ - `clientInfo` and `clientCapabilities`: the client's initialization objects.
124
+
125
+ A tool handler returns an explicit MCP `CallToolResult`, including a `content`
126
+ array and optional `structuredContent`, `isError`, `_meta`, or other protocol
127
+ fields. All supplied content blocks and JSON fields pass through. Resource
128
+ handlers return an explicit `ReadResourceResult` with a `contents` array;
129
+ complete text or already encoded binary resource contents pass through.
130
+ The application creates these protocol result objects deliberately. A plain
131
+ domain object with `content: 'some text'` is not a `CallToolResult` and produces
132
+ an internal protocol error rather than being silently converted.
133
+
134
+ For a domain failure, return a complete `CallToolResult` with `isError: true`.
135
+ An ordinary exception thrown by a tool handler becomes an `isError: true`
136
+ text result containing the exception's complete message, and the complete
137
+ exception is also reported through diagnostics. Throw `McpProtocolError` for
138
+ an explicit JSON-RPC failure. Other resource or adapter exceptions produce
139
+ `-32603` and a diagnostic; malformed result containers and non-serializable
140
+ results also produce `-32603`.
141
+ Custom fields on an ordinary thrown error remain in diagnostics unless the
142
+ application explicitly maps its known outcome to `structuredContent` in an
143
+ `isError: true` tool result.
144
+
145
+ ## start
146
+
147
+ `server.start({input, output, error})` attaches to caller-owned Node streams and
148
+ returns the same server. Defaults are `process.stdin`, `process.stdout`, and
149
+ `process.stderr`. Start each server exactly once with an open readable input and
150
+ open writable output/diagnostic streams. Output and diagnostics must be separate
151
+ streams. Byte-mode input preserves split UTF-8 sequences; a stream already
152
+ decoded into strings remains responsible for its own decoding.
153
+
154
+ Messages are processed as their newline arrives, without waiting for EOF. A
155
+ final complete JSON record is also accepted when EOF arrives without its final
156
+ newline. LF and CRLF transport endings are accepted. JSON escaping preserves
157
+ embedded line breaks in the decoded payload. Malformed JSON receives `-32700`;
158
+ its complete original line is included in diagnostics. Invalid UTF-8 fails at
159
+ the decoder and closes the transport with the complete affected input chunks
160
+ and pending decoded text in its diagnostic error.
161
+
162
+ Requests execute independently, including overlapping calls to one tool.
163
+ Only complete output writes are ordered, waiting for each Node write callback
164
+ before sending the next record. An error, close, or finish event during a pending
165
+ output or diagnostic write settles that write as failed even when its callback
166
+ never arrives; a later callback cannot settle it again. The application decides
167
+ whether its own data access requires narrower serialization. The adapter adds
168
+ no process-wide queue.
169
+
170
+ Only JSON-RPC messages are written to `output`. Application handlers and their
171
+ dependencies must also reserve stdout for protocol traffic; use stderr or the
172
+ application's diagnostic owner for logs. The adapter does not replace global
173
+ console functions.
174
+
175
+ ## cancel
176
+
177
+ `server.cancel(requestId, reason)` requests cooperative cancellation and returns
178
+ `true` only when an active cancellable handler was signalled. String and numeric
179
+ IDs remain distinct. Unknown, completed, already cancelled, and initialization
180
+ requests return `false`. The same behavior handles client
181
+ `notifications/cancelled` messages. Late results from cancelled work have no
182
+ protocol response, as described by the
183
+ [MCP cancellation contract](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation).
184
+
185
+ The handler must pass its signal to its actual I/O or computation and release
186
+ owned resources. Signalling does not prove external work stopped. The adapter
187
+ retains the handler promise until it really settles; a handler that ignores
188
+ cancellation can keep drain and close pending.
189
+
190
+ ## drain
191
+
192
+ `await server.drain()` waits for accepted handlers, diagnostic observers, and
193
+ output write callbacks to settle. It does not stop input or abort requests, so
194
+ newly accepted work can extend the wait. Use `close()` when input must first
195
+ stop. Tool and request errors are delivered as protocol outcomes; transport and
196
+ diagnostic delivery failures are retained by `closed`.
197
+
198
+ ## close
199
+
200
+ `await server.close({cancelPending: false})` stops reading, pauses the input,
201
+ drains accepted work and writes, detaches the adapter's stream listeners, and
202
+ settles `closed`. It is idempotent. Normal input EOF follows this same graceful
203
+ path. Explicit `cancelPending: true` aborts pending work instead; repeated close
204
+ may request cancellation while an earlier graceful close is draining.
205
+
206
+ Stream failures request cancellation, report diagnostics, and reject `closed`
207
+ after owned work settles. An input closed without EOF is a transport failure.
208
+ The adapter does not end or destroy caller streams, terminate the process,
209
+ install process signal handlers, start an application host, or close
210
+ application services. The launcher owns those actions after the adapter drains.
211
+
212
+ ## closed
213
+
214
+ `server.closed` is a promise for actual session cleanup. It resolves after a
215
+ normal close or EOF and rejects with an `AggregateError` when transport or
216
+ diagnostic delivery failed. Observe it even when the initiating caller continues
217
+ other work. Request failures alone do not close the session.
218
+
219
+ ## MCP_PROTOCOL_VERSION
220
+
221
+ `MCP_PROTOCOL_VERSION` is exactly `'2025-11-25'`. Initialization always returns
222
+ that supported version. A client proposing another version must accept this
223
+ profile or disconnect. This is the initialize/initialized STDIO profile;
224
+ support for later discovery-based profiles is not implied. See the normative
225
+ [lifecycle](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle)
226
+ and [STDIO transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports).
227
+
228
+ Supported requests are `initialize`, `ping`, `tools/list`, `tools/call`,
229
+ `resources/list`, `resources/read`, and `resources/templates/list` (an empty
230
+ template catalog). Tools and resources capabilities are advertised when their
231
+ catalogs are nonempty. Lists return their complete catalogs without pagination;
232
+ supplying a cursor receives `-32602`. Send `notifications/initialized` after
233
+ the initialization response before ordinary operations.
234
+
235
+ There are no prompts, subscriptions, list-change events, MCP logging requests,
236
+ server-to-client requests, sampling, elicitation, task augmentation, HTTP
237
+ transport, or automatic client configuration. Unknown methods return `-32601`;
238
+ notifications never receive responses. JSON-RPC batches are not part of this
239
+ profile. IDs must be strings or integers and are unique for the session.
240
+
241
+ The result shapes follow the official
242
+ [tool](https://modelcontextprotocol.io/specification/2025-11-25/server/tools)
243
+ and [resource](https://modelcontextprotocol.io/specification/2025-11-25/server/resources)
244
+ contracts. This narrow adapter does not claim every optional MCP capability.
245
+
246
+ ## McpProtocolError
247
+
248
+ `new McpProtocolError(code, message, data)` represents an explicit protocol
249
+ failure. `code` is a JSON-RPC integer, `message` is its complete string, and
250
+ optional `data` is JSON-serializable protocol detail. It extends `Error` and
251
+ exposes `name`, `code`, `message`, and optional `data`.
252
+
253
+ The adapter uses `-32700` for malformed JSON, `-32600` for invalid requests,
254
+ `-32601` for unknown methods, `-32602` for malformed params or unknown tools,
255
+ `-32603` for internal failures, and `-32002` for a missing resource. Its
256
+ `-32000` lifecycle error requests initialization before ordinary operations.
257
+
258
+ ## Evidence boundary
259
+
260
+ `test/mcp-stdio-server.test.mjs` contains focused in-memory stream fixtures for
261
+ these contracts. Authored fixtures and source review do not establish an
262
+ executed client integration or platform run. Run local tests only under the
263
+ applicable explicit testing or selected-output authority.