@feniix/bridgekit 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/src/adapters/mcp-signal.d.ts +8 -0
- package/dist/src/adapters/mcp-signal.js +8 -0
- package/dist/src/adapters/pi.d.ts +2 -2
- package/dist/src/adapters/pi.js +12 -1
- package/dist/src/core/define-tool.d.ts +30 -3
- package/dist/src/core/execute-tool.d.ts +3 -8
- package/dist/src/core/execute-tool.js +1 -0
- package/dist/src/index.d.ts +1 -1
- package/examples/README.md +40 -5
- package/llms.txt +4 -2
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -171,8 +171,9 @@ Use `PortableToolHost<CustomHost>` for values that may be either a built-in host
|
|
|
171
171
|
- Keep runtime imports in `dependencies`.
|
|
172
172
|
- Avoid `workspace:` or `file:` dependency ranges in publishable packages.
|
|
173
173
|
- Avoid dangling `sourceMappingURL` comments: publish maps and useful sources together, or disable source maps for package builds.
|
|
174
|
-
- For MCP stdio bins, ensure the
|
|
175
|
-
- If
|
|
174
|
+
- For MCP stdio bins, ensure the executable entrypoint starts with a Node shebang, has executable mode (`chmod +x` or equivalent), and is included by `npm pack --dry-run --json`.
|
|
175
|
+
- If an npm-launched bin depends on generated output, prefer a checked-in wrapper under `bin/` over pointing `bin` directly at `dist/`; the wrapper should resolve the package-local generated file and may run the package-local build for workspace/local execution.
|
|
176
|
+
- If a package keeps a source-loaded host entrypoint (for example a pi extension source file), use a package-local MCP build behind that wrapper and narrow the build to the MCP entrypoint plus shared host-neutral modules.
|
|
176
177
|
- Declare a compatible Node engine (`>=22.19.0`) in downstream packages that expose BridgeKit-powered MCP bins.
|
|
177
178
|
- Run `npm run check`, `npm run test`, `npm run pack:dry-run`, and `npm run package-smoke` before publishing.
|
|
178
179
|
- Treat `docs/releasing.md` as the future release handoff; this repository is not configured for automated publish yet.
|
|
@@ -1 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defensively pulls an `AbortSignal` out of MCP request `extra`. The MCP SDK
|
|
3
|
+
* does not formally type a `signal` on `extra`, so the shape is sniffed
|
|
4
|
+
* structurally.
|
|
5
|
+
*
|
|
6
|
+
* Validated against `@modelcontextprotocol/sdk` v1.x. Revalidate on any SDK
|
|
7
|
+
* major bump in case the cancellation channel moves or gains a typed surface.
|
|
8
|
+
*/
|
|
1
9
|
export declare function signalFromExtra(extra: unknown): AbortSignal | undefined;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defensively pulls an `AbortSignal` out of MCP request `extra`. The MCP SDK
|
|
3
|
+
* does not formally type a `signal` on `extra`, so the shape is sniffed
|
|
4
|
+
* structurally.
|
|
5
|
+
*
|
|
6
|
+
* Validated against `@modelcontextprotocol/sdk` v1.x. Revalidate on any SDK
|
|
7
|
+
* major bump in case the cancellation channel moves or gains a typed surface.
|
|
8
|
+
*/
|
|
1
9
|
export function signalFromExtra(extra) {
|
|
2
10
|
if (!extra || typeof extra !== "object" || !("signal" in extra)) {
|
|
3
11
|
return undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TSchema } from "typebox";
|
|
2
|
-
import type { PortableTool, PortableToolResult } from "../core/define-tool.js";
|
|
2
|
+
import type { PortableTool, PortableToolErrorDetails, PortableToolResult } from "../core/define-tool.js";
|
|
3
3
|
type PiContent = {
|
|
4
4
|
type: "text";
|
|
5
5
|
text: string;
|
|
@@ -23,7 +23,7 @@ export type PiToolRegistration = {
|
|
|
23
23
|
registerTool(tool: PiToolDefinition): unknown;
|
|
24
24
|
};
|
|
25
25
|
export declare class PortableToolExecutionError extends Error {
|
|
26
|
-
readonly details:
|
|
26
|
+
readonly details: PortableToolErrorDetails;
|
|
27
27
|
constructor(result: PortableToolResult);
|
|
28
28
|
}
|
|
29
29
|
export declare function isPortableToolExecutionError(error: unknown): error is PortableToolExecutionError;
|
package/dist/src/adapters/pi.js
CHANGED
|
@@ -2,12 +2,23 @@ import { executePortableTool } from "../core/execute-tool.js";
|
|
|
2
2
|
function toPiDetails(result) {
|
|
3
3
|
return result.structuredContent ?? result.details ?? {};
|
|
4
4
|
}
|
|
5
|
+
function isValidationDetails(source) {
|
|
6
|
+
return source.kind === "validation" && typeof source.tool === "string" && Array.isArray(source.validationErrors);
|
|
7
|
+
}
|
|
8
|
+
function toPortableToolErrorDetails(result) {
|
|
9
|
+
const source = toPiDetails(result);
|
|
10
|
+
if (isValidationDetails(source)) {
|
|
11
|
+
return source;
|
|
12
|
+
}
|
|
13
|
+
const { kind: _ignored, ...rest } = source;
|
|
14
|
+
return { kind: "domain", ...rest };
|
|
15
|
+
}
|
|
5
16
|
export class PortableToolExecutionError extends Error {
|
|
6
17
|
details;
|
|
7
18
|
constructor(result) {
|
|
8
19
|
super(result.text);
|
|
9
20
|
this.name = "PortableToolExecutionError";
|
|
10
|
-
this.details =
|
|
21
|
+
this.details = toPortableToolErrorDetails(result);
|
|
11
22
|
}
|
|
12
23
|
}
|
|
13
24
|
export function isPortableToolExecutionError(error) {
|
|
@@ -1,14 +1,41 @@
|
|
|
1
1
|
import type { Static, TSchema } from "typebox";
|
|
2
|
-
export interface PortableToolResult {
|
|
2
|
+
export interface PortableToolResult<TStructured extends Record<string, unknown> = Record<string, unknown>> {
|
|
3
3
|
/** Plain text sent back to the model in every host. */
|
|
4
4
|
text: string;
|
|
5
5
|
/** Structured data for hosts that support it. Preferred by both pi and MCP adapters. */
|
|
6
|
-
structuredContent?:
|
|
7
|
-
/**
|
|
6
|
+
structuredContent?: TStructured;
|
|
7
|
+
/**
|
|
8
|
+
* Legacy/adapter debug details used only when `structuredContent` is absent.
|
|
9
|
+
*
|
|
10
|
+
* @deprecated Slated for removal in 1.0. Prefer `structuredContent` for
|
|
11
|
+
* machine-readable data. Both adapters still fall back to this field, but
|
|
12
|
+
* new tool code should not set it.
|
|
13
|
+
*/
|
|
8
14
|
details?: Record<string, unknown>;
|
|
9
15
|
/** Tool-level error flag. Throw for unexpected adapter/runtime failures. */
|
|
10
16
|
isError?: boolean;
|
|
11
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Discriminated union describing the shape `executePortableTool` puts into a
|
|
20
|
+
* failure result's `structuredContent`, and that the pi adapter exposes as
|
|
21
|
+
* `PortableToolExecutionError.details`.
|
|
22
|
+
*
|
|
23
|
+
* - `kind: "validation"` — TypeBox rejected the args. Always carries the
|
|
24
|
+
* offending tool name and the validation errors.
|
|
25
|
+
* - `kind: "domain"` — the tool's own handler returned `isError: true`. The
|
|
26
|
+
* shape of the rest of the object is whatever the handler chose to expose.
|
|
27
|
+
*/
|
|
28
|
+
export type PortableToolErrorDetails = {
|
|
29
|
+
kind: "validation";
|
|
30
|
+
tool: string;
|
|
31
|
+
validationErrors: PortableValidationError[];
|
|
32
|
+
} | ({
|
|
33
|
+
kind: "domain";
|
|
34
|
+
} & Record<string, unknown>);
|
|
35
|
+
export interface PortableValidationError {
|
|
36
|
+
path: string;
|
|
37
|
+
message: string;
|
|
38
|
+
}
|
|
12
39
|
export type PortableToolBuiltInHost = "pi" | "mcp" | "test";
|
|
13
40
|
export type PortableToolHost<TExtension extends string = never> = PortableToolBuiltInHost | TExtension;
|
|
14
41
|
export interface PortableToolContext<THost extends string = PortableToolBuiltInHost> {
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import type { TSchema } from "typebox";
|
|
2
|
-
import type { PortableTool, PortableToolBuiltInHost, PortableToolContext, PortableToolResult } from "./define-tool.js";
|
|
3
|
-
type
|
|
4
|
-
export interface PortableValidationError {
|
|
5
|
-
path: string;
|
|
6
|
-
message: string;
|
|
7
|
-
}
|
|
2
|
+
import type { PortableTool, PortableToolBuiltInHost, PortableToolContext, PortableToolResult, PortableValidationError } from "./define-tool.js";
|
|
3
|
+
export type { PortableValidationError };
|
|
8
4
|
export declare function validatePortableToolArgs<THost extends string = PortableToolBuiltInHost>(tool: PortableTool<TSchema, THost>, args: unknown): {
|
|
9
5
|
ok: true;
|
|
10
6
|
} | {
|
|
11
7
|
ok: false;
|
|
12
8
|
errors: PortableValidationError[];
|
|
13
9
|
};
|
|
14
|
-
export declare function executePortableTool<THost extends string = PortableToolBuiltInHost>(tool: PortableTool<TSchema, THost>, args: unknown, ctx: PortableToolContext<
|
|
15
|
-
export {};
|
|
10
|
+
export declare function executePortableTool<THost extends string = PortableToolBuiltInHost>(tool: PortableTool<TSchema, THost>, args: unknown, ctx: PortableToolContext<NoInfer<THost>>): Promise<PortableToolResult>;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { definePortableTool, type PortableTool, type PortableToolBuiltInHost, type PortableToolContext, type PortableToolHost, type PortableToolResult, } from "./core/define-tool.js";
|
|
1
|
+
export { definePortableTool, type PortableTool, type PortableToolBuiltInHost, type PortableToolContext, type PortableToolErrorDetails, type PortableToolHost, type PortableToolResult, } from "./core/define-tool.js";
|
|
2
2
|
export { executePortableTool, type PortableValidationError, validatePortableToolArgs, } from "./core/execute-tool.js";
|
package/examples/README.md
CHANGED
|
@@ -20,6 +20,8 @@ Some packages have a host that intentionally loads TypeScript source directly, w
|
|
|
20
20
|
```text
|
|
21
21
|
my-pi-extension-package/
|
|
22
22
|
package.json
|
|
23
|
+
bin/
|
|
24
|
+
my-tools-mcp.js # checked-in npm bin wrapper for local/workspace resilience
|
|
23
25
|
extensions/
|
|
24
26
|
tools.ts # host-neutral portable tools
|
|
25
27
|
index.ts # source-loaded pi adapter wiring
|
|
@@ -177,7 +179,7 @@ In `package.json`:
|
|
|
177
179
|
}
|
|
178
180
|
```
|
|
179
181
|
|
|
180
|
-
For mixed source-loaded pi + compiled MCP packages, keep the pi source entrypoint and point
|
|
182
|
+
For mixed source-loaded pi + compiled MCP packages, keep the pi source entrypoint and point npm `bin` at a checked-in wrapper rather than directly at generated `dist/` output:
|
|
181
183
|
|
|
182
184
|
```json
|
|
183
185
|
{
|
|
@@ -186,11 +188,11 @@ For mixed source-loaded pi + compiled MCP packages, keep the pi source entrypoin
|
|
|
186
188
|
"extensions": ["./extensions/index.ts"]
|
|
187
189
|
},
|
|
188
190
|
"bin": {
|
|
189
|
-
"my-tools-mcp": "./
|
|
191
|
+
"my-tools-mcp": "./bin/my-tools-mcp.js"
|
|
190
192
|
},
|
|
191
|
-
"files": ["extensions/", "dist/", "README.md", "LICENSE"],
|
|
193
|
+
"files": ["bin/", "extensions/", "dist/", "README.md", "LICENSE"],
|
|
192
194
|
"scripts": {
|
|
193
|
-
"build:mcp": "tsc --project tsconfig.mcp.json
|
|
195
|
+
"build:mcp": "tsc --project tsconfig.mcp.json",
|
|
194
196
|
"prepack": "npm run build:mcp"
|
|
195
197
|
},
|
|
196
198
|
"engines": {
|
|
@@ -203,6 +205,38 @@ For mixed source-loaded pi + compiled MCP packages, keep the pi source entrypoin
|
|
|
203
205
|
}
|
|
204
206
|
```
|
|
205
207
|
|
|
208
|
+
The wrapper should resolve the generated MCP server relative to the installed package, build it when missing in local/workspace execution, and preserve build failures:
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
#!/usr/bin/env node
|
|
212
|
+
import { existsSync } from "node:fs";
|
|
213
|
+
import { spawnSync } from "node:child_process";
|
|
214
|
+
import { dirname, join, resolve } from "node:path";
|
|
215
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
216
|
+
|
|
217
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
218
|
+
const serverPath = join(packageRoot, "dist", "extensions", "mcp-server.js");
|
|
219
|
+
|
|
220
|
+
if (!existsSync(serverPath)) {
|
|
221
|
+
const build = spawnSync("npm", ["run", "build:mcp", "--silent"], {
|
|
222
|
+
cwd: packageRoot,
|
|
223
|
+
stdio: "inherit",
|
|
224
|
+
shell: process.platform === "win32",
|
|
225
|
+
timeout: 60_000,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
if (build.status !== 0 || !existsSync(serverPath)) {
|
|
229
|
+
console.error("[my-tools] Failed to build the local MCP server. Run `npm run build:mcp` and try again.");
|
|
230
|
+
process.exit(build.status && build.status !== 0 ? build.status : 1);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const { runServer } = await import(pathToFileURL(serverPath).href);
|
|
235
|
+
await runServer();
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Commit the wrapper with executable mode (`chmod +x bin/my-tools-mcp.js`) and verify `npm pack --dry-run --json` includes it with executable mode.
|
|
239
|
+
|
|
206
240
|
MCP behavior:
|
|
207
241
|
|
|
208
242
|
- `tools/list` exposes TypeBox schemas directly as JSON Schema.
|
|
@@ -269,7 +303,8 @@ For publishable tool packages:
|
|
|
269
303
|
- Declare Node `>=22.19.0` when publishing BridgeKit-powered MCP bins.
|
|
270
304
|
- Avoid `workspace:` or `file:` ranges in publishable package dependencies.
|
|
271
305
|
- Avoid dangling `sourceMappingURL` comments: either publish maps and useful sources, or disable source maps for package builds.
|
|
272
|
-
- Ensure
|
|
306
|
+
- Ensure the npm bin entrypoint starts with a shebang, is executable (`chmod +x` or equivalent), and appears in `npm pack --dry-run --json` with executable mode.
|
|
307
|
+
- When a bin depends on generated output, prefer a checked-in wrapper under `bin/` over pointing directly at `dist/`; test existing output, missing output, failed builds, and successful builds that omit the expected file.
|
|
273
308
|
- If only the MCP bin needs compiled output, narrow its tsconfig to the MCP entrypoint and shared host-neutral modules instead of compiling unrelated host adapters.
|
|
274
309
|
- Add a packed-install smoke test that installs tarballs into a temporary project.
|
|
275
310
|
- For BridgeKit itself, run `npm run check`, `npm run test`, `npm run pack:dry-run`, and `npm run package-smoke` before release.
|
package/llms.txt
CHANGED
|
@@ -91,11 +91,13 @@ Use `PortableToolHost<"custom-host">` when a value may be either a built-in host
|
|
|
91
91
|
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:
|
|
92
92
|
|
|
93
93
|
- Keep the host source entrypoint if that is the package convention, e.g. `pi.extensions: ["./extensions/index.ts"]`.
|
|
94
|
-
- Add a package-local MCP build
|
|
94
|
+
- Add a package-local MCP build and point npm `bin` at a checked-in wrapper under `bin/`, not directly at generated `dist/` output.
|
|
95
|
+
- The wrapper should resolve the package-local generated MCP server, run the package-local build when output is missing in workspace/local execution, and preserve non-zero build failures.
|
|
95
96
|
- Narrow the MCP build to the MCP entrypoint and shared host-neutral modules; avoid compiling pi adapter entrypoints into the standalone MCP path.
|
|
96
97
|
- Put runtime imports used by the compiled MCP bin in `dependencies`, not only peers or dev dependencies.
|
|
97
98
|
- Declare BridgeKit's Node engine requirement in the downstream package (`>=22.19.0`).
|
|
98
|
-
- Ensure the
|
|
99
|
+
- Ensure the checked-in wrapper is executable (`chmod +x` or equivalent) and verify the mode with `npm pack --dry-run --json`.
|
|
100
|
+
- Test wrapper behavior for existing output, missing output, failed builds, and builds that exit successfully without creating the expected file.
|
|
99
101
|
|
|
100
102
|
## Anti-patterns
|
|
101
103
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feniix/bridgekit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "BridgeKit defines TypeBox-backed tools once and adapts them to pi, MCP, and other hosts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -48,10 +48,9 @@
|
|
|
48
48
|
"build": "npm run clean && tsc -b tsconfig.json",
|
|
49
49
|
"test": "npm run build && node scripts/run-built-tests.mjs",
|
|
50
50
|
"test:coverage": "npm run build && node scripts/run-built-tests-coverage.mjs",
|
|
51
|
-
"verify:dist": "node scripts/verify-bridgekit-dist.mjs",
|
|
52
51
|
"pack:dry-run": "npm pack --dry-run --json",
|
|
53
52
|
"package-smoke": "node scripts/smoke-package.mjs",
|
|
54
|
-
"prepack": "npm run build
|
|
53
|
+
"prepack": "npm run build"
|
|
55
54
|
},
|
|
56
55
|
"files": [
|
|
57
56
|
"dist/**/*.js",
|