sampling.mjs
78 lines 2.6 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 2 days ago
1 /**
2 * MCP Sampling helpers (Issue #1 Phase F).
3 * Generic wrapper around Server#createMessage for all sampling use cases.
4 * Falls back to null when the client does not advertise sampling capability.
5 */
6
7 /** @param {unknown} result @returns {string} */
8 export function samplingResultToText(result) {
9 const c = result?.content;
10 if (!c) return '';
11 if (typeof c === 'object' && !Array.isArray(c) && c.type === 'text' && typeof c.text === 'string') {
12 return c.text;
13 }
14 if (Array.isArray(c)) {
15 return c
16 .filter((b) => b && b.type === 'text' && typeof b.text === 'string')
17 .map((b) => b.text)
18 .join('\n');
19 }
20 return '';
21 }
22
23 /**
24 * Check whether the connected MCP client supports sampling.
25 * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} mcpServer
26 * @returns {boolean}
27 */
28 export function clientSupportsSampling(mcpServer) {
29 const caps = mcpServer.server.getClientCapabilities?.();
30 return Boolean(caps?.sampling);
31 }
32
33 /**
34 * Delegate an LLM completion to the MCP client via sampling.
35 * Returns trimmed text on success, or null when sampling is unavailable / fails.
36 *
37 * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} mcpServer
38 * @param {{ system: string, user: string, maxTokens?: number }} opts
39 * @returns {Promise<string | null>}
40 */
41 export async function trySampling(mcpServer, opts) {
42 if (!clientSupportsSampling(mcpServer)) return null;
43 const maxTokens = Math.max(1, Math.min(8192, Math.floor(opts.maxTokens ?? 512)));
44 try {
45 const result = await mcpServer.server.createMessage({
46 systemPrompt: opts.system,
47 messages: [{ role: 'user', content: { type: 'text', text: opts.user } }],
48 maxTokens,
49 includeContext: 'none',
50 });
51 const text = samplingResultToText(result).trim();
52 return text.length > 0 ? text : null;
53 } catch (_) {
54 return null;
55 }
56 }
57
58 /**
59 * Like trySampling but expects a JSON response. Parses and returns the object,
60 * or null when sampling is unavailable, fails, or produces invalid JSON.
61 *
62 * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} mcpServer
63 * @param {{ system: string, user: string, maxTokens?: number }} opts
64 * @returns {Promise<Record<string, unknown> | null>}
65 */
66 export async function trySamplingJson(mcpServer, opts) {
67 const raw = await trySampling(mcpServer, {
68 ...opts,
69 system: opts.system + '\n\nRespond ONLY with valid JSON. No markdown fences, no explanation.',
70 });
71 if (!raw) return null;
72 try {
73 const cleaned = raw.replace(/^```(?:json)?\s*/i, '').replace(/```\s*$/, '').trim();
74 return JSON.parse(cleaned);
75 } catch (_) {
76 return null;
77 }
78 }
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 2 days ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 2 days ago