From 6fbf301d3526db3a8c4d9d149397ed74689962b9 Mon Sep 17 00:00:00 2001 From: Michal Date: Mon, 23 Feb 2026 00:07:42 +0000 Subject: [PATCH] fix: show server name in instances table, allow logs by server name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Instance list now shows server NAME instead of cryptic server ID - Include server relation in findAll query (Prisma include) - Logs command accepts server name, server ID, or instance ID (resolves server name → first RUNNING instance) Co-Authored-By: Claude Opus 4.6 --- src/cli/src/commands/get.ts | 3 +- src/cli/src/commands/logs.ts | 34 +++++++++++++++++-- src/cli/tests/commands/get.test.ts | 4 ++- .../repositories/mcp-instance.repository.ts | 1 + 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/cli/src/commands/get.ts b/src/cli/src/commands/get.ts index 624ed93..ccefd42 100644 --- a/src/cli/src/commands/get.ts +++ b/src/cli/src/commands/get.ts @@ -42,6 +42,7 @@ interface TemplateRow { interface InstanceRow { id: string; serverId: string; + server?: { name: string }; status: string; containerId: string | null; port: number | null; @@ -78,9 +79,9 @@ const templateColumns: Column[] = [ ]; const instanceColumns: Column[] = [ + { header: 'NAME', key: (r) => r.server?.name ?? '-', width: 20 }, { header: 'STATUS', key: 'status', width: 10 }, { header: 'HEALTH', key: (r) => r.healthStatus ?? '-', width: 10 }, - { header: 'SERVER ID', key: 'serverId' }, { header: 'PORT', key: (r) => r.port != null ? String(r.port) : '-', width: 6 }, { header: 'CONTAINER', key: (r) => r.containerId ? r.containerId.slice(0, 12) : '-', width: 14 }, { header: 'ID', key: 'id' }, diff --git a/src/cli/src/commands/logs.ts b/src/cli/src/commands/logs.ts index 5b50083..8454426 100644 --- a/src/cli/src/commands/logs.ts +++ b/src/cli/src/commands/logs.ts @@ -6,15 +6,43 @@ export interface LogsCommandDeps { log: (...args: unknown[]) => void; } +/** + * Resolve a name/ID to an instance ID. + * Accepts: instance ID, server name, or server ID. + * For servers, picks the first RUNNING instance. + */ +async function resolveInstanceId(client: ApiClient, nameOrId: string): Promise { + // Try as instance ID first + try { + await client.get(`/api/v1/instances/${nameOrId}`); + return nameOrId; + } catch { + // Not a valid instance ID + } + + // Try as server name → find its instances + const servers = await client.get>('/api/v1/servers'); + const server = servers.find((s) => s.name === nameOrId || s.id === nameOrId); + if (server) { + const instances = await client.get>(`/api/v1/instances?serverId=${server.id}`); + const running = instances.find((i) => i.status === 'RUNNING') ?? instances[0]; + if (running) return running.id; + throw new Error(`No instances found for server '${nameOrId}'`); + } + + throw new Error(`Instance or server '${nameOrId}' not found`); +} + export function createLogsCommand(deps: LogsCommandDeps): Command { const { client, log } = deps; return new Command('logs') .description('Get logs from an MCP server instance') - .argument('', 'Instance ID') + .argument('', 'Server name, server ID, or instance ID') .option('-t, --tail ', 'Number of lines to show') - .action(async (id: string, opts: { tail?: string }) => { - let url = `/api/v1/instances/${id}/logs`; + .action(async (nameOrId: string, opts: { tail?: string }) => { + const instanceId = await resolveInstanceId(client, nameOrId); + let url = `/api/v1/instances/${instanceId}/logs`; if (opts.tail) { url += `?tail=${opts.tail}`; } diff --git a/src/cli/tests/commands/get.test.ts b/src/cli/tests/commands/get.test.ts index 793d712..e01de8b 100644 --- a/src/cli/tests/commands/get.test.ts +++ b/src/cli/tests/commands/get.test.ts @@ -69,11 +69,13 @@ describe('get command', () => { it('lists instances with correct columns', async () => { const deps = makeDeps([ - { id: 'inst-1', serverId: 'srv-1', status: 'RUNNING', containerId: 'abc123def456', port: 3000 }, + { id: 'inst-1', serverId: 'srv-1', server: { name: 'my-grafana' }, status: 'RUNNING', containerId: 'abc123def456', port: 3000 }, ]); const cmd = createGetCommand(deps); await cmd.parseAsync(['node', 'test', 'instances']); + expect(deps.output[0]).toContain('NAME'); expect(deps.output[0]).toContain('STATUS'); + expect(deps.output.join('\n')).toContain('my-grafana'); expect(deps.output.join('\n')).toContain('RUNNING'); }); diff --git a/src/mcpd/src/repositories/mcp-instance.repository.ts b/src/mcpd/src/repositories/mcp-instance.repository.ts index 0a6aa13..e0e8a37 100644 --- a/src/mcpd/src/repositories/mcp-instance.repository.ts +++ b/src/mcpd/src/repositories/mcp-instance.repository.ts @@ -11,6 +11,7 @@ export class McpInstanceRepository implements IMcpInstanceRepository { } return this.prisma.mcpInstance.findMany({ where, + include: { server: { select: { name: true } } }, orderBy: { createdAt: 'desc' }, }); }