@@ -669,6 +669,134 @@ describe("mcpClientToAgentTools", () => {
669669 } ) ;
670670} ) ;
671671
672+ describe ( "mcpClientToAgentTools Linear result projection" , ( ) => {
673+ const fullIssue = {
674+ id : "issue-uuid" ,
675+ identifier : "CL-9495" ,
676+ url : "https://linear.app/corbits/issue/CL-9495" ,
677+ title : "Project MCP results" ,
678+ status : "In Progress" ,
679+ description : "# long body that must not echo" ,
680+ } ;
681+
682+ function linearClient (
683+ toolName : string ,
684+ envelope : ScriptedMcpEnvelope ,
685+ ) : MCPClient {
686+ const client : MCPClient = {
687+ serverName : "linear" ,
688+ tools : [
689+ {
690+ name : toolName ,
691+ description : toolName ,
692+ inputSchema : { type : "object" , properties : { } } ,
693+ } ,
694+ ] ,
695+ call : async ( ) => "" ,
696+ callBlocks : async ( ) => envelope . blocks ,
697+ close : async ( ) => undefined ,
698+ } ;
699+ Object . assign ( client , {
700+ callResult : async ( ) => envelope ,
701+ } ) ;
702+ return client ;
703+ }
704+
705+ async function runLinear (
706+ toolName : string ,
707+ envelope : ScriptedMcpEnvelope ,
708+ args : Record < string , unknown > = { } ,
709+ spillOptions ?: Parameters < typeof mcpClientToAgentTools > [ 2 ] ,
710+ ) {
711+ const [ tool ] = mcpClientToAgentTools (
712+ linearClient ( toolName , envelope ) ,
713+ skipGate ( ) ,
714+ spillOptions ,
715+ ) ;
716+ if ( tool ?. kind !== "full" ) throw new Error ( "expected full tool" ) ;
717+ return tool . handler (
718+ { id : "c-linear" , name : `mcp__linear__${ toolName } ` , arguments : args } ,
719+ new AbortController ( ) . signal ,
720+ ) ;
721+ }
722+
723+ test ( "save_issue returns id url status title and not the body just sent" , async ( ) => {
724+ const result = await runLinear ( "save_issue" , {
725+ blocks : [ { type : "text" , text : JSON . stringify ( fullIssue ) } ] ,
726+ } ) ;
727+ const parsed = JSON . parse ( String ( result . content ) ) as Record <
728+ string ,
729+ unknown
730+ > ;
731+ expect ( parsed ) . toEqual ( {
732+ id : "issue-uuid" ,
733+ identifier : "CL-9495" ,
734+ url : "https://linear.app/corbits/issue/CL-9495" ,
735+ status : "In Progress" ,
736+ title : "Project MCP results" ,
737+ } ) ;
738+ expect ( String ( result . content ) ) . not . toContain ( "long body" ) ;
739+ } ) ;
740+
741+ test ( "list_issues is short by default and expands when fields asks" , async ( ) => {
742+ const envelope : ScriptedMcpEnvelope = {
743+ blocks : [
744+ {
745+ type : "text" ,
746+ text : JSON . stringify ( { issues : [ fullIssue ] , hasNextPage : false } ) ,
747+ } ,
748+ ] ,
749+ } ;
750+ const shortResult = await runLinear ( "list_issues" , envelope , { } ) ;
751+ const shortParsed = JSON . parse ( String ( shortResult . content ) ) as {
752+ issues : Record < string , unknown > [ ] ;
753+ hasNextPage : boolean ;
754+ } ;
755+ expect ( shortParsed . hasNextPage ) . toBe ( false ) ;
756+ expect ( shortParsed . issues [ 0 ] ) . toEqual ( {
757+ id : "issue-uuid" ,
758+ identifier : "CL-9495" ,
759+ url : "https://linear.app/corbits/issue/CL-9495" ,
760+ status : "In Progress" ,
761+ title : "Project MCP results" ,
762+ } ) ;
763+ expect ( String ( shortResult . content ) ) . not . toContain ( "long body" ) ;
764+
765+ const expanded = await runLinear ( "list_issues" , envelope , {
766+ fields : [ "id" , "description" ] ,
767+ } ) ;
768+ expect ( JSON . parse ( String ( expanded . content ) ) ) . toEqual ( {
769+ issues : [
770+ {
771+ id : "issue-uuid" ,
772+ description : "# long body that must not echo" ,
773+ } ,
774+ ] ,
775+ hasNextPage : false ,
776+ } ) ;
777+ } ) ;
778+
779+ test ( "archives the full entity and returns the projection to the model" , async ( ) => {
780+ const { archive } = memoryEvidenceArchive ( ) ;
781+ const result = await runLinear (
782+ "save_issue" ,
783+ {
784+ blocks : [ { type : "text" , text : JSON . stringify ( fullIssue ) } ] ,
785+ structuredContent : fullIssue ,
786+ } ,
787+ { } ,
788+ { getEvidenceArchive : ( ) => archive } ,
789+ ) ;
790+ expect ( String ( result . content ) ) . not . toContain ( "long body" ) ;
791+ const [ occurrence ] = await archive . listOccurrences ( ) ;
792+ if ( occurrence === undefined ) throw new Error ( "missing archive occurrence" ) ;
793+ const payload = await archive . readAuthorizedPayload (
794+ occurrence . occurrenceId ,
795+ ) ;
796+ expect ( payload ) . toContain ( "long body" ) ;
797+ } ) ;
798+ } ) ;
799+
672800function makeFakeClient ( serverName : string , toolNames : string [ ] ) : MCPClient {
673801 return {
674802 serverName,
0 commit comments