@@ -292,7 +292,16 @@ export class HttpDispatcher {
292292 } ;
293293
294294 if ( action === 'create' ) {
295- if ( ql ) {
295+ // Prefer the protocol service (validations + RLS + audit), mirroring
296+ // the read paths below. The MCP bridge passes `context.dataDriver` as
297+ // `ql`, which in the multi-env runtime is a RAW db driver with no ORM
298+ // `insert` — so going straight to `ql.insert` broke MCP create_record
299+ // ("ql.insert is not a function") while REST (which uses `createData`)
300+ // worked. Routing writes through the protocol keeps them aligned.
301+ if ( protocol && typeof protocol . createData === 'function' ) {
302+ return await protocol . createData ( { object : params . object , data : params . data , ...( scopeId ? { environmentId : scopeId } : { } ) , context : executionContext } ) ;
303+ }
304+ if ( ql && typeof ql . insert === 'function' ) {
296305 const res = await ql . insert ( params . object , params . data , qlOpts ) ;
297306 const record = { ...params . data , ...res } ;
298307 return { object : params . object , id : record . id , record } ;
@@ -315,7 +324,10 @@ export class HttpDispatcher {
315324 }
316325
317326 if ( action === 'update' ) {
318- if ( ql && params . id ) {
327+ if ( protocol && typeof protocol . updateData === 'function' ) {
328+ return await protocol . updateData ( { object : params . object , id : params . id , data : params . data , ...( scopeId ? { environmentId : scopeId } : { } ) , context : executionContext } ) ;
329+ }
330+ if ( ql && params . id && typeof ql . update === 'function' ) {
319331 let all = await ql . find ( params . object , findOpts ( { where : { id : params . id } , limit : 1 } ) ) ;
320332 if ( all && ( all as any ) . value ) all = ( all as any ) . value ;
321333 if ( ! all ) all = [ ] ;
@@ -328,7 +340,10 @@ export class HttpDispatcher {
328340 }
329341
330342 if ( action === 'delete' ) {
331- if ( ql ) {
343+ if ( protocol && typeof protocol . deleteData === 'function' ) {
344+ return await protocol . deleteData ( { object : params . object , id : params . id , ...( scopeId ? { environmentId : scopeId } : { } ) , context : executionContext } ) ;
345+ }
346+ if ( ql && typeof ql . delete === 'function' ) {
332347 await ql . delete ( params . object , findOpts ( { where : { id : params . id } } ) ) ;
333348 return { object : params . object , id : params . id , deleted : true } ;
334349 }
@@ -3499,7 +3514,23 @@ export class HttpDispatcher {
34993514 try {
35003515 context . executionContext = await resolveExecutionContext ( {
35013516 getService : ( n : string ) => this . resolveService ( n , context . environmentId ) ,
3502- getQl : ( ) => Promise . resolve ( this . getObjectQLService ( context . environmentId ) ) ,
3517+ // Resolve ObjectQL from the per-request kernel DIRECTLY. The scoped
3518+ // `resolveService('objectql', envId)` factory can return a different
3519+ // instance that doesn't see THIS env's rows (the gotcha
3520+ // `handleActions` works around) — which made the api-key lookup miss
3521+ // `sys_api_key` on the MCP path and reject valid keys with 401, while
3522+ // REST accepted them (rest-server resolves identity via
3523+ // `kernel.getServiceAsync('objectql')`). Resolving off `this.kernel`
3524+ // keeps REST + MCP identity resolution aligned; falls back to the
3525+ // scoped path when the kernel can't hand back an objectql directly.
3526+ getQl : async ( ) => {
3527+ const k : any = this . kernel ;
3528+ if ( k && typeof k . getServiceAsync === 'function' ) {
3529+ const ql = await k . getServiceAsync ( 'objectql' ) . catch ( ( ) => undefined ) ;
3530+ if ( ql && ( ql . registry || typeof ql . find === 'function' ) ) return ql ;
3531+ }
3532+ return this . getObjectQLService ( context . environmentId ) ;
3533+ } ,
35033534 request : context . request ,
35043535 } ) ;
35053536 } catch {
0 commit comments