@hostafrica/connector-core 1.0.0 → 1.0.2

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,103 @@
1
+ # @hostafrica/connector-core
2
+
3
+ The registrar behind the HostAfrica Connector: detect which AI clients are installed on a machine, and register the HostAfrica [remote MCP server](https://modelcontextprotocol.io/) with each of them. OAuth first, with an API-token fallback.
4
+
5
+ This is the shared library. Most people want one of the two things built on it:
6
+
7
+ - [`@hostafrica/connect`](https://www.npmjs.com/package/@hostafrica/connect): the CLI, `npx @hostafrica/connect install`
8
+ - [HostAfrica Connector for VS Code](https://github.com/hostafrica-dev/ha-connector/tree/main/packages/vscode-extension): the extension
9
+
10
+ Reach for this package directly if you're building your own installer, panel action or onboarding flow.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @hostafrica/connector-core
16
+ ```
17
+
18
+ CommonJS, targets ES2022, no runtime dependencies beyond `smol-toml` (for Codex CLI's TOML config). Requires Node 18 or newer.
19
+
20
+ ## Usage
21
+
22
+ ```ts
23
+ import { clientStatuses, clientById } from "@hostafrica/connector-core";
24
+
25
+ // What's installed, and what's already registered?
26
+ for (const { client, installed, registered } of await clientStatuses()) {
27
+ console.log(client.id, installed, registered, client.describeTarget());
28
+ }
29
+
30
+ // Register one client using the browser OAuth flow.
31
+ await clientById("cursor")?.register({ auth: { kind: "oauth" } });
32
+ ```
33
+
34
+ Every client implements the same interface, so callers don't need to know whether a given tool stores config as JSON, as TOML, or behind a CLI:
35
+
36
+ ```ts
37
+ interface McpClient {
38
+ id: string; // "cursor"
39
+ name: string; // "Cursor"
40
+ supportsOAuth: boolean; // false → registration needs a token
41
+ detect(): Promise<boolean>; // installed on this machine?
42
+ isRegistered(): Promise<boolean>; // HostAfrica already present?
43
+ register(opts: RegisterOptions): Promise<void>;
44
+ unregister(): Promise<void>;
45
+ describeTarget(): string; // config path or command, for display
46
+ }
47
+ ```
48
+
49
+ ### API
50
+
51
+ | Export | Purpose |
52
+ |---|---|
53
+ | `allClients()` | Every client the registrar can configure |
54
+ | `clientById(id)` | One client by its stable id, or `undefined` |
55
+ | `clientStatuses()` | `{ client, installed, registered }` for all of them |
56
+ | `createClients(home?)` | Build the client set rooted at a given home directory (tests pass a temp dir) |
57
+ | `endpointFor(auth)` | Resolve the URL and headers an auth mode maps to |
58
+ | `jsonClient(spec)` | Factory for the common `{ mcpServers: { … } }` config shape |
59
+ | `cursorInstallDeeplink()` | One-click `cursor://` install link, no extension involved |
60
+ | `claudeAddArgs(auth)` | The `claude mcp add` argument list, as a pure function |
61
+ | `makeCodex(home?)` | Codex CLI adapter rooted at a given home |
62
+
63
+ Constants: `MCP_OAUTH_URL`, `MCP_BEARER_URL`, `SERVER_KEY`, `SERVER_LABEL`, `CLIENT_AREA_URL`, `DEVELOPER_DOCS_URL`.
64
+
65
+ ## Supported clients
66
+
67
+ | id | Registration target |
68
+ |---|---|
69
+ | `claude-code` | `claude mcp add`, user scope |
70
+ | `cursor` | `~/.cursor/mcp.json` |
71
+ | `windsurf` | `~/.codeium/windsurf/mcp_config.json` |
72
+ | `gemini-cli` | `~/.gemini/settings.json` |
73
+ | `codex` | `~/.codex/config.toml` |
74
+ | `antigravity` | `~/.gemini/config/mcp_config.json` |
75
+ | `devin-cli` | `~/.config/devin/mcp_config.json` |
76
+ | `junie` | `~/.junie/mcp/mcp.json` |
77
+ | `claude-desktop` | `claude_desktop_config.json`, bridged via `npx mcp-remote` |
78
+
79
+ VS Code is deliberately absent: the extension registers the server natively through the `McpServerDefinitionProvider` API rather than writing a config file.
80
+
81
+ Clients differ in more than path. Windsurf and Antigravity want `serverUrl`, Gemini CLI wants `httpUrl`, Devin CLI wants `url` plus `transport: "http"`, Codex uses TOML, and Claude Desktop only accepts stdio servers so it's bridged through `mcp-remote`. `jsonClient()` covers the common shape; the rest are hand-written adapters.
82
+
83
+ ## Auth modes
84
+
85
+ ```ts
86
+ type AuthMode =
87
+ | { kind: "oauth" }
88
+ | { kind: "token"; token: string };
89
+ ```
90
+
91
+ OAuth registrations contain no credentials, only the endpoint. The client runs its own browser sign-in against a server supporting Dynamic Client Registration, PKCE and refresh tokens. Prefer this wherever `supportsOAuth` is true.
92
+
93
+ Token mode writes an `Authorization: Bearer <token>` header into the client's config, for the rare tool that can't run OAuth. Files written this way are `chmod 600`.
94
+
95
+ Writes are read-modify-write: entries for other MCP servers, and unrelated top-level keys, are preserved. Invalid existing JSON or TOML raises a clear error naming the file rather than overwriting it.
96
+
97
+ ## Links
98
+
99
+ - [Source and issues](https://github.com/hostafrica-dev/ha-connector)
100
+ - [Developer docs](https://www.hostafrica.com/developers/)
101
+ - [HostAfrica Client Area](https://panel.hostafrica.com/)
102
+
103
+ MIT licensed.
package/dist/fsutil.d.ts CHANGED
@@ -6,9 +6,19 @@ export declare function whichBin(bin: string): Promise<string | undefined>;
6
6
  export declare function readTextIfPresent(p: string): Promise<string | undefined>;
7
7
  /**
8
8
  * Write a config file that may contain a bearer token: parent dirs created,
9
- * mode 600 so other local users can't read it. The mode option on writeFile
10
- * only applies to newly created files, so chmod explicitly for existing ones
11
- * (no-op semantics on Windows, where fs permissions work differently).
9
+ * readable by the owning user only. On POSIX that is mode 600; the mode
10
+ * option on writeFile only applies to newly created files, so chmod
11
+ * explicitly for existing ones. On Windows mode bits are a no-op, so the
12
+ * file gets an explicit NTFS ACL instead (see restrictAclToCurrentUser).
12
13
  */
13
14
  export declare function writePrivate(p: string, content: string): Promise<void>;
15
+ /**
16
+ * Windows equivalent of chmod 600: drop inherited ACEs and grant full
17
+ * control to the current user only. Files under the profile folder already
18
+ * inherit an owner/SYSTEM/Administrators ACL, so this mostly matters when
19
+ * that folder has been loosened or the config lives on a share. Failures
20
+ * are swallowed: the file is already written and a missing icacls (or a
21
+ * filesystem without ACLs) should not break registration.
22
+ */
23
+ export declare function restrictAclToCurrentUser(p: string): Promise<void>;
14
24
  export { execFileP };
package/dist/fsutil.js CHANGED
@@ -38,6 +38,7 @@ exports.exists = exists;
38
38
  exports.whichBin = whichBin;
39
39
  exports.readTextIfPresent = readTextIfPresent;
40
40
  exports.writePrivate = writePrivate;
41
+ exports.restrictAclToCurrentUser = restrictAclToCurrentUser;
41
42
  const fs = __importStar(require("node:fs/promises"));
42
43
  const path = __importStar(require("node:path"));
43
44
  const node_child_process_1 = require("node:child_process");
@@ -75,15 +76,56 @@ async function readTextIfPresent(p) {
75
76
  }
76
77
  /**
77
78
  * Write a config file that may contain a bearer token: parent dirs created,
78
- * mode 600 so other local users can't read it. The mode option on writeFile
79
- * only applies to newly created files, so chmod explicitly for existing ones
80
- * (no-op semantics on Windows, where fs permissions work differently).
79
+ * readable by the owning user only. On POSIX that is mode 600; the mode
80
+ * option on writeFile only applies to newly created files, so chmod
81
+ * explicitly for existing ones. On Windows mode bits are a no-op, so the
82
+ * file gets an explicit NTFS ACL instead (see restrictAclToCurrentUser).
81
83
  */
82
84
  async function writePrivate(p, content) {
83
85
  await fs.mkdir(path.dirname(p), { recursive: true });
84
86
  await fs.writeFile(p, content, { encoding: "utf8", mode: 0o600 });
85
- if (process.platform !== "win32") {
87
+ if (process.platform === "win32") {
88
+ await restrictAclToCurrentUser(p);
89
+ }
90
+ else {
86
91
  await fs.chmod(p, 0o600);
87
92
  }
88
93
  }
94
+ /**
95
+ * Windows equivalent of chmod 600: drop inherited ACEs and grant full
96
+ * control to the current user only. Files under the profile folder already
97
+ * inherit an owner/SYSTEM/Administrators ACL, so this mostly matters when
98
+ * that folder has been loosened or the config lives on a share. Failures
99
+ * are swallowed: the file is already written and a missing icacls (or a
100
+ * filesystem without ACLs) should not break registration.
101
+ */
102
+ async function restrictAclToCurrentUser(p) {
103
+ if (process.platform !== "win32")
104
+ return;
105
+ const domain = process.env.USERDOMAIN;
106
+ const user = process.env.USERNAME;
107
+ if (!user)
108
+ return;
109
+ const principal = domain ? `${domain}\\${user}` : user;
110
+ try {
111
+ // /inheritance:r drops inherited ACEs, /grant:r replaces the user's
112
+ // explicit ACE, and /remove:g strips the broad groups that could remain
113
+ // as explicit entries: Everyone, Authenticated Users, Users (by SID so
114
+ // it is locale-independent). SYSTEM and Administrators may remain; that
115
+ // is parity with root on POSIX.
116
+ await execFileP("icacls", [
117
+ p,
118
+ "/inheritance:r",
119
+ "/grant:r",
120
+ `${principal}:F`,
121
+ "/remove:g",
122
+ "*S-1-1-0",
123
+ "*S-1-5-11",
124
+ "*S-1-5-32-545",
125
+ ]);
126
+ }
127
+ catch {
128
+ /* best effort */
129
+ }
130
+ }
89
131
  //# sourceMappingURL=fsutil.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fsutil.js","sourceRoot":"","sources":["../src/fsutil.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,wBAOC;AAGD,4BASC;AAED,8CAMC;AAQD,oCAMC;AAhDD,qDAAuC;AACvC,gDAAkC;AAClC,2DAA8C;AAC9C,yCAAsC;AAEtC,MAAM,SAAS,GAAG,IAAA,qBAAS,EAAC,6BAAQ,CAAC,CAAC;AA6C7B,8BAAS;AA3CX,KAAK,UAAU,MAAM,CAAC,CAAS;IACpC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,sEAAsE;AAC/D,KAAK,UAAU,QAAQ,CAAC,GAAW;IACxC,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IAChE,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACrE,OAAO,KAAK,EAAE,IAAI,EAAE,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,iBAAiB,CAAC,CAAS;IAC/C,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,YAAY,CAAC,CAAS,EAAE,OAAe;IAC3D,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"fsutil.js","sourceRoot":"","sources":["../src/fsutil.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "name": "@hostafrica/connector-core",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Registrar core for the HostAfrica Connector: detects AI clients and registers the HostAfrica MCP server with each, OAuth-first",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "mcp-client",
9
+ "hostafrica",
10
+ "oauth",
11
+ "vps",
12
+ "dns",
13
+ "registrar"
14
+ ],
5
15
  "license": "MIT",
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
6
19
  "repository": {
7
20
  "type": "git",
8
21
  "url": "git+https://github.com/hostafrica-dev/ha-connector.git",