@@ -93,16 +93,177 @@ describe('opencodeAdapter', () => {
9393 expect ( result . configPaths ) . toEqual ( [ ] )
9494 } )
9595
96- it ( 'reads and writes mcp section' , async ( ) => {
96+ it ( 'reads legacy mcp server shape' , async ( ) => {
97+ const configPath = join ( tmpDir , 'opencode.json' )
98+ writeFileSync (
99+ configPath ,
100+ JSON . stringify ( {
101+ mcp : {
102+ legacy : {
103+ command : 'npx' ,
104+ args : [ '-y' , 'legacy-mcp' ] ,
105+ env : { TOKEN : 'abc' } ,
106+ } ,
107+ } ,
108+ } ) ,
109+ )
110+
111+ const read = await opencodeAdapter . read ( configPath )
112+ expect ( read ) . toEqual ( {
113+ legacy : {
114+ command : 'npx' ,
115+ args : [ '-y' , 'legacy-mcp' ] ,
116+ env : { TOKEN : 'abc' } ,
117+ } ,
118+ } )
119+ } )
120+
121+ it ( 'reads new OpenCode local/remote shapes into canonical format' , async ( ) => {
122+ const configPath = join ( tmpDir , 'opencode.json' )
123+ writeFileSync (
124+ configPath ,
125+ JSON . stringify ( {
126+ mcp : {
127+ localServer : {
128+ type : 'local' ,
129+ command : [ 'npx' , '-y' , 'local-mcp' ] ,
130+ environment : { TOKEN : 'abc' } ,
131+ } ,
132+ sseRemote : {
133+ type : 'remote' ,
134+ url : 'https://example.test/sse' ,
135+ transport : 'sse' ,
136+ headers : { Authorization : 'Bearer token' } ,
137+ } ,
138+ httpRemote : {
139+ type : 'remote' ,
140+ url : 'https://example.test/http' ,
141+ } ,
142+ } ,
143+ } ) ,
144+ )
145+
146+ const read = await opencodeAdapter . read ( configPath )
147+ expect ( read ) . toEqual ( {
148+ localServer : {
149+ command : 'npx' ,
150+ args : [ '-y' , 'local-mcp' ] ,
151+ env : { TOKEN : 'abc' } ,
152+ } ,
153+ sseRemote : {
154+ command : 'fetch' ,
155+ type : 'sse' ,
156+ url : 'https://example.test/sse' ,
157+ headers : { Authorization : 'Bearer token' } ,
158+ } ,
159+ httpRemote : {
160+ command : 'fetch' ,
161+ type : 'http' ,
162+ url : 'https://example.test/http' ,
163+ } ,
164+ } )
165+ } )
166+
167+ it ( 'writes canonical stdio server shape to OpenCode local format' , async ( ) => {
97168 const configPath = join ( tmpDir , 'opencode.json' )
98169 writeFileSync ( configPath , JSON . stringify ( { other : true } ) )
99170
100- await opencodeAdapter . write ( configPath , { myServer : { command : 'npx' } } )
171+ await opencodeAdapter . write ( configPath , {
172+ myServer : {
173+ command : 'npx' ,
174+ args : [ '-y' , 'my-mcp' ] ,
175+ env : { TOKEN : 'abc' } ,
176+ } ,
177+ } )
101178 const written = JSON . parse ( readFileSync ( configPath , 'utf-8' ) ) as Record < string , unknown >
102- expect ( written [ 'mcp' ] ) . toEqual ( { myServer : { command : 'npx' } } )
179+ const mcp = written [ 'mcp' ] as Record < string , unknown >
180+ expect ( mcp [ 'myServer' ] ) . toEqual ( {
181+ type : 'local' ,
182+ command : [ 'npx' , '-y' , 'my-mcp' ] ,
183+ environment : { TOKEN : 'abc' } ,
184+ enabled : true ,
185+ } )
103186 expect ( written [ 'other' ] ) . toBe ( true )
187+ } )
188+
189+ it ( 'writes canonical remote server shape to OpenCode remote format' , async ( ) => {
190+ const configPath = join ( tmpDir , 'opencode.json' )
191+ writeFileSync ( configPath , JSON . stringify ( { } ) )
192+
193+ await opencodeAdapter . write ( configPath , {
194+ sseRemote : {
195+ command : 'fetch' ,
196+ type : 'sse' ,
197+ url : 'https://example.test/sse' ,
198+ headers : { Authorization : 'Bearer sse' } ,
199+ } ,
200+ httpRemote : {
201+ command : 'fetch' ,
202+ type : 'http' ,
203+ url : 'https://example.test/http' ,
204+ } ,
205+ } )
104206
207+ const written = JSON . parse ( readFileSync ( configPath , 'utf-8' ) ) as Record < string , unknown >
208+ const mcp = written [ 'mcp' ] as Record < string , unknown >
209+ expect ( mcp [ 'sseRemote' ] ) . toEqual ( {
210+ type : 'remote' ,
211+ url : 'https://example.test/sse' ,
212+ headers : { Authorization : 'Bearer sse' } ,
213+ transport : 'sse' ,
214+ enabled : true ,
215+ } )
216+ expect ( mcp [ 'httpRemote' ] ) . toEqual ( {
217+ type : 'remote' ,
218+ url : 'https://example.test/http' ,
219+ transport : 'streamable-http' ,
220+ enabled : true ,
221+ } )
222+ } )
223+
224+ it ( 'round-trips canonical config through write/read' , async ( ) => {
225+ const configPath = join ( tmpDir , 'opencode.json' )
226+ writeFileSync ( configPath , JSON . stringify ( { } ) )
227+
228+ await opencodeAdapter . write ( configPath , {
229+ myServer : { command : 'npx' , args : [ '-y' , 'pkg' ] } ,
230+ } )
105231 const read = await opencodeAdapter . read ( configPath )
106- expect ( read ) . toEqual ( { myServer : { command : 'npx' } } )
232+ expect ( read ) . toEqual ( { myServer : { command : 'npx' , args : [ '-y' , 'pkg' ] } } )
233+ } )
234+
235+ it ( 'validates new and legacy mcp entry formats' , async ( ) => {
236+ const configPath = join ( tmpDir , 'opencode.json' )
237+ writeFileSync (
238+ configPath ,
239+ JSON . stringify ( {
240+ mcp : {
241+ legacy : { command : 'npx' , args : [ '-y' , 'legacy' ] } ,
242+ local : { type : 'local' , command : [ 'npx' , '-y' , 'local' ] } ,
243+ remote : { type : 'remote' , url : 'https://example.test' , transport : 'sse' } ,
244+ } ,
245+ } ) ,
246+ )
247+
248+ const validation = await opencodeAdapter . validate ( configPath )
249+ expect ( validation . valid ) . toBe ( true )
250+ expect ( validation . errors ) . toEqual ( [ ] )
251+ } )
252+
253+ it ( 'rejects malformed OpenCode entries' , async ( ) => {
254+ const configPath = join ( tmpDir , 'opencode.json' )
255+ writeFileSync (
256+ configPath ,
257+ JSON . stringify ( {
258+ mcp : {
259+ brokenLocal : { type : 'local' , command : [ ] } ,
260+ brokenRemote : { type : 'remote' , url : '' } ,
261+ } ,
262+ } ) ,
263+ )
264+
265+ const validation = await opencodeAdapter . validate ( configPath )
266+ expect ( validation . valid ) . toBe ( false )
267+ expect ( validation . errors . length ) . toBeGreaterThan ( 0 )
107268 } )
108269} )
0 commit comments