@belgie/mcp 0.1.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 ADDED
@@ -0,0 +1,98 @@
1
+ # `@belgie/mcp`
2
+
3
+ TypeScript helpers for building Belgie MCP Apps, generating typed MCP tool callers, and packaging path-based React
4
+ widgets with Vite.
5
+
6
+ ## Installation
7
+
8
+ Install the package and its MCP Apps peer dependency with npm:
9
+
10
+ ```sh
11
+ npm install @belgie/mcp @modelcontextprotocol/ext-apps
12
+ ```
13
+
14
+ Install Vite when using the widget plugin:
15
+
16
+ ```sh
17
+ npm install --save-dev vite
18
+ ```
19
+
20
+ ## Package exports
21
+
22
+ - `@belgie/mcp` exports `Widget`, `mountWidget`, `useWidget`, `useToolResult`, context-bound App helpers, and MCP tool
23
+ errors.
24
+ - `@belgie/mcp/codegen` exports programmatic MCP tool-type generation.
25
+ - `@belgie/mcp/internal` contains the runtime factories used by generated callers.
26
+ - `@belgie/mcp/vite` exports the `belgie()` Vite plugin.
27
+ - `@belgie/mcp/package.json` exposes the package metadata.
28
+
29
+ The package is ESM-only and requires Node.js 22 or newer.
30
+
31
+ ## Generate typed tool callers
32
+
33
+ Run the CLI against a streamable HTTP MCP endpoint:
34
+
35
+ ```sh
36
+ npx belgie-mcp generate https://example.com/mcp --output src/mcp-tools.ts
37
+ ```
38
+
39
+ OAuth is enabled by default. Use `--no-oauth` for an endpoint that does not require it, `--no-open` to print the
40
+ authorization URL, `--header NAME:VALUE` for a direct header, or `--header-env NAME=ENV_VAR` to read a secret from the
41
+ environment. `--check` verifies that an existing output file is current without rewriting it.
42
+
43
+ Generated callers execute only when called, use the connected widget App by default, and accept an explicit App as the
44
+ optional second argument:
45
+
46
+ ```ts
47
+ import { getWeather } from "./mcp-tools.ts";
48
+
49
+ const { result, error } = await getWeather({ city: "Austin" });
50
+ ```
51
+
52
+ ## Build a widget
53
+
54
+ Widgets are discovered at `<srcDir>/<name>/widget.tsx` and must have a default export:
55
+
56
+ ```tsx
57
+ import { Widget, mountWidget, useToolResult } from "@belgie/mcp";
58
+ import { getWeather } from "../../../mcp-tools.ts";
59
+
60
+ function Weather() {
61
+ const weather = useToolResult(getWeather);
62
+ return (
63
+ <Widget metadata={{ name: "weather", version: "1.0.0" }}>
64
+ {weather.data?.summary ?? "Waiting for weather"}
65
+ </Widget>
66
+ );
67
+ }
68
+
69
+ mountWidget(Weather);
70
+ ```
71
+
72
+ Add the plugin to a normal Vite configuration. Development serves each widget at `/widgets/<name>/index.html`;
73
+ production emits a self-contained `dist/widgets/<name>/index.html` with JavaScript, CSS, and assets inlined.
74
+
75
+ ```ts
76
+ import { defineConfig } from "vite";
77
+ import { belgie } from "@belgie/mcp/vite";
78
+
79
+ export default defineConfig({
80
+ plugins: [belgie({ srcDir: "src/widgets" })],
81
+ });
82
+ ```
83
+
84
+ ## Development
85
+
86
+ This package uses npm and keeps its lockfile in version control:
87
+
88
+ ```sh
89
+ npm ci
90
+ npm run dev
91
+ npm run check
92
+ npm run test:watch
93
+ npm test
94
+ npm pack --dry-run
95
+ ```
96
+
97
+ `npm test` builds with tsdown, validates the package with publint and `@arethetypeswrong/cli` rules, runs the serialized
98
+ Vitest suite with V8 coverage, and checks TypeScript 7 declarations and API fixtures.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ //#region src/cli.d.ts
2
+ declare function values(value: string | string[] | undefined): string[];
3
+ declare function parseHeader(value: string): [string, string];
4
+ declare function parseEnvironmentHeader(value: string): [string, string];
5
+ declare function runCli(args?: string[]): Promise<void>;
6
+ declare function isDirectExecution(moduleUrl: string, executable: string | undefined): boolean;
7
+ declare function reportCliError(cause: unknown): void;
8
+ //#endregion
9
+ export { isDirectExecution, parseEnvironmentHeader, parseHeader, reportCliError, runCli, values };
10
+ //# sourceMappingURL=cli.d.ts.map
package/dist/cli.js ADDED
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ import { t as generateToolTypes } from "./codegen-DnMS1wg_.js";
3
+ import { realpathSync } from "node:fs";
4
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
5
+ import { dirname, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { parseArgs } from "node:util";
8
+ //#region src/cli.ts
9
+ const USAGE = `Usage:
10
+ belgie-mcp generate <url> --output <file> [options]
11
+
12
+ Options:
13
+ --check Fail when the output file is missing or stale
14
+ --header NAME:VALUE Add an introspection header (repeatable)
15
+ --header-env NAME=ENV_VAR Read a header value from the environment (repeatable)
16
+ --no-oauth Disable automatic OAuth discovery and PKCE
17
+ --no-open Print the OAuth URL instead of opening a browser
18
+ `;
19
+ function values(value) {
20
+ if (value === void 0) return [];
21
+ return Array.isArray(value) ? value : [value];
22
+ }
23
+ function parseHeader(value) {
24
+ const separator = value.indexOf(":");
25
+ if (separator <= 0) throw new Error(`Invalid --header ${JSON.stringify(value)}; expected NAME:VALUE`);
26
+ const name = value.slice(0, separator).trim();
27
+ const headerValue = value.slice(separator + 1).trim();
28
+ if (name.length === 0 || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u.test(name)) throw new Error(`Invalid header name ${JSON.stringify(name)}`);
29
+ return [name, headerValue];
30
+ }
31
+ function parseEnvironmentHeader(value) {
32
+ const separator = value.indexOf("=");
33
+ if (separator <= 0 || separator === value.length - 1) throw new Error(`Invalid --header-env ${JSON.stringify(value)}; expected NAME=ENV_VAR`);
34
+ const name = value.slice(0, separator).trim();
35
+ const environmentName = value.slice(separator + 1).trim();
36
+ if (name.length === 0 || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u.test(name)) throw new Error(`Invalid header name ${JSON.stringify(name)}`);
37
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(environmentName)) throw new Error(`Invalid environment variable name ${JSON.stringify(environmentName)}`);
38
+ const headerValue = process.env[environmentName];
39
+ if (headerValue === void 0) throw new Error(`Environment variable ${environmentName} is not set`);
40
+ return [name, headerValue];
41
+ }
42
+ async function runCli(args = process.argv.slice(2)) {
43
+ const { positionals, values: options } = parseArgs({
44
+ args,
45
+ allowPositionals: true,
46
+ options: {
47
+ check: {
48
+ type: "boolean",
49
+ default: false
50
+ },
51
+ header: {
52
+ type: "string",
53
+ multiple: true
54
+ },
55
+ "header-env": {
56
+ type: "string",
57
+ multiple: true
58
+ },
59
+ "no-oauth": {
60
+ type: "boolean",
61
+ default: false
62
+ },
63
+ "no-open": {
64
+ type: "boolean",
65
+ default: false
66
+ },
67
+ output: {
68
+ type: "string",
69
+ short: "o"
70
+ }
71
+ },
72
+ strict: true
73
+ });
74
+ if (positionals[0] !== "generate" || positionals.length !== 2) throw new Error(USAGE);
75
+ if (options.output === void 0) throw new Error(`--output is required\n\n${USAGE}`);
76
+ const headers = Object.fromEntries([...values(options.header).map(parseHeader), ...values(options["header-env"]).map(parseEnvironmentHeader)]);
77
+ const output = resolve(options.output);
78
+ const generated = await generateToolTypes({
79
+ url: positionals[1],
80
+ headers,
81
+ oauth: !options["no-oauth"],
82
+ openBrowser: !options["no-open"]
83
+ });
84
+ if (options.check) {
85
+ let current;
86
+ try {
87
+ current = await readFile(output, "utf8");
88
+ } catch (cause) {
89
+ if (!(cause instanceof Error && "code" in cause && cause.code === "ENOENT")) throw cause;
90
+ }
91
+ if (current !== generated) throw new Error(`Generated MCP tool types are stale or missing: ${output}`);
92
+ process.stdout.write(`MCP tool types are current: ${output}\n`);
93
+ return;
94
+ }
95
+ await mkdir(dirname(output), { recursive: true });
96
+ await writeFile(output, generated, "utf8");
97
+ process.stdout.write(`Generated MCP tool types: ${output}\n`);
98
+ }
99
+ function isDirectExecution(moduleUrl, executable) {
100
+ if (executable === void 0) return false;
101
+ const executionPath = (value) => {
102
+ try {
103
+ return realpathSync(value);
104
+ } catch {
105
+ return resolve(value);
106
+ }
107
+ };
108
+ return executionPath(executable) === executionPath(fileURLToPath(moduleUrl));
109
+ }
110
+ function reportCliError(cause) {
111
+ const message = cause instanceof Error ? cause.message : String(cause);
112
+ process.stderr.write(`${message}\n`);
113
+ process.exitCode = 1;
114
+ }
115
+ if (isDirectExecution(import.meta.url, process.argv[1])) runCli().catch(reportCliError);
116
+ //#endregion
117
+ export { isDirectExecution, parseEnvironmentHeader, parseHeader, reportCliError, runCli, values };
118
+
119
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { realpathSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseArgs } from \"node:util\";\n\nimport { generateToolTypes } from \"./codegen\";\n\nconst USAGE = `Usage:\n belgie-mcp generate <url> --output <file> [options]\n\nOptions:\n --check Fail when the output file is missing or stale\n --header NAME:VALUE Add an introspection header (repeatable)\n --header-env NAME=ENV_VAR Read a header value from the environment (repeatable)\n --no-oauth Disable automatic OAuth discovery and PKCE\n --no-open Print the OAuth URL instead of opening a browser\n`;\n\nexport function values(value: string | string[] | undefined): string[] {\n if (value === undefined) {\n return [];\n }\n return Array.isArray(value) ? value : [value];\n}\n\nexport function parseHeader(value: string): [string, string] {\n const separator = value.indexOf(\":\");\n if (separator <= 0) {\n throw new Error(`Invalid --header ${JSON.stringify(value)}; expected NAME:VALUE`);\n }\n const name = value.slice(0, separator).trim();\n const headerValue = value.slice(separator + 1).trim();\n if (name.length === 0 || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u.test(name)) {\n throw new Error(`Invalid header name ${JSON.stringify(name)}`);\n }\n return [name, headerValue];\n}\n\nexport function parseEnvironmentHeader(value: string): [string, string] {\n const separator = value.indexOf(\"=\");\n if (separator <= 0 || separator === value.length - 1) {\n throw new Error(\n `Invalid --header-env ${JSON.stringify(value)}; expected NAME=ENV_VAR`,\n );\n }\n const name = value.slice(0, separator).trim();\n const environmentName = value.slice(separator + 1).trim();\n if (name.length === 0 || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u.test(name)) {\n throw new Error(`Invalid header name ${JSON.stringify(name)}`);\n }\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(environmentName)) {\n throw new Error(`Invalid environment variable name ${JSON.stringify(environmentName)}`);\n }\n const headerValue = process.env[environmentName];\n if (headerValue === undefined) {\n throw new Error(`Environment variable ${environmentName} is not set`);\n }\n return [name, headerValue];\n}\n\nexport async function runCli(args: string[] = process.argv.slice(2)): Promise<void> {\n const { positionals, values: options } = parseArgs({\n args,\n allowPositionals: true,\n options: {\n check: { type: \"boolean\", default: false },\n header: { type: \"string\", multiple: true },\n \"header-env\": { type: \"string\", multiple: true },\n \"no-oauth\": { type: \"boolean\", default: false },\n \"no-open\": { type: \"boolean\", default: false },\n output: { type: \"string\", short: \"o\" },\n },\n strict: true,\n });\n\n if (positionals[0] !== \"generate\" || positionals.length !== 2) {\n throw new Error(USAGE);\n }\n if (options.output === undefined) {\n throw new Error(`--output is required\\n\\n${USAGE}`);\n }\n\n const headers = Object.fromEntries([\n ...values(options.header).map(parseHeader),\n ...values(options[\"header-env\"]).map(parseEnvironmentHeader),\n ]);\n const output = resolve(options.output);\n const generated = await generateToolTypes({\n url: positionals[1]!,\n headers,\n oauth: !options[\"no-oauth\"],\n openBrowser: !options[\"no-open\"],\n });\n\n if (options.check) {\n let current: string | undefined;\n try {\n current = await readFile(output, \"utf8\");\n } catch (cause: unknown) {\n if (!(cause instanceof Error && \"code\" in cause && cause.code === \"ENOENT\")) {\n throw cause;\n }\n }\n if (current !== generated) {\n throw new Error(`Generated MCP tool types are stale or missing: ${output}`);\n }\n process.stdout.write(`MCP tool types are current: ${output}\\n`);\n return;\n }\n\n await mkdir(dirname(output), { recursive: true });\n await writeFile(output, generated, \"utf8\");\n process.stdout.write(`Generated MCP tool types: ${output}\\n`);\n}\n\nexport function isDirectExecution(moduleUrl: string, executable: string | undefined): boolean {\n if (executable === undefined) {\n return false;\n }\n const executionPath = (value: string) => {\n try {\n return realpathSync(value);\n } catch {\n return resolve(value);\n }\n };\n return executionPath(executable) === executionPath(fileURLToPath(moduleUrl));\n}\n\nexport function reportCliError(cause: unknown): void {\n const message = cause instanceof Error ? cause.message : String(cause);\n process.stderr.write(`${message}\\n`);\n process.exitCode = 1;\n}\n\nif (isDirectExecution(import.meta.url, process.argv[1])) {\n runCli().catch(reportCliError);\n}\n"],"mappings":"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"}