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:
parent
398d8cd375
commit
c500866295
8 changed files with 695 additions and 15 deletions
21
cli/mcp.ts
21
cli/mcp.ts
|
|
@ -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>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue