11import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
23import { resolvePrincipalSubjectUserId } from '@sim/auth/principal'
34import { createLogger } from '@sim/logger'
45import { isPlainRecord } from '@sim/utils/object'
@@ -47,17 +48,58 @@ async function callerErrorMessage(error: unknown): Promise<string | undefined> {
4748 : undefined
4849}
4950
51+ interface SourceRow {
52+ documentName : string | null
53+ sourceUrl : string | null
54+ sourceModifiedAt ?: string | null
55+ sourceDate ?: string | null
56+ sourceContainerName ?: string
57+ }
58+
59+ /**
60+ * A document's title, link and dates, each sent once: every turn of the client's conversation
61+ * re-reads them. `sourceModifiedAt` repeats `sourceDate` except where the date is an event start,
62+ * and a Slack message is titled by its channel, so each is kept only when it says something else.
63+ */
64+ function sourceFacts ( row : SourceRow ) {
65+ const { documentName : title , sourceModifiedAt, sourceDate, sourceContainerName } = row
66+ const modifiedDiffers =
67+ sourceModifiedAt && ( ! sourceDate || Date . parse ( sourceModifiedAt ) !== Date . parse ( sourceDate ) )
68+ return {
69+ title,
70+ citationUrl : row . sourceUrl ,
71+ ...( modifiedDiffers ? { sourceModifiedAt } : { } ) ,
72+ sourceDate : sourceDate ?? null ,
73+ ...( sourceContainerName && sourceContainerName !== title ? { sourceContainerName } : { } ) ,
74+ }
75+ }
76+
5077/** One read_document result in the shape the tool returns it. */
5178function projectReadResult ( result : Awaited < ReturnType < typeof readLiveDocument . execute > > ) {
52- const { knowledgeBaseId : _knowledgeBaseId , knowledgeBaseName : _name , ...document } = result
79+ const {
80+ knowledgeBaseId : _knowledgeBaseId ,
81+ knowledgeBaseName : _name ,
82+ documentName : _documentName ,
83+ sourceUrl : _sourceUrl ,
84+ sourceModifiedAt : _sourceModifiedAt ,
85+ sourceContainerName : _sourceContainerName ,
86+ ...document
87+ } = result
5388 return {
5489 ...document ,
55- title : result . documentName ,
5690 citationId : liveCitationId ( result . documentId ) ,
57- citationUrl : result . sourceUrl ,
91+ ... sourceFacts ( result ) ,
5892 }
5993}
6094
95+ /** Characters of text a tool result hands the client. */
96+ function resultCharacters ( result : CallToolResult ) : number {
97+ return result . content . reduce (
98+ ( total , part ) => total + ( part . type === 'text' ? part . text . length : 0 ) ,
99+ 0
100+ )
101+ }
102+
61103interface KnowledgeMcpContext {
62104 organizationId : string
63105 request : NextRequest
@@ -74,6 +116,7 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe
74116 const startedAt = performance . now ( )
75117 const signal = AbortSignal . any ( [ request . signal , toolSignal ] )
76118 let outcome : SearchMcpActivityInput [ 'outcome' ] = 'error'
119+ let resultChars : number | undefined
77120 try {
78121 signal . throwIfAborted ( )
79122 const limited = await v2RateLimits . publicApi . enforce ( request , auth , operation , cost )
@@ -92,6 +135,7 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe
92135 }
93136 const result = await run ( new ResolvedSecretTraceRegistry ( ) , signal )
94137 outcome = signal . aborted ? 'cancelled' : result . isError ? 'error' : 'success'
138+ resultChars = resultCharacters ( result )
95139 return result
96140 } catch ( error ) {
97141 if ( signal . aborted ) outcome = 'cancelled'
@@ -118,6 +162,7 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe
118162 userId : activity . userId ,
119163 outcome,
120164 durationMs : activity . durationMs ,
165+ resultChars,
121166 } )
122167 afterResponse ( ( ) => recordOrganizationSearchMcpActivity ( activity ) )
123168 }
@@ -154,18 +199,12 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe
154199 {
155200 results : result . results . map ( ( row ) => ( {
156201 documentId : row . documentId ,
157- title : row . documentName ,
158- sourceUrl : row . sourceUrl ,
159202 citationId : liveCitationId ( row . documentId ) ,
160- citationUrl : row . sourceUrl ,
161- sourceModifiedAt : row . sourceModifiedAt ?? null ,
162- sourceDate : row . sourceDate ,
163- sourceContainerName : row . sourceContainerName ,
203+ ...sourceFacts ( row ) ,
164204 sourceContainerUrl : row . sourceContainerUrl ,
165205 ...( row . queryIndexes ? { queryIndexes : row . queryIndexes } : { } ) ,
166206 connectorType : row . connectorType ,
167207 content : row . content ,
168- chunkIndex : row . chunkIndex ,
169208 } ) ) ,
170209 retrieval : result . retrieval ,
171210 live : result . live ,
0 commit comments