@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 +48 -0
- package/dist/helpers.d.ts +32 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +16 -0
- package/dist/source-policy.d.ts +8 -0
- package/dist/types.d.ts +153 -0
- package/dist/version-compatibility.d.ts +2 -0
- package/package.json +33 -0
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;
|
package/dist/index.d.ts
ADDED
|
@@ -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;
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
}
|
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
|
+
}
|