fix(mcp): handle legacy segment summaries without migration

This commit is contained in:
moesnow authored and digua committed 2026-10-24 16:26:22 +08:00
1 parent eda2694261
commit bedc8f1d71
2 files changed
+112 -1

No files matched your search

+4 -1
View File
@@ -317,7 +317,10 @@ export function getSegmentSummaries(
db: DatabaseAdapter,
options?: { limit?: number; timeFilter?: TimeFilter }
): SegmentSummaryData[] {
if (!hasTable(db, 'segment')) return []
// Schema v10 introduced summary_message_count. Earlier summaries have unknown
// coverage and are treated as stale by the v10 migration; do the same for
// read-only callers without mutating older databases.
if (!hasTable(db, 'segment') || !hasColumn(db, 'segment', 'summary_message_count')) return []
const { limit = 50, timeFilter } = options ?? {}
+108
View File
@@ -7,6 +7,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import Database from 'better-sqlite3'
import { CHAT_DB_TABLES } from '@openchatlab/core'
import { MCP_TOOL_REGISTRY } from '@openchatlab/tools'
import { DatabaseManager, NodePathProvider, readDataDirCompatibilityMeta } from '@openchatlab/node-runtime'
import { registerTools } from './server'
@@ -128,3 +129,110 @@ test('MCP queries do not migrate a pending-schema session or raise the data comp
after.close()
}
})
test('segment summaries handle v9 pending schema without migration and preserve v10 results', async (t) => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chatlab-mcp-v9-summaries-'))
t.after(() => fs.rmSync(root, { recursive: true, force: true }))
const dataDir = path.join(root, 'data')
const dbDir = path.join(dataDir, 'databases')
fs.mkdirSync(dbDir, { recursive: true })
const legacyPath = path.join(dbDir, 'v9.db')
const legacyDb = new Database(legacyPath)
try {
legacyDb.exec(`
CREATE TABLE meta (
name TEXT NOT NULL, platform TEXT NOT NULL, type TEXT NOT NULL,
imported_at INTEGER NOT NULL, schema_version INTEGER DEFAULT 9
);
INSERT INTO meta (name, platform, type, imported_at, schema_version)
VALUES ('v9 chat', 'qq', 'group', 1000, 9);
CREATE TABLE member (id INTEGER PRIMARY KEY, platform_id TEXT UNIQUE, account_name TEXT);
INSERT INTO member VALUES (1, 'legacy-user', 'Alice');
CREATE TABLE message (id INTEGER PRIMARY KEY, sender_id INTEGER, ts INTEGER, type INTEGER, content TEXT);
INSERT INTO message VALUES (1, 1, 1000, 0, 'hello');
CREATE TABLE segment (
id INTEGER PRIMARY KEY, start_ts INTEGER, end_ts INTEGER,
message_count INTEGER, summary TEXT
);
INSERT INTO segment VALUES (1, 1000, 1000, 1, 'Old unverified summary');
CREATE TABLE message_context (message_id INTEGER PRIMARY KEY, segment_id INTEGER, topic_id INTEGER);
INSERT INTO message_context VALUES (1, 1, NULL);
`)
} finally {
legacyDb.close()
}
const currentPath = path.join(dbDir, 'v10.db')
const currentDb = new Database(currentPath)
try {
currentDb.exec(CHAT_DB_TABLES)
currentDb.exec(`
INSERT INTO meta (name, platform, type, imported_at)
VALUES ('v10 chat', 'qq', 'group', 1000);
INSERT INTO member (id, platform_id, account_name) VALUES (1, 'new-user', 'Bob');
INSERT INTO message (id, sender_id, ts, type, content) VALUES (1, 1, 1000, 0, 'hello');
INSERT INTO segment (id, start_ts, end_ts, message_count, summary, summary_message_count)
VALUES (1, 1000, 1000, 1, 'Verified summary', 1);
INSERT INTO message_context (message_id, segment_id, topic_id) VALUES (1, 1, NULL);
`)
} finally {
currentDb.close()
}
const beforeV9 = fs.readFileSync(legacyPath)
const beforeV10 = fs.readFileSync(currentPath)
assert.equal(readDataDirCompatibilityMeta(dataDir), null)
const dbManager = new DatabaseManager(new NodePathProvider(dataDir), {
runtime: { version: '0.37.6', kind: 'mcp' },
})
t.after(() => dbManager.closeAll())
const server = new McpServer({ name: 'chatlab-test', version: '0.37.6' })
const client = new Client({ name: 'chatlab-test-client', version: '0.0.0' })
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair()
try {
registerTools(server, dbManager)
await server.connect(serverTransport)
await client.connect(clientTransport)
const oldResult = await client.callTool({
name: 'chatlab_get_segment_summaries',
arguments: { session_id: 'v9', format: 'json' },
})
assert.equal(oldResult.isError, undefined, JSON.stringify(oldResult.content))
assert.match((oldResult.content as Array<{ text: string }>)[0].text, /No segments with summaries found/)
assert.deepEqual(fs.readFileSync(legacyPath), beforeV9)
assert.equal(readDataDirCompatibilityMeta(dataDir), null)
const currentResult = await client.callTool({
name: 'chatlab_get_segment_summaries',
arguments: { session_id: 'v10', format: 'json' },
})
assert.equal(currentResult.isError, undefined, JSON.stringify(currentResult.content))
const parsed = JSON.parse((currentResult.content as Array<{ text: string }>)[0].text)
assert.equal(parsed.total, 1)
assert.equal(parsed.segments[0].summary, 'Verified summary')
assert.deepEqual(parsed.segments[0].participants, ['Bob'])
assert.deepEqual(fs.readFileSync(currentPath), beforeV10)
assert.equal(readDataDirCompatibilityMeta(dataDir), null)
} finally {
await client.close()
await server.close()
}
const verifyLegacy = new Database(legacyPath, { readonly: true })
try {
assert.equal(
(verifyLegacy.prepare('SELECT schema_version FROM meta').get() as { schema_version: number }).schema_version,
9
)
assert.equal(
verifyLegacy.prepare("SELECT name FROM pragma_table_info('segment') WHERE name='summary_message_count'").get(),
undefined
)
} finally {
verifyLegacy.close()
}
})