feat: prompt section drill-down via prompts/get arguments
Extends section drill-down (previously tool-only) to work with prompts/get using _resultId + _section arguments. Shares the same section store as tool results, enabling cross-method drill-down. Large prompts (>2000 chars) are automatically split into sections. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
122
src/mcplocal/tests/smoke/prompt-drilldown.test.ts
Normal file
122
src/mcplocal/tests/smoke/prompt-drilldown.test.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Smoke tests: Prompt section drill-down.
|
||||
*
|
||||
* Verifies that large prompts served via prompts/get are section-split
|
||||
* and that subsequent calls with _resultId + _section return cached sections.
|
||||
*
|
||||
* Requires: mcplocal running on localhost:3200, mcpd on 10.0.0.194:3100
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { SmokeMcpSession, isMcplocalRunning } from './mcp-client.js';
|
||||
|
||||
const PROJECT_NAME = 'smoke-data';
|
||||
|
||||
describe('Smoke: Prompt section drill-down', () => {
|
||||
let available = false;
|
||||
let session: SmokeMcpSession;
|
||||
|
||||
beforeAll(async () => {
|
||||
available = await isMcplocalRunning();
|
||||
if (!available) return;
|
||||
|
||||
session = new SmokeMcpSession(PROJECT_NAME);
|
||||
await session.initialize();
|
||||
await session.sendNotification('notifications/initialized');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (session) await session.close();
|
||||
});
|
||||
|
||||
it('prompts/list returns available prompts', async () => {
|
||||
if (!available) return;
|
||||
|
||||
const result = await session.send('prompts/list') as { prompts: Array<{ name: string; description?: string }> };
|
||||
expect(result.prompts).toBeDefined();
|
||||
expect(Array.isArray(result.prompts)).toBe(true);
|
||||
// Should have at least mcpctl-managed prompts
|
||||
const mcpctlPrompts = result.prompts.filter((p) => p.name.startsWith('mcpctl/'));
|
||||
console.log(` Found ${result.prompts.length} prompts (${mcpctlPrompts.length} mcpctl-managed)`);
|
||||
});
|
||||
|
||||
it('prompts/get returns prompt content', async () => {
|
||||
if (!available) return;
|
||||
|
||||
const listResult = await session.send('prompts/list') as { prompts: Array<{ name: string }> };
|
||||
if (listResult.prompts.length === 0) {
|
||||
console.log(' No prompts available — skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
const promptName = listResult.prompts[0].name;
|
||||
const getResult = await session.send('prompts/get', { name: promptName }) as {
|
||||
messages?: Array<{ role: string; content: unknown }>;
|
||||
};
|
||||
|
||||
expect(getResult.messages).toBeDefined();
|
||||
expect(getResult.messages!.length).toBeGreaterThan(0);
|
||||
console.log(` prompts/get "${promptName}": ${getResult.messages!.length} message(s)`);
|
||||
});
|
||||
|
||||
it('large prompt response includes section TOC with _resultId', async () => {
|
||||
if (!available) return;
|
||||
|
||||
// Find a mcpctl-managed prompt (these tend to be large system prompts)
|
||||
const listResult = await session.send('prompts/list') as { prompts: Array<{ name: string }> };
|
||||
const mcpctlPrompts = listResult.prompts.filter((p) => p.name.startsWith('mcpctl/'));
|
||||
|
||||
if (mcpctlPrompts.length === 0) {
|
||||
console.log(' No mcpctl prompts — skipping section drill-down test');
|
||||
return;
|
||||
}
|
||||
|
||||
// Try each prompt to find one large enough to be section-split
|
||||
let foundSections = false;
|
||||
for (const prompt of mcpctlPrompts) {
|
||||
const getResult = await session.send('prompts/get', { name: prompt.name }) as {
|
||||
messages?: Array<{ role: string; content: unknown }>;
|
||||
};
|
||||
|
||||
if (!getResult.messages || getResult.messages.length === 0) continue;
|
||||
|
||||
const msg = getResult.messages[0];
|
||||
const text = typeof msg.content === 'string'
|
||||
? msg.content
|
||||
: (msg.content as { text?: string }).text ?? '';
|
||||
|
||||
if (text.includes('_resultId')) {
|
||||
foundSections = true;
|
||||
console.log(` "${prompt.name}": section-split TOC detected (${text.length} chars)`);
|
||||
console.log(` TOC preview: ${text.slice(0, 200)}...`);
|
||||
|
||||
// Extract _resultId
|
||||
const match = /_resultId:\s*(pm-[a-z0-9]+)/.exec(text);
|
||||
if (match) {
|
||||
const resultId = match[1];
|
||||
// Extract first section id from TOC
|
||||
const sectionMatch = /\[([^\]]+)\]/.exec(text);
|
||||
if (sectionMatch) {
|
||||
const sectionId = sectionMatch[1];
|
||||
console.log(` Drilling into section "${sectionId}" with resultId "${resultId}"...`);
|
||||
|
||||
// Drill down
|
||||
const drillResult = await session.send('prompts/get', {
|
||||
name: prompt.name,
|
||||
arguments: { _resultId: resultId, _section: sectionId },
|
||||
}) as { content?: Array<{ text: string }> };
|
||||
|
||||
expect(drillResult).toBeDefined();
|
||||
console.log(` Drill-down returned: ${JSON.stringify(drillResult).length} chars`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
console.log(` "${prompt.name}": ${text.length} chars (not section-split — likely too small)`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundSections) {
|
||||
console.log(' No prompts large enough for section-split — test inconclusive but not failing');
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user