@particle-academy/agent-integrations 0.12.0 → 0.13.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 +20 -0
- package/dist/chunk-54QEFRMS.js +285 -0
- package/dist/chunk-54QEFRMS.js.map +1 -0
- package/dist/chunk-GO2Y6H6U.js +62 -0
- package/dist/chunk-GO2Y6H6U.js.map +1 -0
- package/dist/connectors/build.d.cts +56 -0
- package/dist/connectors/build.d.ts +56 -0
- package/dist/connectors/index.d.cts +130 -0
- package/dist/connectors/index.d.ts +130 -0
- package/dist/connectors-build.cjs +133 -0
- package/dist/connectors-build.cjs.map +1 -0
- package/dist/connectors-build.js +66 -0
- package/dist/connectors-build.js.map +1 -0
- package/dist/connectors.cjs +366 -0
- package/dist/connectors.cjs.map +1 -0
- package/dist/connectors.js +4 -0
- package/dist/connectors.js.map +1 -0
- package/dist/index.cjs +357 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/mcpb-BXOrsRnv.d.cts +54 -0
- package/dist/mcpb-BXOrsRnv.d.ts +54 -0
- package/dist/styles.css +156 -0
- package/dist/styles.css.map +1 -1
- package/docs/connectors.md +118 -0
- package/package.json +27 -2
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, CSSProperties, ComponentType, SVGProps } from 'react';
|
|
3
|
+
export { D as DEFAULT_MCPB_ENTRY_POINT, b as MCPB_MANIFEST_VERSION, c as MCPB_MIN_NODE, M as McpbManifestInput, a as McpbTool, d as buildMcpbManifest, e as buildMcpbProxyStub } from '../mcpb-BXOrsRnv.cjs';
|
|
4
|
+
|
|
5
|
+
/** The MCP hosts we know how to generate an install affordance for. */
|
|
6
|
+
type ConnectorClient = "claude-web" | "claude-desktop" | "cursor" | "vscode" | "manual";
|
|
7
|
+
/** A remote MCP server to generate install artifacts for. */
|
|
8
|
+
interface ConnectorServer {
|
|
9
|
+
/** Human-readable server name, e.g. `"Decksmith"`. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** The remote MCP endpoint, e.g. `"https://decksmith.dev/mcp"`. */
|
|
12
|
+
url: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Claude's Connectors page — the manual "Add custom connector" flow.
|
|
16
|
+
*
|
|
17
|
+
* Claude exposes no install deeplink, so a button can only copy the URL and
|
|
18
|
+
* open this page for a paste. Override per-app if Claude moves it (it has
|
|
19
|
+
* historically lived at both `/settings/connectors` and `/customize/connectors`).
|
|
20
|
+
*/
|
|
21
|
+
declare const CLAUDE_CONNECTORS_URL = "https://claude.ai/settings/connectors";
|
|
22
|
+
/** Base64-encode a JSON value, working in both the browser and Node. */
|
|
23
|
+
declare function encodeBase64Json(value: unknown): string;
|
|
24
|
+
/**
|
|
25
|
+
* Cursor install deeplink for a remote (HTTP) MCP server.
|
|
26
|
+
*
|
|
27
|
+
* `cursor://anysphere.cursor-deeplink/mcp/install?name=<name>&config=<base64>`,
|
|
28
|
+
* where `config` is base64 of `{"url":"<mcpUrl>"}` — the HTTP shape, with no
|
|
29
|
+
* `type`/`transport` keys (those are for the stdio examples in the docs). The
|
|
30
|
+
* base64 is intentionally NOT percent-encoded, matching Cursor's own install
|
|
31
|
+
* links. Docs: https://cursor.com/docs/context/mcp/install-links
|
|
32
|
+
*/
|
|
33
|
+
declare function buildCursorDeeplink(server: ConnectorServer): string;
|
|
34
|
+
/**
|
|
35
|
+
* VS Code install deeplink for a remote (HTTP) MCP server.
|
|
36
|
+
*
|
|
37
|
+
* `vscode://mcp/install?<urlencoded-json>` — URL-ENCODED JSON (not base64), the
|
|
38
|
+
* opposite encoding from Cursor and an easy one to mix up. The payload is
|
|
39
|
+
* `{ "name", "url" }`; for VS Code Insiders pass `{ insiders: true }`.
|
|
40
|
+
*/
|
|
41
|
+
declare function buildVscodeDeeplink(server: ConnectorServer, opts?: {
|
|
42
|
+
insiders?: boolean;
|
|
43
|
+
}): string;
|
|
44
|
+
/** A normalized server key for a config file (`My App` → `my-app`). */
|
|
45
|
+
declare function slugifyServerName(name: string): string;
|
|
46
|
+
/** The `claude_desktop_config.json` object for a manual install. */
|
|
47
|
+
interface ManualMcpConfig {
|
|
48
|
+
mcpServers: Record<string, {
|
|
49
|
+
command: string;
|
|
50
|
+
args: string[];
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The `claude_desktop_config.json` (or any stdio MCP client config) entry for a
|
|
55
|
+
* remote server, wrapping it with `npx -y mcp-remote <url>` — the standard
|
|
56
|
+
* stdio→HTTP bridge, since stdio clients can't take an HTTP URL directly.
|
|
57
|
+
* Requires Node 18+ on the user's machine.
|
|
58
|
+
*/
|
|
59
|
+
declare function buildManualConfig(server: ConnectorServer): ManualMcpConfig;
|
|
60
|
+
/** Pretty-printed JSON snippet of {@link buildManualConfig}, for a copy box. */
|
|
61
|
+
declare function buildManualConfigSnippet(server: ConnectorServer): string;
|
|
62
|
+
/** How a given client's button behaves — drives the default UI. */
|
|
63
|
+
type ConnectorMechanism = "copy-open" | "download" | "deeplink" | "snippet";
|
|
64
|
+
/** Display metadata for a client, so consumers don't redraw the marks. */
|
|
65
|
+
interface ConnectorTargetMeta {
|
|
66
|
+
id: ConnectorClient;
|
|
67
|
+
/** Default button label. */
|
|
68
|
+
label: string;
|
|
69
|
+
mechanism: ConnectorMechanism;
|
|
70
|
+
/** One-line tooltip explaining what the button does. */
|
|
71
|
+
hint: string;
|
|
72
|
+
}
|
|
73
|
+
declare const CONNECTOR_TARGETS: Record<ConnectorClient, ConnectorTargetMeta>;
|
|
74
|
+
/**
|
|
75
|
+
* Resolve the navigable href for a deeplink client (cursor / vscode), or null
|
|
76
|
+
* for clients whose mechanism isn't a plain navigation.
|
|
77
|
+
*/
|
|
78
|
+
declare function connectorHref(client: ConnectorClient, server: ConnectorServer, opts?: {
|
|
79
|
+
insiders?: boolean;
|
|
80
|
+
}): string | null;
|
|
81
|
+
|
|
82
|
+
interface ConnectorButtonsProps {
|
|
83
|
+
/** Human-readable server name, e.g. `"Decksmith"`. */
|
|
84
|
+
serverName: string;
|
|
85
|
+
/** The remote MCP endpoint, e.g. `"https://decksmith.dev/mcp"`. */
|
|
86
|
+
mcpUrl: string;
|
|
87
|
+
/**
|
|
88
|
+
* Which client buttons to render, in order. Defaults to
|
|
89
|
+
* `["claude-web", "cursor", "vscode", "manual"]` — plus `"claude-desktop"`
|
|
90
|
+
* when {@link mcpbDownloadUrl} is set. A `"claude-desktop"` entry with no
|
|
91
|
+
* `mcpbDownloadUrl` is skipped (there's nothing to download).
|
|
92
|
+
*/
|
|
93
|
+
clients?: ConnectorClient[];
|
|
94
|
+
/** URL of a prebuilt `.mcpb` bundle; enables the Claude Desktop button. */
|
|
95
|
+
mcpbDownloadUrl?: string;
|
|
96
|
+
/** Override Claude's Connectors page (it has moved before). */
|
|
97
|
+
claudeConnectorsUrl?: string;
|
|
98
|
+
/** Target VS Code Insiders instead of stable. */
|
|
99
|
+
vscodeInsiders?: boolean;
|
|
100
|
+
/** Fired when a value is copied to the clipboard (URL or snippet). */
|
|
101
|
+
onCopy?: (target: ConnectorClient) => void;
|
|
102
|
+
/** Fired when any button is activated (after its side effect). */
|
|
103
|
+
onAction?: (target: ConnectorClient) => void;
|
|
104
|
+
/** Per-client label override. */
|
|
105
|
+
labels?: Partial<Record<ConnectorClient, ReactNode>>;
|
|
106
|
+
className?: string;
|
|
107
|
+
style?: CSSProperties;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Per-client "Add to <host>" buttons for a remote MCP server, each with the
|
|
111
|
+
* right (and subtly different) install behavior baked in — see {@link
|
|
112
|
+
* ./targets}. Brand glyphs, copy/feedback states, and the manual-config popover
|
|
113
|
+
* are owned here so a consumer just passes a name + URL.
|
|
114
|
+
*
|
|
115
|
+
* Needs the package stylesheet for its default look:
|
|
116
|
+
* `import "@particle-academy/agent-integrations/styles.css"`.
|
|
117
|
+
*/
|
|
118
|
+
declare function ConnectorButtons({ serverName, mcpUrl, clients, mcpbDownloadUrl, claudeConnectorsUrl, vscodeInsiders, onCopy, onAction, labels, className, style, }: ConnectorButtonsProps): react.JSX.Element;
|
|
119
|
+
|
|
120
|
+
type GlyphProps = SVGProps<SVGSVGElement>;
|
|
121
|
+
declare function ClaudeMark(props: GlyphProps): react.JSX.Element;
|
|
122
|
+
declare function CursorMark(props: GlyphProps): react.JSX.Element;
|
|
123
|
+
declare function VscodeMark(props: GlyphProps): react.JSX.Element;
|
|
124
|
+
declare function DesktopMark(props: GlyphProps): react.JSX.Element;
|
|
125
|
+
declare function WrenchMark(props: GlyphProps): react.JSX.Element;
|
|
126
|
+
|
|
127
|
+
/** Glyph component for a given client. */
|
|
128
|
+
declare const CONNECTOR_GLYPHS: Record<ConnectorClient, ComponentType<GlyphProps>>;
|
|
129
|
+
|
|
130
|
+
export { CLAUDE_CONNECTORS_URL, CONNECTOR_GLYPHS, CONNECTOR_TARGETS, ClaudeMark, ConnectorButtons, type ConnectorButtonsProps, type ConnectorClient, type ConnectorMechanism, type ConnectorServer, type ConnectorTargetMeta, CursorMark, DesktopMark, type ManualMcpConfig, VscodeMark, WrenchMark, buildCursorDeeplink, buildManualConfig, buildManualConfigSnippet, buildVscodeDeeplink, connectorHref, encodeBase64Json, slugifyServerName };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, CSSProperties, ComponentType, SVGProps } from 'react';
|
|
3
|
+
export { D as DEFAULT_MCPB_ENTRY_POINT, b as MCPB_MANIFEST_VERSION, c as MCPB_MIN_NODE, M as McpbManifestInput, a as McpbTool, d as buildMcpbManifest, e as buildMcpbProxyStub } from '../mcpb-BXOrsRnv.js';
|
|
4
|
+
|
|
5
|
+
/** The MCP hosts we know how to generate an install affordance for. */
|
|
6
|
+
type ConnectorClient = "claude-web" | "claude-desktop" | "cursor" | "vscode" | "manual";
|
|
7
|
+
/** A remote MCP server to generate install artifacts for. */
|
|
8
|
+
interface ConnectorServer {
|
|
9
|
+
/** Human-readable server name, e.g. `"Decksmith"`. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** The remote MCP endpoint, e.g. `"https://decksmith.dev/mcp"`. */
|
|
12
|
+
url: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Claude's Connectors page — the manual "Add custom connector" flow.
|
|
16
|
+
*
|
|
17
|
+
* Claude exposes no install deeplink, so a button can only copy the URL and
|
|
18
|
+
* open this page for a paste. Override per-app if Claude moves it (it has
|
|
19
|
+
* historically lived at both `/settings/connectors` and `/customize/connectors`).
|
|
20
|
+
*/
|
|
21
|
+
declare const CLAUDE_CONNECTORS_URL = "https://claude.ai/settings/connectors";
|
|
22
|
+
/** Base64-encode a JSON value, working in both the browser and Node. */
|
|
23
|
+
declare function encodeBase64Json(value: unknown): string;
|
|
24
|
+
/**
|
|
25
|
+
* Cursor install deeplink for a remote (HTTP) MCP server.
|
|
26
|
+
*
|
|
27
|
+
* `cursor://anysphere.cursor-deeplink/mcp/install?name=<name>&config=<base64>`,
|
|
28
|
+
* where `config` is base64 of `{"url":"<mcpUrl>"}` — the HTTP shape, with no
|
|
29
|
+
* `type`/`transport` keys (those are for the stdio examples in the docs). The
|
|
30
|
+
* base64 is intentionally NOT percent-encoded, matching Cursor's own install
|
|
31
|
+
* links. Docs: https://cursor.com/docs/context/mcp/install-links
|
|
32
|
+
*/
|
|
33
|
+
declare function buildCursorDeeplink(server: ConnectorServer): string;
|
|
34
|
+
/**
|
|
35
|
+
* VS Code install deeplink for a remote (HTTP) MCP server.
|
|
36
|
+
*
|
|
37
|
+
* `vscode://mcp/install?<urlencoded-json>` — URL-ENCODED JSON (not base64), the
|
|
38
|
+
* opposite encoding from Cursor and an easy one to mix up. The payload is
|
|
39
|
+
* `{ "name", "url" }`; for VS Code Insiders pass `{ insiders: true }`.
|
|
40
|
+
*/
|
|
41
|
+
declare function buildVscodeDeeplink(server: ConnectorServer, opts?: {
|
|
42
|
+
insiders?: boolean;
|
|
43
|
+
}): string;
|
|
44
|
+
/** A normalized server key for a config file (`My App` → `my-app`). */
|
|
45
|
+
declare function slugifyServerName(name: string): string;
|
|
46
|
+
/** The `claude_desktop_config.json` object for a manual install. */
|
|
47
|
+
interface ManualMcpConfig {
|
|
48
|
+
mcpServers: Record<string, {
|
|
49
|
+
command: string;
|
|
50
|
+
args: string[];
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The `claude_desktop_config.json` (or any stdio MCP client config) entry for a
|
|
55
|
+
* remote server, wrapping it with `npx -y mcp-remote <url>` — the standard
|
|
56
|
+
* stdio→HTTP bridge, since stdio clients can't take an HTTP URL directly.
|
|
57
|
+
* Requires Node 18+ on the user's machine.
|
|
58
|
+
*/
|
|
59
|
+
declare function buildManualConfig(server: ConnectorServer): ManualMcpConfig;
|
|
60
|
+
/** Pretty-printed JSON snippet of {@link buildManualConfig}, for a copy box. */
|
|
61
|
+
declare function buildManualConfigSnippet(server: ConnectorServer): string;
|
|
62
|
+
/** How a given client's button behaves — drives the default UI. */
|
|
63
|
+
type ConnectorMechanism = "copy-open" | "download" | "deeplink" | "snippet";
|
|
64
|
+
/** Display metadata for a client, so consumers don't redraw the marks. */
|
|
65
|
+
interface ConnectorTargetMeta {
|
|
66
|
+
id: ConnectorClient;
|
|
67
|
+
/** Default button label. */
|
|
68
|
+
label: string;
|
|
69
|
+
mechanism: ConnectorMechanism;
|
|
70
|
+
/** One-line tooltip explaining what the button does. */
|
|
71
|
+
hint: string;
|
|
72
|
+
}
|
|
73
|
+
declare const CONNECTOR_TARGETS: Record<ConnectorClient, ConnectorTargetMeta>;
|
|
74
|
+
/**
|
|
75
|
+
* Resolve the navigable href for a deeplink client (cursor / vscode), or null
|
|
76
|
+
* for clients whose mechanism isn't a plain navigation.
|
|
77
|
+
*/
|
|
78
|
+
declare function connectorHref(client: ConnectorClient, server: ConnectorServer, opts?: {
|
|
79
|
+
insiders?: boolean;
|
|
80
|
+
}): string | null;
|
|
81
|
+
|
|
82
|
+
interface ConnectorButtonsProps {
|
|
83
|
+
/** Human-readable server name, e.g. `"Decksmith"`. */
|
|
84
|
+
serverName: string;
|
|
85
|
+
/** The remote MCP endpoint, e.g. `"https://decksmith.dev/mcp"`. */
|
|
86
|
+
mcpUrl: string;
|
|
87
|
+
/**
|
|
88
|
+
* Which client buttons to render, in order. Defaults to
|
|
89
|
+
* `["claude-web", "cursor", "vscode", "manual"]` — plus `"claude-desktop"`
|
|
90
|
+
* when {@link mcpbDownloadUrl} is set. A `"claude-desktop"` entry with no
|
|
91
|
+
* `mcpbDownloadUrl` is skipped (there's nothing to download).
|
|
92
|
+
*/
|
|
93
|
+
clients?: ConnectorClient[];
|
|
94
|
+
/** URL of a prebuilt `.mcpb` bundle; enables the Claude Desktop button. */
|
|
95
|
+
mcpbDownloadUrl?: string;
|
|
96
|
+
/** Override Claude's Connectors page (it has moved before). */
|
|
97
|
+
claudeConnectorsUrl?: string;
|
|
98
|
+
/** Target VS Code Insiders instead of stable. */
|
|
99
|
+
vscodeInsiders?: boolean;
|
|
100
|
+
/** Fired when a value is copied to the clipboard (URL or snippet). */
|
|
101
|
+
onCopy?: (target: ConnectorClient) => void;
|
|
102
|
+
/** Fired when any button is activated (after its side effect). */
|
|
103
|
+
onAction?: (target: ConnectorClient) => void;
|
|
104
|
+
/** Per-client label override. */
|
|
105
|
+
labels?: Partial<Record<ConnectorClient, ReactNode>>;
|
|
106
|
+
className?: string;
|
|
107
|
+
style?: CSSProperties;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Per-client "Add to <host>" buttons for a remote MCP server, each with the
|
|
111
|
+
* right (and subtly different) install behavior baked in — see {@link
|
|
112
|
+
* ./targets}. Brand glyphs, copy/feedback states, and the manual-config popover
|
|
113
|
+
* are owned here so a consumer just passes a name + URL.
|
|
114
|
+
*
|
|
115
|
+
* Needs the package stylesheet for its default look:
|
|
116
|
+
* `import "@particle-academy/agent-integrations/styles.css"`.
|
|
117
|
+
*/
|
|
118
|
+
declare function ConnectorButtons({ serverName, mcpUrl, clients, mcpbDownloadUrl, claudeConnectorsUrl, vscodeInsiders, onCopy, onAction, labels, className, style, }: ConnectorButtonsProps): react.JSX.Element;
|
|
119
|
+
|
|
120
|
+
type GlyphProps = SVGProps<SVGSVGElement>;
|
|
121
|
+
declare function ClaudeMark(props: GlyphProps): react.JSX.Element;
|
|
122
|
+
declare function CursorMark(props: GlyphProps): react.JSX.Element;
|
|
123
|
+
declare function VscodeMark(props: GlyphProps): react.JSX.Element;
|
|
124
|
+
declare function DesktopMark(props: GlyphProps): react.JSX.Element;
|
|
125
|
+
declare function WrenchMark(props: GlyphProps): react.JSX.Element;
|
|
126
|
+
|
|
127
|
+
/** Glyph component for a given client. */
|
|
128
|
+
declare const CONNECTOR_GLYPHS: Record<ConnectorClient, ComponentType<GlyphProps>>;
|
|
129
|
+
|
|
130
|
+
export { CLAUDE_CONNECTORS_URL, CONNECTOR_GLYPHS, CONNECTOR_TARGETS, ClaudeMark, ConnectorButtons, type ConnectorButtonsProps, type ConnectorClient, type ConnectorMechanism, type ConnectorServer, type ConnectorTargetMeta, CursorMark, DesktopMark, type ManualMcpConfig, VscodeMark, WrenchMark, buildCursorDeeplink, buildManualConfig, buildManualConfigSnippet, buildVscodeDeeplink, connectorHref, encodeBase64Json, slugifyServerName };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var child_process = require('child_process');
|
|
4
|
+
var promises = require('fs/promises');
|
|
5
|
+
var os = require('os');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
11
|
+
|
|
12
|
+
// src/connectors/build.ts
|
|
13
|
+
|
|
14
|
+
// src/connectors/mcpb.ts
|
|
15
|
+
var MCPB_MANIFEST_VERSION = "0.2";
|
|
16
|
+
var MCPB_MIN_NODE = ">=18.0.0";
|
|
17
|
+
var DEFAULT_MCPB_ENTRY_POINT = "server/proxy.js";
|
|
18
|
+
function buildMcpbManifest(input) {
|
|
19
|
+
const entryPoint = input.entryPoint ?? DEFAULT_MCPB_ENTRY_POINT;
|
|
20
|
+
return {
|
|
21
|
+
manifest_version: MCPB_MANIFEST_VERSION,
|
|
22
|
+
name: input.name,
|
|
23
|
+
display_name: input.display_name ?? input.name,
|
|
24
|
+
version: input.version,
|
|
25
|
+
description: input.description,
|
|
26
|
+
...input.long_description ? { long_description: input.long_description } : {},
|
|
27
|
+
author: input.author,
|
|
28
|
+
...input.homepage ? { homepage: input.homepage } : {},
|
|
29
|
+
...input.documentation ? { documentation: input.documentation } : {},
|
|
30
|
+
...input.support ? { support: input.support } : {},
|
|
31
|
+
server: {
|
|
32
|
+
type: "node",
|
|
33
|
+
entry_point: entryPoint,
|
|
34
|
+
mcp_config: {
|
|
35
|
+
command: "npx",
|
|
36
|
+
args: ["-y", "mcp-remote", input.mcpUrl]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
tools: input.tools ?? [],
|
|
40
|
+
tools_generated: false,
|
|
41
|
+
prompts_generated: false,
|
|
42
|
+
...input.keywords ? { keywords: input.keywords } : {},
|
|
43
|
+
license: input.license ?? "MIT",
|
|
44
|
+
compatibility: {
|
|
45
|
+
claude_desktop: ">=0.10.0",
|
|
46
|
+
platforms: ["darwin", "win32", "linux"],
|
|
47
|
+
runtimes: { node: MCPB_MIN_NODE }
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function buildMcpbProxyStub(mcpUrl) {
|
|
52
|
+
const urlLiteral = JSON.stringify(mcpUrl);
|
|
53
|
+
return `#!/usr/bin/env node
|
|
54
|
+
// MCPB proxy shim (generated by @particle-academy/agent-integrations).
|
|
55
|
+
//
|
|
56
|
+
// MCPB (Claude Desktop Extensions) only supports local stdio servers, but this
|
|
57
|
+
// MCP server is a remote HTTP endpoint. The manifest's \`mcp_config\` invokes
|
|
58
|
+
// \`npx -y mcp-remote <url>\` to bridge the gap \u2014 this file is the entry_point
|
|
59
|
+
// fallback the manifest validator requires. If you're seeing this run,
|
|
60
|
+
// mcp_config wasn't honored; spawn mcp-remote directly so the bundle still works.
|
|
61
|
+
|
|
62
|
+
const { spawn } = require("node:child_process");
|
|
63
|
+
|
|
64
|
+
const url = ${urlLiteral};
|
|
65
|
+
const child = spawn("npx", ["-y", "mcp-remote", url], { stdio: "inherit" });
|
|
66
|
+
|
|
67
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
68
|
+
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
69
|
+
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/connectors/build.ts
|
|
74
|
+
async function writeMcpbBundle(opts) {
|
|
75
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
76
|
+
const outFile = path__default.default.resolve(cwd, opts.outFile);
|
|
77
|
+
const validate = opts.validate ?? true;
|
|
78
|
+
const entryPoint = opts.manifest.entryPoint ?? DEFAULT_MCPB_ENTRY_POINT;
|
|
79
|
+
const manifest = buildMcpbManifest(opts.manifest);
|
|
80
|
+
const proxy = buildMcpbProxyStub(opts.manifest.mcpUrl);
|
|
81
|
+
const workDir = await promises.mkdtemp(path__default.default.join(os.tmpdir(), "fai-mcpb-"));
|
|
82
|
+
try {
|
|
83
|
+
await promises.writeFile(
|
|
84
|
+
path__default.default.join(workDir, "manifest.json"),
|
|
85
|
+
JSON.stringify(manifest, null, 2),
|
|
86
|
+
"utf8"
|
|
87
|
+
);
|
|
88
|
+
const entryAbs = path__default.default.join(workDir, entryPoint);
|
|
89
|
+
await promises.mkdir(path__default.default.dirname(entryAbs), { recursive: true });
|
|
90
|
+
await promises.writeFile(entryAbs, proxy, "utf8");
|
|
91
|
+
await promises.mkdir(path__default.default.dirname(outFile), { recursive: true });
|
|
92
|
+
const bin = normalizeBin(opts.mcpbBin);
|
|
93
|
+
if (validate) {
|
|
94
|
+
await run(bin, ["validate", "manifest.json"], workDir);
|
|
95
|
+
}
|
|
96
|
+
await run(bin, ["pack", workDir, outFile], cwd);
|
|
97
|
+
const bytes = (await promises.stat(outFile)).size;
|
|
98
|
+
return { outFile, bytes, manifest, workDir };
|
|
99
|
+
} finally {
|
|
100
|
+
if (!opts.keepWorkDir) {
|
|
101
|
+
await promises.rm(workDir, { recursive: true, force: true }).catch(() => {
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function normalizeBin(bin) {
|
|
107
|
+
if (!bin) return ["npx", "-y", "@anthropic-ai/mcpb"];
|
|
108
|
+
return Array.isArray(bin) ? bin : [bin];
|
|
109
|
+
}
|
|
110
|
+
function run(bin, args, cwd) {
|
|
111
|
+
const [cmd, ...binArgs] = bin;
|
|
112
|
+
const argv = [...binArgs, ...args];
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const child = child_process.spawn(cmd, argv, {
|
|
115
|
+
cwd,
|
|
116
|
+
stdio: "inherit",
|
|
117
|
+
shell: process.platform === "win32"
|
|
118
|
+
// npx resolves via .cmd on Windows
|
|
119
|
+
});
|
|
120
|
+
child.on("error", reject);
|
|
121
|
+
child.on("exit", (code) => {
|
|
122
|
+
if (code === 0) resolve();
|
|
123
|
+
else
|
|
124
|
+
reject(
|
|
125
|
+
new Error(`${cmd} ${argv.join(" ")} exited with code ${code ?? "null"}`)
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
exports.writeMcpbBundle = writeMcpbBundle;
|
|
132
|
+
//# sourceMappingURL=connectors-build.cjs.map
|
|
133
|
+
//# sourceMappingURL=connectors-build.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connectors/mcpb.ts","../src/connectors/build.ts"],"names":["path","mkdtemp","tmpdir","writeFile","mkdir","stat","rm","spawn"],"mappings":"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","file":"connectors-build.cjs","sourcesContent":["// MCPB (Claude Desktop \"Extensions\" bundle) manifest + proxy generation.\n//\n// PURE — no filesystem, no child_process — so it's trivially testable. The\n// `writeMcpbBundle` Node helper (./build) layers fs + the official mcpb CLI on\n// top of these.\n//\n// The hard fact MCPB forces on you: it is STDIO-ONLY. The manifest's\n// `server.type` is one of `node` / `python` / `binary` / `uv` — there is no\n// `type: \"http\"`. So to bundle a REMOTE MCP server (what fancy-* apps almost\n// always are) you ship a thin `node` server whose `mcp_config` runs\n// `npx -y mcp-remote <url>`, bridging stdio→HTTP. The manifest validator still\n// requires `entry_point`, so an (otherwise unused) proxy stub is emitted too.\n//\n// Future-proof: when MCPB grows a real `type: \"http\"`, drop the proxy and point\n// straight at the URL — the call site here doesn't change.\n\n/** The MCPB manifest schema version this helper emits. */\nexport const MCPB_MANIFEST_VERSION = \"0.2\";\n\n/** Minimum Node the mcp-remote proxy needs on the user's machine. */\nexport const MCPB_MIN_NODE = \">=18.0.0\";\n\n/** A tool advertised in the bundle manifest (display-only metadata). */\nexport interface McpbTool {\n name: string;\n description?: string;\n}\n\n/** Inputs for {@link buildMcpbManifest} / `writeMcpbBundle`. */\nexport interface McpbManifestInput {\n /** Machine name, e.g. `\"decksmith\"` (lowercase, no spaces). */\n name: string;\n /** Human display name, e.g. `\"Decksmith\"`. Defaults to `name`. */\n display_name?: string;\n /** Bundle version, e.g. `\"0.2.0\"`. */\n version: string;\n /** Short one-line description. */\n description: string;\n /** Optional longer description shown on the extension's detail view. */\n long_description?: string;\n author: { name: string; url?: string; email?: string };\n homepage?: string;\n documentation?: string;\n support?: string;\n /** The remote MCP endpoint the bundle proxies to. */\n mcpUrl: string;\n /** Advertised tools (display metadata only). */\n tools?: McpbTool[];\n keywords?: string[];\n license?: string;\n /** Entry-point path inside the bundle. Defaults to `\"server/proxy.js\"`. */\n entryPoint?: string;\n}\n\n/** The default entry-point path the proxy stub is written to. */\nexport const DEFAULT_MCPB_ENTRY_POINT = \"server/proxy.js\";\n\n/**\n * Build the full MCPB `manifest.json` object for a remote MCP server, wrapping\n * it with `npx -y mcp-remote <url>` (stdio→HTTP). Returns a plain object ready\n * to `JSON.stringify`.\n */\nexport function buildMcpbManifest(\n input: McpbManifestInput,\n): Record<string, unknown> {\n const entryPoint = input.entryPoint ?? DEFAULT_MCPB_ENTRY_POINT;\n return {\n manifest_version: MCPB_MANIFEST_VERSION,\n name: input.name,\n display_name: input.display_name ?? input.name,\n version: input.version,\n description: input.description,\n ...(input.long_description\n ? { long_description: input.long_description }\n : {}),\n author: input.author,\n ...(input.homepage ? { homepage: input.homepage } : {}),\n ...(input.documentation ? { documentation: input.documentation } : {}),\n ...(input.support ? { support: input.support } : {}),\n server: {\n type: \"node\",\n entry_point: entryPoint,\n mcp_config: {\n command: \"npx\",\n args: [\"-y\", \"mcp-remote\", input.mcpUrl],\n },\n },\n tools: input.tools ?? [],\n tools_generated: false,\n prompts_generated: false,\n ...(input.keywords ? { keywords: input.keywords } : {}),\n license: input.license ?? \"MIT\",\n compatibility: {\n claude_desktop: \">=0.10.0\",\n platforms: [\"darwin\", \"win32\", \"linux\"],\n runtimes: { node: MCPB_MIN_NODE },\n },\n };\n}\n\n/**\n * The `server/proxy.js` stub. MCPB requires an `entry_point` file even though\n * `mcp_config.command` overrides it; if it ever DOES run, it spawns\n * `npx -y mcp-remote <url>` itself so the bundle still works.\n */\nexport function buildMcpbProxyStub(mcpUrl: string): string {\n // JSON.stringify gives us a safely-quoted JS string literal for the URL.\n const urlLiteral = JSON.stringify(mcpUrl);\n return `#!/usr/bin/env node\n// MCPB proxy shim (generated by @particle-academy/agent-integrations).\n//\n// MCPB (Claude Desktop Extensions) only supports local stdio servers, but this\n// MCP server is a remote HTTP endpoint. The manifest's \\`mcp_config\\` invokes\n// \\`npx -y mcp-remote <url>\\` to bridge the gap — this file is the entry_point\n// fallback the manifest validator requires. If you're seeing this run,\n// mcp_config wasn't honored; spawn mcp-remote directly so the bundle still works.\n\nconst { spawn } = require(\"node:child_process\");\n\nconst url = ${urlLiteral};\nconst child = spawn(\"npx\", [\"-y\", \"mcp-remote\", url], { stdio: \"inherit\" });\n\nchild.on(\"exit\", (code) => process.exit(code ?? 0));\nprocess.on(\"SIGINT\", () => child.kill(\"SIGINT\"));\nprocess.on(\"SIGTERM\", () => child.kill(\"SIGTERM\"));\n`;\n}\n","// Node-only build helper: generate + pack a `.mcpb` bundle for a remote MCP\n// server. Imported from `@particle-academy/agent-integrations/connectors/build`\n// at BUILD time (a CI step / a `scripts/build-mcpb.mjs`), never from browser\n// code — it touches fs + shells out to the official mcpb CLI.\n\nimport { spawn } from \"node:child_process\";\nimport { mkdtemp, writeFile, mkdir, rm, stat } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport {\n buildMcpbManifest,\n buildMcpbProxyStub,\n DEFAULT_MCPB_ENTRY_POINT,\n type McpbManifestInput,\n} from \"./mcpb\";\n\nexport type { McpbManifestInput, McpbTool } from \"./mcpb\";\n\nexport interface WriteMcpbBundleOptions {\n /** Where to write the packed `.mcpb` (absolute or cwd-relative). */\n outFile: string;\n /** The manifest inputs (server name, version, mcpUrl, tools, …). */\n manifest: McpbManifestInput;\n /**\n * The mcpb CLI to shell out to. Default: `[\"npx\", \"-y\", \"@anthropic-ai/mcpb\"]`.\n * Pass a locally-installed binary path (string) or argv (array) to avoid the\n * npx network fetch.\n */\n mcpbBin?: string | string[];\n /** Run `mcpb validate` before packing. Default true. */\n validate?: boolean;\n /** Keep the temp work dir instead of removing it (debugging). Default false. */\n keepWorkDir?: boolean;\n /** Working directory the CLI runs in. Default `process.cwd()`. */\n cwd?: string;\n}\n\nexport interface WriteMcpbBundleResult {\n /** Absolute path to the written `.mcpb`. */\n outFile: string;\n /** Size of the written bundle in bytes. */\n bytes: number;\n /** The manifest object that was packed. */\n manifest: Record<string, unknown>;\n /** The work dir used (returned even after cleanup, for logging). */\n workDir: string;\n}\n\n/**\n * Generate a `manifest.json` + `server/proxy.js` for a remote MCP server, then\n * validate and pack them into a `.mcpb` using the official `@anthropic-ai/mcpb`\n * CLI. Returns the output path + size.\n *\n * ```ts\n * import { writeMcpbBundle } from \"@particle-academy/agent-integrations/connectors/build\";\n *\n * await writeMcpbBundle({\n * outFile: \"public/decksmith.mcpb\",\n * manifest: {\n * name: \"decksmith\",\n * display_name: \"Decksmith\",\n * version: \"0.2.0\",\n * description: \"Agent-driven slide deck builder.\",\n * author: { name: \"Particle Academy\", url: \"https://decksmith.dev\" },\n * mcpUrl: \"https://decksmith.dev/mcp\",\n * tools: [{ name: \"start_session\", description: \"…\" }],\n * },\n * });\n * ```\n */\nexport async function writeMcpbBundle(\n opts: WriteMcpbBundleOptions,\n): Promise<WriteMcpbBundleResult> {\n const cwd = opts.cwd ?? process.cwd();\n const outFile = path.resolve(cwd, opts.outFile);\n const validate = opts.validate ?? true;\n const entryPoint = opts.manifest.entryPoint ?? DEFAULT_MCPB_ENTRY_POINT;\n\n const manifest = buildMcpbManifest(opts.manifest);\n const proxy = buildMcpbProxyStub(opts.manifest.mcpUrl);\n\n const workDir = await mkdtemp(path.join(tmpdir(), \"fai-mcpb-\"));\n try {\n // Lay out the bundle source: manifest.json + the entry-point stub.\n await writeFile(\n path.join(workDir, \"manifest.json\"),\n JSON.stringify(manifest, null, 2),\n \"utf8\",\n );\n const entryAbs = path.join(workDir, entryPoint);\n await mkdir(path.dirname(entryAbs), { recursive: true });\n await writeFile(entryAbs, proxy, \"utf8\");\n\n // Ensure the output dir exists.\n await mkdir(path.dirname(outFile), { recursive: true });\n\n const bin = normalizeBin(opts.mcpbBin);\n if (validate) {\n await run(bin, [\"validate\", \"manifest.json\"], workDir);\n }\n await run(bin, [\"pack\", workDir, outFile], cwd);\n\n const bytes = (await stat(outFile)).size;\n return { outFile, bytes, manifest, workDir };\n } finally {\n if (!opts.keepWorkDir) {\n await rm(workDir, { recursive: true, force: true }).catch(() => {});\n }\n }\n}\n\nfunction normalizeBin(bin: string | string[] | undefined): string[] {\n if (!bin) return [\"npx\", \"-y\", \"@anthropic-ai/mcpb\"];\n return Array.isArray(bin) ? bin : [bin];\n}\n\n/** Spawn `[cmd, ...args]`, inheriting stdio, rejecting on a non-zero exit. */\nfunction run(bin: string[], args: string[], cwd: string): Promise<void> {\n const [cmd, ...binArgs] = bin;\n const argv = [...binArgs, ...args];\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, argv, {\n cwd,\n stdio: \"inherit\",\n shell: process.platform === \"win32\", // npx resolves via .cmd on Windows\n });\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => {\n if (code === 0) resolve();\n else\n reject(\n new Error(`${cmd} ${argv.join(\" \")} exited with code ${code ?? \"null\"}`),\n );\n });\n });\n}\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { DEFAULT_MCPB_ENTRY_POINT, buildMcpbManifest, buildMcpbProxyStub } from './chunk-GO2Y6H6U.js';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { mkdtemp, writeFile, mkdir, stat, rm } from 'fs/promises';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
async function writeMcpbBundle(opts) {
|
|
8
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
9
|
+
const outFile = path.resolve(cwd, opts.outFile);
|
|
10
|
+
const validate = opts.validate ?? true;
|
|
11
|
+
const entryPoint = opts.manifest.entryPoint ?? DEFAULT_MCPB_ENTRY_POINT;
|
|
12
|
+
const manifest = buildMcpbManifest(opts.manifest);
|
|
13
|
+
const proxy = buildMcpbProxyStub(opts.manifest.mcpUrl);
|
|
14
|
+
const workDir = await mkdtemp(path.join(tmpdir(), "fai-mcpb-"));
|
|
15
|
+
try {
|
|
16
|
+
await writeFile(
|
|
17
|
+
path.join(workDir, "manifest.json"),
|
|
18
|
+
JSON.stringify(manifest, null, 2),
|
|
19
|
+
"utf8"
|
|
20
|
+
);
|
|
21
|
+
const entryAbs = path.join(workDir, entryPoint);
|
|
22
|
+
await mkdir(path.dirname(entryAbs), { recursive: true });
|
|
23
|
+
await writeFile(entryAbs, proxy, "utf8");
|
|
24
|
+
await mkdir(path.dirname(outFile), { recursive: true });
|
|
25
|
+
const bin = normalizeBin(opts.mcpbBin);
|
|
26
|
+
if (validate) {
|
|
27
|
+
await run(bin, ["validate", "manifest.json"], workDir);
|
|
28
|
+
}
|
|
29
|
+
await run(bin, ["pack", workDir, outFile], cwd);
|
|
30
|
+
const bytes = (await stat(outFile)).size;
|
|
31
|
+
return { outFile, bytes, manifest, workDir };
|
|
32
|
+
} finally {
|
|
33
|
+
if (!opts.keepWorkDir) {
|
|
34
|
+
await rm(workDir, { recursive: true, force: true }).catch(() => {
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function normalizeBin(bin) {
|
|
40
|
+
if (!bin) return ["npx", "-y", "@anthropic-ai/mcpb"];
|
|
41
|
+
return Array.isArray(bin) ? bin : [bin];
|
|
42
|
+
}
|
|
43
|
+
function run(bin, args, cwd) {
|
|
44
|
+
const [cmd, ...binArgs] = bin;
|
|
45
|
+
const argv = [...binArgs, ...args];
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const child = spawn(cmd, argv, {
|
|
48
|
+
cwd,
|
|
49
|
+
stdio: "inherit",
|
|
50
|
+
shell: process.platform === "win32"
|
|
51
|
+
// npx resolves via .cmd on Windows
|
|
52
|
+
});
|
|
53
|
+
child.on("error", reject);
|
|
54
|
+
child.on("exit", (code) => {
|
|
55
|
+
if (code === 0) resolve();
|
|
56
|
+
else
|
|
57
|
+
reject(
|
|
58
|
+
new Error(`${cmd} ${argv.join(" ")} exited with code ${code ?? "null"}`)
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { writeMcpbBundle };
|
|
65
|
+
//# sourceMappingURL=connectors-build.js.map
|
|
66
|
+
//# sourceMappingURL=connectors-build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connectors/build.ts"],"names":[],"mappings":";;;;;;AAsEA,eAAsB,gBACpB,IAAA,EACgC;AAChC,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,IAAO,OAAA,CAAQ,GAAA,EAAI;AACpC,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAK,KAAK,OAAO,CAAA;AAC9C,EAAA,MAAM,QAAA,GAAW,KAAK,QAAA,IAAY,IAAA;AAClC,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,QAAA,CAAS,UAAA,IAAc,wBAAA;AAE/C,EAAA,MAAM,QAAA,GAAW,iBAAA,CAAkB,IAAA,CAAK,QAAQ,CAAA;AAChD,EAAA,MAAM,KAAA,GAAQ,kBAAA,CAAmB,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA;AAErD,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,IAAA,CAAK,KAAK,MAAA,EAAO,EAAG,WAAW,CAAC,CAAA;AAC9D,EAAA,IAAI;AAEF,IAAA,MAAM,SAAA;AAAA,MACJ,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,eAAe,CAAA;AAAA,MAClC,IAAA,CAAK,SAAA,CAAU,QAAA,EAAU,IAAA,EAAM,CAAC,CAAA;AAAA,MAChC;AAAA,KACF;AACA,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,UAAU,CAAA;AAC9C,IAAA,MAAM,KAAA,CAAM,KAAK,OAAA,CAAQ,QAAQ,GAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AACvD,IAAA,MAAM,SAAA,CAAU,QAAA,EAAU,KAAA,EAAO,MAAM,CAAA;AAGvC,IAAA,MAAM,KAAA,CAAM,KAAK,OAAA,CAAQ,OAAO,GAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AAEtD,IAAA,MAAM,GAAA,GAAM,YAAA,CAAa,IAAA,CAAK,OAAO,CAAA;AACrC,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,MAAM,IAAI,GAAA,EAAK,CAAC,UAAA,EAAY,eAAe,GAAG,OAAO,CAAA;AAAA,IACvD;AACA,IAAA,MAAM,IAAI,GAAA,EAAK,CAAC,QAAQ,OAAA,EAAS,OAAO,GAAG,GAAG,CAAA;AAE9C,IAAA,MAAM,KAAA,GAAA,CAAS,MAAM,IAAA,CAAK,OAAO,CAAA,EAAG,IAAA;AACpC,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,QAAA,EAAU,OAAA,EAAQ;AAAA,EAC7C,CAAA,SAAE;AACA,IAAA,IAAI,CAAC,KAAK,WAAA,EAAa;AACrB,MAAA,MAAM,EAAA,CAAG,OAAA,EAAS,EAAE,SAAA,EAAW,IAAA,EAAM,OAAO,IAAA,EAAM,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAAA,IACpE;AAAA,EACF;AACF;AAEA,SAAS,aAAa,GAAA,EAA8C;AAClE,EAAA,IAAI,CAAC,GAAA,EAAK,OAAO,CAAC,KAAA,EAAO,MAAM,oBAAoB,CAAA;AACnD,EAAA,OAAO,MAAM,OAAA,CAAQ,GAAG,CAAA,GAAI,GAAA,GAAM,CAAC,GAAG,CAAA;AACxC;AAGA,SAAS,GAAA,CAAI,GAAA,EAAe,IAAA,EAAgB,GAAA,EAA4B;AACtE,EAAA,MAAM,CAAC,GAAA,EAAK,GAAG,OAAO,CAAA,GAAI,GAAA;AAC1B,EAAA,MAAM,IAAA,GAAO,CAAC,GAAG,OAAA,EAAS,GAAG,IAAI,CAAA;AACjC,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,EAAK,IAAA,EAAM;AAAA,MAC7B,GAAA;AAAA,MACA,KAAA,EAAO,SAAA;AAAA,MACP,KAAA,EAAO,QAAQ,QAAA,KAAa;AAAA;AAAA,KAC7B,CAAA;AACD,IAAA,KAAA,CAAM,EAAA,CAAG,SAAS,MAAM,CAAA;AACxB,IAAA,KAAA,CAAM,EAAA,CAAG,MAAA,EAAQ,CAAC,IAAA,KAAS;AACzB,MAAA,IAAI,IAAA,KAAS,GAAG,OAAA,EAAQ;AAAA;AAEtB,QAAA,MAAA;AAAA,UACE,IAAI,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,CAAA,EAAI,IAAA,CAAK,IAAA,CAAK,GAAG,CAAC,CAAA,kBAAA,EAAqB,IAAA,IAAQ,MAAM,CAAA,CAAE;AAAA,SACzE;AAAA,IACJ,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH","file":"connectors-build.js","sourcesContent":["// Node-only build helper: generate + pack a `.mcpb` bundle for a remote MCP\n// server. Imported from `@particle-academy/agent-integrations/connectors/build`\n// at BUILD time (a CI step / a `scripts/build-mcpb.mjs`), never from browser\n// code — it touches fs + shells out to the official mcpb CLI.\n\nimport { spawn } from \"node:child_process\";\nimport { mkdtemp, writeFile, mkdir, rm, stat } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport {\n buildMcpbManifest,\n buildMcpbProxyStub,\n DEFAULT_MCPB_ENTRY_POINT,\n type McpbManifestInput,\n} from \"./mcpb\";\n\nexport type { McpbManifestInput, McpbTool } from \"./mcpb\";\n\nexport interface WriteMcpbBundleOptions {\n /** Where to write the packed `.mcpb` (absolute or cwd-relative). */\n outFile: string;\n /** The manifest inputs (server name, version, mcpUrl, tools, …). */\n manifest: McpbManifestInput;\n /**\n * The mcpb CLI to shell out to. Default: `[\"npx\", \"-y\", \"@anthropic-ai/mcpb\"]`.\n * Pass a locally-installed binary path (string) or argv (array) to avoid the\n * npx network fetch.\n */\n mcpbBin?: string | string[];\n /** Run `mcpb validate` before packing. Default true. */\n validate?: boolean;\n /** Keep the temp work dir instead of removing it (debugging). Default false. */\n keepWorkDir?: boolean;\n /** Working directory the CLI runs in. Default `process.cwd()`. */\n cwd?: string;\n}\n\nexport interface WriteMcpbBundleResult {\n /** Absolute path to the written `.mcpb`. */\n outFile: string;\n /** Size of the written bundle in bytes. */\n bytes: number;\n /** The manifest object that was packed. */\n manifest: Record<string, unknown>;\n /** The work dir used (returned even after cleanup, for logging). */\n workDir: string;\n}\n\n/**\n * Generate a `manifest.json` + `server/proxy.js` for a remote MCP server, then\n * validate and pack them into a `.mcpb` using the official `@anthropic-ai/mcpb`\n * CLI. Returns the output path + size.\n *\n * ```ts\n * import { writeMcpbBundle } from \"@particle-academy/agent-integrations/connectors/build\";\n *\n * await writeMcpbBundle({\n * outFile: \"public/decksmith.mcpb\",\n * manifest: {\n * name: \"decksmith\",\n * display_name: \"Decksmith\",\n * version: \"0.2.0\",\n * description: \"Agent-driven slide deck builder.\",\n * author: { name: \"Particle Academy\", url: \"https://decksmith.dev\" },\n * mcpUrl: \"https://decksmith.dev/mcp\",\n * tools: [{ name: \"start_session\", description: \"…\" }],\n * },\n * });\n * ```\n */\nexport async function writeMcpbBundle(\n opts: WriteMcpbBundleOptions,\n): Promise<WriteMcpbBundleResult> {\n const cwd = opts.cwd ?? process.cwd();\n const outFile = path.resolve(cwd, opts.outFile);\n const validate = opts.validate ?? true;\n const entryPoint = opts.manifest.entryPoint ?? DEFAULT_MCPB_ENTRY_POINT;\n\n const manifest = buildMcpbManifest(opts.manifest);\n const proxy = buildMcpbProxyStub(opts.manifest.mcpUrl);\n\n const workDir = await mkdtemp(path.join(tmpdir(), \"fai-mcpb-\"));\n try {\n // Lay out the bundle source: manifest.json + the entry-point stub.\n await writeFile(\n path.join(workDir, \"manifest.json\"),\n JSON.stringify(manifest, null, 2),\n \"utf8\",\n );\n const entryAbs = path.join(workDir, entryPoint);\n await mkdir(path.dirname(entryAbs), { recursive: true });\n await writeFile(entryAbs, proxy, \"utf8\");\n\n // Ensure the output dir exists.\n await mkdir(path.dirname(outFile), { recursive: true });\n\n const bin = normalizeBin(opts.mcpbBin);\n if (validate) {\n await run(bin, [\"validate\", \"manifest.json\"], workDir);\n }\n await run(bin, [\"pack\", workDir, outFile], cwd);\n\n const bytes = (await stat(outFile)).size;\n return { outFile, bytes, manifest, workDir };\n } finally {\n if (!opts.keepWorkDir) {\n await rm(workDir, { recursive: true, force: true }).catch(() => {});\n }\n }\n}\n\nfunction normalizeBin(bin: string | string[] | undefined): string[] {\n if (!bin) return [\"npx\", \"-y\", \"@anthropic-ai/mcpb\"];\n return Array.isArray(bin) ? bin : [bin];\n}\n\n/** Spawn `[cmd, ...args]`, inheriting stdio, rejecting on a non-zero exit. */\nfunction run(bin: string[], args: string[], cwd: string): Promise<void> {\n const [cmd, ...binArgs] = bin;\n const argv = [...binArgs, ...args];\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, argv, {\n cwd,\n stdio: \"inherit\",\n shell: process.platform === \"win32\", // npx resolves via .cmd on Windows\n });\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => {\n if (code === 0) resolve();\n else\n reject(\n new Error(`${cmd} ${argv.join(\" \")} exited with code ${code ?? \"null\"}`),\n );\n });\n });\n}\n"]}
|