@fenglimg/fabric-server 2.3.0-rc.16 → 2.3.0-rc.17

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/index.d.ts CHANGED
@@ -1173,7 +1173,33 @@ declare const AGENTS_MD_RESOURCE_URI = "fabric://bootstrap-readme";
1173
1173
  declare function formatPreexistingRootMessage(projectRoot: string): string | null;
1174
1174
 
1175
1175
  declare const FABRIC_SERVER_INSTRUCTIONS: string;
1176
+ declare function buildServerInstructions(projectRoot: string): string;
1176
1177
  declare function createFabricServer(tracker?: InFlightTracker): McpServer;
1178
+ /**
1179
+ * The slice of the MCP `Server` the roots adoption consumes — injectable so
1180
+ * tests can drive it without a live transport.
1181
+ */
1182
+ interface McpRootsSource {
1183
+ getClientCapabilities(): {
1184
+ roots?: unknown;
1185
+ } | undefined;
1186
+ listRoots(): Promise<{
1187
+ roots: Array<{
1188
+ uri?: string;
1189
+ }>;
1190
+ }>;
1191
+ }
1192
+ /**
1193
+ * ISS werewolf-minigame (rootless MCP spawn, KT-PIT-0046): fetch the client's
1194
+ * workspace roots (when the client declares the `roots` capability) and record
1195
+ * them as project-root candidates via `setMcpRootsHint`. Returns the accepted
1196
+ * absolute paths ([] when the capability is absent or the fetch fails —
1197
+ * best-effort, never sinks startup). Called from `oninitialized`; the
1198
+ * env > CLAUDE_PROJECT_DIR > roots > cwd priority lives in
1199
+ * `resolveProjectRoot`, which every tool call re-runs, so adoption heals
1200
+ * subsequent calls without a restart.
1201
+ */
1202
+ declare function adoptMcpClientRoots(source: McpRootsSource): Promise<string[]>;
1177
1203
  declare function startStdioServer(): Promise<void>;
1178
1204
  /**
1179
1205
  * Dependencies for the shutdown handler factory. Tests inject `exit` to assert
@@ -1199,4 +1225,4 @@ interface ShutdownHandlerDeps {
1199
1225
  */
1200
1226
  declare function createShutdownHandler(deps: ShutdownHandlerDeps): () => void;
1201
1227
 
1202
- export { AGENTS_MD_RESOURCE_URI, type AlwaysActiveBody, type ArchiveHistoryEntry, type ArchiveHistoryReport, type BacklogAgeMetric, COLD_EVAL_RUBRIC, type CiteCoverageReport, type ColdEvalBatch, type ColdEvalCandidate, type ColdEvalVerdict, type ConflictEntry, type ConflictJudge, type ConflictLintReport, type ConflictPair, type ConflictVerdict, type ConsumptionEntry, type ConsumptionInspection, type ConsumptionLintConfig, DEFAULT_CONFLICT_SIMILARITY_THRESHOLD, type DoctorApplyLintMutation, type DoctorApplyLintMutationKind, type DoctorApplyLintReport, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type EnrichDescriptionsCandidate, type EnrichDescriptionsMode, type EnrichDescriptionsReport, FABRIC_SERVER_INSTRUCTIONS, type HistoryAllReport, type HistoryDayRow, type InFlightTracker, type KnowledgeCensus, LEDGER_PATH, LEGACY_LEDGER_PATH, METRICS_LEDGER_PATH, METRIC_COUNTER_NAMES, type MetricCounterName, type MetricsRow, OPTIONAL_EMBED_PACKAGE, type PlanContextInput, type PlanContextResult, type PrecheckResult, RETIRED_TOKENS, type RecallInput, type RecallResult, type RelatedBrokenLink, type RelatedGraphInspection, type RelatedGraphNode, type RelatedHubEntry, type RequirementProfile, type RetiredReferenceHit, type RetiredReferenceInspection, type RetiredToken, type SelectionTokenState, type ShutdownHandlerDeps, type StoreCanonicalEntry, type StoreReachability, type SurfaceVerdict, type UnboundProjectViolation, type WhyNotSurfacedResult, aggregateConsumption, appendEventLedgerEvent, buildAlwaysActiveBodies, buildColdEvalBatch, buildKnowledgeCensus, buildRelatedGraph, bumpCounter, checkBacklogAge, clearPrecheckCache, collectStoreCanonicalEntries, computeReadSetRevision, contextCache, createFabricServer, createInFlightTracker, createShutdownHandler, defaultEmbedCacheDir, detectUnboundProject, drainCounters, enrichDescriptions, evaluateStoreDir, explainWhyNotSurfaced, extractKnowledge, findConflictCandidates, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, inspectConsumption, inspectRelatedGraph, inspectRetiredReferences, isEmbedderResolvable, lintConflicts, loadConflictEntries, loadEmbedder, pairSimilarity, planContext, precheckStoreReachability, readEmbedConfig, readEventLedger, readFusion, readLedger, readMetrics, readSelectionToken, recall, rehydrateAgentsMetaAt, renderBacklogAgeLine, resolveLedgerPaths, reviewKnowledge, reviewPending, runDoctorApplyLint, runDoctorArchiveHistory, runDoctorCiteCoverage, runDoctorConflictLint, runDoctorFix, runDoctorHistoryAll, runDoctorReport, startMetricsFlush, startRotationTick, startStdioServer, stopMetricsFlush, stopRotationTick };
1228
+ export { AGENTS_MD_RESOURCE_URI, type AlwaysActiveBody, type ArchiveHistoryEntry, type ArchiveHistoryReport, type BacklogAgeMetric, COLD_EVAL_RUBRIC, type CiteCoverageReport, type ColdEvalBatch, type ColdEvalCandidate, type ColdEvalVerdict, type ConflictEntry, type ConflictJudge, type ConflictLintReport, type ConflictPair, type ConflictVerdict, type ConsumptionEntry, type ConsumptionInspection, type ConsumptionLintConfig, DEFAULT_CONFLICT_SIMILARITY_THRESHOLD, type DoctorApplyLintMutation, type DoctorApplyLintMutationKind, type DoctorApplyLintReport, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type EnrichDescriptionsCandidate, type EnrichDescriptionsMode, type EnrichDescriptionsReport, FABRIC_SERVER_INSTRUCTIONS, type HistoryAllReport, type HistoryDayRow, type InFlightTracker, type KnowledgeCensus, LEDGER_PATH, LEGACY_LEDGER_PATH, METRICS_LEDGER_PATH, METRIC_COUNTER_NAMES, type McpRootsSource, type MetricCounterName, type MetricsRow, OPTIONAL_EMBED_PACKAGE, type PlanContextInput, type PlanContextResult, type PrecheckResult, RETIRED_TOKENS, type RecallInput, type RecallResult, type RelatedBrokenLink, type RelatedGraphInspection, type RelatedGraphNode, type RelatedHubEntry, type RequirementProfile, type RetiredReferenceHit, type RetiredReferenceInspection, type RetiredToken, type SelectionTokenState, type ShutdownHandlerDeps, type StoreCanonicalEntry, type StoreReachability, type SurfaceVerdict, type UnboundProjectViolation, type WhyNotSurfacedResult, adoptMcpClientRoots, aggregateConsumption, appendEventLedgerEvent, buildAlwaysActiveBodies, buildColdEvalBatch, buildKnowledgeCensus, buildRelatedGraph, buildServerInstructions, bumpCounter, checkBacklogAge, clearPrecheckCache, collectStoreCanonicalEntries, computeReadSetRevision, contextCache, createFabricServer, createInFlightTracker, createShutdownHandler, defaultEmbedCacheDir, detectUnboundProject, drainCounters, enrichDescriptions, evaluateStoreDir, explainWhyNotSurfaced, extractKnowledge, findConflictCandidates, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, inspectConsumption, inspectRelatedGraph, inspectRetiredReferences, isEmbedderResolvable, lintConflicts, loadConflictEntries, loadEmbedder, pairSimilarity, planContext, precheckStoreReachability, readEmbedConfig, readEventLedger, readFusion, readLedger, readMetrics, readSelectionToken, recall, rehydrateAgentsMetaAt, renderBacklogAgeLine, resolveLedgerPaths, reviewKnowledge, reviewPending, runDoctorApplyLint, runDoctorArchiveHistory, runDoctorCiteCoverage, runDoctorConflictLint, runDoctorFix, runDoctorHistoryAll, runDoctorReport, startMetricsFlush, startRotationTick, startStdioServer, stopMetricsFlush, stopRotationTick };
package/dist/index.js CHANGED
@@ -112,6 +112,31 @@ function normalizeTrustedRootOverride(raw) {
112
112
  return null;
113
113
  }
114
114
  }
115
+ function climbToAnchor(start) {
116
+ let dir = start;
117
+ let firstFabric = null;
118
+ for (let i = 0; i < 64; i++) {
119
+ if (existsSync(join(dir, ".git"))) return dir;
120
+ if (firstFabric === null && existsSync(join(dir, ".fabric"))) firstFabric = dir;
121
+ const parent = dirname(dir);
122
+ if (parent === dir) break;
123
+ dir = parent;
124
+ }
125
+ return firstFabric;
126
+ }
127
+ var mcpRootsHint = [];
128
+ function setMcpRootsHint(paths) {
129
+ const accepted = [];
130
+ for (const path of paths) {
131
+ const normalized = normalizeTrustedRootOverride(path);
132
+ if (normalized !== null && existsSync(normalized)) accepted.push(normalized);
133
+ }
134
+ mcpRootsHint = accepted;
135
+ return accepted.slice();
136
+ }
137
+ function isProjectRootConfigured(projectRoot) {
138
+ return existsSync(join(projectRoot, ".fabric", "fabric-config.json"));
139
+ }
115
140
  function resolveProjectRoot(startCwd) {
116
141
  const envOverride = process.env.FABRIC_PROJECT_ROOT;
117
142
  if (typeof envOverride === "string" && envOverride.length > 0) {
@@ -123,17 +148,30 @@ function resolveProjectRoot(startCwd) {
123
148
  const normalized = normalizeTrustedRootOverride(claudeRoot);
124
149
  if (normalized !== null) return normalized;
125
150
  }
126
- const start = typeof startCwd === "string" && startCwd.length > 0 ? startCwd : process.cwd();
127
- let dir = start;
128
- let firstFabric = null;
129
- for (let i = 0; i < 64; i++) {
130
- if (existsSync(join(dir, ".git"))) return dir;
131
- if (firstFabric === null && existsSync(join(dir, ".fabric"))) firstFabric = dir;
132
- const parent = dirname(dir);
133
- if (parent === dir) break;
134
- dir = parent;
151
+ for (const rootHint of mcpRootsHint) {
152
+ const anchored2 = climbToAnchor(rootHint);
153
+ if (anchored2 !== null) return anchored2;
135
154
  }
136
- return firstFabric ?? start;
155
+ const start = typeof startCwd === "string" && startCwd.length > 0 ? startCwd : process.cwd();
156
+ const anchored = climbToAnchor(start);
157
+ if (anchored !== null) return anchored;
158
+ return mcpRootsHint[0] ?? start;
159
+ }
160
+
161
+ // src/services/project-root-warning.ts
162
+ var PROJECT_ROOT_UNRESOLVED_CODE = "project_root_unresolved";
163
+ function projectRootUnresolvedMessage(projectRoot) {
164
+ return `project root unresolved \u2014 serving personal store only (resolved "${projectRoot}", no .fabric/fabric-config.json found; team stores are NOT loaded)`;
165
+ }
166
+ var PROJECT_ROOT_ACTION_HINT = "Set FABRIC_PROJECT_ROOT (or CLAUDE_PROJECT_DIR) in the MCP server env, launch the server from inside the project, or use an MCP client that exposes workspace roots (adopted automatically after initialize). Run `fabric doctor` in the project to verify.";
167
+ function projectRootWarning(projectRoot) {
168
+ if (isProjectRootConfigured(projectRoot)) return null;
169
+ return {
170
+ code: PROJECT_ROOT_UNRESOLVED_CODE,
171
+ file: "<server>",
172
+ message: projectRootUnresolvedMessage(projectRoot),
173
+ action_hint: PROJECT_ROOT_ACTION_HINT
174
+ };
137
175
  }
138
176
 
139
177
  // src/services/event-ledger.ts
@@ -2881,6 +2919,10 @@ function registerExtractKnowledge(server, tracker) {
2881
2919
  if (gateWarn) {
2882
2920
  response.warnings = [gateWarn];
2883
2921
  }
2922
+ const rootWarn = projectRootWarning(projectRoot);
2923
+ if (rootWarn) {
2924
+ response.warnings = [...response.warnings ?? [], rootWarn];
2925
+ }
2884
2926
  const scopeWarn = unsealedProjectScopeWarning(projectRoot);
2885
2927
  if (scopeWarn) {
2886
2928
  response.warnings = [...response.warnings ?? [], scopeWarn];
@@ -4518,6 +4560,7 @@ function registerRecall(server, tracker) {
4518
4560
  const gateResult = await awaitFirstReconcileGate();
4519
4561
  const gateWarn = gateWarning(gateResult);
4520
4562
  const projectRoot = resolveProjectRoot();
4563
+ const rootWarn = projectRootWarning(projectRoot);
4521
4564
  const input = {
4522
4565
  paths,
4523
4566
  intent,
@@ -4538,7 +4581,8 @@ function registerRecall(server, tracker) {
4538
4581
  const response = {
4539
4582
  ...result,
4540
4583
  warnings: [
4541
- ...gateWarn ? [gateWarn] : []
4584
+ ...gateWarn ? [gateWarn] : [],
4585
+ ...rootWarn ? [rootWarn] : []
4542
4586
  ]
4543
4587
  };
4544
4588
  const payloadLimits = readPayloadLimits(projectRoot);
@@ -4714,6 +4758,10 @@ function registerArchiveScan(server, tracker) {
4714
4758
  correlation_id,
4715
4759
  session_id
4716
4760
  });
4761
+ const rootWarn = projectRootWarning(projectRoot);
4762
+ if (rootWarn) {
4763
+ result.warnings = [...result.warnings ?? [], rootWarn];
4764
+ }
4717
4765
  const payloadLimits = readPayloadLimits(projectRoot);
4718
4766
  const serialized = JSON.stringify(result);
4719
4767
  const guardResult = enforcePayloadLimit3(serialized, payloadLimits);
@@ -6301,6 +6349,10 @@ function registerReview(server, tracker) {
6301
6349
  if (gateWarn) {
6302
6350
  response.warnings = [gateWarn];
6303
6351
  }
6352
+ const rootWarn = projectRootWarning(projectRoot);
6353
+ if (rootWarn) {
6354
+ response.warnings = [...response.warnings ?? [], rootWarn];
6355
+ }
6304
6356
  if (narrowed.action !== "retire") {
6305
6357
  const scopeWarn = unsealedProjectScopeWarning(projectRoot);
6306
6358
  if (scopeWarn) {
@@ -6368,6 +6420,10 @@ function registerPending(server, tracker) {
6368
6420
  if (gateWarn) {
6369
6421
  response.warnings = [gateWarn];
6370
6422
  }
6423
+ const rootWarn = projectRootWarning(projectRoot);
6424
+ if (rootWarn) {
6425
+ response.warnings = [...response.warnings ?? [], rootWarn];
6426
+ }
6371
6427
  const payloadLimits = readPayloadLimits(projectRoot);
6372
6428
  const serialized = JSON.stringify(response);
6373
6429
  const guardResult = enforcePayloadLimit5(serialized, payloadLimits);
@@ -12952,14 +13008,23 @@ var FABRIC_SERVER_INSTRUCTIONS = [
12952
13008
  "- Pass the client `session_id` to `fab_recall` so cross-session knowledge-debt tracking stays accurate.",
12953
13009
  "- Cite the KB id you applied or dismissed before edits, per the project's cite policy."
12954
13010
  ].join("\n");
13011
+ function buildServerInstructions(projectRoot) {
13012
+ if (isProjectRootConfigured(projectRoot)) return FABRIC_SERVER_INSTRUCTIONS;
13013
+ return [
13014
+ `\u26A0\uFE0F WARNING: ${projectRootUnresolvedMessage(projectRoot)}`,
13015
+ "If this client exposes MCP workspace roots, the server adopts them right after initialize and team stores load on the next tool call \u2014 otherwise set FABRIC_PROJECT_ROOT (or CLAUDE_PROJECT_DIR) in the MCP server env, or launch the server from inside the project. Tool responses carry a `project_root_unresolved` warning until resolved.",
13016
+ "",
13017
+ FABRIC_SERVER_INSTRUCTIONS
13018
+ ].join("\n");
13019
+ }
12955
13020
  function createFabricServer(tracker) {
12956
13021
  const server = new McpServer(
12957
13022
  {
12958
13023
  name: "fabric-knowledge-server",
12959
- version: "2.3.0-rc.16"
13024
+ version: "2.3.0-rc.17"
12960
13025
  },
12961
13026
  {
12962
- instructions: FABRIC_SERVER_INSTRUCTIONS
13027
+ instructions: buildServerInstructions(resolveProjectRoot())
12963
13028
  }
12964
13029
  );
12965
13030
  registerRecall(server, tracker);
@@ -12994,16 +13059,70 @@ function createFabricServer(tracker) {
12994
13059
  );
12995
13060
  return server;
12996
13061
  }
13062
+ async function adoptMcpClientRoots(source) {
13063
+ try {
13064
+ if (source.getClientCapabilities()?.roots === void 0) return [];
13065
+ const { roots } = await source.listRoots();
13066
+ const paths = [];
13067
+ for (const root of roots) {
13068
+ if (typeof root.uri !== "string" || !root.uri.startsWith("file:")) continue;
13069
+ try {
13070
+ paths.push(fileURLToPath(root.uri));
13071
+ } catch {
13072
+ }
13073
+ }
13074
+ return setMcpRootsHint(paths);
13075
+ } catch {
13076
+ return [];
13077
+ }
13078
+ }
12997
13079
  async function startStdioServer() {
12998
13080
  const tracker = createInFlightTracker();
12999
- const projectRoot = resolveProjectRoot();
13081
+ let projectRoot = resolveProjectRoot();
13000
13082
  const rootMsg = formatPreexistingRootMessage(projectRoot);
13001
13083
  if (rootMsg !== null) {
13002
13084
  process.stderr.write(`${rootMsg}
13003
13085
  `);
13004
13086
  }
13087
+ if (!isProjectRootConfigured(projectRoot)) {
13088
+ process.stderr.write(
13089
+ `[startup] warning: ${projectRootUnresolvedMessage(projectRoot)}. Waiting for MCP client roots after initialize; set FABRIC_PROJECT_ROOT to pin explicitly.
13090
+ `
13091
+ );
13092
+ }
13005
13093
  const server = createFabricServer(tracker);
13006
13094
  const transport = new StdioServerTransport();
13095
+ server.server.oninitialized = () => {
13096
+ void (async () => {
13097
+ const adopted = await adoptMcpClientRoots(server.server);
13098
+ if (adopted.length === 0) {
13099
+ if (!isProjectRootConfigured(projectRoot)) {
13100
+ process.stderr.write(
13101
+ "[roots] client exposed no usable MCP roots \u2014 project root stays unresolved; serving personal store only.\n"
13102
+ );
13103
+ }
13104
+ return;
13105
+ }
13106
+ const newRoot = resolveProjectRoot();
13107
+ if (newRoot !== projectRoot) {
13108
+ process.stderr.write(
13109
+ `[roots] project root re-resolved via MCP client roots: "${projectRoot}" \u2192 "${newRoot}"
13110
+ `
13111
+ );
13112
+ stopMetricsFlush(projectRoot);
13113
+ stopRotationTick(projectRoot);
13114
+ startMetricsFlush(newRoot);
13115
+ startRotationTick(newRoot);
13116
+ projectRoot = newRoot;
13117
+ }
13118
+ if (!isProjectRootConfigured(projectRoot)) {
13119
+ process.stderr.write(
13120
+ `[roots] warning: ${projectRootUnresolvedMessage(projectRoot)}
13121
+ `
13122
+ );
13123
+ }
13124
+ })();
13125
+ };
13007
13126
  setFirstReconcile(Promise.resolve());
13008
13127
  await server.connect(transport);
13009
13128
  startMetricsFlush(projectRoot);
@@ -13011,18 +13130,17 @@ async function startStdioServer() {
13011
13130
  const closeServer = async () => {
13012
13131
  await server.close();
13013
13132
  };
13014
- process.on(
13015
- "SIGINT",
13016
- createShutdownHandler({ signal: "SIGINT", tracker, projectRoot, closeServer })
13017
- );
13018
- process.on(
13019
- "SIGTERM",
13020
- createShutdownHandler({ signal: "SIGTERM", tracker, projectRoot, closeServer })
13021
- );
13022
- process.on(
13023
- "SIGHUP",
13024
- createShutdownHandler({ signal: "SIGHUP", tracker, projectRoot, closeServer })
13025
- );
13133
+ const shutdownDeps = (signal) => ({
13134
+ signal,
13135
+ tracker,
13136
+ get projectRoot() {
13137
+ return projectRoot;
13138
+ },
13139
+ closeServer
13140
+ });
13141
+ process.on("SIGINT", createShutdownHandler(shutdownDeps("SIGINT")));
13142
+ process.on("SIGTERM", createShutdownHandler(shutdownDeps("SIGTERM")));
13143
+ process.on("SIGHUP", createShutdownHandler(shutdownDeps("SIGHUP")));
13026
13144
  }
13027
13145
  function createShutdownHandler(deps3) {
13028
13146
  const exit = deps3.exit ?? ((code) => process.exit(code));
@@ -13080,12 +13198,14 @@ export {
13080
13198
  METRIC_COUNTER_NAMES,
13081
13199
  OPTIONAL_EMBED_PACKAGE,
13082
13200
  RETIRED_TOKENS,
13201
+ adoptMcpClientRoots,
13083
13202
  aggregateConsumption,
13084
13203
  appendEventLedgerEvent,
13085
13204
  buildAlwaysActiveBodies,
13086
13205
  buildColdEvalBatch,
13087
13206
  buildKnowledgeCensus,
13088
13207
  buildRelatedGraph,
13208
+ buildServerInstructions,
13089
13209
  bumpCounter,
13090
13210
  checkBacklogAge,
13091
13211
  clearPrecheckCache,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-server",
3
- "version": "2.3.0-rc.16",
3
+ "version": "2.3.0-rc.17",
4
4
  "description": "Fabric MCP knowledge server — stdio transport for Claude Code / Codex CLI, manages .fabric/ knowledge base + agents.meta.json + event ledger.",
5
5
  "license": "MIT",
6
6
  "author": "wangzhichao <fenglimg90@gmail.com>",
@@ -37,7 +37,7 @@
37
37
  "@modelcontextprotocol/sdk": "^1.29.0",
38
38
  "minimatch": "^10.0.1",
39
39
  "zod": "^3.25.0",
40
- "@fenglimg/fabric-shared": "2.3.0-rc.16"
40
+ "@fenglimg/fabric-shared": "2.3.0-rc.17"
41
41
  },
42
42
  "optionalDependencies": {
43
43
  "fastembed": "^2.0.0"