# @feniix/bridgekit

Use this package when you need to define a tool once and expose it through both pi and MCP hosts.

## Read order for agents

1. `README.md` — public API, contracts, best practices, packaging notes.
2. `llms.txt` — compact agent-facing usage rules and anti-patterns.
3. `examples/README.md` — copyable end-to-end layouts for shared tools, pi extension wiring, MCP stdio server wiring, and custom hosts.
4. Published declarations such as `dist/src/index.d.ts`, `dist/src/pi.d.ts`, and `dist/src/mcp.d.ts` — canonical installed-package type contracts. In a source checkout, the matching `src/` files contain implementation context.

## Import map

```ts
import { definePortableTool, executePortableTool } from "@feniix/bridgekit";
import { registerPiTools } from "@feniix/bridgekit/pi";
import { createMcpServer, runMcpStdioServer } from "@feniix/bridgekit/mcp";
```

- Root entrypoint: host-neutral tool definitions, validation, and execution helpers.
- `/pi`: pi adapter only.
- `/mcp`: MCP server adapter only.

Do not deep-import from `dist/` or `src/` in consumer code. Reading published declarations for documentation is fine; imports should use the package entrypoints above.

## Core pattern

```ts
import { Type } from "typebox";
import { definePortableTool } from "@feniix/bridgekit";

export const echoTool = definePortableTool({
  name: "echo",
  title: "Echo",
  description: "Echo text.",
  parameters: Type.Object({ text: Type.String() }),
  execute(args, ctx) {
    return {
      text: args.text,
      structuredContent: { text: args.text, host: ctx.host },
    };
  },
});

export function createTools() {
  return [echoTool];
}
```

## Best practices

- Keep portable tools host-neutral. Do not import pi or MCP SDKs from files that define tool behavior.
- Use TypeBox `Type.Object(...)` schemas for MCP-compatible tools.
- Return `{ text, structuredContent }` for successful results.
- Use `isError: true` for domain/tool-level failures that should reach the model as tool output.
- Throw only for unexpected programmer, adapter, or runtime failures.
- Respect `ctx.signal` in long-running tools.
- Use `ctx.progress?.(...)` for incremental progress updates when the host supports them.
- Wire hosts explicitly: pi registration and MCP server startup are separate adapter calls.
- Keep modules import-passive. Do not register tools, start servers, read package metadata, read env vars, or touch files at import time.
- For stateful tools, export a `createTools()` factory instead of a module-level `tools` singleton so each pi extension instance or MCP stdio process gets isolated state.
- TypeBox validation runs before the portable handler. If you need custom guidance for missing fields or wrong primitive types, use a permissive schema plus domain validation; otherwise accept BridgeKit's structural validation result.
- MCP stdio entrypoints should be testable: export a server-options factory and a run function, then start stdio only when executed as the package bin.
- Keep package runtime Node >=22.19.0 and ESM-only.

## Host behavior

- pi invalid arguments and portable `isError: true` results throw `PortableToolExecutionError`, because pi expects native tool failures. Tests should assert rejected execution plus `.details`, not a returned `{ isError: true }` object.
- MCP invalid arguments and portable `isError: true` results return `CallToolResult` with `isError: true`.
- MCP preserves `structuredContent`; pi maps `structuredContent` into the pi `details` field. Both adapters fall back to `details` for legacy/debug payloads when `structuredContent` is absent.

## Custom host typing

Default tools accept only built-in hosts: `"pi" | "mcp" | "test"`.
For a custom adapter, opt in explicitly:

```ts
const customTool = definePortableTool<typeof params, "custom-host">({
  // ...
  execute(args, ctx) {
    const host: "custom-host" = ctx.host;
    return { text: host };
  },
});
```

Use `PortableToolHost<"custom-host">` when a value may be either a built-in host or that extension.

## Mixed source-loaded hosts and compiled MCP bins

Some hosts, such as pi in source-extension packages, may intentionally load TypeScript source while MCP clients launched from npm need compiled JavaScript. In that case:

- Keep the host source entrypoint if that is the package convention, e.g. `pi.extensions: ["./extensions/index.ts"]`.
- Add a package-local MCP build whose `bin` points at emitted JavaScript, e.g. `./dist/extensions/mcp-server.js`.
- Narrow the MCP build to the MCP entrypoint and shared host-neutral modules; avoid compiling pi adapter entrypoints into the standalone MCP path.
- Put runtime imports used by the compiled MCP bin in `dependencies`, not only peers or dev dependencies.
- Declare BridgeKit's Node engine requirement in the downstream package (`>=22.19.0`).
- Ensure the built MCP bin is executable (`chmod +x` or equivalent) and verify the mode with `npm pack --dry-run --json`.

## Anti-patterns

- Do not create a separate pi implementation and MCP implementation for the same logic.
- Do not make tool files read package metadata, environment variables, files, or network resources at import time.
- Do not start MCP stdio at module top level in files that tests need to import.
- Do not expose unsupported high-level MCP helpers such as `registerMcpTools` unless tests prove compatibility with the installed MCP SDK.
- Do not return host-specific response shapes from portable tool handlers.
- Do not use `workspace:` or `file:` dependency ranges for packages intended to install from npm.
