11import fs from "node:fs" ;
22import path from "node:path" ;
33import type { OpenClawConfig } from "../config/config.js" ;
4+ import type { PluginBundleFormat } from "./types.js" ;
45import { applyMergePatch } from "../config/merge-patch.js" ;
56import { openBoundaryFileSync } from "../infra/boundary-file-read.js" ;
67import { isRecord } from "../utils.js" ;
@@ -13,7 +14,7 @@ import {
1314} from "./bundle-manifest.js" ;
1415import { normalizePluginsConfig , resolveEffectiveEnableState } from "./config-state.js" ;
1516import { loadPluginManifestRegistry } from "./manifest-registry.js" ;
16- import type { PluginBundleFormat } from "./types .js" ;
17+ import { safeRealpathSync } from "./path-safety .js" ;
1718
1819export type BundleMcpServerConfig = Record < string , unknown > ;
1920
@@ -121,6 +122,14 @@ function expandBundleRootPlaceholders(value: string, rootDir: string): string {
121122 return value . split ( CLAUDE_PLUGIN_ROOT_PLACEHOLDER ) . join ( rootDir ) ;
122123}
123124
125+ function canonicalizeBundlePath ( targetPath : string ) : string {
126+ return path . normalize ( safeRealpathSync ( targetPath ) ?? path . resolve ( targetPath ) ) ;
127+ }
128+
129+ function normalizeExpandedAbsolutePath ( value : string ) : string {
130+ return path . isAbsolute ( value ) ? path . normalize ( value ) : value ;
131+ }
132+
124133function absolutizeBundleMcpServer ( params : {
125134 rootDir : string ;
126135 baseDir : string ;
@@ -137,7 +146,7 @@ function absolutizeBundleMcpServer(params: {
137146 const expanded = expandBundleRootPlaceholders ( command , params . rootDir ) ;
138147 next . command = isExplicitRelativePath ( expanded )
139148 ? path . resolve ( params . baseDir , expanded )
140- : expanded ;
149+ : normalizeExpandedAbsolutePath ( expanded ) ;
141150 }
142151
143152 const cwd = next . cwd ;
@@ -150,7 +159,7 @@ function absolutizeBundleMcpServer(params: {
150159 if ( typeof workingDirectory === "string" ) {
151160 const expanded = expandBundleRootPlaceholders ( workingDirectory , params . rootDir ) ;
152161 next . workingDirectory = path . isAbsolute ( expanded )
153- ? expanded
162+ ? path . normalize ( expanded )
154163 : path . resolve ( params . baseDir , expanded ) ;
155164 }
156165
@@ -161,7 +170,7 @@ function absolutizeBundleMcpServer(params: {
161170 }
162171 const expanded = expandBundleRootPlaceholders ( entry , params . rootDir ) ;
163172 if ( ! isExplicitRelativePath ( expanded ) ) {
164- return expanded ;
173+ return normalizeExpandedAbsolutePath ( expanded ) ;
165174 }
166175 return path . resolve ( params . baseDir , expanded ) ;
167176 } ) ;
@@ -171,7 +180,9 @@ function absolutizeBundleMcpServer(params: {
171180 next . env = Object . fromEntries (
172181 Object . entries ( next . env ) . map ( ( [ key , value ] ) => [
173182 key ,
174- typeof value === "string" ? expandBundleRootPlaceholders ( value , params . rootDir ) : value ,
183+ typeof value === "string"
184+ ? normalizeExpandedAbsolutePath ( expandBundleRootPlaceholders ( value , params . rootDir ) )
185+ : value ,
175186 ] ) ,
176187 ) ;
177188 }
@@ -183,10 +194,11 @@ function loadBundleFileBackedMcpConfig(params: {
183194 rootDir : string ;
184195 relativePath : string ;
185196} ) : BundleMcpConfig {
186- const absolutePath = path . resolve ( params . rootDir , params . relativePath ) ;
197+ const rootDir = canonicalizeBundlePath ( params . rootDir ) ;
198+ const absolutePath = path . resolve ( rootDir , params . relativePath ) ;
187199 const opened = openBoundaryFileSync ( {
188200 absolutePath,
189- rootPath : params . rootDir ,
201+ rootPath : rootDir ,
190202 boundaryLabel : "plugin root" ,
191203 rejectHardlinks : true ,
192204 } ) ;
@@ -200,12 +212,12 @@ function loadBundleFileBackedMcpConfig(params: {
200212 }
201213 const raw = JSON . parse ( fs . readFileSync ( opened . fd , "utf-8" ) ) as unknown ;
202214 const servers = extractMcpServerMap ( raw ) ;
203- const baseDir = path . dirname ( absolutePath ) ;
215+ const baseDir = canonicalizeBundlePath ( path . dirname ( absolutePath ) ) ;
204216 return {
205217 mcpServers : Object . fromEntries (
206218 Object . entries ( servers ) . map ( ( [ serverName , server ] ) => [
207219 serverName ,
208- absolutizeBundleMcpServer ( { rootDir : params . rootDir , baseDir, server } ) ,
220+ absolutizeBundleMcpServer ( { rootDir, baseDir, server } ) ,
209221 ] ) ,
210222 ) ,
211223 } ;
@@ -221,12 +233,13 @@ function loadBundleInlineMcpConfig(params: {
221233 if ( ! isRecord ( params . raw . mcpServers ) ) {
222234 return { mcpServers : { } } ;
223235 }
236+ const baseDir = canonicalizeBundlePath ( params . baseDir ) ;
224237 const servers = extractMcpServerMap ( params . raw . mcpServers ) ;
225238 return {
226239 mcpServers : Object . fromEntries (
227240 Object . entries ( servers ) . map ( ( [ serverName , server ] ) => [
228241 serverName ,
229- absolutizeBundleMcpServer ( { rootDir : params . baseDir , baseDir : params . baseDir , server } ) ,
242+ absolutizeBundleMcpServer ( { rootDir : baseDir , baseDir, server } ) ,
230243 ] ) ,
231244 ) ,
232245 } ;
0 commit comments