mcp --http --public: key-less public server (skills, pitfalls, limits, listing lint, App Store search), rate limit, 64 KB bodies

This commit is contained in:
CRTkafa 2026-10-29 18:54:58 +07:00
parent 398d8cd375
commit c500866295
8 changed files with 695 additions and 15 deletions

View file

@ -56,6 +56,13 @@ export const INSTRUCTIONS = [
'Run `check` (offline) and `validate` (read-only) before writes. Long uploads (images, screenshots) are better run in a terminal with a log.',
].join(' ');
/** A replacement tool surface (the public key-less server, mcp-public.ts): its own instructions, tool list and calls. */
export interface Surface {
instructions: string;
list(): Record<string, unknown>[];
call(name: string, input: unknown): Promise<{ text: string; data?: unknown; isError?: boolean } | null>;
}
type Id = string | number;
interface Msg { jsonrpc: '2.0'; id?: Id; method?: string; params?: Record<string, unknown> }
@ -71,7 +78,7 @@ export function eraOf(m: Msg): { era: 'modern' | 'legacy'; version: string | nul
}
/** Handles one message; returns the response object (or null for notifications). Exported for tests. */
export async function handle(m: Msg, run: (argv: string[], id: Id) => Promise<{ code: number; out: string }> = runCli): Promise<Record<string, unknown> | null> {
export async function handle(m: Msg, run: (argv: string[], id: Id) => Promise<{ code: number; out: string }> = runCli, surface?: Surface): Promise<Record<string, unknown> | null> {
const err = (code: number, message: string, data?: unknown) => ({ jsonrpc: '2.0', id: m.id ?? null, error: { code, message, ...(data === undefined ? {} : { data }) } });
if (m.id === undefined) {
if (m.method === 'notifications/cancelled') cancel(m.params?.requestId as Id);
@ -83,7 +90,7 @@ export async function handle(m: Msg, run: (argv: string[], id: Id) => Promise<{
protocolVersion: LEGACY.includes(asked) ? asked : LEGACY_PROTOCOL,
capabilities: { tools: { listChanged: false } },
serverInfo: { ...serverInfo(), description: pkg.description },
instructions: INSTRUCTIONS,
instructions: surface?.instructions ?? INSTRUCTIONS,
} };
}
@ -101,11 +108,17 @@ export async function handle(m: Msg, run: (argv: string[], id: Id) => Promise<{
switch (m.method) {
case 'server/discover':
return ok({ supportedVersions: SUPPORTED, capabilities: { tools: {} }, instructions: INSTRUCTIONS, ttlMs: TOOLS_TTL_MS, cacheScope: 'public' });
return ok({ supportedVersions: SUPPORTED, capabilities: { tools: {} }, instructions: surface?.instructions ?? INSTRUCTIONS, ttlMs: TOOLS_TTL_MS, cacheScope: 'public' });
case 'ping': return ok({});
case 'tools/list': return ok({ tools: listTools(bypassOn()), ...(era === 'modern' ? { ttlMs: TOOLS_TTL_MS, cacheScope: 'public' } : {}) });
case 'tools/list': return ok({ tools: surface ? surface.list() : listTools(bypassOn()), ...(era === 'modern' ? { ttlMs: TOOLS_TTL_MS, cacheScope: 'public' } : {}) });
case 'tools/call': {
const name = String(m.params?.name ?? '');
if (surface) {
// public mode: only the surface's own tools exist; nothing below (the CLI tools, owner_step) is reachable
const r = await surface.call(name, m.params?.arguments ?? {});
if (!r) return err(-32602, `Unknown tool: ${name}`);
return ok({ content: [{ type: 'text', text: r.text }], ...(r.data === undefined ? {} : { structuredContent: { result: r.data } }), isError: !!r.isError });
}
const tool = TOOLS.find(t => t.name === name) ?? (name === OWNER_STEP.name && bypassOn() ? OWNER_STEP : undefined);
if (!tool) return err(-32602, `Unknown tool: ${name}`);
const input = (m.params?.arguments ?? {}) as Record<string, unknown>;