@okfit/mcp 0.6.4 → 0.6.6
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/index.d.ts +2 -19
- package/internal/conceptNotFound.js +39 -0
- package/package.json +4 -4
- package/schema/ConceptSummary.js +1 -25
- package/tools/conceptNeighbors.js +12 -44
- package/tools/getConcept.js +7 -40
- package/tools/listConcepts.js +6 -13
- package/version.js +1 -1
package/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Remediation } from "@effected/engine";
|
|
2
2
|
import { FileSystem, Layer, Path, Schema, Stdio } from "effect";
|
|
3
3
|
import { AppDirs, Xdg } from "@effected/xdg";
|
|
4
|
-
import {
|
|
5
|
-
import { Distribution } from "@okfit/engine";
|
|
4
|
+
import { ConceptSummary, Distribution, toConceptSummary } from "@okfit/engine";
|
|
6
5
|
import { ChildProcessSpawner } from "effect/process";
|
|
7
6
|
import { Toolkit } from "effect/ai";
|
|
8
7
|
//#region src/errors.d.ts
|
|
@@ -144,22 +143,6 @@ export declare const ConceptResources: (projectRoot: string) => Layer.Layer<neve
|
|
|
144
143
|
*/
|
|
145
144
|
export declare const IndexResource: (projectRoot: string) => import("effect/Layer").Layer<never, never, import("@effected/xdg").AppDirs | FileSystem.FileSystem | Path.Path | import("@effected/xdg").Xdg>;
|
|
146
145
|
//#endregion
|
|
147
|
-
//#region src/schema/ConceptSummary.d.ts
|
|
148
|
-
/** The seven-field shape every list-like tool result carries (N-16). @public */
|
|
149
|
-
export declare const ConceptSummary: Schema.Struct<{
|
|
150
|
-
readonly id: Schema.String;
|
|
151
|
-
readonly type: Schema.String;
|
|
152
|
-
readonly title: Schema.String;
|
|
153
|
-
readonly description: Schema.NullOr<Schema.String>;
|
|
154
|
-
readonly status: Schema.Literals<readonly ["draft", "stable", "deprecated"]>;
|
|
155
|
-
readonly tags: Schema.$Array<Schema.String>;
|
|
156
|
-
readonly path: Schema.String;
|
|
157
|
-
}>;
|
|
158
|
-
/** @public */
|
|
159
|
-
export type ConceptSummary = typeof ConceptSummary.Type;
|
|
160
|
-
/** Project one loaded concept into its summary. @public */
|
|
161
|
-
export declare const toConceptSummary: (concept: LoadedConcept) => ConceptSummary;
|
|
162
|
-
//#endregion
|
|
163
146
|
//#region src/schema/tools.d.ts
|
|
164
147
|
/** `describe_vocabulary`'s result (§5.2). @public */
|
|
165
148
|
export declare const DescribeVocabularySuccess: Schema.Struct<{
|
|
@@ -789,5 +772,5 @@ export declare const ToolsLayer: (projectRoot: string, distribution?: Distributi
|
|
|
789
772
|
*/
|
|
790
773
|
export declare const MCP_VERSION: string;
|
|
791
774
|
//#endregion
|
|
792
|
-
export { type PlatformServices, Remediation, type ServerOptions };
|
|
775
|
+
export { ConceptSummary, type PlatformServices, Remediation, type ServerOptions, toConceptSummary };
|
|
793
776
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ConceptNotFound, InvalidArgument } from "../errors.js";
|
|
2
|
+
import { ToolFailure } from "@effected/mcp";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/internal/conceptNotFound.ts
|
|
6
|
+
/**
|
|
7
|
+
* Maps the engine's `QueryConceptNotFoundError` to the tool-facing failure
|
|
8
|
+
* `get_concept` and `concept_neighbors` share: `InvalidArgument` for an empty
|
|
9
|
+
* id, `ConceptNotFound` otherwise. `message` is composed through
|
|
10
|
+
* `ToolFailure.message` at construction, since a declared typed failure under
|
|
11
|
+
* `failureMode: "error"` reaches the wire as `error.message` alone.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
const toConceptLookupFailure = (id, error) => {
|
|
16
|
+
if (error.reason === "empty-id") {
|
|
17
|
+
const remediation = {
|
|
18
|
+
hint: "Pass a bundle-relative concept id such as decisions/cli-exit-codes.",
|
|
19
|
+
suggestedTool: "list_concepts"
|
|
20
|
+
};
|
|
21
|
+
return Effect.fail(new InvalidArgument({
|
|
22
|
+
argument: "id",
|
|
23
|
+
message: ToolFailure.message("id must not be empty", remediation),
|
|
24
|
+
remediation
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
const remediation = {
|
|
28
|
+
hint: "Call list_concepts to see the ids this bundle contains.",
|
|
29
|
+
suggestedTool: "list_concepts"
|
|
30
|
+
};
|
|
31
|
+
return Effect.fail(new ConceptNotFound({
|
|
32
|
+
id,
|
|
33
|
+
message: ToolFailure.message(`no concept "${ToolFailure.truncate(id)}" in this bundle`, remediation),
|
|
34
|
+
remediation
|
|
35
|
+
}));
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { toConceptLookupFailure };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okfit/mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
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": [
|
|
@@ -54,11 +54,11 @@
|
|
|
54
54
|
"@effected/mcp": "^0.3.0",
|
|
55
55
|
"@effected/schemastore": "^0.17.0",
|
|
56
56
|
"@effected/toml": "^0.10.0",
|
|
57
|
-
"@effected/walker": "^0.14.
|
|
58
|
-
"@effected/xdg": "^0.8.
|
|
57
|
+
"@effected/walker": "^0.14.1",
|
|
58
|
+
"@effected/xdg": "^0.8.2",
|
|
59
59
|
"@effected/yaml": "^0.18.0",
|
|
60
60
|
"@okfit/core": "0.8.3",
|
|
61
|
-
"@okfit/engine": "0.
|
|
61
|
+
"@okfit/engine": "0.10.0",
|
|
62
62
|
"@okfit/profiles": "0.8.2",
|
|
63
63
|
"effect": "4.0.0-rc.118"
|
|
64
64
|
},
|
package/schema/ConceptSummary.js
CHANGED
|
@@ -1,27 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Derive, Status } from "@okfit/core";
|
|
1
|
+
import { ConceptSummary, toConceptSummary } from "@okfit/engine";
|
|
3
2
|
|
|
4
|
-
//#region src/schema/ConceptSummary.ts
|
|
5
|
-
/** The seven-field shape every list-like tool result carries (N-16). @public */
|
|
6
|
-
const ConceptSummary = Schema.Struct({
|
|
7
|
-
id: Schema.String,
|
|
8
|
-
type: Schema.String,
|
|
9
|
-
title: Schema.String,
|
|
10
|
-
description: Schema.NullOr(Schema.String),
|
|
11
|
-
status: Status,
|
|
12
|
-
tags: Schema.Array(Schema.String),
|
|
13
|
-
path: Schema.String
|
|
14
|
-
});
|
|
15
|
-
/** Project one loaded concept into its summary. @public */
|
|
16
|
-
const toConceptSummary = (concept) => ({
|
|
17
|
-
id: concept.id,
|
|
18
|
-
type: concept.frontmatter.type,
|
|
19
|
-
title: Derive.title(concept),
|
|
20
|
-
description: concept.frontmatter.description ?? null,
|
|
21
|
-
status: Derive.status(concept.frontmatter),
|
|
22
|
-
tags: [...concept.frontmatter.tags ?? []],
|
|
23
|
-
path: concept.path
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
//#endregion
|
|
27
3
|
export { ConceptSummary, toConceptSummary };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { 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 {
|
|
6
|
-
import { Effect, FileSystem,
|
|
7
|
-
import { ConceptId, Graph } from "@okfit/core";
|
|
5
|
+
import { toConceptLookupFailure } from "../internal/conceptNotFound.js";
|
|
6
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
8
7
|
import { Tool } from "effect/ai";
|
|
8
|
+
import { ConceptQuery } from "@okfit/engine";
|
|
9
9
|
import { AppDirs, Xdg } from "@effected/xdg";
|
|
10
10
|
|
|
11
11
|
//#region src/tools/conceptNeighbors.ts
|
|
@@ -40,47 +40,15 @@ const conceptNeighbors = Tool.make("concept_neighbors", {
|
|
|
40
40
|
*/
|
|
41
41
|
const handleConceptNeighbors = (projectRoot, params) => Effect.gen(function* () {
|
|
42
42
|
const ctx = yield* loadToolContext(projectRoot);
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return yield* Effect.fail(new InvalidArgument({
|
|
50
|
-
argument: "id",
|
|
51
|
-
message: ToolFailure.message("id must not be empty", remediation),
|
|
52
|
-
remediation
|
|
53
|
-
}));
|
|
54
|
-
}
|
|
55
|
-
const id = normalized.value;
|
|
56
|
-
if (!ctx.bundle.concepts.has(id)) {
|
|
57
|
-
const remediation = {
|
|
58
|
-
hint: "Call list_concepts to see the ids this bundle contains.",
|
|
59
|
-
suggestedTool: "list_concepts"
|
|
60
|
-
};
|
|
61
|
-
return yield* Effect.fail(new ConceptNotFound({
|
|
62
|
-
id: params.id,
|
|
63
|
-
message: ToolFailure.message(`no concept "${ToolFailure.truncate(params.id)}" in this bundle`, remediation),
|
|
64
|
-
remediation
|
|
65
|
-
}));
|
|
66
|
-
}
|
|
67
|
-
const graph = Graph.fromBundle(ctx.bundle);
|
|
68
|
-
const project = (node) => {
|
|
69
|
-
if (node.kind !== "concept") return {
|
|
70
|
-
id: node.id,
|
|
71
|
-
kind: node.kind,
|
|
72
|
-
summary: null
|
|
73
|
-
};
|
|
74
|
-
const concept = ctx.bundle.concepts.get(node.id);
|
|
75
|
-
return {
|
|
76
|
-
id: node.id,
|
|
77
|
-
kind: node.kind,
|
|
78
|
-
summary: concept === void 0 ? null : toConceptSummary(concept)
|
|
79
|
-
};
|
|
80
|
-
};
|
|
43
|
+
const result = yield* ConceptQuery.neighbors(ctx.bundle, params.id).pipe(Effect.catchTag("QueryConceptNotFoundError", (e) => toConceptLookupFailure(params.id, e)));
|
|
44
|
+
const project = (neighbor) => ({
|
|
45
|
+
id: neighbor.id,
|
|
46
|
+
kind: neighbor.kind,
|
|
47
|
+
summary: neighbor.concept === null ? null : toConceptSummary(neighbor.concept)
|
|
48
|
+
});
|
|
81
49
|
return {
|
|
82
|
-
outgoing:
|
|
83
|
-
incoming:
|
|
50
|
+
outgoing: result.outgoing.map(project),
|
|
51
|
+
incoming: result.incoming.map(project)
|
|
84
52
|
};
|
|
85
53
|
});
|
|
86
54
|
|
package/tools/getConcept.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { McpToolError } from "../errors.js";
|
|
2
2
|
import { loadToolContext } from "../internal/toolContext.js";
|
|
3
3
|
import { GetConceptSuccess } from "../schema/tools.js";
|
|
4
|
-
import {
|
|
5
|
-
import { Effect, FileSystem,
|
|
6
|
-
import {
|
|
4
|
+
import { toConceptLookupFailure } from "../internal/conceptNotFound.js";
|
|
5
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
6
|
+
import { Derive } from "@okfit/core";
|
|
7
7
|
import { Tool } from "effect/ai";
|
|
8
|
+
import { ConceptQuery } from "@okfit/engine";
|
|
8
9
|
import { AppDirs, Xdg } from "@effected/xdg";
|
|
9
10
|
|
|
10
11
|
//#region src/tools/getConcept.ts
|
|
@@ -41,43 +42,9 @@ const getConcept = Tool.make("get_concept", {
|
|
|
41
42
|
*/
|
|
42
43
|
const handleGetConcept = (projectRoot, params) => Effect.gen(function* () {
|
|
43
44
|
const ctx = yield* loadToolContext(projectRoot);
|
|
44
|
-
const
|
|
45
|
-
if (Option.isNone(normalized)) {
|
|
46
|
-
const remediation = {
|
|
47
|
-
hint: "Pass a bundle-relative concept id such as decisions/cli-exit-codes.",
|
|
48
|
-
suggestedTool: "list_concepts"
|
|
49
|
-
};
|
|
50
|
-
return yield* Effect.fail(new InvalidArgument({
|
|
51
|
-
argument: "id",
|
|
52
|
-
message: ToolFailure.message("id must not be empty", remediation),
|
|
53
|
-
remediation
|
|
54
|
-
}));
|
|
55
|
-
}
|
|
56
|
-
const id = normalized.value;
|
|
57
|
-
const concept = ctx.bundle.concepts.get(id);
|
|
58
|
-
if (concept === void 0) {
|
|
59
|
-
const remediation = {
|
|
60
|
-
hint: "Call list_concepts to see the ids this bundle contains.",
|
|
61
|
-
suggestedTool: "list_concepts"
|
|
62
|
-
};
|
|
63
|
-
return yield* Effect.fail(new ConceptNotFound({
|
|
64
|
-
id: params.id,
|
|
65
|
-
message: ToolFailure.message(`no concept "${ToolFailure.truncate(params.id)}" in this bundle`, remediation),
|
|
66
|
-
remediation
|
|
67
|
-
}));
|
|
68
|
-
}
|
|
69
|
-
const graph = Graph.fromBundle(ctx.bundle);
|
|
70
|
-
const links = graph.edges.filter((edge) => edge.from === id).map((edge) => ({
|
|
71
|
-
to: edge.to,
|
|
72
|
-
kind: Option.match(graph.node(edge.to), {
|
|
73
|
-
onNone: () => "missing",
|
|
74
|
-
onSome: (node) => node.kind
|
|
75
|
-
}),
|
|
76
|
-
source: edge.data.source,
|
|
77
|
-
...edge.data.field === void 0 ? {} : { field: edge.data.field }
|
|
78
|
-
}));
|
|
45
|
+
const { concept, links } = yield* ConceptQuery.get(ctx.bundle, params.id).pipe(Effect.catchTag("QueryConceptNotFoundError", (e) => toConceptLookupFailure(params.id, e)));
|
|
79
46
|
return {
|
|
80
|
-
id,
|
|
47
|
+
id: concept.id,
|
|
81
48
|
type: concept.frontmatter.type,
|
|
82
49
|
title: Derive.title(concept),
|
|
83
50
|
description: concept.frontmatter.description ?? null,
|
package/tools/listConcepts.js
CHANGED
|
@@ -4,8 +4,8 @@ import { toConceptSummary } from "../schema/ConceptSummary.js";
|
|
|
4
4
|
import { ListConceptsParams, ListConceptsSuccess } from "../schema/tools.js";
|
|
5
5
|
import { ToolFailure } from "@effected/mcp";
|
|
6
6
|
import { Effect, FileSystem, Path } from "effect";
|
|
7
|
-
import { Derive } from "@okfit/core";
|
|
8
7
|
import { Tool } from "effect/ai";
|
|
8
|
+
import { ConceptQuery } from "@okfit/engine";
|
|
9
9
|
import { AppDirs, Xdg } from "@effected/xdg";
|
|
10
10
|
|
|
11
11
|
//#region src/tools/listConcepts.ts
|
|
@@ -58,18 +58,11 @@ const unknown = (kind, requested, valid) => {
|
|
|
58
58
|
/** @public */
|
|
59
59
|
const handleListConcepts = (projectRoot, params) => Effect.gen(function* () {
|
|
60
60
|
const ctx = yield* loadToolContext(projectRoot);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const filtered = [...ctx.bundle.concepts.values()].filter((concept) => {
|
|
67
|
-
if (params.type !== void 0 && concept.frontmatter.type !== params.type) return false;
|
|
68
|
-
const tags = concept.frontmatter.tags ?? [];
|
|
69
|
-
if (!requestedTags.every((tag) => tags.includes(tag))) return false;
|
|
70
|
-
if (params.status !== void 0 && Derive.status(concept.frontmatter) !== params.status) return false;
|
|
71
|
-
return true;
|
|
72
|
-
}).toSorted((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
61
|
+
const filtered = yield* ConceptQuery.list(ctx.bundle, ctx.config, {
|
|
62
|
+
...params.type === void 0 ? {} : { types: [params.type] },
|
|
63
|
+
...params.tags === void 0 ? {} : { tags: params.tags },
|
|
64
|
+
...params.status === void 0 ? {} : { statuses: [params.status] }
|
|
65
|
+
}).pipe(Effect.catchTag("QueryUnknownVocabularyError", (e) => Effect.fail(unknown(e.kind, e.requested, e.valid))));
|
|
73
66
|
const limit = params.limit ?? 200;
|
|
74
67
|
const offset = params.offset ?? 0;
|
|
75
68
|
return {
|