@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 +103 -0
- package/dist/fsutil.d.ts +13 -3
- package/dist/fsutil.js +46 -4
- package/dist/fsutil.js.map +1 -1
- package/package.json +14 -1
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
|
-
*
|
|
10
|
-
* only applies to newly created files, so chmod
|
|
11
|
-
*
|
|
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
|
-
*
|
|
79
|
-
* only applies to newly created files, so chmod
|
|
80
|
-
*
|
|
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
|
|
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
|
package/dist/fsutil.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fsutil.js","sourceRoot":"","sources":["../src/fsutil.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,wBAOC;AAGD,4BASC;AAED,8CAMC;
|
|
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.
|
|
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",
|