@mcp-com-ai/plugin-sdk 1.0.1-beta.260912

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,48 @@
1
+ # @hapi/plugin-sdk
2
+
3
+ The contract every HAPI CLI plugin implements.
4
+
5
+ This package is intentionally tiny and stable. It exports:
6
+
7
+ - `HapiCliPlugin` — the plugin shape the core CLI loads
8
+ - `CliCommand` / `CliContext` / `CliInvocation` — the runtime types
9
+ - `defineCommand` / `definePlugin` — identity helpers for IntelliSense + validation
10
+ - `parseArgs` — a small, dependency-free flag parser
11
+ - `renderCommandHelp` — consistent help output
12
+ - `HAPI_CLI_API_VERSION` — the current plugin-contract version
13
+
14
+ ## Writing a plugin
15
+
16
+ ```ts
17
+ import { definePlugin, defineCommand, HAPI_CLI_API_VERSION } from '@mcp-com-ai/plugin-sdk';
18
+
19
+ const connect = defineCommand({
20
+ name: 'connect',
21
+ description: 'Connect to an MCP server',
22
+ arguments: [{ name: 'server-url', description: 'Target MCP server URL', required: true }],
23
+ async run({ positionals }, ctx) {
24
+ const [url] = positionals;
25
+ ctx.stdout.write(`Connecting to ${url}\n`);
26
+ },
27
+ });
28
+
29
+ export default definePlugin({
30
+ name: '@mcp-com-ai/client',
31
+ version: '0.1.0',
32
+ compatibility: { cliApiVersion: HAPI_CLI_API_VERSION },
33
+ commands: [
34
+ {
35
+ name: 'client',
36
+ description: 'MCP client operations',
37
+ subcommands: [connect],
38
+ },
39
+ ],
40
+ });
41
+ ```
42
+
43
+ ## Stability policy
44
+
45
+ The SDK follows a simple rule: **additive changes keep the same
46
+ `HAPI_CLI_API_VERSION`. Any breaking change bumps the major.** The CLI
47
+ refuses to load plugins whose `compatibility.cliApiVersion` does not
48
+ match the CLI's advertised version.
@@ -0,0 +1,32 @@
1
+ import type { CliCommand, CliContext, CliInvocation, CliOption, HapiCliPlugin } from './types';
2
+ /**
3
+ * `defineCommand` is a no-op helper that exists purely to give plugin
4
+ * authors good IntelliSense without forcing them to import the `CliCommand`
5
+ * type explicitly. Works at the top level of subcommand trees too.
6
+ */
7
+ export declare function defineCommand(command: CliCommand): CliCommand;
8
+ /**
9
+ * `definePlugin` validates a plugin shape at declaration time and returns it
10
+ * unchanged. Keep the runtime cost trivial.
11
+ */
12
+ export declare function definePlugin(plugin: HapiCliPlugin): HapiCliPlugin;
13
+ /**
14
+ * Minimal flag parser built on top of standard argv. Recognises:
15
+ * - --flag
16
+ * - --flag=value
17
+ * - --flag value
18
+ * - --no-flag (sets boolean false)
19
+ * - -x value / -x=value (short alias)
20
+ * - -- (marks end of flags; everything after goes to `rest`)
21
+ *
22
+ * Unknown flags are preserved in `options` as raw string values so commands
23
+ * can choose to forward them to child processes (e.g., wrangler, docker).
24
+ */
25
+ export declare function parseArgs(argv: readonly string[], options?: readonly CliOption[], args?: {
26
+ allowUnknown?: boolean;
27
+ }): CliInvocation;
28
+ /**
29
+ * Emit a standard help block for a command. Plugins can call this from
30
+ * `run` to render consistent help text.
31
+ */
32
+ export declare function renderCommandHelp(command: CliCommand, context: CliContext, crumbs?: string[]): void;
@@ -0,0 +1,4 @@
1
+ export * from "./types";
2
+ export { defineCommand, definePlugin, parseArgs, renderCommandHelp, } from "./helpers";
3
+ export { DEFAULT_DOCUMENT_SOURCE_POLICY, assertRemoteDocumentUrl, redactDocumentUrl, } from "./source-policy";
4
+ export { isSupportedVersionRange, satisfiesVersionRange, } from "./version-compatibility";
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ // @bun
2
+ var M="1.0.1-beta.260912";var T=Number(M.split(".")[0]);function P(e){return e}function F(e){if(!e.name)throw Error("HAPI plugin missing required `name`");if(!e.version)throw Error(`HAPI plugin '${e.name}' missing required \`version\``);if(!e.compatibility?.cliApiVersion)throw Error(`HAPI plugin '${e.name}' must declare compatibility.cliApiVersion (current: '${T}')`);if(!Array.isArray(e.commands))throw Error(`HAPI plugin '${e.name}' must declare a commands[] array`);let r=new Set;for(let s of e.serveProviders??[]){if(!s.id||!s.kind||!s.rootField||!s.description)throw Error(`HAPI plugin '${e.name}' has an invalid serve provider declaration`);if(typeof s.serve!=="function")throw Error(`HAPI plugin '${e.name}' serve provider '${s.id}' must declare serve()`);if(r.has(s.id))throw Error(`HAPI plugin '${e.name}' declares duplicate serve provider '${s.id}'`);r.add(s.id)}return e}function H(e,r=[],s={}){let c=s.allowUnknown??!0,n=[],t=[],f={};for(let i of r)if(i.env&&process.env[i.env]!==void 0)f[i.name]=E(process.env[i.env],i);else if(i.default!==void 0)f[i.name]=i.default;let u=new Map,j=new Map;for(let i of r)if(u.set(i.name,i),i.alias)j.set(i.alias,i);let h=0;while(h<e.length){let i=e[h];if(i==="--"){t.push(...e.slice(h+1));break}if(i.startsWith("--")){let d=i.slice(2),R=d.indexOf("="),x=R>=0?d.slice(0,R):d,S=R>=0?d.slice(R+1):void 0,U=x.startsWith("no-"),V=U?x.slice(3):x,L=u.get(V)??u.get(Z(V));if(!L){if(!c)throw Error(`Unknown option: --${x}`);if(S!==void 0)f[D(x)]=S,h++;else if(U)f[D(V)]=!1,h++;else{let A=e[h+1];if(A!==void 0&&!String(A).startsWith("-"))f[D(x)]=A,h+=2;else f[D(x)]=!0,h++}continue}if(L.type==="boolean"){f[L.name]=U?!1:S?O(S):!0,h++;continue}let m=S;if(m===void 0)m=e[h+1],h+=m!==void 0&&!String(m).startsWith("-")?2:1;else h++;if(L.type==="array"){let A=f[L.name]??[];f[L.name]=[...A,...String(m??"").split(",").map((_)=>_.trim()).filter(Boolean)]}else f[L.name]=E(m,L);continue}if(i.startsWith("-")&&i.length>1){let d=i.slice(1),R=d.indexOf("="),x=R>=0?d.slice(0,R):d,S=R>=0?d.slice(R+1):void 0,U=j.get(x);if(!U){if(!c)throw Error(`Unknown option: -${x}`);n.push(i),h++;continue}if(U.type==="boolean"){f[U.name]=S?O(S):!0,h++;continue}let V=S;if(V===void 0)V=e[h+1],h+=V!==void 0&&!String(V).startsWith("-")?2:1;else h++;f[U.name]=E(V,U);continue}n.push(i),h++}return{positionals:n,options:f,rest:t}}function E(e,r){if(e===void 0)return e;switch(r.type){case"number":{let s=Number(e);return Number.isFinite(s)?s:e}case"boolean":return O(e);case"array":return String(e).split(",").map((s)=>s.trim()).filter(Boolean);default:return String(e)}}function O(e){if(typeof e==="boolean")return e;let r=String(e).toLowerCase();return r==="1"||r==="true"||r==="yes"||r==="on"}function Z(e){return e.replace(/-([a-z0-9])/g,(r,s)=>s.toUpperCase())}function D(e){return Z(e)}function W(e,r,s=[]){let c=s.length?`${s.join(" ")} `:"",n=(t)=>r.stdout.write(t);if(n(`
3
+ ${c}${e.name} \u2014 ${e.description}
4
+ `),e.arguments?.length){n(`
5
+ Arguments:
6
+ `);for(let t of e.arguments){let f=t.variadic?`${t.name}...`:t.name,u=t.required?`<${f}>`:`[${f}]`;n(` ${u.padEnd(24)} ${t.description}
7
+ `)}}if(e.options?.length){n(`
8
+ Options:
9
+ `);for(let t of e.options){let f=t.alias?`-${t.alias}, --${t.name}`:` --${t.name}`,u=t.default!==void 0?`${t.description} (default: ${JSON.stringify(t.default)})`:t.description;n(` ${f.padEnd(28)} ${u}
10
+ `)}}if(e.subcommands?.length){n(`
11
+ Subcommands:
12
+ `);for(let t of e.subcommands){if(t.hidden)continue;n(` ${t.name.padEnd(14)} ${t.description}
13
+ `)}}if(e.longHelp)n(`
14
+ ${e.longHelp}
15
+ `);n(`
16
+ `)}var Y=Object.freeze({maxBytes:5242880,timeoutMs:1e4,maxRedirects:5});function B(e){if(e.protocol!=="http:"&&e.protocol!=="https:")throw Error(`Remote documents cannot resolve ${e.protocol} sources`)}function b(e){try{let r=e instanceof URL?new URL(e.href):new URL(e);if(r.username="",r.password="",r.search)r.search="?redacted";return r.href}catch{return String(e)}}var G=/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.-]+)?$/,$=/^(<=|>=|<|>|=)?(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z.-]+)?)$/;function J(e,r){let s=C(e);if(!s||!y(r))return!1;if(r.startsWith("^")){let n=C(r.slice(1));return n!==void 0&&z(s,n)>=0&&z(s,K(n))<0}let c=r.trim().split(/\s+/).filter(Boolean);return c.length>0&&c.every((n)=>{let t=n.match($);if(!t)return!1;let f=t[2];if(!f)return!1;let u=C(f);if(!u)return!1;let j=z(s,u);switch(t[1]??"="){case"=":return j===0;case">":return j>0;case">=":return j>=0;case"<":return j<0;case"<=":return j<=0;default:return!1}})}function y(e){if(e.startsWith("^"))return C(e.slice(1))!==void 0;let r=e.trim().split(/\s+/).filter(Boolean);return r.length>0&&r.every((s)=>{let n=s.match($)?.[2];return n!==void 0&&C(n)!==void 0})}function C(e){let r=e.match(G);if(!r)return;return{major:Number(r[1]),minor:Number(r[2]),patch:Number(r[3]),prerelease:r[4]?.split(".")??[]}}function K(e){if(e.major>0)return{major:e.major+1,minor:0,patch:0,prerelease:["0"]};if(e.minor>0)return{major:0,minor:e.minor+1,patch:0,prerelease:["0"]};return{major:0,minor:0,patch:e.patch+1,prerelease:["0"]}}function z(e,r){for(let c of["major","minor","patch"])if(e[c]!==r[c])return e[c]<r[c]?-1:1;if(e.prerelease.length===0||r.prerelease.length===0){if(e.prerelease.length===r.prerelease.length)return 0;return e.prerelease.length===0?1:-1}let s=Math.max(e.prerelease.length,r.prerelease.length);for(let c=0;c<s;c++){let n=e.prerelease[c],t=r.prerelease[c];if(n===void 0)return-1;if(t===void 0)return 1;if(n===t)continue;let f=/^\d+$/.test(n),u=/^\d+$/.test(t);if(f&&u)return Number(n)<Number(t)?-1:1;if(f!==u)return f?-1:1;return n<t?-1:1}return 0}export{J as satisfiesVersionRange,W as renderCommandHelp,b as redactDocumentUrl,H as parseArgs,y as isSupportedVersionRange,F as definePlugin,P as defineCommand,B as assertRemoteDocumentUrl,T as HAPI_CLI_API_VERSION,Y as DEFAULT_DOCUMENT_SOURCE_POLICY};
@@ -0,0 +1,8 @@
1
+ /** Shared conservative defaults used by document-owning serve providers. */
2
+ export declare const DEFAULT_DOCUMENT_SOURCE_POLICY: Readonly<{
3
+ maxBytes: number;
4
+ timeoutMs: 10000;
5
+ maxRedirects: 5;
6
+ }>;
7
+ export declare function assertRemoteDocumentUrl(url: URL): void;
8
+ export declare function redactDocumentUrl(value: URL | string): string;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * The plugin contract for the HAPI CLI.
3
+ *
4
+ * Everything a plugin needs to declare itself to the core belongs here.
5
+ * Keep this surface small and boring — stability matters more than ergonomics.
6
+ */
7
+ /**
8
+ * Version of the plugin API that the CLI advertises.
9
+ * Plugins declare the version they target; the loader refuses to load mismatches.
10
+ */
11
+ export declare const HAPI_CLI_API_VERSION: number;
12
+ export type HapiCliApiVersion = typeof HAPI_CLI_API_VERSION;
13
+ /**
14
+ * Write-able streams injected into commands so plugins don't need to touch
15
+ * `process.stdout` directly. Mostly convenience — tests can substitute streams.
16
+ */
17
+ export interface CliWritableStream {
18
+ write(chunk: string): void;
19
+ }
20
+ export interface CliReadableStream {
21
+ read?(): unknown;
22
+ }
23
+ export interface CliContext {
24
+ cwd: string;
25
+ env: Record<string, string | undefined>;
26
+ stdout: CliWritableStream;
27
+ stderr: CliWritableStream;
28
+ stdin: CliReadableStream;
29
+ rawArgv: readonly string[];
30
+ /**
31
+ * HAPI home directory. Resolved from $HAPI_HOME, falling back to $HOME/.hapi.
32
+ * Plugins should read/write their state under this directory.
33
+ */
34
+ hapiHome: string;
35
+ /**
36
+ * Absolute path to the root of the monorepo or install, for tools that
37
+ * need to resolve bundled assets.
38
+ */
39
+ installRoot?: string;
40
+ /**
41
+ * Opaque service registry. Plugins can use it to share cross-plugin
42
+ * services (e.g., a license service, a telemetry reporter) without
43
+ * creating direct dependencies.
44
+ */
45
+ services: Map<string, unknown>;
46
+ }
47
+ /**
48
+ * Flags/options recognised by a command. Parsing is intentionally simple —
49
+ * commands can opt out and parse rawArgs themselves if they need more.
50
+ */
51
+ export interface CliOption {
52
+ name: string;
53
+ alias?: string;
54
+ description: string;
55
+ type?: "string" | "boolean" | "number" | "array";
56
+ default?: unknown;
57
+ required?: boolean;
58
+ env?: string;
59
+ choices?: readonly string[];
60
+ }
61
+ /**
62
+ * A runnable command. Subcommands are first-class: a command that has
63
+ * subcommands does not need a `run`; if it has both, `run` handles the
64
+ * case where no subcommand was supplied.
65
+ */
66
+ export interface CliCommand {
67
+ name: string;
68
+ description: string;
69
+ aliases?: readonly string[];
70
+ options?: readonly CliOption[];
71
+ arguments?: readonly CliArgument[];
72
+ subcommands?: readonly CliCommand[];
73
+ run?: (args: CliInvocation, context: CliContext) => Promise<number | void> | number | void;
74
+ /** Optional long-form help appended below the autogenerated help block. */
75
+ longHelp?: string;
76
+ /** Marks a command as hidden from help output. */
77
+ hidden?: boolean;
78
+ }
79
+ export interface CliArgument {
80
+ name: string;
81
+ description: string;
82
+ required?: boolean;
83
+ variadic?: boolean;
84
+ }
85
+ /**
86
+ * What a command's `run` receives. Positional args are `positionals`; any
87
+ * unknown tokens are surfaced in `rest`.
88
+ */
89
+ export interface CliInvocation {
90
+ positionals: string[];
91
+ options: Record<string, unknown>;
92
+ rest: string[];
93
+ }
94
+ /** A parsed source passed from the CLI to a document-specific serve provider. */
95
+ export interface DocumentSourceEnvelope {
96
+ /** The value typed by the user, before path or URL resolution. */
97
+ identifier: string;
98
+ /** Canonical absolute file URL or final HTTP(S) retrieval URL. */
99
+ canonicalUri: string;
100
+ /** URI used to resolve relative source descriptions. */
101
+ baseUri: string;
102
+ /** Original UTF-8 document text. */
103
+ raw: string;
104
+ /** Parsed JSON/YAML root value. */
105
+ document: unknown;
106
+ /** Whether the entry document was retrieved over HTTP(S). */
107
+ remote: boolean;
108
+ }
109
+ export interface ServeProviderRequest {
110
+ source?: DocumentSourceEnvelope;
111
+ invocation: CliInvocation;
112
+ }
113
+ /**
114
+ * Optional contribution used by the generic `hapi serve` dispatcher.
115
+ * Document semantics remain inside the plugin that owns the provider.
116
+ */
117
+ export interface ServeProvider {
118
+ id: string;
119
+ kind: string;
120
+ rootField: string;
121
+ description: string;
122
+ options?: readonly CliOption[];
123
+ /** Permit legacy identifiers that do not resolve to a document, e.g. an OpenAPI project name. */
124
+ legacyFallback?: boolean;
125
+ serve: (request: ServeProviderRequest, context: CliContext) => Promise<number | void> | number | void;
126
+ }
127
+ export interface HapiCliPlugin {
128
+ name: string;
129
+ version: string;
130
+ displayName?: string;
131
+ description?: string;
132
+ /** Declared plugin-contract version. Must match HAPI_CLI_API_VERSION. */
133
+ compatibility: {
134
+ cliApiVersion: HapiCliApiVersion | string;
135
+ /**
136
+ * Present when the plugin consumes @mcp-com-ai/core. The version is read
137
+ * from the same resolved core package that the plugin imports at runtime.
138
+ */
139
+ core?: {
140
+ version: string;
141
+ range: string;
142
+ };
143
+ };
144
+ commands: readonly CliCommand[];
145
+ /** Document kinds this plugin can serve through the top-level dispatcher. */
146
+ serveProviders?: readonly ServeProvider[];
147
+ /**
148
+ * Optional lifecycle hook called once at CLI boot after all plugins load.
149
+ * Useful for plugins that need to register cross-plugin services
150
+ * (telemetry, license) without attaching them to commands.
151
+ */
152
+ onLoad?: (context: CliContext) => Promise<void> | void;
153
+ }
@@ -0,0 +1,2 @@
1
+ export declare function satisfiesVersionRange(version: string, range: string): boolean;
2
+ export declare function isSupportedVersionRange(range: string): boolean;
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@mcp-com-ai/plugin-sdk",
3
+ "version": "1.0.1-beta.260912",
4
+ "description": "The shared plugin contract for the HAPI CLI. Consumed by the core CLI, first-party plugins, and third-party extensions.",
5
+ "license": "LRL-CL",
6
+ "type": "module",
7
+ "module": "./dist/index.js",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "hapi-build-source": "./src/index.ts",
14
+ "bun": "./dist/index.js",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": ["dist", "README.md"],
19
+ "scripts": {
20
+ "typecheck": "bunx tsc --noEmit",
21
+ "build": "bun ../../scripts/build-package.ts ."
22
+ },
23
+ "peerDependencies": {
24
+ "typescript": "^5"
25
+ },
26
+ "engines": {
27
+ "bun": ">=1.3.0"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "keywords": ["hapi", "hapi-plugin", "mcp", "cli"]
33
+ }