{
  "$schema": "https://hyperjs.ai/schema/registry-item.json",
  "name": "mcp",
  "version": "0.1.0",
  "description": "Model Context Protocol (MCP) adapter for Hyper.",
  "readme": "# @hyper/mcp\n\nModel Context Protocol (MCP) adapter for Hyper — turn any Hyper app into an MCP server.\n\n## Install\n\nComponents are installed as source into your repo, not pulled from npm:\n\n```bash\nbunx hyper add mcp\n```\n\nWires the alias `@hyper/mcp` to `src/hyper/mcp/` (configurable in `hyper.config.json`). See [hyperjs.ai](https://hyperjs.ai) for the full registry.\n\n## Usage\n\n```ts\nimport { Hyper, ok } from \"@hyper/core\"\nimport { mcpServer } from \"@hyper/mcp\"\n\nconst app = new Hyper().get(\"/ping\", () => ok({ pong: true }))\n\nconst server = mcpServer(app)\nBun.serve({ port: 5174, fetch: server.handle })\n```\n\n## Docs\n\nSee the [main README](../../README.md) and [docs/](../../docs) for guides and integration recipes.\n\n## License\n\nMIT\n",
  "registryDeps": [
    "core"
  ],
  "peerDeps": {},
  "optionalPeerDeps": {},
  "files": [
    {
      "path": "mcp/audit.ts",
      "contents": "/**\n * Audit — pretty-print or JSON-dump the MCP-exposed surface, including\n * auth requirements inferred from route meta.\n */\n\nimport type { HyperApp } from \"@hyper/core\"\n\nexport interface AuditReport {\n  readonly exposedCount: number\n  readonly total: number\n  readonly tools: readonly {\n    readonly name: string\n    readonly description: string\n    readonly method: string\n    readonly path: string\n    readonly requiresAuth: boolean\n  }[]\n}\n\nexport function auditMcp(app: HyperApp): AuditReport {\n  const manifest = app.toMCPManifest()\n  const byPath = new Map(app.routeList.map((r) => [`${r.method} ${r.path}`, r]))\n  const tools = manifest.tools.map((t) => {\n    const route = byPath.get(`${t.method} ${t.path}`)\n    const requiresAuth = Boolean(\n      route && (route.meta.authEndpoint || route.meta.tags?.includes(\"auth\")),\n    )\n    return {\n      name: t.name,\n      description: t.description,\n      method: t.method,\n      path: t.path,\n      requiresAuth,\n    }\n  })\n  return {\n    exposedCount: tools.length,\n    total: app.routeList.filter((r) => !r.meta.internal).length,\n    tools,\n  }\n}\n\nexport function formatAuditHuman(report: AuditReport): string {\n  const lines: string[] = []\n  lines.push(`MCP surface: ${report.exposedCount}/${report.total} routes exposed\\n`)\n  for (const t of report.tools) {\n    const auth = t.requiresAuth ? \" [auth]\" : \"\"\n    lines.push(`  ${t.method.padEnd(6)} ${t.path}${auth}`)\n    lines.push(`    ${t.description}`)\n  }\n  return lines.join(\"\\n\")\n}\n",
      "sha256": "6b13a9c2e22e10693248b643646a14f6d7500417e3ce5d797185ac645f1598b7"
    },
    {
      "path": "mcp/index.ts",
      "contents": "/**\n * @hyper/mcp — exposes declared routes over the Model Context Protocol.\n *\n * Usage:\n *   const mcp = mcpServer(app)\n *   Bun.serve({ port: 5174, fetch: mcp.handle })\n *\n * Routes annotated with `meta.mcp = { description }` are exposed as tools.\n * `hyper mcp --audit` prints the surface before it ships.\n */\n\nexport { auditMcp, formatAuditHuman } from \"./audit.ts\"\nexport type { AuditReport } from \"./audit.ts\"\nexport { mcpServer } from \"./server.ts\"\nexport type { McpServer, McpServerConfig } from \"./server.ts\"\n",
      "sha256": "35444e98257b9d6d2639d1af143f16b75bebe491f69d0f21182cc5debe3334af"
    },
    {
      "path": "mcp/server.ts",
      "contents": "/**\n * Minimal MCP server — JSON-RPC 2.0 over HTTP POST / stdio pipes.\n *\n * We implement the subset needed for tools:\n *   - initialize\n *   - tools/list\n *   - tools/call\n *\n * Anything declared with `meta.mcp = { description }` becomes a tool.\n * Tool invocation funnels through the shared `app.invoke()` path so\n * middleware, logging, and validation run exactly once.\n */\n\nimport type { HttpMethod, HyperApp, MCPManifest } from \"@hyper/core\"\n\nexport interface McpServer {\n  readonly handle: (req: Request) => Promise<Response>\n  readonly manifest: MCPManifest\n  readonly listTools: () => readonly { name: string; description: string }[]\n  readonly callTool: (name: string, args: unknown) => Promise<unknown>\n}\n\ninterface JsonRpcRequest {\n  readonly jsonrpc: \"2.0\"\n  readonly id?: number | string | null\n  readonly method: string\n  readonly params?: unknown\n}\n\ninterface JsonRpcResponse {\n  readonly jsonrpc: \"2.0\"\n  readonly id: number | string | null\n  readonly result?: unknown\n  readonly error?: { code: number; message: string; data?: unknown }\n}\n\nexport interface McpServerConfig {\n  /** Override the manifest (usually omitted; taken from app). */\n  readonly manifest?: MCPManifest\n  /** Require auth check on every tool call. Defaults to always-allow. */\n  readonly authorize?: (args: { toolName: string; req: Request }) => boolean | Promise<boolean>\n  /** Server identity (surfaced on initialize). */\n  readonly info?: { name: string; version: string }\n}\n\nexport function mcpServer(app: HyperApp, cfg: McpServerConfig = {}): McpServer {\n  const manifest = cfg.manifest ?? app.toMCPManifest()\n  const byName = new Map(manifest.tools.map((t) => [t.name, t]))\n\n  const callTool = async (name: string, args: unknown): Promise<unknown> => {\n    const tool = byName.get(name)\n    if (!tool) throw rpcError(-32601, `unknown tool: ${name}`)\n    const input = (args ?? {}) as {\n      params?: Record<string, string>\n      query?: Record<string, unknown>\n      body?: unknown\n    }\n    const result = await app.invoke({\n      method: tool.method as HttpMethod,\n      path: tool.path,\n      ...(input.params && { params: input.params }),\n      ...(input.query && { query: input.query }),\n      ...(input.body !== undefined && { body: input.body }),\n    })\n    if (result.status >= 400) {\n      throw rpcError(-32000, `tool failed with ${result.status}`, result.data)\n    }\n    return result.data\n  }\n\n  const handle = async (req: Request): Promise<Response> => {\n    if (req.method !== \"POST\") {\n      return json(\n        { jsonrpc: \"2.0\", id: null, error: { code: -32600, message: \"expected POST\" } },\n        405,\n      )\n    }\n    let msg: JsonRpcRequest\n    try {\n      msg = (await req.json()) as JsonRpcRequest\n    } catch {\n      return json(\n        { jsonrpc: \"2.0\", id: null, error: { code: -32700, message: \"parse error\" } },\n        400,\n      )\n    }\n    try {\n      switch (msg.method) {\n        case \"initialize\":\n          return rpcOk(msg.id ?? null, {\n            serverInfo: cfg.info ?? { name: \"hyper-mcp\", version: \"0.0.0\" },\n            capabilities: { tools: {} },\n          })\n        case \"tools/list\":\n          return rpcOk(msg.id ?? null, {\n            tools: manifest.tools.map((t) => ({\n              name: t.name,\n              description: t.description,\n              inputSchema: t.inputSchema,\n            })),\n          })\n        case \"tools/call\": {\n          const params = (msg.params ?? {}) as { name: string; arguments?: unknown }\n          if (cfg.authorize) {\n            const ok = await cfg.authorize({ toolName: params.name, req })\n            if (!ok) {\n              return rpcErr(msg.id ?? null, -32001, `unauthorized: ${params.name}`)\n            }\n          }\n          const output = await callTool(params.name, params.arguments)\n          return rpcOk(msg.id ?? null, {\n            content: [{ type: \"text\", text: JSON.stringify(output) }],\n          })\n        }\n        default:\n          return rpcErr(msg.id ?? null, -32601, `method not found: ${msg.method}`)\n      }\n    } catch (e) {\n      const err = e as { code?: number; message?: string; data?: unknown }\n      return rpcErr(msg.id ?? null, err.code ?? -32000, err.message ?? \"server error\", err.data)\n    }\n  }\n\n  return {\n    handle,\n    manifest,\n    listTools: () => manifest.tools.map((t) => ({ name: t.name, description: t.description })),\n    callTool,\n  }\n}\n\nfunction rpcError(\n  code: number,\n  message: string,\n  data?: unknown,\n): Error & {\n  code: number\n  data?: unknown\n} {\n  return Object.assign(new Error(message), { code, data })\n}\n\nfunction rpcOk(id: number | string | null, result: unknown): Response {\n  const body: JsonRpcResponse = { jsonrpc: \"2.0\", id, result }\n  return json(body, 200)\n}\n\nfunction rpcErr(\n  id: number | string | null,\n  code: number,\n  message: string,\n  data?: unknown,\n): Response {\n  const body: JsonRpcResponse = {\n    jsonrpc: \"2.0\",\n    id,\n    error: { code, message, ...(data !== undefined && { data }) },\n  }\n  return json(body, 200)\n}\n\nfunction json(body: unknown, status: number): Response {\n  return new Response(JSON.stringify(body), {\n    status,\n    headers: { \"content-type\": \"application/json\" },\n  })\n}\n",
      "sha256": "3008a688528e4b0eab27c5b9ed03225711c5638457a00bfc2343420a45a7eeab"
    }
  ],
  "subpaths": {}
}