@@ -622,7 +622,8 @@ export async function fetchSkillFileFromAo(skillName, path = 'SKILL.md', skillId
622622 * skill server-side. A 409 with needs_confirmation means a skill of that name
623623 * already exists for this user (not auto-overwritten here).
624624 */
625- export async function uploadSkillFileToAo ( file ) {
625+ // TODO: org-scope create needs an admin-gated scope selector in the UI; defaults to owner.
626+ export async function uploadSkillFileToAo ( file , scope = 'owner' ) {
626627 const ctx = await aoAuthContext ( ) ;
627628 if ( ! ctx ) return { ok : false , error : 'Not signed in' } ;
628629
@@ -631,6 +632,7 @@ export async function uploadSkillFileToAo(file) {
631632 const form = new FormData ( ) ;
632633 form . append ( 'file' , file , file . name ) ;
633634 form . append ( 'display_title' , file . name . replace ( / \. m d $ / i, '' ) ) ;
635+ if ( scope === 'org' ) form . append ( 'scope' , 'org' ) ;
634636 const resp = await fetch ( `${ ctx . base } /api/v1/skills` , {
635637 method : 'POST' ,
636638 headers : {
@@ -1078,6 +1080,37 @@ function mcpKeyMatch(serverKey) {
10781080 return ( r ) => normaliseRowKey ( r ) === serverKey ;
10791081}
10801082
1083+ // ─── MCP servers on the CMA bridge (AO `overrides.settings.mcp_servers`) ─────
1084+ // On the alt harness, MCP servers live in the bridge's per-user overrides
1085+ // instead of the DA config sheet. Each entry keeps the same shape as an
1086+ // `mcp-servers` config row ({ key, url, description?, headers?, enabled? }) so
1087+ // the editor UI round-trips unchanged.
1088+ // TODO: org-scope MCP needs an admin-gated scope selector in the UI; personal only for now.
1089+ function bridgeHeaders ( ctx ) {
1090+ return {
1091+ authorization : `Bearer ${ ctx . token } ` ,
1092+ 'x-tenant-id' : ctx . orgId ,
1093+ 'x-user-id' : ctx . userId || '' ,
1094+ } ;
1095+ }
1096+
1097+ async function bridgeGetMcpServers ( ctx ) {
1098+ const resp = await fetch ( `${ ctx . base } /api/v1/overrides/user` , { headers : bridgeHeaders ( ctx ) } ) ;
1099+ if ( ! resp . ok ) throw new Error ( `Could not load overrides (${ resp . status } )` ) ;
1100+ const json = await resp . json ( ) ;
1101+ const servers = json ?. settings ?. mcp_servers ;
1102+ return Array . isArray ( servers ) ? servers : [ ] ;
1103+ }
1104+
1105+ async function bridgePutMcpServers ( ctx , servers ) {
1106+ const resp = await fetch ( `${ ctx . base } /api/v1/overrides/user/settings/mcp_servers` , {
1107+ method : 'PUT' ,
1108+ headers : { ...bridgeHeaders ( ctx ) , 'content-type' : 'application/json' } ,
1109+ body : JSON . stringify ( { value : servers } ) ,
1110+ } ) ;
1111+ if ( ! resp . ok ) throw new Error ( `Save failed (${ resp . status } )` ) ;
1112+ }
1113+
10811114export async function registerMcpServer (
10821115 org ,
10831116 site ,
@@ -1095,6 +1128,28 @@ export async function registerMcpServer(
10951128 value : String ( h ?. value || '' ) . trim ( ) ,
10961129 } ) )
10971130 . filter ( ( h ) => h . name && h . value ) ;
1131+
1132+ if ( altHarnessEnabled ) {
1133+ const ctx = await aoAuthContext ( ) ;
1134+ if ( ! ctx ) return { ok : false , error : 'Not signed in' } ;
1135+ try {
1136+ const servers = await bridgeGetMcpServers ( ctx ) ;
1137+ const entry = { key : serverKey , url : serverUrl } ;
1138+ if ( description ) entry . description = String ( description ) . trim ( ) ;
1139+ if ( safeHeaders . length ) entry . headers = safeHeaders ;
1140+ const idx = servers . findIndex ( ( s ) => s ?. key === serverKey ) ;
1141+ const prevEnabled = idx >= 0 ? servers [ idx ] ?. enabled : undefined ;
1142+ if ( prevEnabled !== undefined ) entry . enabled = prevEnabled ;
1143+ const next = idx >= 0
1144+ ? servers . map ( ( s , i ) => ( i === idx ? entry : s ) )
1145+ : [ ...servers , entry ] ;
1146+ await bridgePutMcpServers ( ctx , next ) ;
1147+ return { ok : true } ;
1148+ } catch ( err ) {
1149+ return { ok : false , error : String ( err ?. message ?? err ) } ;
1150+ }
1151+ }
1152+
10981153 return upsertSheetRow (
10991154 org ,
11001155 site ,
@@ -1117,6 +1172,21 @@ export async function setMcpServerEnabled(org, site, key, enabled) {
11171172 const serverKey = String ( key || '' ) . trim ( ) ;
11181173 if ( ! serverKey ) return { ok : false , error : 'Server id required' } ;
11191174
1175+ if ( altHarnessEnabled ) {
1176+ const ctx = await aoAuthContext ( ) ;
1177+ if ( ! ctx ) return { ok : false , error : 'Not signed in' } ;
1178+ try {
1179+ const servers = await bridgeGetMcpServers ( ctx ) ;
1180+ const idx = servers . findIndex ( ( s ) => s ?. key === serverKey ) ;
1181+ if ( idx < 0 ) return { ok : false , error : 'Server not found' } ;
1182+ const next = servers . map ( ( s , i ) => ( i === idx ? { ...s , enabled : ! ! enabled } : s ) ) ;
1183+ await bridgePutMcpServers ( ctx , next ) ;
1184+ return { ok : true } ;
1185+ } catch ( err ) {
1186+ return { ok : false , error : String ( err ?. message ?? err ) } ;
1187+ }
1188+ }
1189+
11201190 const loaded = await fetchDaConfigSheets ( org , site ) ;
11211191 if ( ! loaded . ok ) return { ok : false , error : 'Could not load config' } ;
11221192
@@ -1137,6 +1207,20 @@ export async function setMcpServerEnabled(org, site, key, enabled) {
11371207export async function deleteMcpServer ( org , site , key ) {
11381208 const serverKey = String ( key || '' ) . trim ( ) ;
11391209 if ( ! serverKey ) return { ok : false , error : 'Key required' } ;
1210+
1211+ if ( altHarnessEnabled ) {
1212+ const ctx = await aoAuthContext ( ) ;
1213+ if ( ! ctx ) return { ok : false , error : 'Not signed in' } ;
1214+ try {
1215+ const servers = await bridgeGetMcpServers ( ctx ) ;
1216+ const next = servers . filter ( ( s ) => s ?. key !== serverKey ) ;
1217+ await bridgePutMcpServers ( ctx , next ) ;
1218+ return { ok : true } ;
1219+ } catch ( err ) {
1220+ return { ok : false , error : String ( err ?. message ?? err ) } ;
1221+ }
1222+ }
1223+
11401224 return deleteSheetRow ( org , site , MCP_SHEET , mcpKeyMatch ( serverKey ) , 'MCP server' ) ;
11411225}
11421226
0 commit comments