@nifrajs/cli 1.2.1 → 1.3.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/dist/check.d.ts +10 -2
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +48 -2
- package/dist/check.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/mcp-http.js +1 -1
- package/dist/mcp.d.ts +14 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +250 -11
- package/dist/mcp.js.map +1 -1
- package/package.json +7 -7
- package/src/check.ts +59 -3
- package/src/cli.ts +1 -1
- package/src/mcp-http.ts +1 -1
- package/src/mcp.ts +306 -16
package/src/mcp.ts
CHANGED
|
@@ -40,10 +40,12 @@ import {
|
|
|
40
40
|
type JsonRpcRequest,
|
|
41
41
|
type JsonRpcResponse,
|
|
42
42
|
type McpPrompt,
|
|
43
|
+
type McpPromptMessage,
|
|
43
44
|
type McpResource,
|
|
44
45
|
type McpServerFeatures,
|
|
45
46
|
type McpTool,
|
|
46
47
|
type McpToolContext,
|
|
48
|
+
type McpToolResult,
|
|
47
49
|
rpcError,
|
|
48
50
|
} from "./mcp-protocol.ts"
|
|
49
51
|
import { loadTypesCorpus } from "./types-search.ts"
|
|
@@ -566,6 +568,27 @@ export function projectFeatures(
|
|
|
566
568
|
return { resources: projectResources(cwd, loadAppCached), prompts: projectPrompts() }
|
|
567
569
|
}
|
|
568
570
|
|
|
571
|
+
/**
|
|
572
|
+
* Resolve an optional `dir` tool argument — a subdirectory of the project root the caller wants to scope a
|
|
573
|
+
* check/test to (e.g. `nifra check` on `app/` when the MCP server's root is a monorepo). Returns the
|
|
574
|
+
* absolute target, or `null` if `dir` escapes the root (a path-traversal guard: no `..` out, no absolute
|
|
575
|
+
* path elsewhere). `undefined`/empty → the root itself.
|
|
576
|
+
*/
|
|
577
|
+
export function resolveProjectDir(root: string, dir: string | undefined): string | null {
|
|
578
|
+
if (dir === undefined || dir === "") return root
|
|
579
|
+
const target = resolve(root, dir)
|
|
580
|
+
return target === root || target.startsWith(root + sep) ? target : null
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/** Consistent error string for a `dir` that escapes the project root. */
|
|
584
|
+
function dirError(dir: string | undefined): string {
|
|
585
|
+
return JSON.stringify(
|
|
586
|
+
{ ok: false, error: `dir must be a subdirectory of the project root — "${dir}" escapes it.` },
|
|
587
|
+
null,
|
|
588
|
+
2,
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
|
|
569
592
|
/** Build the project-scoped tools for `cwd`. */
|
|
570
593
|
export function projectTools(
|
|
571
594
|
cwd: string,
|
|
@@ -755,13 +778,21 @@ export function projectTools(
|
|
|
755
778
|
type: "number",
|
|
756
779
|
description: "Timeout in milliseconds (default 30000, max 300000).",
|
|
757
780
|
},
|
|
781
|
+
dir: {
|
|
782
|
+
type: "string",
|
|
783
|
+
description:
|
|
784
|
+
'Run the tests in this subdirectory (relative to the project root), e.g. "app". Use it when the MCP server\'s root is a monorepo but you want to test just one app. Default: the project root.',
|
|
785
|
+
},
|
|
758
786
|
},
|
|
759
787
|
additionalProperties: false,
|
|
760
788
|
},
|
|
761
789
|
handler: async (args, context) => {
|
|
762
790
|
const { collectTestResult } = await import("./test-tool.ts")
|
|
791
|
+
const opts = args as { dir?: string }
|
|
792
|
+
const target = resolveProjectDir(cwd, opts.dir)
|
|
793
|
+
if (target === null) return dirError(opts.dir)
|
|
763
794
|
return JSON.stringify(
|
|
764
|
-
await collectTestResult(
|
|
795
|
+
await collectTestResult(target, args, { signal: context.signal }),
|
|
765
796
|
null,
|
|
766
797
|
2,
|
|
767
798
|
)
|
|
@@ -817,22 +848,108 @@ export function projectTools(
|
|
|
817
848
|
type: "boolean",
|
|
818
849
|
description: "Skip tsc; run only the near-instant source lints (inner-loop mode).",
|
|
819
850
|
},
|
|
851
|
+
dir: {
|
|
852
|
+
type: "string",
|
|
853
|
+
description:
|
|
854
|
+
'Run the check in this subdirectory (relative to the project root), e.g. "app" or "packages/api". Use it when the MCP server\'s root is a monorepo but you want to check just one app. Default: the project root.',
|
|
855
|
+
},
|
|
820
856
|
},
|
|
821
857
|
additionalProperties: false,
|
|
822
858
|
},
|
|
823
859
|
handler: async (args, context) => {
|
|
824
860
|
const { collectCheckResult } = await import("./check.ts")
|
|
825
|
-
const opts = args as { lintsOnly?: boolean }
|
|
861
|
+
const opts = args as { lintsOnly?: boolean; dir?: string }
|
|
862
|
+
const target = resolveProjectDir(cwd, opts.dir)
|
|
863
|
+
if (target === null) return dirError(opts.dir)
|
|
826
864
|
return JSON.stringify(
|
|
827
|
-
await collectCheckResult(
|
|
865
|
+
await collectCheckResult(target, {
|
|
828
866
|
lintsOnly: opts.lintsOnly ?? false,
|
|
829
867
|
signal: context.signal,
|
|
868
|
+
// Bound the result so a large project can't emit an MCP message big enough to break the stdio
|
|
869
|
+
// transport (`-32000: Connection closed`). If `truncated` comes back, fix the shown diagnostics
|
|
870
|
+
// and re-run. The scan already skips gitignored trees (walkSource), so this is the safety net.
|
|
871
|
+
maxDiagnostics: 100,
|
|
830
872
|
}),
|
|
831
873
|
null,
|
|
832
874
|
2,
|
|
833
875
|
)
|
|
834
876
|
},
|
|
835
877
|
},
|
|
878
|
+
{
|
|
879
|
+
name: "nifra_fix",
|
|
880
|
+
description:
|
|
881
|
+
"Automatically fix diagnostic lints (such as rewriting hand-rolled fetch() calls to the typed nifra client, adding generic types to client factory calls, and resolving dependency drift in package.json). Runs diagnostics, applies all mechanical edit suggestions, applies doctor dependency fixes, and returns the remaining unresolved diagnostics.",
|
|
882
|
+
inputSchema: {
|
|
883
|
+
type: "object",
|
|
884
|
+
properties: {},
|
|
885
|
+
additionalProperties: false,
|
|
886
|
+
},
|
|
887
|
+
handler: async (_args, context) => {
|
|
888
|
+
const { collectCheckResult } = await import("./check.ts")
|
|
889
|
+
const { applyDoctorAutoFix } = await import("./doctor.ts")
|
|
890
|
+
const { writeFile, readFile } = await import("node:fs/promises")
|
|
891
|
+
const { resolve } = await import("node:path")
|
|
892
|
+
|
|
893
|
+
let doctorResult: Awaited<ReturnType<typeof applyDoctorAutoFix>> | null = null
|
|
894
|
+
try {
|
|
895
|
+
doctorResult = await applyDoctorAutoFix(cwd)
|
|
896
|
+
} catch {
|
|
897
|
+
// ignore doctor errors
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
const checkResult = await collectCheckResult(cwd, {
|
|
901
|
+
lintsOnly: true,
|
|
902
|
+
signal: context.signal,
|
|
903
|
+
maxDiagnostics: 100,
|
|
904
|
+
})
|
|
905
|
+
|
|
906
|
+
const fixed: Array<{ file: string; line: number; title: string }> = []
|
|
907
|
+
|
|
908
|
+
for (const diag of checkResult.diagnostics) {
|
|
909
|
+
if (diag.file && diag.line && diag.suggestion?.kind === "edit" && diag.suggestion.diff) {
|
|
910
|
+
try {
|
|
911
|
+
const diffLines = diag.suggestion.diff.split("\n")
|
|
912
|
+
const beforeLine = diffLines.find((l) => l.startsWith("-"))?.slice(1)
|
|
913
|
+
const afterLine = diffLines.find((l) => l.startsWith("+"))?.slice(1)
|
|
914
|
+
if (beforeLine !== undefined && afterLine !== undefined) {
|
|
915
|
+
const filePath = resolve(cwd, diag.file)
|
|
916
|
+
const content = await readFile(filePath, "utf-8")
|
|
917
|
+
const lines = content.split("\n")
|
|
918
|
+
const idx = diag.line - 1
|
|
919
|
+
if (lines[idx] === beforeLine) {
|
|
920
|
+
lines[idx] = afterLine
|
|
921
|
+
await writeFile(filePath, lines.join("\n"), "utf-8")
|
|
922
|
+
fixed.push({
|
|
923
|
+
file: diag.file,
|
|
924
|
+
line: diag.line,
|
|
925
|
+
title: diag.suggestion.title,
|
|
926
|
+
})
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
} catch {
|
|
930
|
+
// ignore edit errors
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const finalResult = await collectCheckResult(cwd, {
|
|
936
|
+
lintsOnly: false,
|
|
937
|
+
signal: context.signal,
|
|
938
|
+
maxDiagnostics: 100,
|
|
939
|
+
})
|
|
940
|
+
|
|
941
|
+
return JSON.stringify(
|
|
942
|
+
{
|
|
943
|
+
ok: finalResult.ok,
|
|
944
|
+
fixed,
|
|
945
|
+
doctorFixed: doctorResult?.fixed ?? [],
|
|
946
|
+
remainingDiagnostics: finalResult.diagnostics,
|
|
947
|
+
},
|
|
948
|
+
null,
|
|
949
|
+
2,
|
|
950
|
+
)
|
|
951
|
+
},
|
|
952
|
+
},
|
|
836
953
|
{
|
|
837
954
|
name: "nifra_doctor",
|
|
838
955
|
description:
|
|
@@ -861,6 +978,151 @@ export function projectTools(
|
|
|
861
978
|
]
|
|
862
979
|
}
|
|
863
980
|
|
|
981
|
+
/** A `.tool()`-registered route as seen through the backend's `routes()` introspection. */
|
|
982
|
+
type ToolRoute = {
|
|
983
|
+
readonly schema?: { readonly body?: unknown; readonly response?: unknown }
|
|
984
|
+
readonly tool?: {
|
|
985
|
+
readonly name: string
|
|
986
|
+
readonly description: string
|
|
987
|
+
readonly annotations?: McpTool["annotations"]
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
type ToolBackend = {
|
|
991
|
+
readonly routes?: () => ToolRoute[]
|
|
992
|
+
readonly fetch: (req: Request) => Promise<Response>
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/** Extract tools registered via .tool() routes on the Nifra backend. Exported for the test that proves a
|
|
996
|
+
* `server().tool()` route surfaces in `tools/list` and executes through `tools/call`. */
|
|
997
|
+
export function extractBackendTools(backend: unknown): McpTool[] {
|
|
998
|
+
const b = backend as ToolBackend | null
|
|
999
|
+
if (!b || typeof b.routes !== "function") return []
|
|
1000
|
+
let routes: ToolRoute[] = []
|
|
1001
|
+
try {
|
|
1002
|
+
routes = b.routes()
|
|
1003
|
+
} catch {
|
|
1004
|
+
return []
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const jsonSchemaOf = (schema: unknown): unknown => {
|
|
1008
|
+
if (schema && typeof schema === "object" && "jsonSchema" in schema) {
|
|
1009
|
+
const js = (schema as { jsonSchema?: unknown }).jsonSchema
|
|
1010
|
+
if (js !== undefined && js !== null) return js
|
|
1011
|
+
}
|
|
1012
|
+
return undefined
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return routes
|
|
1016
|
+
.filter(
|
|
1017
|
+
(
|
|
1018
|
+
r,
|
|
1019
|
+
): r is ToolRoute & {
|
|
1020
|
+
schema: NonNullable<ToolRoute["schema"]>
|
|
1021
|
+
tool: NonNullable<ToolRoute["tool"]>
|
|
1022
|
+
} => r.schema !== undefined && r.tool !== undefined,
|
|
1023
|
+
)
|
|
1024
|
+
.map((r) => {
|
|
1025
|
+
const toolInfo = r.tool
|
|
1026
|
+
const s = r.schema
|
|
1027
|
+
return {
|
|
1028
|
+
name: toolInfo.name,
|
|
1029
|
+
description: toolInfo.description,
|
|
1030
|
+
inputSchema: (jsonSchemaOf(s.body) ?? {
|
|
1031
|
+
type: "object",
|
|
1032
|
+
properties: {},
|
|
1033
|
+
}) as McpTool["inputSchema"],
|
|
1034
|
+
...(toolInfo.annotations !== undefined ? { annotations: toolInfo.annotations } : {}),
|
|
1035
|
+
handler: async (args): Promise<string | McpToolResult> => {
|
|
1036
|
+
const res = await b.fetch(
|
|
1037
|
+
new Request(`http://localhost/_nifra/tool/${toolInfo.name}`, {
|
|
1038
|
+
method: "POST",
|
|
1039
|
+
headers: { "content-type": "application/json" },
|
|
1040
|
+
body: JSON.stringify(args),
|
|
1041
|
+
}),
|
|
1042
|
+
)
|
|
1043
|
+
if (!res.ok) {
|
|
1044
|
+
const text = await res.text()
|
|
1045
|
+
throw new Error(`Tool execution failed (${res.status}): ${text}`)
|
|
1046
|
+
}
|
|
1047
|
+
const body: unknown = await res.json()
|
|
1048
|
+
if (typeof body === "string") return body
|
|
1049
|
+
if (body && typeof body === "object") {
|
|
1050
|
+
if ("content" in body || "structuredContent" in body) {
|
|
1051
|
+
return body as McpToolResult
|
|
1052
|
+
}
|
|
1053
|
+
return {
|
|
1054
|
+
content: [{ type: "text", text: JSON.stringify(body, null, 2) }],
|
|
1055
|
+
structuredContent: body as Record<string, unknown>,
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return String(body)
|
|
1059
|
+
},
|
|
1060
|
+
}
|
|
1061
|
+
})
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/** A resource declared on the backend via `server().resource()`, seen through `mcpResources()`. */
|
|
1065
|
+
type ResourceDescriptor = {
|
|
1066
|
+
readonly uri: string
|
|
1067
|
+
readonly name: string
|
|
1068
|
+
readonly description?: string
|
|
1069
|
+
readonly mimeType?: string
|
|
1070
|
+
readonly read: () => unknown | Promise<unknown>
|
|
1071
|
+
}
|
|
1072
|
+
type ResourceBackend = { readonly mcpResources?: () => readonly ResourceDescriptor[] }
|
|
1073
|
+
|
|
1074
|
+
/** Extract MCP resources registered via `.resource()` on the Nifra backend. Exported for the surfacing test. */
|
|
1075
|
+
export function extractBackendResources(backend: unknown): McpResource[] {
|
|
1076
|
+
const b = backend as ResourceBackend | null
|
|
1077
|
+
if (!b || typeof b.mcpResources !== "function") return []
|
|
1078
|
+
let list: readonly ResourceDescriptor[]
|
|
1079
|
+
try {
|
|
1080
|
+
list = b.mcpResources()
|
|
1081
|
+
} catch {
|
|
1082
|
+
return []
|
|
1083
|
+
}
|
|
1084
|
+
return list.map((r) => ({
|
|
1085
|
+
uri: r.uri,
|
|
1086
|
+
name: r.name,
|
|
1087
|
+
...(r.description !== undefined ? { description: r.description } : {}),
|
|
1088
|
+
...(r.mimeType !== undefined ? { mimeType: r.mimeType } : {}),
|
|
1089
|
+
read: async () => {
|
|
1090
|
+
const out = await r.read()
|
|
1091
|
+
if (typeof out === "string") return { text: out }
|
|
1092
|
+
const o = out as { text: string; mimeType?: string }
|
|
1093
|
+
return o.mimeType !== undefined ? { text: o.text, mimeType: o.mimeType } : { text: o.text }
|
|
1094
|
+
},
|
|
1095
|
+
}))
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/** A prompt declared on the backend via `server().prompt()`, seen through `mcpPrompts()`. */
|
|
1099
|
+
type PromptDescriptor = {
|
|
1100
|
+
readonly name: string
|
|
1101
|
+
readonly description: string
|
|
1102
|
+
readonly arguments?: readonly { name: string; description?: string; required?: boolean }[]
|
|
1103
|
+
readonly handler: (args: Record<string, string>) => unknown | Promise<unknown>
|
|
1104
|
+
}
|
|
1105
|
+
type PromptBackend = { readonly mcpPrompts?: () => readonly PromptDescriptor[] }
|
|
1106
|
+
|
|
1107
|
+
/** Extract MCP prompts registered via `.prompt()` on the Nifra backend. Exported for the surfacing test. */
|
|
1108
|
+
export function extractBackendPrompts(backend: unknown): McpPrompt[] {
|
|
1109
|
+
const b = backend as PromptBackend | null
|
|
1110
|
+
if (!b || typeof b.mcpPrompts !== "function") return []
|
|
1111
|
+
let list: readonly PromptDescriptor[]
|
|
1112
|
+
try {
|
|
1113
|
+
list = b.mcpPrompts()
|
|
1114
|
+
} catch {
|
|
1115
|
+
return []
|
|
1116
|
+
}
|
|
1117
|
+
return list.map((p) => ({
|
|
1118
|
+
name: p.name,
|
|
1119
|
+
description: p.description,
|
|
1120
|
+
...(p.arguments !== undefined ? { arguments: p.arguments } : {}),
|
|
1121
|
+
handler: async (args: Record<string, unknown>) =>
|
|
1122
|
+
(await p.handler(args as Record<string, string>)) as readonly McpPromptMessage[],
|
|
1123
|
+
}))
|
|
1124
|
+
}
|
|
1125
|
+
|
|
864
1126
|
/** Prefix every tool name and resource URI for a named app in a monorepo. */
|
|
865
1127
|
function namespaceForApp(
|
|
866
1128
|
name: string,
|
|
@@ -868,7 +1130,10 @@ function namespaceForApp(
|
|
|
868
1130
|
features: McpServerFeatures,
|
|
869
1131
|
): { tools: McpTool[]; features: McpServerFeatures } {
|
|
870
1132
|
const prefix = `nifra_${name}_`
|
|
871
|
-
const namespacedTools = tools.map((t) => ({
|
|
1133
|
+
const namespacedTools = tools.map((t) => ({
|
|
1134
|
+
...t,
|
|
1135
|
+
name: t.name.startsWith("nifra_") ? t.name.replace(/^nifra_/, prefix) : `${name}_${t.name}`,
|
|
1136
|
+
}))
|
|
872
1137
|
const namespacedResources = (features.resources ?? []).map((r) => ({
|
|
873
1138
|
...r,
|
|
874
1139
|
uri: r.uri.replace(/^nifra:\/\//, `nifra://${name}/`),
|
|
@@ -885,40 +1150,65 @@ function namespaceForApp(
|
|
|
885
1150
|
|
|
886
1151
|
/** Run the stdio MCP server: read newline-delimited JSON-RPC from stdin, write responses to stdout. */
|
|
887
1152
|
export async function runMcpServer(cwd: string, version: string): Promise<void> {
|
|
888
|
-
let tools: McpTool[]
|
|
889
1153
|
let features: McpServerFeatures
|
|
890
1154
|
|
|
891
1155
|
const monorepo = await detectMonorepo(cwd)
|
|
892
1156
|
if (monorepo) {
|
|
893
1157
|
const appEntries = await loadMonorepoApps(cwd, monorepo)
|
|
894
|
-
const allTools: McpTool[] = []
|
|
895
1158
|
const allResources: McpResource[] = []
|
|
896
1159
|
const allPrompts: McpPrompt[] = []
|
|
897
1160
|
for (const { name, cwd: appCwd } of appEntries) {
|
|
898
1161
|
const loader = createCachedAppLoader(appCwd)
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1162
|
+
const app = await loader()
|
|
1163
|
+
const base = projectFeatures(appCwd, loader)
|
|
1164
|
+
const ns = namespaceForApp(name, [], {
|
|
1165
|
+
resources: [...(base.resources ?? []), ...extractBackendResources(app.backend)],
|
|
1166
|
+
prompts: [...(base.prompts ?? []), ...extractBackendPrompts(app.backend)],
|
|
1167
|
+
})
|
|
905
1168
|
allResources.push(...(ns.features.resources ?? []))
|
|
906
1169
|
allPrompts.push(...(ns.features.prompts ?? []))
|
|
907
1170
|
}
|
|
908
|
-
tools = [...docsTools(loadDocsCorpus, loadExamplesCorpus, loadTypesCorpus), ...allTools]
|
|
909
1171
|
features = { resources: allResources, prompts: allPrompts }
|
|
910
1172
|
} else {
|
|
911
1173
|
const loadAppCached = createCachedAppLoader(cwd)
|
|
912
|
-
|
|
913
|
-
|
|
1174
|
+
const base = projectFeatures(cwd, loadAppCached)
|
|
1175
|
+
const app = await loadAppCached()
|
|
1176
|
+
features = {
|
|
1177
|
+
resources: [...(base.resources ?? []), ...extractBackendResources(app.backend)],
|
|
1178
|
+
prompts: [...(base.prompts ?? []), ...extractBackendPrompts(app.backend)],
|
|
1179
|
+
}
|
|
914
1180
|
}
|
|
1181
|
+
const loadAppCached = createCachedAppLoader(cwd)
|
|
915
1182
|
const serverInfo = { name: "nifra", version }
|
|
916
1183
|
const state = createMcpProtocolState()
|
|
917
1184
|
const send = (message: JsonRpcResponse | JsonRpcNotification): void => {
|
|
918
1185
|
process.stdout.write(`${JSON.stringify(message)}\n`)
|
|
919
1186
|
}
|
|
920
1187
|
const dispatch = async (message: JsonRpcRequest): Promise<void> => {
|
|
921
|
-
|
|
1188
|
+
let activeTools: McpTool[]
|
|
1189
|
+
if (monorepo) {
|
|
1190
|
+
const appEntries = await loadMonorepoApps(cwd, monorepo)
|
|
1191
|
+
const allTools: McpTool[] = []
|
|
1192
|
+
for (const { name, cwd: appCwd } of appEntries) {
|
|
1193
|
+
const loader = createCachedAppLoader(appCwd)
|
|
1194
|
+
const app = await loader()
|
|
1195
|
+
const baseTools = projectTools(appCwd, loader)
|
|
1196
|
+
const backendTools = extractBackendTools(app.backend)
|
|
1197
|
+
const ns = namespaceForApp(name, [...baseTools, ...backendTools], {
|
|
1198
|
+
resources: [],
|
|
1199
|
+
prompts: [],
|
|
1200
|
+
})
|
|
1201
|
+
allTools.push(...ns.tools)
|
|
1202
|
+
}
|
|
1203
|
+
activeTools = [...docsTools(loadDocsCorpus, loadExamplesCorpus, loadTypesCorpus), ...allTools]
|
|
1204
|
+
} else {
|
|
1205
|
+
const app = await loadAppCached()
|
|
1206
|
+
const baseTools = projectTools(cwd, loadAppCached)
|
|
1207
|
+
const backendTools = extractBackendTools(app.backend)
|
|
1208
|
+
activeTools = [...baseTools, ...backendTools]
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const response = await handleRpc(message, activeTools, serverInfo, features, {
|
|
922
1212
|
state,
|
|
923
1213
|
sendNotification: send,
|
|
924
1214
|
})
|