Skip to content

Commit 61555b9

Browse files
committed
fix(cli): preserve MCP discovery metadata
1 parent 38f2ca6 commit 61555b9

2 files changed

Lines changed: 69 additions & 8 deletions

File tree

packages/devframe/src/cli/connect.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { DevframeDefinition } from '../types/devframe'
44
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'
55
import { afterEach, describe, expect, it } from 'vitest'
66
import { createDevServer } from '../adapters/dev'
7-
import { buildInstanceRequestHeaders, resolveAuthToken } from './connect'
7+
import { buildInstanceRequestHeaders, resolveAuthToken, selectInstanceRecord, toIndexedTool } from './connect'
88

99
const TOKEN = 'a-high-entropy-connect-test-token'
1010

@@ -60,6 +60,39 @@ describe('buildInstanceRequestHeaders', () => {
6060
})
6161
})
6262

63+
describe('selectInstanceRecord', () => {
64+
it('prefers an MCP-capable base when several instances share a port', () => {
65+
const withoutMcp = makeRecord({ id: 'ui-only', mcp: null })
66+
const withMcp = makeRecord({ id: 'vite-devtools', basePath: '/__devtools/' })
67+
68+
expect(selectInstanceRecord([withoutMcp, withMcp], 9999)).toBe(withMcp)
69+
})
70+
})
71+
72+
describe('connector discovery metadata', () => {
73+
it('keeps downstream tool schemas available to gateway agents', () => {
74+
expect(toIndexedTool({
75+
name: 'refetch',
76+
description: 'Refetch queries.',
77+
inputSchema: {
78+
type: 'object',
79+
properties: { arg0: { type: 'object' } },
80+
required: ['arg0'],
81+
},
82+
annotations: { readOnlyHint: false },
83+
})).toEqual({
84+
name: 'refetch',
85+
description: 'Refetch queries.',
86+
inputSchema: {
87+
type: 'object',
88+
properties: { arg0: { type: 'object' } },
89+
required: ['arg0'],
90+
},
91+
annotations: { readOnlyHint: false },
92+
})
93+
})
94+
})
95+
6396
describe('connector bearer against a live authenticated MCP route', () => {
6497
let server: StartedServer | undefined
6598

packages/devframe/src/cli/connect.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,19 @@ export interface ConnectServerHandle {
6666
}
6767

6868
/** One discovered instance in the `list-instances` payload: the registry record plus its probed MCP surface. */
69+
interface IndexedTool {
70+
name: string
71+
title?: string
72+
description?: string
73+
inputSchema: unknown
74+
outputSchema?: unknown
75+
annotations?: unknown
76+
}
77+
6978
interface IndexedInstance extends Omit<DevframeInstanceRecord, 'mcp'> {
7079
mcp: {
7180
url: string
72-
tools?: { name: string, description?: string }[]
81+
tools?: IndexedTool[]
7382
error?: string
7483
} | null
7584
hint?: string
@@ -243,16 +252,25 @@ async function probePort(port: number, timeoutMs?: number): Promise<DevframeInst
243252
}
244253
}
245254

246-
async function listInstanceTools(sdk: ConnectSdk, url: string, token: string | undefined): Promise<{ name: string, description?: string }[]> {
255+
async function listInstanceTools(sdk: ConnectSdk, url: string, token: string | undefined): Promise<IndexedTool[]> {
247256
return withInstanceClient(sdk, url, token, async (client) => {
248257
const listed = await client.listTools()
249-
return listed.tools.map((tool: { name: string, description?: string }) => ({
250-
name: tool.name,
251-
description: tool.description,
252-
}))
258+
return listed.tools.map(toIndexedTool)
253259
})
254260
}
255261

262+
/** Preserve downstream tool metadata needed by an agent before invocation. */
263+
export function toIndexedTool(tool: IndexedTool): IndexedTool {
264+
return {
265+
name: tool.name,
266+
...(tool.title ? { title: tool.title } : {}),
267+
...(tool.description ? { description: tool.description } : {}),
268+
inputSchema: tool.inputSchema,
269+
...(tool.outputSchema ? { outputSchema: tool.outputSchema } : {}),
270+
...(tool.annotations ? { annotations: tool.annotations } : {}),
271+
}
272+
}
273+
256274
async function call(
257275
sdk: ConnectSdk,
258276
options: ConnectServerOptions,
@@ -265,7 +283,8 @@ async function call(
265283
instancesDir: options.instancesDir,
266284
timeoutMs: options.timeoutMs,
267285
})
268-
const record = live.find(r => r.port === args.port) ?? await probePort(args.port, options.timeoutMs)
286+
const record = selectInstanceRecord(live, args.port)
287+
?? await probePort(args.port, options.timeoutMs)
269288
if (!record)
270289
throw diagnostics.DF0050({ port: args.port })
271290
if (!record.mcp)
@@ -284,6 +303,15 @@ async function call(
284303
})
285304
}
286305

306+
/** Select the agent-capable instance when several bases share one port. */
307+
export function selectInstanceRecord(
308+
records: readonly DevframeInstanceRecord[],
309+
port: number,
310+
): DevframeInstanceRecord | undefined {
311+
const matching = records.filter(record => record.port === port)
312+
return matching.find(record => record.mcp) ?? matching[0]
313+
}
314+
287315
async function withInstanceClient<T>(
288316
sdk: ConnectSdk,
289317
url: string,

0 commit comments

Comments
 (0)