feat: add server assembly and stdio entry
This commit is contained in:
1 parent
7514aedb1e
commit
6bf2aa74ca
6 files changed
+120
-8
No files matched your search
+10
@@ -0,0 +1,10 @@
|
|||||||
|
export const parseArgs = (
|
||||||
|
argv: string[],
|
||||||
|
env: NodeJS.ProcessEnv = process.env
|
||||||
|
): { configPath?: string } => {
|
||||||
|
const flagIndex = argv.indexOf('--config')
|
||||||
|
if (flagIndex !== -1 && argv[flagIndex + 1]) {
|
||||||
|
return { configPath: argv[flagIndex + 1] }
|
||||||
|
}
|
||||||
|
return { configPath: env.DBMOLE_CONFIG }
|
||||||
|
}
|
||||||
+35
-1
@@ -1 +1,35 @@
|
|||||||
export const placeholder = true
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
||||||
|
import { parseArgs } from './cli.js'
|
||||||
|
import { createRegistry } from './config/registry.js'
|
||||||
|
import { defaultStorePath } from './config/store.js'
|
||||||
|
import { createManager } from './db/manager.js'
|
||||||
|
import { createServer } from './server.js'
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
const { configPath } = parseArgs(process.argv.slice(2))
|
||||||
|
const registry = createRegistry({ storePath: defaultStorePath(), configPath })
|
||||||
|
const manager = createManager(registry)
|
||||||
|
const server = createServer(registry, manager)
|
||||||
|
|
||||||
|
let shuttingDown = false
|
||||||
|
const shutdown = async () => {
|
||||||
|
if (shuttingDown) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shuttingDown = true
|
||||||
|
await manager.disposeAll().catch(() => {})
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
process.on('SIGINT', shutdown)
|
||||||
|
process.on('SIGTERM', shutdown)
|
||||||
|
process.stdin.on('close', shutdown)
|
||||||
|
|
||||||
|
await server.connect(new StdioServerTransport())
|
||||||
|
console.error('dbmole: MCP server listening on stdio')
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
const message = error instanceof Error ? error.message : String(error)
|
||||||
|
console.error('dbmole: fatal: ' + message)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||||
|
import type { Registry } from './config/registry.js'
|
||||||
|
import type { Manager } from './db/manager.js'
|
||||||
|
import { registerConnectionTools } from './tools/connections.js'
|
||||||
|
import { registerQueryTools } from './tools/query.js'
|
||||||
|
import { registerSchemaTools } from './tools/schema.js'
|
||||||
|
|
||||||
|
export const SERVER_VERSION = '0.1.0'
|
||||||
|
|
||||||
|
export const createServer = (registry: Registry, manager: Manager): McpServer => {
|
||||||
|
const server = new McpServer({ name: 'dbmole-mcp', version: SERVER_VERSION })
|
||||||
|
registerConnectionTools(server, registry, manager)
|
||||||
|
registerQueryTools(server, manager)
|
||||||
|
registerSchemaTools(server, manager)
|
||||||
|
return server
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { parseArgs } from '../../src/cli.js'
|
||||||
|
|
||||||
|
describe('parseArgs', () => {
|
||||||
|
it('prefers the --config flag', () => {
|
||||||
|
expect(parseArgs(['--config', '/tmp/c.json'], { DBMOLE_CONFIG: '/env.json' })).toEqual({
|
||||||
|
configPath: '/tmp/c.json'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('falls back to DBMOLE_CONFIG env', () => {
|
||||||
|
expect(parseArgs([], { DBMOLE_CONFIG: '/env.json' })).toEqual({ configPath: '/env.json' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns undefined without flag or env', () => {
|
||||||
|
expect(parseArgs([], {})).toEqual({ configPath: undefined })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ignores --config without a value', () => {
|
||||||
|
expect(parseArgs(['--config'], {})).toEqual({ configPath: undefined })
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { mkdtempSync, rmSync } from 'node:fs'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||||
|
import { createRegistry } from '../../src/config/registry.js'
|
||||||
|
import { createServer } from '../../src/server.js'
|
||||||
|
import { connectClient, fakeManager } from './helpers.js'
|
||||||
|
|
||||||
|
describe('createServer', () => {
|
||||||
|
let dir: string
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dir = mkdtempSync(join(tmpdir(), 'dbmole-server-'))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
rmSync(dir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('registers all nine tools', async () => {
|
||||||
|
const registry = createRegistry({ storePath: join(dir, 'connections.json'), env: {} })
|
||||||
|
const server = createServer(registry, fakeManager())
|
||||||
|
const client = await connectClient(server)
|
||||||
|
const { tools } = await client.listTools()
|
||||||
|
expect(tools.map((tool) => tool.name).sort()).toEqual([
|
||||||
|
'add_connection',
|
||||||
|
'describe_table',
|
||||||
|
'execute_sql',
|
||||||
|
'list_connections',
|
||||||
|
'list_databases',
|
||||||
|
'list_tables',
|
||||||
|
'remove_connection',
|
||||||
|
'test_connection',
|
||||||
|
'update_connection'
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
|
||||||
|
|
||||||
describe('smoke', () => {
|
|
||||||
it('runs', () => {
|
|
||||||
expect(1 + 1).toBe(2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Reference in new issue
Block a user