@okfit/mcp 0.5.3 → 0.6.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/errors.js +17 -74
- package/index.d.ts +46 -84
- package/index.js +2 -2
- package/internal/projectRoot.js +21 -6
- package/internal/resolveNow.js +3 -2
- package/internal/toolContext.js +4 -3
- package/main.d.ts +17 -5
- package/main.js +21 -9
- package/package.json +4 -2
- package/server.js +30 -19
- package/tools/conceptNeighbors.js +6 -5
- package/tools/getConcept.js +6 -5
- package/tools/listConcepts.js +10 -9
- package/tools/validateBundle.js +4 -3
- package/tsdoc-metadata.json +1 -1
- package/version.js +1 -1
package/errors.js
CHANGED
|
@@ -1,100 +1,43 @@
|
|
|
1
|
+
import { Remediation } from "@effected/engine";
|
|
2
|
+
import { ToolFailure } from "@effected/mcp";
|
|
1
3
|
import { Schema } from "effect";
|
|
2
4
|
|
|
3
5
|
//#region src/errors.ts
|
|
4
|
-
/** What a caller should do next about a failed tool call. @public */
|
|
5
|
-
const Remediation = Schema.Struct({
|
|
6
|
-
hint: Schema.String,
|
|
7
|
-
suggestedTool: Schema.optionalKey(Schema.String)
|
|
8
|
-
});
|
|
9
6
|
/**
|
|
10
|
-
*
|
|
11
|
-
* remediation: the human message, then the hint, then `Try <suggestedTool>.`
|
|
12
|
-
* when one is present. Every `McpToolError` member's `message` field is
|
|
13
|
-
* built through this at construction — under `failureMode: "error"` (the
|
|
14
|
-
* only mode this contract's tools use), `McpServer`'s own `registerToolkit`
|
|
15
|
-
* collapses a caught typed failure to
|
|
16
|
-
* `{ isError: true, content: [{ type: "text", text: error.message }] }`
|
|
17
|
-
* and never surfaces `structuredContent` for it
|
|
18
|
-
* (`.repos/effect/packages/effect/src/unstable/ai/McpServer.ts:1774-1778,1842-1846`
|
|
19
|
-
* at `effect@4.0.0-rc.116`: `toolErrorResult` and `declaredFailureResult`'s
|
|
20
|
-
* `error instanceof Error` branch; confirmed empirically in Task B1's own
|
|
21
|
-
* build). Since rc.116 that declared branch is rendered WITHOUT a log line
|
|
22
|
-
* — only an internal failure goes through `Effect.logError` — so the
|
|
23
|
-
* message text is the whole of what a client ever sees. `remediation` itself is
|
|
24
|
-
* left on the schema unchanged, both for anything that inspects the typed
|
|
25
|
-
* error directly (a defect handler, a future in-process caller) and
|
|
26
|
-
* because it is what this function reads to build `message`.
|
|
27
|
-
*
|
|
28
|
-
* @public
|
|
7
|
+
* Config discovery, parsing, or validation failed. @public
|
|
29
8
|
*/
|
|
30
|
-
|
|
31
|
-
const ECHO_LIMIT = 200;
|
|
9
|
+
var ConfigError = class extends Schema.TaggedError()("ConfigError", { ...ToolFailure.fields }) {};
|
|
32
10
|
/**
|
|
33
|
-
*
|
|
34
|
-
* error's `message` — the only field a `failureMode: "error"` failure
|
|
35
|
-
* actually delivers to the wire (see {@link composeRemediatedMessage}'s
|
|
36
|
-
* doc comment). Without this, a pathological argument (a multi-megabyte
|
|
37
|
-
* `id`, say) is echoed once in the response's `content[0].text` and once
|
|
38
|
-
* more in the corresponding log line, wasting an agent's context on what
|
|
39
|
-
* is usually a pure typo (final whole-branch review, Minor finding 5).
|
|
40
|
-
*
|
|
41
|
-
* @public
|
|
42
|
-
*/
|
|
43
|
-
const truncateEchoed = (value, limit = ECHO_LIMIT) => value.length > limit ? `${value.slice(0, limit)}…` : value;
|
|
44
|
-
/**
|
|
45
|
-
* Config discovery, parsing, or validation failed. `message` is composed
|
|
46
|
-
* through {@link composeRemediatedMessage} at construction, so it is what
|
|
47
|
-
* reaches the wire as `tools/call`'s `content[0].text` (see that
|
|
48
|
-
* function's doc comment for why). @public
|
|
49
|
-
*/
|
|
50
|
-
var ConfigError = class extends Schema.TaggedError()("ConfigError", {
|
|
51
|
-
message: Schema.String,
|
|
52
|
-
remediation: Remediation
|
|
53
|
-
}) {};
|
|
54
|
-
/**
|
|
55
|
-
* The configured bundle root does not exist or could not be read.
|
|
56
|
-
* `message` is composed through {@link composeRemediatedMessage} at
|
|
57
|
-
* construction, so it is what reaches the wire as `tools/call`'s
|
|
58
|
-
* `content[0].text`. @public
|
|
11
|
+
* The configured bundle root does not exist or could not be read. @public
|
|
59
12
|
*/
|
|
60
13
|
var BundleNotFound = class extends Schema.TaggedError()("BundleNotFound", {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
remediation: Remediation
|
|
14
|
+
...ToolFailure.fields,
|
|
15
|
+
root: Schema.String
|
|
64
16
|
}) {};
|
|
65
17
|
/**
|
|
66
|
-
* No concept in the bundle has the requested id.
|
|
67
|
-
* through {@link composeRemediatedMessage} at construction, so it is what
|
|
68
|
-
* reaches the wire as `tools/call`'s `content[0].text`. @public
|
|
18
|
+
* No concept in the bundle has the requested id. @public
|
|
69
19
|
*/
|
|
70
20
|
var ConceptNotFound = class extends Schema.TaggedError()("ConceptNotFound", {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
remediation: Remediation
|
|
21
|
+
...ToolFailure.fields,
|
|
22
|
+
id: Schema.String
|
|
74
23
|
}) {};
|
|
75
24
|
/**
|
|
76
25
|
* A requested type or tag name is not declared in the resolved config.
|
|
77
|
-
*
|
|
78
|
-
* construction, so it is what reaches the wire as `tools/call`'s
|
|
79
|
-
* `content[0].text`. @public
|
|
26
|
+
* @public
|
|
80
27
|
*/
|
|
81
28
|
var UnknownVocabulary = class extends Schema.TaggedError()("UnknownVocabulary", {
|
|
29
|
+
...ToolFailure.fields,
|
|
82
30
|
kind: Schema.Literals(["type", "tag"]),
|
|
83
31
|
requested: Schema.String,
|
|
84
|
-
valid: Schema.Array(Schema.String)
|
|
85
|
-
message: Schema.String,
|
|
86
|
-
remediation: Remediation
|
|
32
|
+
valid: Schema.Array(Schema.String)
|
|
87
33
|
}) {};
|
|
88
34
|
/**
|
|
89
35
|
* A tool argument was structurally acceptable but semantically invalid.
|
|
90
|
-
*
|
|
91
|
-
* construction, so it is what reaches the wire as `tools/call`'s
|
|
92
|
-
* `content[0].text`. @public
|
|
36
|
+
* @public
|
|
93
37
|
*/
|
|
94
38
|
var InvalidArgument = class extends Schema.TaggedError()("InvalidArgument", {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
remediation: Remediation
|
|
39
|
+
...ToolFailure.fields,
|
|
40
|
+
argument: Schema.String
|
|
98
41
|
}) {};
|
|
99
42
|
/** The one failure schema every tool declares (N-14). @public */
|
|
100
43
|
const McpToolError = Schema.Union([
|
|
@@ -106,4 +49,4 @@ const McpToolError = Schema.Union([
|
|
|
106
49
|
]);
|
|
107
50
|
|
|
108
51
|
//#endregion
|
|
109
|
-
export { BundleNotFound, ConceptNotFound, ConfigError, InvalidArgument, McpToolError, Remediation, UnknownVocabulary
|
|
52
|
+
export { BundleNotFound, ConceptNotFound, ConfigError, InvalidArgument, McpToolError, Remediation, UnknownVocabulary };
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Remediation } from "@effected/engine";
|
|
1
2
|
import { FileSystem, Layer, Path, Schema, Stdio } from "effect";
|
|
2
3
|
import { AppDirs, Xdg } from "@effected/xdg";
|
|
3
4
|
import { LoadedConcept } from "@okfit/core";
|
|
@@ -5,120 +6,72 @@ import { Distribution } from "@okfit/engine";
|
|
|
5
6
|
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
6
7
|
import { Toolkit } from "effect/unstable/ai";
|
|
7
8
|
//#region src/errors.d.ts
|
|
8
|
-
/** What a caller should do next about a failed tool call. @public */
|
|
9
|
-
export declare const Remediation: Schema.Struct<{
|
|
10
|
-
readonly hint: Schema.String;
|
|
11
|
-
readonly suggestedTool: Schema.optionalKey<Schema.String>;
|
|
12
|
-
}>;
|
|
13
|
-
/** @public */
|
|
14
|
-
export type Remediation = typeof Remediation.Type;
|
|
15
|
-
/**
|
|
16
|
-
* Compose a self-contained wire message from a raw cause message and its
|
|
17
|
-
* remediation: the human message, then the hint, then `Try <suggestedTool>.`
|
|
18
|
-
* when one is present. Every `McpToolError` member's `message` field is
|
|
19
|
-
* built through this at construction — under `failureMode: "error"` (the
|
|
20
|
-
* only mode this contract's tools use), `McpServer`'s own `registerToolkit`
|
|
21
|
-
* collapses a caught typed failure to
|
|
22
|
-
* `{ isError: true, content: [{ type: "text", text: error.message }] }`
|
|
23
|
-
* and never surfaces `structuredContent` for it
|
|
24
|
-
* (`.repos/effect/packages/effect/src/unstable/ai/McpServer.ts:1774-1778,1842-1846`
|
|
25
|
-
* at `effect@4.0.0-rc.116`: `toolErrorResult` and `declaredFailureResult`'s
|
|
26
|
-
* `error instanceof Error` branch; confirmed empirically in Task B1's own
|
|
27
|
-
* build). Since rc.116 that declared branch is rendered WITHOUT a log line
|
|
28
|
-
* — only an internal failure goes through `Effect.logError` — so the
|
|
29
|
-
* message text is the whole of what a client ever sees. `remediation` itself is
|
|
30
|
-
* left on the schema unchanged, both for anything that inspects the typed
|
|
31
|
-
* error directly (a defect handler, a future in-process caller) and
|
|
32
|
-
* because it is what this function reads to build `message`.
|
|
33
|
-
*
|
|
34
|
-
* @public
|
|
35
|
-
*/
|
|
36
|
-
export declare const composeRemediatedMessage: (message: string, remediation: Remediation) => string;
|
|
37
|
-
/**
|
|
38
|
-
* Truncate a caller-supplied value before it is echoed back inside an
|
|
39
|
-
* error's `message` — the only field a `failureMode: "error"` failure
|
|
40
|
-
* actually delivers to the wire (see {@link composeRemediatedMessage}'s
|
|
41
|
-
* doc comment). Without this, a pathological argument (a multi-megabyte
|
|
42
|
-
* `id`, say) is echoed once in the response's `content[0].text` and once
|
|
43
|
-
* more in the corresponding log line, wasting an agent's context on what
|
|
44
|
-
* is usually a pure typo (final whole-branch review, Minor finding 5).
|
|
45
|
-
*
|
|
46
|
-
* @public
|
|
47
|
-
*/
|
|
48
|
-
export declare const truncateEchoed: (value: string, limit?: number) => string;
|
|
49
9
|
declare const ConfigError_base: Schema.Class<ConfigError, Schema.TaggedStruct<"ConfigError", {
|
|
50
10
|
readonly message: Schema.String;
|
|
51
11
|
readonly remediation: Schema.Struct<{
|
|
52
12
|
readonly hint: Schema.String;
|
|
53
13
|
readonly suggestedTool: Schema.optionalKey<Schema.String>;
|
|
14
|
+
readonly suggestedArgs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
54
15
|
}>;
|
|
55
16
|
}>, import("effect/Cause").YieldableError>;
|
|
56
17
|
/**
|
|
57
|
-
* Config discovery, parsing, or validation failed.
|
|
58
|
-
* through {@link composeRemediatedMessage} at construction, so it is what
|
|
59
|
-
* reaches the wire as `tools/call`'s `content[0].text` (see that
|
|
60
|
-
* function's doc comment for why). @public
|
|
18
|
+
* Config discovery, parsing, or validation failed. @public
|
|
61
19
|
*/
|
|
62
20
|
export declare class ConfigError extends ConfigError_base {}
|
|
63
21
|
declare const BundleNotFound_base: Schema.Class<BundleNotFound, Schema.TaggedStruct<"BundleNotFound", {
|
|
64
|
-
readonly root: Schema.String;
|
|
65
22
|
readonly message: Schema.String;
|
|
66
23
|
readonly remediation: Schema.Struct<{
|
|
67
24
|
readonly hint: Schema.String;
|
|
68
25
|
readonly suggestedTool: Schema.optionalKey<Schema.String>;
|
|
26
|
+
readonly suggestedArgs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
69
27
|
}>;
|
|
28
|
+
readonly root: Schema.String;
|
|
70
29
|
}>, import("effect/Cause").YieldableError>;
|
|
71
30
|
/**
|
|
72
|
-
* The configured bundle root does not exist or could not be read.
|
|
73
|
-
* `message` is composed through {@link composeRemediatedMessage} at
|
|
74
|
-
* construction, so it is what reaches the wire as `tools/call`'s
|
|
75
|
-
* `content[0].text`. @public
|
|
31
|
+
* The configured bundle root does not exist or could not be read. @public
|
|
76
32
|
*/
|
|
77
33
|
export declare class BundleNotFound extends BundleNotFound_base {}
|
|
78
34
|
declare const ConceptNotFound_base: Schema.Class<ConceptNotFound, Schema.TaggedStruct<"ConceptNotFound", {
|
|
79
|
-
readonly id: Schema.String;
|
|
80
35
|
readonly message: Schema.String;
|
|
81
36
|
readonly remediation: Schema.Struct<{
|
|
82
37
|
readonly hint: Schema.String;
|
|
83
38
|
readonly suggestedTool: Schema.optionalKey<Schema.String>;
|
|
39
|
+
readonly suggestedArgs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
84
40
|
}>;
|
|
41
|
+
readonly id: Schema.String;
|
|
85
42
|
}>, import("effect/Cause").YieldableError>;
|
|
86
43
|
/**
|
|
87
|
-
* No concept in the bundle has the requested id.
|
|
88
|
-
* through {@link composeRemediatedMessage} at construction, so it is what
|
|
89
|
-
* reaches the wire as `tools/call`'s `content[0].text`. @public
|
|
44
|
+
* No concept in the bundle has the requested id. @public
|
|
90
45
|
*/
|
|
91
46
|
export declare class ConceptNotFound extends ConceptNotFound_base {}
|
|
92
47
|
declare const UnknownVocabulary_base: Schema.Class<UnknownVocabulary, Schema.TaggedStruct<"UnknownVocabulary", {
|
|
93
|
-
readonly kind: Schema.Literals<readonly ["type", "tag"]>;
|
|
94
|
-
readonly requested: Schema.String;
|
|
95
|
-
readonly valid: Schema.$Array<Schema.String>;
|
|
96
48
|
readonly message: Schema.String;
|
|
97
49
|
readonly remediation: Schema.Struct<{
|
|
98
50
|
readonly hint: Schema.String;
|
|
99
51
|
readonly suggestedTool: Schema.optionalKey<Schema.String>;
|
|
52
|
+
readonly suggestedArgs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
100
53
|
}>;
|
|
54
|
+
readonly kind: Schema.Literals<readonly ["type", "tag"]>;
|
|
55
|
+
readonly requested: Schema.String;
|
|
56
|
+
readonly valid: Schema.$Array<Schema.String>;
|
|
101
57
|
}>, import("effect/Cause").YieldableError>;
|
|
102
58
|
/**
|
|
103
59
|
* A requested type or tag name is not declared in the resolved config.
|
|
104
|
-
*
|
|
105
|
-
* construction, so it is what reaches the wire as `tools/call`'s
|
|
106
|
-
* `content[0].text`. @public
|
|
60
|
+
* @public
|
|
107
61
|
*/
|
|
108
62
|
export declare class UnknownVocabulary extends UnknownVocabulary_base {}
|
|
109
63
|
declare const InvalidArgument_base: Schema.Class<InvalidArgument, Schema.TaggedStruct<"InvalidArgument", {
|
|
110
|
-
readonly argument: Schema.String;
|
|
111
64
|
readonly message: Schema.String;
|
|
112
65
|
readonly remediation: Schema.Struct<{
|
|
113
66
|
readonly hint: Schema.String;
|
|
114
67
|
readonly suggestedTool: Schema.optionalKey<Schema.String>;
|
|
68
|
+
readonly suggestedArgs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
115
69
|
}>;
|
|
70
|
+
readonly argument: Schema.String;
|
|
116
71
|
}>, import("effect/Cause").YieldableError>;
|
|
117
72
|
/**
|
|
118
73
|
* A tool argument was structurally acceptable but semantically invalid.
|
|
119
|
-
*
|
|
120
|
-
* construction, so it is what reaches the wire as `tools/call`'s
|
|
121
|
-
* `content[0].text`. @public
|
|
74
|
+
* @public
|
|
122
75
|
*/
|
|
123
76
|
export declare class InvalidArgument extends InvalidArgument_base {}
|
|
124
77
|
/** The one failure schema every tool declares (N-14). @public */
|
|
@@ -371,9 +324,8 @@ export type ValidateBundleParams = typeof ValidateBundleParams.Type;
|
|
|
371
324
|
/**
|
|
372
325
|
* Everything `ServerLayer` still needs from the platform: the four services
|
|
373
326
|
* `describe_vocabulary`'s declared `Tool.make` dependencies pull through
|
|
374
|
-
* `
|
|
375
|
-
* `Stdio`, which `
|
|
376
|
-
* (`unstable/ai/McpServer.ts:1207-1216`) and which the brief's own
|
|
327
|
+
* `McpToolkit.layer`'s own `Tool.HandlerServices<Tools>` requirement, plus
|
|
328
|
+
* `Stdio`, which `McpStdio.layer` itself requires and which the brief's own
|
|
377
329
|
* `PlatformServices` literal omitted.
|
|
378
330
|
*
|
|
379
331
|
* `ChildProcessSpawner` is new (contract §10.3, S-16): `validate_bundle`'s
|
|
@@ -415,24 +367,34 @@ export declare const SERVER_INSTRUCTIONS: string;
|
|
|
415
367
|
* The whole server as one layer: the toolkit, one static resource per
|
|
416
368
|
* concept (`okf://concept/<id>`, built once at boot — see
|
|
417
369
|
* {@link ConceptResources}), and `okf://index` (re-read from disk on every
|
|
418
|
-
* call), over `
|
|
370
|
+
* call), over `@effected/mcp`'s `McpStdio.layer`.
|
|
419
371
|
*
|
|
420
|
-
* `
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
* adapter; a second fails the layer with `Cause.IllegalArgumentError`.
|
|
431
|
-
* Never reduce this to one entry.
|
|
372
|
+
* `McpStdio.layer` (not core's own `McpServer.layerStdio`, hand-wired) is
|
|
373
|
+
* load-bearing, not a style choice: it wraps core's stdin decoder with a
|
|
374
|
+
* guard that answers a non-JSON line with a JSON-RPC `-32700` and keeps
|
|
375
|
+
* serving, where hand-wiring `layerStdio` directly wedges permanently on
|
|
376
|
+
* the first bad line (effect-v4-mcp's `server-wiring.md#stdin-guard`). It
|
|
377
|
+
* also merges `LogToStderr` into everything it provides, so `main.ts` no
|
|
378
|
+
* longer assembles a logger of its own. `McpToolkit.layer` (not core's
|
|
379
|
+
* `McpServer.toolkit`) reports every unknown argument key a strict tool
|
|
380
|
+
* call carries, at every depth, in one response, instead of only the
|
|
381
|
+
* first.
|
|
432
382
|
*
|
|
433
|
-
* `
|
|
434
|
-
*
|
|
435
|
-
*
|
|
383
|
+
* `protocols` ships three adapters, newest first -- the same list
|
|
384
|
+
* `McpStdio.protocols` defaults to, spelled out here since the decision
|
|
385
|
+
* behind the exact order is this package's own
|
|
386
|
+
* (`okf/decisions/mcp-stateless-first-protocol-list.md`), not the kit's.
|
|
387
|
+
* `McpProtocol.v2026_07_28` is the stateless adapter (SEP-2575): no
|
|
388
|
+
* `initialize`, no session, every request self-identifies through
|
|
389
|
+
* `params._meta`, and the client discovers the server with
|
|
390
|
+
* `server/discover`. The two stateful adapters stay because `initialize`
|
|
391
|
+
* matches stateful adapters only — a client that opens with `initialize`
|
|
392
|
+
* (Claude Code by default, Copilot, Cursor, the Inspector) would otherwise
|
|
393
|
+
* get `METHOD_NOT_FOUND`. Array order is load-bearing: a request with no
|
|
394
|
+
* session and no `_meta` protocol version falls to `protocols[0]`, and
|
|
395
|
+
* `server/discover` advertises every listed adapter in `supportedVersions`.
|
|
396
|
+
* The runtime allows at most one stateless adapter; a second fails the
|
|
397
|
+
* layer. Never reduce this to one entry.
|
|
436
398
|
*
|
|
437
399
|
* @public
|
|
438
400
|
*/
|
|
@@ -827,5 +789,5 @@ export declare const ToolsLayer: (projectRoot: string, distribution?: Distributi
|
|
|
827
789
|
*/
|
|
828
790
|
export declare const MCP_VERSION: string;
|
|
829
791
|
//#endregion
|
|
830
|
-
export type
|
|
792
|
+
export { type PlatformServices, Remediation, type ServerOptions };
|
|
831
793
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BundleNotFound, ConceptNotFound, ConfigError, InvalidArgument, McpToolError, Remediation, UnknownVocabulary
|
|
1
|
+
import { BundleNotFound, ConceptNotFound, ConfigError, InvalidArgument, McpToolError, Remediation, UnknownVocabulary } from "./errors.js";
|
|
2
2
|
import { ConceptResources } from "./resources/conceptResource.js";
|
|
3
3
|
import { IndexResource } from "./resources/indexResource.js";
|
|
4
4
|
import { ConceptSummary, toConceptSummary } from "./schema/ConceptSummary.js";
|
|
@@ -7,4 +7,4 @@ import { MCP_VERSION } from "./version.js";
|
|
|
7
7
|
import { OkfitToolkit, ToolsLayer } from "./toolkit.js";
|
|
8
8
|
import { SERVER_INSTRUCTIONS, ServerLayer } from "./server.js";
|
|
9
9
|
|
|
10
|
-
export { BundleNotFound, ConceptNeighborsSuccess, ConceptNotFound, ConceptResources, ConceptSummary, ConfigError, DescribeVocabularySuccess, GetConceptSuccess, IndexResource, InvalidArgument, ListConceptsParams, ListConceptsSuccess, MCP_VERSION, McpToolError, Neighbor, OkfitToolkit, Remediation, SERVER_INSTRUCTIONS, ServerLayer, StaleReportParams, StaleReportSuccess, ToolsLayer, UnknownVocabulary, ValidateBundleParams,
|
|
10
|
+
export { BundleNotFound, ConceptNeighborsSuccess, ConceptNotFound, ConceptResources, ConceptSummary, ConfigError, DescribeVocabularySuccess, GetConceptSuccess, IndexResource, InvalidArgument, ListConceptsParams, ListConceptsSuccess, MCP_VERSION, McpToolError, Neighbor, OkfitToolkit, Remediation, SERVER_INSTRUCTIONS, ServerLayer, StaleReportParams, StaleReportSuccess, ToolsLayer, UnknownVocabulary, ValidateBundleParams, toConceptSummary };
|
package/internal/projectRoot.js
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
|
+
import { LaunchContext } from "@effected/engine";
|
|
2
|
+
|
|
1
3
|
//#region src/internal/projectRoot.ts
|
|
2
4
|
/**
|
|
3
|
-
* N-21's precedence
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
5
|
+
* N-21's precedence: `OKFIT_PROJECT_DIR`, else `CLAUDE_PROJECT_DIR`, else
|
|
6
|
+
* `cwd`. Pure over its `env`/`cwd` arguments so tests never touch
|
|
7
|
+
* `process.env`/`process.cwd()` -- both are read once, in `main.ts`, and
|
|
8
|
+
* passed down as plain values (`main.ts` is the only place this package
|
|
9
|
+
* reads `process`).
|
|
10
|
+
*
|
|
11
|
+
* Built on `@effected/engine`'s `LaunchContext.projectDir` with an empty
|
|
12
|
+
* `argv`: this server has no command parser and `start-mcp.sh` forwards no
|
|
13
|
+
* positional arguments, so the only candidates are the two env keys and the
|
|
14
|
+
* cwd fallback. `LaunchContext.projectDir` additionally treats a value
|
|
15
|
+
* still carrying a literal `${VAR}` placeholder as unusable -- protection
|
|
16
|
+
* the old hand-rolled `??` chain did not have -- against an agent host that
|
|
17
|
+
* leaves `CLAUDE_PROJECT_DIR` unexpanded in some launch paths.
|
|
8
18
|
*
|
|
9
19
|
* @internal
|
|
10
20
|
*/
|
|
11
|
-
const resolveMcpProjectRoot = (env) =>
|
|
21
|
+
const resolveMcpProjectRoot = (env, cwd) => LaunchContext.projectDir({
|
|
22
|
+
argv: [],
|
|
23
|
+
env,
|
|
24
|
+
keys: ["OKFIT_PROJECT_DIR", "CLAUDE_PROJECT_DIR"],
|
|
25
|
+
cwd
|
|
26
|
+
});
|
|
12
27
|
|
|
13
28
|
//#endregion
|
|
14
29
|
export { resolveMcpProjectRoot };
|
package/internal/resolveNow.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { InvalidArgument
|
|
1
|
+
import { InvalidArgument } from "../errors.js";
|
|
2
|
+
import { ToolFailure } from "@effected/mcp";
|
|
2
3
|
import { DateTime, Effect, Schema } from "effect";
|
|
3
4
|
import { Timestamp } from "@okfit/core";
|
|
4
5
|
|
|
@@ -17,7 +18,7 @@ const resolveNow = (input) => input === void 0 ? DateTime.now : Schema.decodeUnk
|
|
|
17
18
|
const remediation = { hint: "now must be an ISO-8601 instant with an explicit offset, for example 2026-09-06T00:00:00Z." };
|
|
18
19
|
return new InvalidArgument({
|
|
19
20
|
argument: "now",
|
|
20
|
-
message:
|
|
21
|
+
message: ToolFailure.message(String(issue), remediation),
|
|
21
22
|
remediation
|
|
22
23
|
});
|
|
23
24
|
}));
|
package/internal/toolContext.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { BundleNotFound, ConfigError
|
|
1
|
+
import { BundleNotFound, ConfigError } from "../errors.js";
|
|
2
|
+
import { ToolFailure } from "@effected/mcp";
|
|
2
3
|
import { Effect, Option } from "effect";
|
|
3
4
|
import { Bundle } from "@okfit/core";
|
|
4
5
|
import { provideConfig, resolveProjectConfig } from "@okfit/engine";
|
|
@@ -43,7 +44,7 @@ const resolveConfigOnly = (projectRoot) => resolveProjectConfig({
|
|
|
43
44
|
suggestedTool: "describe_vocabulary"
|
|
44
45
|
};
|
|
45
46
|
return new ConfigError({
|
|
46
|
-
message:
|
|
47
|
+
message: ToolFailure.message(messageOf(cause), remediation),
|
|
47
48
|
remediation
|
|
48
49
|
});
|
|
49
50
|
}));
|
|
@@ -60,7 +61,7 @@ const loadToolContext = (projectRoot) => Effect.gen(function* () {
|
|
|
60
61
|
const remediation = { hint: `The bundle root "${resolved.bundleRoot}" does not exist or could not be read; check the config's [bundle].path, or run \`okfit init\`.` };
|
|
61
62
|
return new BundleNotFound({
|
|
62
63
|
root: resolved.bundleRoot,
|
|
63
|
-
message:
|
|
64
|
+
message: ToolFailure.message(cause.message, remediation),
|
|
64
65
|
remediation
|
|
65
66
|
});
|
|
66
67
|
}));
|
package/main.d.ts
CHANGED
|
@@ -16,11 +16,23 @@ export interface MainOptions {
|
|
|
16
16
|
*
|
|
17
17
|
* This module deliberately carries NO static imports of the server graph:
|
|
18
18
|
* the `uncaughtException` and `unhandledRejection` handlers are registered
|
|
19
|
-
* before `NodeRuntime`,
|
|
20
|
-
* a throw during module evaluation is still reported on
|
|
21
|
-
* crashing silently. Adding a static import here would
|
|
22
|
-
* `Distribution` above is a type-only import, so it carries
|
|
23
|
-
* import at all.
|
|
19
|
+
* before `NodeRuntime`, `@effected/mcp` and `ServerLayer` are ever
|
|
20
|
+
* evaluated, so a throw during module evaluation is still reported on
|
|
21
|
+
* stderr rather than crashing silently. Adding a static import here would
|
|
22
|
+
* defeat that -- `Distribution` above is a type-only import, so it carries
|
|
23
|
+
* no runtime import at all. `@effected/mcp`'s `McpStdio.launch`/`teardown`
|
|
24
|
+
* sits AROUND this requirement, not in place of it (design-patterns'
|
|
25
|
+
* `carrier-entry-contract.md`).
|
|
26
|
+
*
|
|
27
|
+
* Assembled with `McpStdio.launch`/`McpStdio.teardown` (effect-v4-mcp's
|
|
28
|
+
* `server-wiring.md`): `launch` reports a launch failure itself, on
|
|
29
|
+
* stderr, before `NodeRuntime.runMain`'s own out-of-scope report could
|
|
30
|
+
* print it to stdout -- the JSON-RPC wire; `teardown` maps stdin EOF (a
|
|
31
|
+
* normal client disconnect) to exit `0` instead of the default `130`. Both
|
|
32
|
+
* replace this module's own hand-rolled equivalents. `ServerLayer`'s stdio
|
|
33
|
+
* server (`server.ts`) is built on `McpStdio.layer`, which already merges
|
|
34
|
+
* `LogToStderr` into everything it provides, so this module no longer
|
|
35
|
+
* assembles a logger itself either.
|
|
24
36
|
*
|
|
25
37
|
* @public
|
|
26
38
|
*/
|
package/main.js
CHANGED
|
@@ -17,25 +17,37 @@ const fatal = (label, error) => {
|
|
|
17
17
|
*
|
|
18
18
|
* This module deliberately carries NO static imports of the server graph:
|
|
19
19
|
* the `uncaughtException` and `unhandledRejection` handlers are registered
|
|
20
|
-
* before `NodeRuntime`,
|
|
21
|
-
* a throw during module evaluation is still reported on
|
|
22
|
-
* crashing silently. Adding a static import here would
|
|
23
|
-
* `Distribution` above is a type-only import, so it carries
|
|
24
|
-
* import at all.
|
|
20
|
+
* before `NodeRuntime`, `@effected/mcp` and `ServerLayer` are ever
|
|
21
|
+
* evaluated, so a throw during module evaluation is still reported on
|
|
22
|
+
* stderr rather than crashing silently. Adding a static import here would
|
|
23
|
+
* defeat that -- `Distribution` above is a type-only import, so it carries
|
|
24
|
+
* no runtime import at all. `@effected/mcp`'s `McpStdio.launch`/`teardown`
|
|
25
|
+
* sits AROUND this requirement, not in place of it (design-patterns'
|
|
26
|
+
* `carrier-entry-contract.md`).
|
|
27
|
+
*
|
|
28
|
+
* Assembled with `McpStdio.launch`/`McpStdio.teardown` (effect-v4-mcp's
|
|
29
|
+
* `server-wiring.md`): `launch` reports a launch failure itself, on
|
|
30
|
+
* stderr, before `NodeRuntime.runMain`'s own out-of-scope report could
|
|
31
|
+
* print it to stdout -- the JSON-RPC wire; `teardown` maps stdin EOF (a
|
|
32
|
+
* normal client disconnect) to exit `0` instead of the default `130`. Both
|
|
33
|
+
* replace this module's own hand-rolled equivalents. `ServerLayer`'s stdio
|
|
34
|
+
* server (`server.ts`) is built on `McpStdio.layer`, which already merges
|
|
35
|
+
* `LogToStderr` into everything it provides, so this module no longer
|
|
36
|
+
* assembles a logger itself either.
|
|
25
37
|
*
|
|
26
38
|
* @public
|
|
27
39
|
*/
|
|
28
40
|
const main = async (options = {}) => {
|
|
29
41
|
process.on("uncaughtException", (error) => fatal("uncaught exception", error));
|
|
30
42
|
process.on("unhandledRejection", (reason) => fatal("unhandled rejection", reason));
|
|
43
|
+
const { McpStdio } = await import("@effected/mcp");
|
|
31
44
|
const NodeRuntime = await import("@effect/platform-node/NodeRuntime");
|
|
32
45
|
const { OkfitPlatform } = await import("@okfit/engine");
|
|
33
|
-
const {
|
|
46
|
+
const { Layer } = await import("effect");
|
|
34
47
|
const { resolveMcpProjectRoot } = await import("./internal/projectRoot.js");
|
|
35
48
|
const { ServerLayer } = await import("./server.js");
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
NodeRuntime.runMain(program, { teardown: (exit, onExit) => Exit.isSuccess(exit) || Cause.hasInterruptsOnly(exit.cause) ? onExit(0) : Runtime.defaultTeardown(exit, onExit) });
|
|
49
|
+
const Main = ServerLayer(resolveMcpProjectRoot(process.env, process.cwd()), options).pipe(Layer.provide(OkfitPlatform));
|
|
50
|
+
NodeRuntime.runMain(McpStdio.launch(Main), { teardown: McpStdio.teardown });
|
|
39
51
|
};
|
|
40
52
|
|
|
41
53
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okfit/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Model Context Protocol server for okfit: query and understand Open Knowledge Format (OKF) bundles from an agent.",
|
|
6
6
|
"keywords": [
|
|
@@ -45,10 +45,12 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@effect/platform-node": "4.0.0-rc.117",
|
|
48
|
+
"@effected/engine": "^0.1.0",
|
|
48
49
|
"@effected/git": "^0.17.0",
|
|
50
|
+
"@effected/mcp": "^0.1.1",
|
|
49
51
|
"@effected/xdg": "^0.7.0",
|
|
50
52
|
"@okfit/core": "0.8.0",
|
|
51
|
-
"@okfit/engine": "0.
|
|
53
|
+
"@okfit/engine": "0.9.0",
|
|
52
54
|
"@okfit/profiles": "0.8.0",
|
|
53
55
|
"effect": "4.0.0-rc.117"
|
|
54
56
|
},
|
package/server.js
CHANGED
|
@@ -2,8 +2,9 @@ import { ConceptResources } from "./resources/conceptResource.js";
|
|
|
2
2
|
import { IndexResource } from "./resources/indexResource.js";
|
|
3
3
|
import { MCP_VERSION } from "./version.js";
|
|
4
4
|
import { OkfitToolkit, ToolsLayer } from "./toolkit.js";
|
|
5
|
+
import { McpStdio, McpToolkit } from "@effected/mcp";
|
|
5
6
|
import { Layer } from "effect";
|
|
6
|
-
import { McpProtocol
|
|
7
|
+
import { McpProtocol } from "effect/unstable/ai";
|
|
7
8
|
import { Git } from "@effected/git";
|
|
8
9
|
import { GitHistory } from "@okfit/profiles";
|
|
9
10
|
|
|
@@ -33,28 +34,38 @@ const SERVER_INSTRUCTIONS = [
|
|
|
33
34
|
* The whole server as one layer: the toolkit, one static resource per
|
|
34
35
|
* concept (`okf://concept/<id>`, built once at boot — see
|
|
35
36
|
* {@link ConceptResources}), and `okf://index` (re-read from disk on every
|
|
36
|
-
* call), over `
|
|
37
|
+
* call), over `@effected/mcp`'s `McpStdio.layer`.
|
|
37
38
|
*
|
|
38
|
-
* `
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* adapter; a second fails the layer with `Cause.IllegalArgumentError`.
|
|
49
|
-
* Never reduce this to one entry.
|
|
39
|
+
* `McpStdio.layer` (not core's own `McpServer.layerStdio`, hand-wired) is
|
|
40
|
+
* load-bearing, not a style choice: it wraps core's stdin decoder with a
|
|
41
|
+
* guard that answers a non-JSON line with a JSON-RPC `-32700` and keeps
|
|
42
|
+
* serving, where hand-wiring `layerStdio` directly wedges permanently on
|
|
43
|
+
* the first bad line (effect-v4-mcp's `server-wiring.md#stdin-guard`). It
|
|
44
|
+
* also merges `LogToStderr` into everything it provides, so `main.ts` no
|
|
45
|
+
* longer assembles a logger of its own. `McpToolkit.layer` (not core's
|
|
46
|
+
* `McpServer.toolkit`) reports every unknown argument key a strict tool
|
|
47
|
+
* call carries, at every depth, in one response, instead of only the
|
|
48
|
+
* first.
|
|
50
49
|
*
|
|
51
|
-
* `
|
|
52
|
-
*
|
|
53
|
-
*
|
|
50
|
+
* `protocols` ships three adapters, newest first -- the same list
|
|
51
|
+
* `McpStdio.protocols` defaults to, spelled out here since the decision
|
|
52
|
+
* behind the exact order is this package's own
|
|
53
|
+
* (`okf/decisions/mcp-stateless-first-protocol-list.md`), not the kit's.
|
|
54
|
+
* `McpProtocol.v2026_07_28` is the stateless adapter (SEP-2575): no
|
|
55
|
+
* `initialize`, no session, every request self-identifies through
|
|
56
|
+
* `params._meta`, and the client discovers the server with
|
|
57
|
+
* `server/discover`. The two stateful adapters stay because `initialize`
|
|
58
|
+
* matches stateful adapters only — a client that opens with `initialize`
|
|
59
|
+
* (Claude Code by default, Copilot, Cursor, the Inspector) would otherwise
|
|
60
|
+
* get `METHOD_NOT_FOUND`. Array order is load-bearing: a request with no
|
|
61
|
+
* session and no `_meta` protocol version falls to `protocols[0]`, and
|
|
62
|
+
* `server/discover` advertises every listed adapter in `supportedVersions`.
|
|
63
|
+
* The runtime allows at most one stateless adapter; a second fails the
|
|
64
|
+
* layer. Never reduce this to one entry.
|
|
54
65
|
*
|
|
55
66
|
* @public
|
|
56
67
|
*/
|
|
57
|
-
const ServerLayer = (projectRoot, options = {}) => Layer.mergeAll(
|
|
68
|
+
const ServerLayer = (projectRoot, options = {}) => Layer.mergeAll(McpToolkit.layer(OkfitToolkit).pipe(Layer.provideMerge(ToolsLayer(projectRoot, options.distribution))), ConceptResources(projectRoot), IndexResource(projectRoot)).pipe(Layer.provide(Layer.mergeAll(Git.layer, GitHistory.layer)), Layer.provide(McpStdio.layer({
|
|
58
69
|
name: "okfit",
|
|
59
70
|
version: MCP_VERSION,
|
|
60
71
|
instructions: SERVER_INSTRUCTIONS,
|
|
@@ -63,7 +74,7 @@ const ServerLayer = (projectRoot, options = {}) => Layer.mergeAll(McpServer.tool
|
|
|
63
74
|
McpProtocol.v2025_11_25,
|
|
64
75
|
McpProtocol.v2025_06_18
|
|
65
76
|
]
|
|
66
|
-
}))
|
|
77
|
+
})));
|
|
67
78
|
|
|
68
79
|
//#endregion
|
|
69
80
|
export { SERVER_INSTRUCTIONS, ServerLayer };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { ConceptNotFound, InvalidArgument, McpToolError
|
|
1
|
+
import { ConceptNotFound, InvalidArgument, McpToolError } from "../errors.js";
|
|
2
2
|
import { loadToolContext } from "../internal/toolContext.js";
|
|
3
3
|
import { toConceptSummary } from "../schema/ConceptSummary.js";
|
|
4
4
|
import { ConceptNeighborsSuccess } from "../schema/tools.js";
|
|
5
|
+
import { ToolFailure } from "@effected/mcp";
|
|
5
6
|
import { Effect, FileSystem, Option, Path, Schema } from "effect";
|
|
6
7
|
import { ConceptId, Graph } from "@okfit/core";
|
|
7
8
|
import { Tool } from "effect/unstable/ai";
|
|
@@ -31,8 +32,8 @@ const conceptNeighbors = Tool.make("concept_neighbors", {
|
|
|
31
32
|
/**
|
|
32
33
|
* Deviation from the brief's literal snippet, matching the ruling binding
|
|
33
34
|
* every C task (progress.md): `message` is composed through
|
|
34
|
-
*
|
|
35
|
-
* failure under `failureMode: "error"` never reaches the wire with
|
|
35
|
+
* `ToolFailure.message` (`@effected/mcp`) at construction, since a declared
|
|
36
|
+
* typed failure under `failureMode: "error"` never reaches the wire with
|
|
36
37
|
* `structuredContent` — only `error.message` does.
|
|
37
38
|
*
|
|
38
39
|
* @public
|
|
@@ -47,7 +48,7 @@ const handleConceptNeighbors = (projectRoot, params) => Effect.gen(function* ()
|
|
|
47
48
|
};
|
|
48
49
|
return yield* Effect.fail(new InvalidArgument({
|
|
49
50
|
argument: "id",
|
|
50
|
-
message:
|
|
51
|
+
message: ToolFailure.message("id must not be empty", remediation),
|
|
51
52
|
remediation
|
|
52
53
|
}));
|
|
53
54
|
}
|
|
@@ -59,7 +60,7 @@ const handleConceptNeighbors = (projectRoot, params) => Effect.gen(function* ()
|
|
|
59
60
|
};
|
|
60
61
|
return yield* Effect.fail(new ConceptNotFound({
|
|
61
62
|
id: params.id,
|
|
62
|
-
message:
|
|
63
|
+
message: ToolFailure.message(`no concept "${ToolFailure.truncate(params.id)}" in this bundle`, remediation),
|
|
63
64
|
remediation
|
|
64
65
|
}));
|
|
65
66
|
}
|
package/tools/getConcept.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ConceptNotFound, InvalidArgument, McpToolError
|
|
1
|
+
import { ConceptNotFound, InvalidArgument, McpToolError } from "../errors.js";
|
|
2
2
|
import { loadToolContext } from "../internal/toolContext.js";
|
|
3
3
|
import { GetConceptSuccess } from "../schema/tools.js";
|
|
4
|
+
import { ToolFailure } from "@effected/mcp";
|
|
4
5
|
import { Effect, FileSystem, Option, Path, Schema } from "effect";
|
|
5
6
|
import { ConceptId, Derive, Graph } from "@okfit/core";
|
|
6
7
|
import { Tool } from "effect/unstable/ai";
|
|
@@ -32,8 +33,8 @@ const getConcept = Tool.make("get_concept", {
|
|
|
32
33
|
/**
|
|
33
34
|
* Deviation from the brief's literal snippet, matching the ruling binding
|
|
34
35
|
* every C task (progress.md): `message` is composed through
|
|
35
|
-
*
|
|
36
|
-
* failure under `failureMode: "error"` never reaches the wire with
|
|
36
|
+
* `ToolFailure.message` (`@effected/mcp`) at construction, since a declared
|
|
37
|
+
* typed failure under `failureMode: "error"` never reaches the wire with
|
|
37
38
|
* `structuredContent` — only `error.message` does.
|
|
38
39
|
*
|
|
39
40
|
* @public
|
|
@@ -48,7 +49,7 @@ const handleGetConcept = (projectRoot, params) => Effect.gen(function* () {
|
|
|
48
49
|
};
|
|
49
50
|
return yield* Effect.fail(new InvalidArgument({
|
|
50
51
|
argument: "id",
|
|
51
|
-
message:
|
|
52
|
+
message: ToolFailure.message("id must not be empty", remediation),
|
|
52
53
|
remediation
|
|
53
54
|
}));
|
|
54
55
|
}
|
|
@@ -61,7 +62,7 @@ const handleGetConcept = (projectRoot, params) => Effect.gen(function* () {
|
|
|
61
62
|
};
|
|
62
63
|
return yield* Effect.fail(new ConceptNotFound({
|
|
63
64
|
id: params.id,
|
|
64
|
-
message:
|
|
65
|
+
message: ToolFailure.message(`no concept "${ToolFailure.truncate(params.id)}" in this bundle`, remediation),
|
|
65
66
|
remediation
|
|
66
67
|
}));
|
|
67
68
|
}
|
package/tools/listConcepts.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { McpToolError, UnknownVocabulary
|
|
1
|
+
import { McpToolError, UnknownVocabulary } from "../errors.js";
|
|
2
2
|
import { loadToolContext } from "../internal/toolContext.js";
|
|
3
3
|
import { toConceptSummary } from "../schema/ConceptSummary.js";
|
|
4
4
|
import { ListConceptsParams, ListConceptsSuccess } from "../schema/tools.js";
|
|
5
|
+
import { ToolFailure } from "@effected/mcp";
|
|
5
6
|
import { Effect, FileSystem, Path } from "effect";
|
|
6
7
|
import { Derive } from "@okfit/core";
|
|
7
8
|
import { Tool } from "effect/unstable/ai";
|
|
@@ -33,24 +34,24 @@ const listConcepts = Tool.make("list_concepts", {
|
|
|
33
34
|
}).annotate(Tool.Title, "List OKF concepts").annotate(Tool.Readonly, true).annotate(Tool.Idempotent, true).annotate(Tool.OpenWorld, false);
|
|
34
35
|
/**
|
|
35
36
|
* Deviation from the brief's literal snippet: `message` is composed through
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
37
|
+
* `ToolFailure.message` (`@effected/mcp`), and the raw message embeds the
|
|
38
|
+
* full `valid` list rather than leaving it only on the schema's `valid`
|
|
39
|
+
* field — a declared typed failure under `failureMode: "error"` never
|
|
40
|
+
* reaches the wire with `structuredContent` (B1's finding), so `valid`
|
|
41
|
+
* would otherwise be unreachable to a real caller reading only
|
|
42
|
+
* `content[0].text`.
|
|
42
43
|
*/
|
|
43
44
|
const unknown = (kind, requested, valid) => {
|
|
44
45
|
const remediation = {
|
|
45
46
|
hint: "Use one of the listed type names, or call describe_vocabulary.",
|
|
46
47
|
suggestedTool: "describe_vocabulary"
|
|
47
48
|
};
|
|
48
|
-
const rawMessage = `"${
|
|
49
|
+
const rawMessage = `"${ToolFailure.truncate(requested)}" is not a ${kind} declared by this project's okfit config. Valid ${kind}s: ${valid.join(", ")}.`;
|
|
49
50
|
return new UnknownVocabulary({
|
|
50
51
|
kind,
|
|
51
52
|
requested,
|
|
52
53
|
valid,
|
|
53
|
-
message:
|
|
54
|
+
message: ToolFailure.message(rawMessage, remediation),
|
|
54
55
|
remediation
|
|
55
56
|
});
|
|
56
57
|
};
|
package/tools/validateBundle.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { BundleNotFound, InvalidArgument, McpToolError
|
|
1
|
+
import { BundleNotFound, InvalidArgument, McpToolError } from "../errors.js";
|
|
2
2
|
import { resolveConfigOnly } from "../internal/toolContext.js";
|
|
3
3
|
import { ValidateBundleParams } from "../schema/tools.js";
|
|
4
4
|
import { resolveNow } from "../internal/resolveNow.js";
|
|
5
5
|
import { MCP_VERSION } from "../version.js";
|
|
6
|
+
import { ToolFailure } from "@effected/mcp";
|
|
6
7
|
import { Crypto, Effect, FileSystem, Option, Path } from "effect";
|
|
7
8
|
import { OKF_SPEC_VERSION } from "@okfit/core";
|
|
8
9
|
import { Tool } from "effect/unstable/ai";
|
|
@@ -80,14 +81,14 @@ const handleValidateBundle = (projectRoot, params, distribution) => Effect.gen(f
|
|
|
80
81
|
const remediation = { hint: "Give each document path relative to the bundle root, in posix form, naming a .md file once (for example metrics/churn.md)." };
|
|
81
82
|
return new InvalidArgument({
|
|
82
83
|
argument: "documents",
|
|
83
|
-
message:
|
|
84
|
+
message: ToolFailure.message(cause.message, remediation),
|
|
84
85
|
remediation
|
|
85
86
|
});
|
|
86
87
|
}
|
|
87
88
|
const remediation = { hint: `The bundle root "${resolved.bundleRoot}" does not exist or could not be read; check the config's [bundle].path, or run \`okfit init\`.` };
|
|
88
89
|
return new BundleNotFound({
|
|
89
90
|
root: resolved.bundleRoot,
|
|
90
|
-
message:
|
|
91
|
+
message: ToolFailure.message(cause.message, remediation),
|
|
91
92
|
remediation
|
|
92
93
|
});
|
|
93
94
|
}));
|
package/tsdoc-metadata.json
CHANGED