mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-28 10:13:10 +08:00
refactor(mcp): extract MCP core into shared chatlab-mcp package
Move MCP Server logic (tool registration, resource registration, schema conversion) from apps/cli/src/mcp.ts to packages/mcp-server so both CLI and Desktop can share the same MCP implementation. - New package: chatlab-mcp (packages/mcp-server) with McpDatabaseManager interface abstraction - CLI mcp.ts slimmed to thin entry (~37 lines) calling startMcpServer() - Desktop helper prototype at apps/desktop/helper/mcp.ts for dev-time MCP without GUI - All 8 MCP tools and 3 resources unchanged Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -74,6 +74,7 @@
|
||||
"@openchatlab/sync": "workspace:*",
|
||||
"@openchatlab/tools": "workspace:*",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"chatlab-mcp": "workspace:*",
|
||||
"tsup": "^8.5.0",
|
||||
"tsx": "^4.21.0"
|
||||
}
|
||||
|
||||
+2
-2
@@ -269,8 +269,8 @@ program
|
||||
.command('mcp')
|
||||
.description('Start MCP Server (stdio transport, for Claude Desktop / Cursor / AI agents)')
|
||||
.action(async () => {
|
||||
const { startMcpServer } = await import('./mcp')
|
||||
await startMcpServer()
|
||||
const { startCliMcpServer } = await import('./mcp')
|
||||
await startCliMcpServer()
|
||||
})
|
||||
|
||||
program
|
||||
|
||||
+8
-189
@@ -1,14 +1,9 @@
|
||||
/**
|
||||
* ChatLab MCP Server
|
||||
* CLI MCP Server entry point
|
||||
*
|
||||
* 通过 stdio 传输协议与 AI 代理(Claude Desktop、Cursor 等)通信。
|
||||
* 注册 @openchatlab/tools 中的工具为 MCP tools,
|
||||
* 会话列表作为 MCP resources 暴露。
|
||||
* Thin wrapper: initializes Node.js runtime, then delegates to chatlab-mcp.
|
||||
*/
|
||||
|
||||
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
||||
import { z } from 'zod'
|
||||
import { loadConfig } from '@openchatlab/config'
|
||||
import {
|
||||
NodePathProvider,
|
||||
@@ -16,16 +11,10 @@ import {
|
||||
hasPendingElectronDataWarning,
|
||||
verifyCliDataPath,
|
||||
} from '@openchatlab/node-runtime'
|
||||
import { getSessionMeta, getSessionOverview, getDatabaseSchema } from '@openchatlab/core'
|
||||
import { MCP_TOOL_REGISTRY, CoreDataProvider } from '@openchatlab/tools'
|
||||
import type { SessionListContext } from '@openchatlab/tools/src/definitions/sessions'
|
||||
import { startMcpServer } from 'chatlab-mcp'
|
||||
import { getVersion } from './version'
|
||||
|
||||
const MCP_TOOL_PREFIX = 'chatlab_'
|
||||
|
||||
let dbManager: DatabaseManager
|
||||
|
||||
function initMcpRuntime() {
|
||||
function initMcpRuntime(): DatabaseManager {
|
||||
const config = loadConfig()
|
||||
const userDataDir = config.data.user_data_dir || undefined
|
||||
const pathProvider = new NodePathProvider(userDataDir)
|
||||
@@ -37,180 +26,10 @@ function initMcpRuntime() {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
dbManager = new DatabaseManager(pathProvider)
|
||||
return { config, dbManager }
|
||||
return new DatabaseManager(pathProvider)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将简单的 JSON Schema 属性转为 Zod schema 对象
|
||||
*/
|
||||
function jsonSchemaToZod(
|
||||
properties: Record<string, { type: string; description?: string; default?: unknown; enum?: unknown[] }>,
|
||||
required?: string[]
|
||||
): Record<string, z.ZodTypeAny> {
|
||||
const shape: Record<string, z.ZodTypeAny> = {}
|
||||
const requiredSet = new Set(required ?? [])
|
||||
|
||||
for (const [key, prop] of Object.entries(properties)) {
|
||||
let zodType: z.ZodTypeAny
|
||||
|
||||
switch (prop.type) {
|
||||
case 'number':
|
||||
zodType = z.number().describe(prop.description ?? '')
|
||||
break
|
||||
case 'boolean':
|
||||
zodType = z.boolean().describe(prop.description ?? '')
|
||||
break
|
||||
case 'string':
|
||||
default:
|
||||
if (prop.enum) {
|
||||
zodType = z.enum(prop.enum as [string, ...string[]]).describe(prop.description ?? '')
|
||||
} else {
|
||||
zodType = z.string().describe(prop.description ?? '')
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (!requiredSet.has(key)) {
|
||||
zodType = zodType.optional()
|
||||
}
|
||||
|
||||
shape[key] = zodType
|
||||
}
|
||||
|
||||
return shape
|
||||
}
|
||||
|
||||
export async function startMcpServer(): Promise<void> {
|
||||
initMcpRuntime()
|
||||
|
||||
const server = new McpServer({
|
||||
name: 'chatlab',
|
||||
version: getVersion(),
|
||||
})
|
||||
|
||||
// --- 注册 Tools ---
|
||||
|
||||
for (const tool of MCP_TOOL_REGISTRY) {
|
||||
const mcpName = `${MCP_TOOL_PREFIX}${tool.name}`
|
||||
|
||||
if (tool.name === 'list_sessions') {
|
||||
const zodShape = jsonSchemaToZod(tool.inputSchema.properties, tool.inputSchema.required)
|
||||
|
||||
server.tool(mcpName, tool.description, zodShape, async (params) => {
|
||||
const context: SessionListContext = {
|
||||
db: null as any,
|
||||
sessionId: '',
|
||||
listSessionIds: () => dbManager.listSessionIds(),
|
||||
openDb: (id) => dbManager.open(id),
|
||||
}
|
||||
const result = await tool.handler(params as Record<string, unknown>, context)
|
||||
return { content: [{ type: 'text' as const, text: result.content }] }
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
const sessionsToolMcpName = `${MCP_TOOL_PREFIX}list_sessions`
|
||||
const zodShape = {
|
||||
session_id: z.string().describe(`Session ID (use ${sessionsToolMcpName} to get available sessions)`),
|
||||
...jsonSchemaToZod(tool.inputSchema.properties, tool.inputSchema.required),
|
||||
}
|
||||
|
||||
server.tool(mcpName, tool.description, zodShape, async (params) => {
|
||||
const sessionId = params.session_id as string
|
||||
const db = dbManager.open(sessionId)
|
||||
if (!db) {
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: JSON.stringify({ error: `Session ${sessionId} not found` }) }],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
const toolParams = { ...params } as Record<string, unknown>
|
||||
delete toolParams.session_id
|
||||
|
||||
const result = await tool.handler(toolParams, { db, sessionId, dataProvider: new CoreDataProvider(db) })
|
||||
return { content: [{ type: 'text' as const, text: result.content }] }
|
||||
})
|
||||
}
|
||||
|
||||
// --- 注册 Resources ---
|
||||
|
||||
server.resource('sessions-list', 'chatlab://sessions', { description: '所有已导入的聊天会话列表' }, async () => {
|
||||
const sessionIds = dbManager.listSessionIds()
|
||||
const sessions = sessionIds
|
||||
.map((id) => {
|
||||
const db = dbManager.open(id)
|
||||
if (!db) return null
|
||||
const meta = getSessionMeta(db)
|
||||
if (!meta) return null
|
||||
return { id, name: meta.name, platform: meta.platform, type: meta.type }
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: 'chatlab://sessions',
|
||||
text: JSON.stringify(sessions, null, 2),
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
server.resource(
|
||||
'session-meta',
|
||||
new ResourceTemplate('chatlab://sessions/{sessionId}/meta', { list: undefined }),
|
||||
{ description: '会话元信息(名称、平台、消息数等)' },
|
||||
async (uri, params) => {
|
||||
const sessionId = params.sessionId as string
|
||||
const db = dbManager.open(sessionId)
|
||||
if (!db) {
|
||||
return { contents: [{ uri: uri.href, text: '{"error": "Session not found"}', mimeType: 'application/json' }] }
|
||||
}
|
||||
|
||||
const meta = getSessionMeta(db)
|
||||
const overview = getSessionOverview(db)
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify({ ...meta, ...overview }, null, 2),
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
server.resource(
|
||||
'session-schema',
|
||||
new ResourceTemplate('chatlab://sessions/{sessionId}/schema', { list: undefined }),
|
||||
{ description: '会话数据库的表结构' },
|
||||
async (uri, params) => {
|
||||
const sessionId = params.sessionId as string
|
||||
const db = dbManager.open(sessionId)
|
||||
if (!db) {
|
||||
return { contents: [{ uri: uri.href, text: '{"error": "Session not found"}', mimeType: 'application/json' }] }
|
||||
}
|
||||
|
||||
const schema = getDatabaseSchema(db)
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify(schema, null, 2),
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// --- 启动 stdio 传输 ---
|
||||
const transport = new StdioServerTransport()
|
||||
await server.connect(transport)
|
||||
export async function startCliMcpServer(): Promise<void> {
|
||||
const dbManager = initMcpRuntime()
|
||||
await startMcpServer({ version: getVersion(), dbManager })
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"../../packages/config/**/*",
|
||||
"../../packages/parser/**/*",
|
||||
"../../packages/tools/**/*",
|
||||
"../../packages/sync/**/*"
|
||||
"../../packages/sync/**/*",
|
||||
"../../packages/mcp-server/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export default defineConfig({
|
||||
clean: true,
|
||||
target: 'node20',
|
||||
platform: 'node',
|
||||
noExternal: [/^@openchatlab\//, 'stream-json'],
|
||||
noExternal: [/^@openchatlab\//, 'chatlab-mcp', 'stream-json'],
|
||||
external: ['better-sqlite3', '@node-rs/jieba'],
|
||||
banner: {
|
||||
js: [
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Desktop MCP Helper entry point
|
||||
*
|
||||
* Standalone stdio MCP server that shares the same data directory as
|
||||
* the desktop app. Intended to be invoked by MCP clients (Claude Desktop,
|
||||
* Cursor, etc.) without launching the GUI.
|
||||
*
|
||||
* Dev usage:
|
||||
* pnpm --filter @openchatlab/desktop run mcp
|
||||
*/
|
||||
|
||||
import { loadConfig } from '@openchatlab/config'
|
||||
import { NodePathProvider, DatabaseManager } from '@openchatlab/node-runtime'
|
||||
import { startMcpServer } from 'chatlab-mcp'
|
||||
|
||||
const config = loadConfig()
|
||||
const userDataDir = config.data.user_data_dir || undefined
|
||||
const pathProvider = new NodePathProvider(userDataDir)
|
||||
pathProvider.ensureAllDirs()
|
||||
const dbManager = new DatabaseManager(pathProvider)
|
||||
|
||||
const version = process.env.npm_package_version ?? '0.0.0-dev'
|
||||
|
||||
startMcpServer({ version, dbManager })
|
||||
@@ -11,7 +11,8 @@
|
||||
"preview": "electron-vite preview",
|
||||
"build": "electron-vite build",
|
||||
"build:mac": "pnpm run build && electron-builder --mac --config electron-builder.yml -p never",
|
||||
"build:win": "pnpm run build && electron-builder --win --config electron-builder.yml -p never"
|
||||
"build:win": "pnpm run build && electron-builder --win --config electron-builder.yml -p never",
|
||||
"mcp": "tsx helper/mcp.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aptabase/electron": "^0.3.1",
|
||||
@@ -26,8 +27,10 @@
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@electron/rebuild": "^4.0.4",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"chatlab-mcp": "workspace:*",
|
||||
"electron": "35.7.5",
|
||||
"electron-builder": "^26.0.12",
|
||||
"electron-vite": "^5.0.0"
|
||||
"electron-vite": "^5.0.0",
|
||||
"tsx": "^4.21.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"electron.vite.config.*",
|
||||
"main/**/*",
|
||||
"preload/**/*",
|
||||
"helper/**/*",
|
||||
"shared/**/*",
|
||||
"../../src/types/**/*",
|
||||
"../../packages/shared-types/**/*",
|
||||
@@ -13,7 +14,8 @@
|
||||
"../../packages/parser/**/*",
|
||||
"../../apps/cli/**/*",
|
||||
"../../packages/tools/**/*",
|
||||
"../../packages/sync/**/*"
|
||||
"../../packages/sync/**/*",
|
||||
"../../packages/mcp-server/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
@@ -23,7 +25,8 @@
|
||||
"paths": {
|
||||
"@/*": ["../../src/*"],
|
||||
"@electron/*": ["./*"],
|
||||
"@openchatlab/*": ["../../packages/*"]
|
||||
"@openchatlab/*": ["../../packages/*"],
|
||||
"chatlab-mcp": ["../../packages/mcp-server/src"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "chatlab-mcp",
|
||||
"version": "0.1.0",
|
||||
"description": "ChatLab MCP Server — shared core for CLI and Desktop",
|
||||
"main": "./dist/index.mjs",
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ChatLab/ChatLab.git",
|
||||
"directory": "packages/mcp-server"
|
||||
},
|
||||
"homepage": "https://github.com/ChatLab/ChatLab",
|
||||
"keywords": [
|
||||
"chatlab",
|
||||
"mcp",
|
||||
"model-context-protocol",
|
||||
"chat",
|
||||
"analysis"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openchatlab/core": "workspace:*",
|
||||
"@openchatlab/tools": "workspace:*",
|
||||
"tsup": "^8.5.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* chatlab-mcp
|
||||
*
|
||||
* Shared MCP Server core for CLI and Desktop helper.
|
||||
* Registers ChatLab tools and resources over stdio transport.
|
||||
*/
|
||||
|
||||
export { startMcpServer } from './server'
|
||||
export type { McpServerOptions, McpDatabaseManager } from './types'
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* JSON Schema → Zod conversion for MCP tool registration
|
||||
*/
|
||||
|
||||
import { z } from 'zod'
|
||||
|
||||
/**
|
||||
* Convert simple JSON Schema properties to a Zod shape object.
|
||||
* Supports string / number / boolean / enum types.
|
||||
*/
|
||||
export function jsonSchemaToZod(
|
||||
properties: Record<string, { type: string; description?: string; default?: unknown; enum?: unknown[] }>,
|
||||
required?: string[]
|
||||
): Record<string, z.ZodTypeAny> {
|
||||
const shape: Record<string, z.ZodTypeAny> = {}
|
||||
const requiredSet = new Set(required ?? [])
|
||||
|
||||
for (const [key, prop] of Object.entries(properties)) {
|
||||
let zodType: z.ZodTypeAny
|
||||
|
||||
switch (prop.type) {
|
||||
case 'number':
|
||||
zodType = z.number().describe(prop.description ?? '')
|
||||
break
|
||||
case 'boolean':
|
||||
zodType = z.boolean().describe(prop.description ?? '')
|
||||
break
|
||||
case 'string':
|
||||
default:
|
||||
if (prop.enum) {
|
||||
zodType = z.enum(prop.enum as [string, ...string[]]).describe(prop.description ?? '')
|
||||
} else {
|
||||
zodType = z.string().describe(prop.description ?? '')
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (!requiredSet.has(key)) {
|
||||
zodType = zodType.optional()
|
||||
}
|
||||
|
||||
shape[key] = zodType
|
||||
}
|
||||
|
||||
return shape
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* ChatLab MCP Server core
|
||||
*
|
||||
* Registers @openchatlab/tools as MCP tools and exposes session data as MCP resources.
|
||||
* Communicates with AI agents (Claude Desktop, Cursor, etc.) via stdio transport.
|
||||
*/
|
||||
|
||||
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
||||
import { z } from 'zod'
|
||||
import { getSessionMeta, getSessionOverview, getDatabaseSchema } from '@openchatlab/core'
|
||||
import { MCP_TOOL_REGISTRY, CoreDataProvider } from '@openchatlab/tools'
|
||||
import type { SessionListContext } from '@openchatlab/tools/src/definitions/sessions'
|
||||
import type { McpDatabaseManager, McpServerOptions } from './types'
|
||||
import { jsonSchemaToZod } from './schema'
|
||||
|
||||
const MCP_TOOL_PREFIX = 'chatlab_'
|
||||
|
||||
function registerTools(server: McpServer, dbManager: McpDatabaseManager): void {
|
||||
for (const tool of MCP_TOOL_REGISTRY) {
|
||||
const mcpName = `${MCP_TOOL_PREFIX}${tool.name}`
|
||||
|
||||
if (tool.name === 'list_sessions') {
|
||||
const zodShape = jsonSchemaToZod(tool.inputSchema.properties, tool.inputSchema.required)
|
||||
|
||||
server.tool(mcpName, tool.description, zodShape, async (params) => {
|
||||
const context: SessionListContext = {
|
||||
db: null as any,
|
||||
sessionId: '',
|
||||
listSessionIds: () => dbManager.listSessionIds(),
|
||||
openDb: (id) => dbManager.open(id),
|
||||
}
|
||||
const result = await tool.handler(params as Record<string, unknown>, context)
|
||||
return { content: [{ type: 'text' as const, text: result.content }] }
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
const sessionsToolMcpName = `${MCP_TOOL_PREFIX}list_sessions`
|
||||
const zodShape = {
|
||||
session_id: z.string().describe(`Session ID (use ${sessionsToolMcpName} to get available sessions)`),
|
||||
...jsonSchemaToZod(tool.inputSchema.properties, tool.inputSchema.required),
|
||||
}
|
||||
|
||||
server.tool(mcpName, tool.description, zodShape, async (params) => {
|
||||
const sessionId = params.session_id as string
|
||||
const db = dbManager.open(sessionId)
|
||||
if (!db) {
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: JSON.stringify({ error: `Session ${sessionId} not found` }) }],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
|
||||
const toolParams = { ...params } as Record<string, unknown>
|
||||
delete toolParams.session_id
|
||||
|
||||
const result = await tool.handler(toolParams, { db, sessionId, dataProvider: new CoreDataProvider(db) })
|
||||
return { content: [{ type: 'text' as const, text: result.content }] }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function registerResources(server: McpServer, dbManager: McpDatabaseManager): void {
|
||||
server.resource('sessions-list', 'chatlab://sessions', { description: '所有已导入的聊天会话列表' }, async () => {
|
||||
const sessionIds = dbManager.listSessionIds()
|
||||
const sessions = sessionIds
|
||||
.map((id) => {
|
||||
const db = dbManager.open(id)
|
||||
if (!db) return null
|
||||
const meta = getSessionMeta(db)
|
||||
if (!meta) return null
|
||||
return { id, name: meta.name, platform: meta.platform, type: meta.type }
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: 'chatlab://sessions',
|
||||
text: JSON.stringify(sessions, null, 2),
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
server.resource(
|
||||
'session-meta',
|
||||
new ResourceTemplate('chatlab://sessions/{sessionId}/meta', { list: undefined }),
|
||||
{ description: '会话元信息(名称、平台、消息数等)' },
|
||||
async (uri, params) => {
|
||||
const sessionId = params.sessionId as string
|
||||
const db = dbManager.open(sessionId)
|
||||
if (!db) {
|
||||
return { contents: [{ uri: uri.href, text: '{"error": "Session not found"}', mimeType: 'application/json' }] }
|
||||
}
|
||||
|
||||
const meta = getSessionMeta(db)
|
||||
const overview = getSessionOverview(db)
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify({ ...meta, ...overview }, null, 2),
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
server.resource(
|
||||
'session-schema',
|
||||
new ResourceTemplate('chatlab://sessions/{sessionId}/schema', { list: undefined }),
|
||||
{ description: '会话数据库的表结构' },
|
||||
async (uri, params) => {
|
||||
const sessionId = params.sessionId as string
|
||||
const db = dbManager.open(sessionId)
|
||||
if (!db) {
|
||||
return { contents: [{ uri: uri.href, text: '{"error": "Session not found"}', mimeType: 'application/json' }] }
|
||||
}
|
||||
|
||||
const schema = getDatabaseSchema(db)
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify(schema, null, 2),
|
||||
mimeType: 'application/json',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export async function startMcpServer(options: McpServerOptions): Promise<void> {
|
||||
const { version, name = 'chatlab', dbManager } = options
|
||||
|
||||
const server = new McpServer({ name, version })
|
||||
|
||||
registerTools(server, dbManager)
|
||||
registerResources(server, dbManager)
|
||||
|
||||
const transport = new StdioServerTransport()
|
||||
await server.connect(transport)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* MCP Server public types
|
||||
*/
|
||||
|
||||
import type { DatabaseAdapter } from '@openchatlab/core'
|
||||
|
||||
/**
|
||||
* Minimal database manager interface for MCP Server.
|
||||
* Both CLI's DatabaseManager and Desktop's helper can satisfy this contract.
|
||||
*/
|
||||
export interface McpDatabaseManager {
|
||||
listSessionIds(): string[]
|
||||
open(sessionId: string): DatabaseAdapter | null
|
||||
}
|
||||
|
||||
export interface McpServerOptions {
|
||||
version: string
|
||||
/** MCP server name exposed to clients (default: 'chatlab') */
|
||||
name?: string
|
||||
dbManager: McpDatabaseManager
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../../tsconfig.node.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"../../packages/shared-types/**/*",
|
||||
"../../packages/core/**/*",
|
||||
"../../packages/tools/**/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
},
|
||||
format: ['esm'],
|
||||
dts: false,
|
||||
outDir: 'dist',
|
||||
outExtension: () => ({ js: '.mjs' }),
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
target: 'node20',
|
||||
platform: 'node',
|
||||
noExternal: [/^@openchatlab\//],
|
||||
})
|
||||
Generated
+28
@@ -158,6 +158,9 @@ importers:
|
||||
'@types/better-sqlite3':
|
||||
specifier: ^7.6.13
|
||||
version: 7.6.13
|
||||
chatlab-mcp:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/mcp-server
|
||||
tsup:
|
||||
specifier: ^8.5.0
|
||||
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
|
||||
@@ -198,6 +201,9 @@ importers:
|
||||
'@types/better-sqlite3':
|
||||
specifier: ^7.6.13
|
||||
version: 7.6.13
|
||||
chatlab-mcp:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/mcp-server
|
||||
electron:
|
||||
specifier: 35.7.5
|
||||
version: 35.7.5
|
||||
@@ -207,6 +213,9 @@ importers:
|
||||
electron-vite:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0(vite@7.3.3(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||
tsx:
|
||||
specifier: ^4.21.0
|
||||
version: 4.21.0
|
||||
|
||||
packages/config:
|
||||
dependencies:
|
||||
@@ -219,6 +228,25 @@ importers:
|
||||
|
||||
packages/core: {}
|
||||
|
||||
packages/mcp-server:
|
||||
dependencies:
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: ^1.12.1
|
||||
version: 1.29.0(zod@3.25.76)
|
||||
zod:
|
||||
specifier: ^3.24.4
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@openchatlab/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
'@openchatlab/tools':
|
||||
specifier: workspace:*
|
||||
version: link:../tools
|
||||
tsup:
|
||||
specifier: ^8.5.0
|
||||
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
|
||||
|
||||
packages/node-runtime:
|
||||
dependencies:
|
||||
'@mariozechner/pi-agent-core':
|
||||
|
||||
+2
-1
@@ -5,7 +5,8 @@
|
||||
"moduleResolution": "bundler",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@openchatlab/*": ["packages/*"]
|
||||
"@openchatlab/*": ["packages/*"],
|
||||
"chatlab-mcp": ["packages/mcp-server/src"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user