@softacus-software/elwis-docs-mcp 0.1.1 → 0.2.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.
Files changed (50) hide show
  1. package/README.md +122 -43
  2. package/dist/app-scope.js +92 -0
  3. package/dist/app-scope.js.map +1 -0
  4. package/dist/artifact-read.js +114 -0
  5. package/dist/artifact-read.js.map +1 -0
  6. package/dist/cli/client-configs.js +86 -0
  7. package/dist/cli/client-configs.js.map +1 -0
  8. package/dist/cli/login.js +64 -0
  9. package/dist/cli/login.js.map +1 -0
  10. package/dist/cli/prompter.js +85 -0
  11. package/dist/cli/prompter.js.map +1 -0
  12. package/dist/cli/setup.js +152 -0
  13. package/dist/cli/setup.js.map +1 -0
  14. package/dist/config.js +39 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/elwis-api.js +68 -0
  17. package/dist/elwis-api.js.map +1 -0
  18. package/dist/entity-read.js +192 -0
  19. package/dist/entity-read.js.map +1 -0
  20. package/dist/http-client.js +140 -0
  21. package/dist/http-client.js.map +1 -0
  22. package/dist/index.js +8 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/jwt.js +18 -0
  25. package/dist/jwt.js.map +1 -0
  26. package/dist/prune.js +9 -0
  27. package/dist/prune.js.map +1 -0
  28. package/dist/resolve.js +71 -0
  29. package/dist/resolve.js.map +1 -0
  30. package/dist/result.js +16 -0
  31. package/dist/result.js.map +1 -0
  32. package/dist/server.js +51 -0
  33. package/dist/server.js.map +1 -1
  34. package/dist/session-store.js +51 -0
  35. package/dist/session-store.js.map +1 -0
  36. package/dist/tools/explain-access.js +36 -0
  37. package/dist/tools/explain-access.js.map +1 -0
  38. package/dist/tools/get-entity.js +36 -0
  39. package/dist/tools/get-entity.js.map +1 -0
  40. package/dist/tools/get-page.js +75 -0
  41. package/dist/tools/get-page.js.map +1 -0
  42. package/dist/tools/list-apps.js +22 -0
  43. package/dist/tools/list-apps.js.map +1 -0
  44. package/dist/tools/list-entities.js +43 -0
  45. package/dist/tools/list-entities.js.map +1 -0
  46. package/dist/tools/read-artifacts.js +197 -0
  47. package/dist/tools/read-artifacts.js.map +1 -0
  48. package/dist/tools/resolve-entity.js +22 -0
  49. package/dist/tools/resolve-entity.js.map +1 -0
  50. package/package.json +2 -2
package/README.md CHANGED
@@ -1,39 +1,118 @@
1
1
  # @softacus-software/elwis-docs-mcp
2
2
 
3
- A read-only [Model Context Protocol](https://modelcontextprotocol.io) server that teaches your chat client the ELWIS REST API. Ask about endpoints, parameters, schemas, and examples in plain words. Everything is generated from the OpenAPI specification, so the answers cannot disagree with the documented API.
4
-
5
- It serves knowledge about the API only. It never connects to a live ELWIS and cannot read or write any data.
3
+ A read-only [Model Context Protocol](https://modelcontextprotocol.io) server for ELWIS. It teaches your chat client the ELWIS REST API and lets it read your live ELWIS data: applications, folders, types, attributes, records, links, and pages. It can never create, change, or delete anything. There are no write tools inside at all.
6
4
 
7
5
  ```
8
- +-------------------+ +---------------------+ +-------------+
9
- | Your chat client | <--> | elwis-docs-mcp | <--- | OpenAPI |
10
- | (Claude Desktop, | | (this server, | | spec |
11
- | Codex, Gemini) | | runs on your | | (bundled) |
12
- | | | computer) | | |
13
- +-------------------+ +---------------------+ +-------------+
6
+ +-------------------+ +---------------------+ +-----------+
7
+ | Your chat client | <--> | elwis-docs-mcp | <--> | ELWIS |
8
+ | (Claude Desktop, | | (this server, | | (reads |
9
+ | Codex, Gemini) | | runs on your | | only) |
10
+ | | | computer) | | |
11
+ +-------------------+ +---------------------+ +-----------+
14
12
  ```
15
13
 
16
- ## Set up in three minutes
14
+ ## Set up in five minutes
15
+
16
+ You need three things before you start:
17
+
18
+ - your ELWIS domain (copy it from your browser)
19
+ - your own ELWIS email and password (the same ones you use to sign in)
20
+ - an npm account invited to the softacus organization (ask your administrator)
17
21
 
18
22
  ### Step 1. Install Node.js (one time)
19
23
 
20
- Go to [nodejs.org](https://nodejs.org), download the LTS installer, run it, accept the defaults. Check it in a terminal:
24
+ Go to [nodejs.org](https://nodejs.org), download the LTS installer, run it, accept the defaults.
25
+
26
+ Check it worked. Open a terminal (macOS: the Terminal app, Windows: PowerShell) and type:
21
27
 
22
28
  ```bash
23
29
  node --version
24
30
  ```
25
31
 
32
+ You should see a version number like `v22.x.x`.
33
+
26
34
  ### Step 2. Sign in to npm (one time)
27
35
 
28
- The package is private to the Softacus team. Ask your administrator to invite you to the softacus organization on npmjs.com (you need a free npm account), then run:
36
+ The package is private to the Softacus team. Create a free account at npmjs.com if you do not have one, ask your administrator to invite it to the softacus organization, then run:
29
37
 
30
38
  ```bash
31
39
  npm login
32
40
  ```
33
41
 
34
- A browser window opens; sign in with your npm account and return to the terminal.
42
+ A browser window opens; sign in and return to the terminal.
43
+
44
+ ### Step 3. Run the setup and answer its questions
45
+
46
+ ```bash
47
+ npx -y @softacus-software/elwis-docs-mcp@latest setup
48
+ ```
49
+
50
+ It asks, in order:
51
+
52
+ 1. Your ELWIS domain, like elwis.your-company.com. Copy it from your browser.
53
+ 2. Your email and password. The password is never shown while you type and never stored. Only the resulting session stays, on this computer only.
54
+ 3. Which client do you use? It writes the configuration for Claude Desktop, Codex, or Gemini by itself and backs up anything it touches.
55
+
56
+ ### Step 4. Check it works
57
+
58
+ Fully quit your chat client (on a Mac press Cmd+Q, closing the window is not enough), open it again, and ask:
59
+
60
+ ```
61
+ Are you connected to ELWIS?
62
+ ```
63
+
64
+ It should answer with your name and your tenant. Done.
65
+
66
+ ## What to ask
67
+
68
+ About your live data (it sees what your account sees, and only reads):
69
+
70
+ ```
71
+ List the applications I can see in ELWIS.
72
+ Show me the records of type Product.
73
+ What attributes does the Task artifact type have?
74
+ Show me the links of this artifact.
75
+ ```
76
+
77
+ About the API itself:
78
+
79
+ ```
80
+ Which ELWIS endpoint creates an attribute?
81
+ What fields does the artifact type update request take?
82
+ Show me an example request for creating a link type.
83
+ ```
84
+
85
+ You stay signed in about as long as a normal ELWIS browser login (around two weeks) and the session refreshes itself while you use it. When it runs out, sign in again:
86
+
87
+ ```bash
88
+ npx -y @softacus-software/elwis-docs-mcp@latest login
89
+ ```
90
+
91
+ To sign out on this computer:
92
+
93
+ ```bash
94
+ npx -y @softacus-software/elwis-docs-mcp@latest logout
95
+ ```
96
+
97
+ If anything fails, send your administrator the exact text of the error.
98
+
99
+ ## Updating
100
+
101
+ New versions install themselves. The configuration written by the setup points at the latest release, so every time you fully quit and reopen your chat client you are on the newest version. There is nothing to run.
102
+
103
+ Installed before this behaviour existed, or wrote the configuration by hand? Run the setup once more and restart your client:
104
+
105
+ ```bash
106
+ npx -y @softacus-software/elwis-docs-mcp@latest setup
107
+ ```
108
+
109
+ Your saved sign-in stays, the setup only rewrites the client configuration.
35
110
 
36
- ### Step 3. Add the server to your client
111
+ ## For administrators
112
+
113
+ ### Manual client configuration
114
+
115
+ The setup wizard writes these for you; use the blocks below only if you prefer to do it by hand. Without any `env` at all the server still works, it then answers questions about the API only and never contacts a live system.
37
116
 
38
117
  Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows) and Claude Code (`.mcp.json` in the project root, same shape):
39
118
 
@@ -42,7 +121,12 @@ Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json
42
121
  "mcpServers": {
43
122
  "elwis-docs": {
44
123
  "command": "npx",
45
- "args": ["-y", "@softacus-software/elwis-docs-mcp"]
124
+ "args": ["-y", "@softacus-software/elwis-docs-mcp@latest"],
125
+ "env": {
126
+ "ELWIS_API_URL": "https://<your-elwis-host>",
127
+ "ELWIS_TENANT": "<24-char tenant id>",
128
+ "ELWIS_ALLOWED_APPS": "<AppName1,AppName2>"
129
+ }
46
130
  }
47
131
  }
48
132
  }
@@ -53,47 +137,42 @@ Codex (`~/.codex/config.toml`):
53
137
  ```toml
54
138
  [mcp_servers.elwis-docs]
55
139
  command = "npx"
56
- args = ["-y", "@softacus-software/elwis-docs-mcp"]
140
+ args = ["-y", "@softacus-software/elwis-docs-mcp@latest"]
141
+
142
+ [mcp_servers.elwis-docs.env]
143
+ ELWIS_API_URL = "https://<your-elwis-host>"
144
+ ELWIS_TENANT = "<24-char tenant id>"
145
+ ELWIS_ALLOWED_APPS = "<AppName1,AppName2>"
57
146
  ```
58
147
 
59
148
  Gemini CLI (`~/.gemini/settings.json`): same JSON shape as Claude Desktop.
60
149
 
61
- No ELWIS address, no ELWIS login. There is nothing else to configure because it never talks to a live system.
62
-
63
- ### Step 4. Check it works
150
+ Any other MCP client: register a STDIO server with `command: "npx"`, `args: ["-y", "@softacus-software/elwis-docs-mcp@latest"]` and the same `env`. After connecting, call `explain_elwis_access` to verify identity and tenant.
64
151
 
65
- Fully quit your client (on a Mac press Cmd+Q), open it again, and ask:
152
+ ### Configuration
66
153
 
67
- ```
68
- Which ELWIS endpoint creates an attribute?
69
- ```
154
+ | Variable | Required | What it does |
155
+ |---|---|---|
156
+ | `ELWIS_API_URL` | for live reads | Domain of the ELWIS backend, e.g. `https://your-elwis-host`. Without it only the API documentation tools are available. |
157
+ | `ELWIS_TENANT` | for live reads | The 24-character tenant id this connection is locked to. The wizard fills it automatically, `explain_elwis_access` shows it. |
158
+ | `ELWIS_SESSION_JWT` | no | Long-lived access token for automation. When omitted, the saved login from the setup or `login` command is used and refreshed automatically. |
159
+ | `ELWIS_ALLOWED_APPS` | no | Comma-separated application allowlist (names or ids). Everything outside the list is rejected before touching the backend. Default: all applications the user can access. |
70
160
 
71
- You should get the exact endpoint with its method, parameters, and a worked example.
72
-
73
- ## What to ask
74
-
75
- ```
76
- How do I create an application through the ELWIS API?
77
- What fields does the artifact type update request take?
78
- Show me an example request for creating a link type.
79
- What does the data type schema look like?
80
- ```
161
+ The reads run as the signed-in user, so ELWIS access control applies as usual: the server sees exactly what that account sees. For the narrowest possible connection, sign in with a dedicated restricted user and set `ELWIS_ALLOWED_APPS`.
81
162
 
82
163
  ## Tools
83
164
 
84
- | Tool | What it does |
85
- |---|---|
86
- | `search_elwis_api` | Find endpoints by intent, like "create an attribute" |
87
- | `list_elwis_api_groups` | Browse the API area by area |
88
- | `get_elwis_endpoint` | Full detail of one endpoint: method, path, parameters, schemas |
89
- | `get_elwis_schema` | One request or response schema, depth-bounded |
90
- | `get_elwis_examples` | Verified worked examples for an endpoint |
165
+ | Group | Tools | What they do |
166
+ |---|---|---|
167
+ | Check | `explain_elwis_access` | Who am I, which tenant, what is visible. Call this first. |
168
+ | Live reads | `list_elwis_apps`, `list_elwis_entities`, `resolve_elwis_entity`, `get_elwis_entity`, `list_elwis_artifacts`, `get_elwis_artifact`, `list_elwis_artifact_links`, `get_elwis_page` | Browse applications, configuration, records, links, and pages of a live ELWIS. |
169
+ | API documentation | `search_elwis_api`, `list_elwis_api_groups`, `get_elwis_endpoint`, `get_elwis_schema`, `get_elwis_examples` | Endpoints, parameters, schemas, and worked examples, generated from the OpenAPI specification. |
91
170
 
92
- Every response is size-bounded (typically 1 to 6 KB), so it stays friendly to the client's context. Unknown names return clean errors with near-miss suggestions instead of guesses.
171
+ There are no write tools. The HTTP layer of this package cannot even send an update or delete request.
93
172
 
94
- ## Keeping it up to date
173
+ ## Keeping the API data up to date
95
174
 
96
- The data is generated deterministically from the ELWIS OpenAPI spec:
175
+ The documentation data is generated deterministically from the ELWIS OpenAPI spec:
97
176
 
98
177
  ```bash
99
178
  npm run export-spec # fetch the current spec from a running ELWIS
@@ -0,0 +1,92 @@
1
+ import { IsObjectId } from './resolve.js';
2
+ export class AppScopeError extends Error {
3
+ constructor(ref) {
4
+ super(`Application "${ref}" is outside the MCP application allowlist (ELWIS_ALLOWED_APPS)`);
5
+ this.name = 'AppScopeError';
6
+ }
7
+ }
8
+ export function FilterApplicationId(filter) {
9
+ const value = filter.applicationId;
10
+ if (typeof value === 'string')
11
+ return value;
12
+ if (value && typeof value === 'object' && typeof value.$oid === 'string')
13
+ return value.$oid;
14
+ return undefined;
15
+ }
16
+ export class TenantMismatchError extends Error {
17
+ constructor(expected, actual) {
18
+ super(`No data was read or written. This connection is locked to tenant ${expected}, but the access token belongs to a different tenant (${actual ?? 'unknown'}). The saved connection settings are stale or mixed up. Re-run the setup, or fix ELWIS_TENANT if you configured it by hand.`);
19
+ this.name = 'TenantMismatchError';
20
+ }
21
+ }
22
+ export class NoTenantError extends Error {
23
+ constructor() {
24
+ super('No tenant is set, so I do not know which ELWIS environment to work in, and ELWIS has many (dev, pre-prod, prod). Set ELWIS_TENANT to the tenant you mean before reading or changing anything. Run explain_elwis_access to see the tenant id this connection\'s login belongs to, or re-run the setup wizard, which fills it in for you.');
25
+ this.name = 'NoTenantError';
26
+ }
27
+ }
28
+ export class AppScope {
29
+ api;
30
+ entries;
31
+ expectedTenant;
32
+ ids = null;
33
+ tenantVerified = false;
34
+ cachedIdentity = null;
35
+ constructor(api, entries, expectedTenant) {
36
+ this.api = api;
37
+ this.entries = entries;
38
+ this.expectedTenant = expectedTenant;
39
+ }
40
+ identity() {
41
+ this.cachedIdentity = this.cachedIdentity ?? this.api.whoAmI().catch(() => null);
42
+ return this.cachedIdentity;
43
+ }
44
+ async assertTenant() {
45
+ if (!this.expectedTenant || this.tenantVerified)
46
+ return;
47
+ const identity = await this.identity();
48
+ if (identity?.tenantId !== this.expectedTenant)
49
+ throw new TenantMismatchError(this.expectedTenant, identity?.tenantId ?? null);
50
+ this.tenantVerified = true;
51
+ }
52
+ get restricted() {
53
+ return this.entries.length > 0;
54
+ }
55
+ describe() {
56
+ return { restricted: this.restricted, entries: [...this.entries] };
57
+ }
58
+ async allowedIds() {
59
+ if (!this.restricted)
60
+ return null;
61
+ if (this.ids)
62
+ return this.ids;
63
+ const ids = new Set();
64
+ for (const entry of this.entries) {
65
+ if (IsObjectId(entry)) {
66
+ ids.add(entry);
67
+ continue;
68
+ }
69
+ const rows = await this.api.list('application', { name: entry });
70
+ for (const row of rows)
71
+ ids.add(row.id);
72
+ }
73
+ this.ids = ids;
74
+ return ids;
75
+ }
76
+ async assertAllowed(applicationId) {
77
+ await this.assertTenant();
78
+ const ids = await this.allowedIds();
79
+ if (ids && !ids.has(applicationId))
80
+ throw new AppScopeError(applicationId);
81
+ }
82
+ async assertListAllowed(kind, filter) {
83
+ await this.assertTenant();
84
+ if (!this.restricted || kind === 'application')
85
+ return;
86
+ const applicationId = FilterApplicationId(filter);
87
+ if (!applicationId)
88
+ throw new AppScopeError('unspecified, when ELWIS_ALLOWED_APPS is set, list calls require an applicationId filter');
89
+ await this.assertAllowed(applicationId);
90
+ }
91
+ }
92
+ //# sourceMappingURL=app-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-scope.js","sourceRoot":"","sources":["../src/app-scope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ import { AmbiguityError, IsObjectId } from './resolve.js';
2
+ const VALUE_CHAR_LIMIT = 4000;
3
+ const ARRAY_ELEMENT_LIMIT = 50;
4
+ const MAP_FETCH_LIMIT = 500;
5
+ export async function LoadAttributeMap(api, applicationId) {
6
+ const rows = await api.list('attribute', { applicationId: { $oid: applicationId } }, MAP_FETCH_LIMIT + 1);
7
+ if (rows.length > MAP_FETCH_LIMIT)
8
+ throw new Error(`The application has more than ${MAP_FETCH_LIMIT} attributes; pass explicit attribute ids instead of names`);
9
+ const byId = new Map();
10
+ const byName = new Map();
11
+ for (const r of rows) {
12
+ byId.set(r.id, r.name);
13
+ const key = r.name.toLowerCase();
14
+ byName.set(key, [...(byName.get(key) ?? []), { id: r.id, name: r.name }]);
15
+ }
16
+ return { byId, byName };
17
+ }
18
+ export function ResolveAttributeId(map, ref) {
19
+ if (IsObjectId(ref))
20
+ return ref;
21
+ const matches = map.byName.get(ref.toLowerCase()) ?? [];
22
+ if (matches.length === 1)
23
+ return matches[0].id;
24
+ if (matches.length === 0)
25
+ throw new Error(`No attribute named "${ref}" in the application`);
26
+ throw new AmbiguityError(`Multiple attributes named "${ref}"; pass an explicit id`, matches);
27
+ }
28
+ export async function ResolveUnknownAttributeNames(api, map, ids) {
29
+ const unknown = [...new Set(ids)].filter(id => !map.byId.has(id));
30
+ await Promise.all(unknown.map(async (id) => {
31
+ try {
32
+ const doc = await api.getOne('attribute', id);
33
+ map.byId.set(id, doc.name);
34
+ }
35
+ catch {
36
+ map.byId.set(id, id);
37
+ }
38
+ }));
39
+ }
40
+ const WHERE_OPS = {
41
+ equals: (v) => v,
42
+ contains: (v) => ({ $regex: EscapeRegex(String(v)), $options: 'i' }),
43
+ gt: (v) => ({ $gt: v }),
44
+ gte: (v) => ({ $gte: v }),
45
+ lt: (v) => ({ $lt: v }),
46
+ lte: (v) => ({ $lte: v }),
47
+ in: (v) => ({ $in: Array.isArray(v) ? v : [v] }),
48
+ };
49
+ export function EscapeRegex(value) {
50
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
51
+ }
52
+ export function BuildWhereClauses(map, where) {
53
+ return where.map(clause => {
54
+ const attrId = ResolveAttributeId(map, clause.attribute);
55
+ return { [`attributes.${attrId}.value`]: WHERE_OPS[clause.op ?? 'equals'](clause.value) };
56
+ });
57
+ }
58
+ export function TruncateValue(value) {
59
+ if (typeof value === 'string' && value.length > VALUE_CHAR_LIMIT) {
60
+ return `${value.slice(0, VALUE_CHAR_LIMIT)}…[truncated ${value.length - VALUE_CHAR_LIMIT} chars]`;
61
+ }
62
+ if (Array.isArray(value)) {
63
+ const head = value.slice(0, ARRAY_ELEMENT_LIMIT).map(TruncateValue);
64
+ if (value.length > ARRAY_ELEMENT_LIMIT)
65
+ head.push(`…[+${value.length - ARRAY_ELEMENT_LIMIT} more]`);
66
+ return head;
67
+ }
68
+ return value;
69
+ }
70
+ export function SummarizeArtifact(artifact, nameAttributeId) {
71
+ return {
72
+ id: artifact.id,
73
+ name: TruncateValue(artifact.attributes?.[nameAttributeId]?.value ?? null),
74
+ artifactTypeId: artifact.artifactTypeId,
75
+ format: artifact.format,
76
+ folderPath: artifact.folderData?.path,
77
+ created: artifact.created?.on,
78
+ updated: artifact.updated?.on,
79
+ };
80
+ }
81
+ export function TranslateArtifact(artifact, map) {
82
+ const values = {};
83
+ const unmappedValuesById = {};
84
+ const nameCollisions = [];
85
+ for (const [attrId, entry] of Object.entries(artifact.attributes ?? {})) {
86
+ const value = TruncateValue(entry?.value ?? null);
87
+ const name = map.byId.get(attrId);
88
+ if (name !== undefined && !Object.hasOwn(values, name))
89
+ values[name] = value;
90
+ else if (name !== undefined) {
91
+ nameCollisions.push(name);
92
+ unmappedValuesById[attrId] = value;
93
+ }
94
+ else {
95
+ unmappedValuesById[attrId] = value;
96
+ }
97
+ }
98
+ return {
99
+ id: artifact.id,
100
+ artifactTypeId: artifact.artifactTypeId,
101
+ format: artifact.format,
102
+ folderPath: artifact.folderData?.path,
103
+ folderId: artifact.folderData?.folderId ?? artifact.folderData?.id,
104
+ values,
105
+ ...(Object.keys(unmappedValuesById).length ? { unmappedValuesById } : {}),
106
+ ...(nameCollisions.length ? { nameCollisions } : {}),
107
+ created: artifact.created?.on,
108
+ updated: artifact.updated?.on,
109
+ };
110
+ }
111
+ export function PageMeta(meta, returned) {
112
+ return { total: meta.totalCount, returned, skip: meta.skip, hasMore: meta.skip + returned < meta.totalCount };
113
+ }
114
+ //# sourceMappingURL=artifact-read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-read.js","sourceRoot":"","sources":["../src/artifact-read.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { homedir, platform } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ export function BuildEnv(choices) {
5
+ const env = { ELWIS_API_URL: choices.host };
6
+ if (choices.tenantId)
7
+ env.ELWIS_TENANT = choices.tenantId;
8
+ if (choices.allowedApps.length)
9
+ env.ELWIS_ALLOWED_APPS = choices.allowedApps.join(',');
10
+ return env;
11
+ }
12
+ export function ServerCommand() {
13
+ const self = process.argv[1] ?? '';
14
+ if (self.endsWith(join('dist', 'index.js')))
15
+ return { command: process.execPath, args: [self] };
16
+ return { command: 'npx', args: ['-y', '@softacus-software/elwis-docs-mcp@latest'] };
17
+ }
18
+ export function UpsertJsonClientConfig(text, entry) {
19
+ let root;
20
+ try {
21
+ root = text ? JSON.parse(text) : {};
22
+ }
23
+ catch {
24
+ throw new Error('the existing configuration file is not valid JSON; fix or remove it first');
25
+ }
26
+ if (typeof root !== 'object' || root === null || Array.isArray(root))
27
+ throw new Error('the existing configuration file has an unexpected shape');
28
+ if (root.mcpServers !== undefined && (typeof root.mcpServers !== 'object' || root.mcpServers === null || Array.isArray(root.mcpServers))) {
29
+ throw new Error('the existing configuration file has an unexpected mcpServers shape');
30
+ }
31
+ root.mcpServers = { ...(root.mcpServers ?? {}), 'elwis-docs': entry };
32
+ return `${JSON.stringify(root, null, 2)}\n`;
33
+ }
34
+ const OUR_TOML_HEADERS = new Set(['[mcp_servers.elwis-docs]', '[mcp_servers.elwis-docs.env]']);
35
+ export function UpsertCodexToml(text, entry) {
36
+ const kept = [];
37
+ let skipping = false;
38
+ for (const rawLine of (text ?? '').split(/\r?\n/)) {
39
+ const trimmed = rawLine.trim();
40
+ if (OUR_TOML_HEADERS.has(trimmed)) {
41
+ skipping = true;
42
+ continue;
43
+ }
44
+ if (skipping && /^\s*\[/.test(rawLine))
45
+ skipping = false;
46
+ if (!skipping)
47
+ kept.push(rawLine);
48
+ }
49
+ const base = kept.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd();
50
+ const block = [
51
+ '[mcp_servers.elwis-docs]',
52
+ `command = ${JSON.stringify(entry.command)}`,
53
+ `args = [${entry.args.map(a => JSON.stringify(a)).join(', ')}]`,
54
+ '',
55
+ '[mcp_servers.elwis-docs.env]',
56
+ ...Object.entries(entry.env).map(([k, v]) => `${k} = ${JSON.stringify(v)}`),
57
+ '',
58
+ ].join('\n');
59
+ return base.length ? `${base}\n\n${block}` : block;
60
+ }
61
+ export function DetectClients(home = homedir()) {
62
+ const claudeDesktopPath = platform() === 'win32'
63
+ ? join(process.env.APPDATA ?? join(home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json')
64
+ : join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
65
+ const targets = [
66
+ { id: 'claude-desktop', label: 'Claude Desktop', path: claudeDesktopPath, format: 'json', exists: false },
67
+ { id: 'codex', label: 'Codex', path: join(home, '.codex', 'config.toml'), format: 'toml', exists: false },
68
+ { id: 'gemini', label: 'Gemini CLI', path: join(home, '.gemini', 'settings.json'), format: 'json', exists: false },
69
+ ];
70
+ for (const target of targets)
71
+ target.exists = existsSync(target.path) || existsSync(dirname(target.path));
72
+ return targets;
73
+ }
74
+ export function WriteClientConfig(target, entry) {
75
+ const current = existsSync(target.path) ? readFileSync(target.path, 'utf8') : null;
76
+ let backup = null;
77
+ if (current !== null) {
78
+ backup = `${target.path}.backup-${new Date().toISOString().replace(/[:.]/g, '-')}`;
79
+ copyFileSync(target.path, backup);
80
+ }
81
+ const next = target.format === 'json' ? UpsertJsonClientConfig(current, entry) : UpsertCodexToml(current, entry);
82
+ mkdirSync(dirname(target.path), { recursive: true });
83
+ writeFileSync(target.path, next);
84
+ return { path: target.path, backup };
85
+ }
86
+ //# sourceMappingURL=client-configs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-configs.js","sourceRoot":"","sources":["../../src/cli/client-configs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { stdout } from 'node:process';
2
+ import { CookieToken } from '../http-client.js';
3
+ import { SaveSession } from '../session-store.js';
4
+ import { Prompter } from './prompter.js';
5
+ export function NormalizeHost(raw) {
6
+ let host = raw.trim().replace(/\/+$/, '');
7
+ if (!/^https?:\/\//.test(host)) {
8
+ const local = /^(localhost|127\.0\.0\.1)([:/]|$)/.test(host);
9
+ host = `${local ? 'http' : 'https'}://${host}`;
10
+ }
11
+ try {
12
+ return new URL(host).origin.toLowerCase();
13
+ }
14
+ catch {
15
+ return host.toLowerCase();
16
+ }
17
+ }
18
+ export async function PerformLogin(host, email, password) {
19
+ const response = await fetch(`${host}/api/auth/login`, {
20
+ method: 'POST',
21
+ headers: { 'Content-Type': 'application/json', Origin: host },
22
+ body: JSON.stringify({ email, password }),
23
+ });
24
+ const token = CookieToken(response.headers.getSetCookie());
25
+ if (!response.ok || !token) {
26
+ throw new Error(response.status === 401 || response.status === 400
27
+ ? 'Sign-in failed, the email or password is not right. Try again.'
28
+ : `Sign-in failed, ELWIS answered with status ${response.status}. Check the domain and try again.`);
29
+ }
30
+ const info = await fetch(`${host}/api/tenant/user/info`, { headers: { Origin: host, Cookie: `session-jwt=${token}` } });
31
+ const parsed = info.ok ? (await info.json()) : {};
32
+ const user = parsed.sessionUser;
33
+ return { host, token, tenantId: user?.tenant?.id ?? null, email: user?.email ?? email };
34
+ }
35
+ export async function LoginFlow(prompter, hostArg) {
36
+ const host = NormalizeHost(hostArg ?? (await prompter.ask('What is your ELWIS domain? (like elwis.your-company.com): ')));
37
+ const email = (await prompter.ask('Your ELWIS email: ')).trim();
38
+ const password = await prompter.askHidden('Your ELWIS password (not shown while typing): ');
39
+ const result = await PerformLogin(host, email, password);
40
+ const record = {
41
+ host: result.host,
42
+ token: result.token,
43
+ tenantId: result.tenantId,
44
+ email: result.email,
45
+ updatedAt: new Date().toISOString(),
46
+ };
47
+ SaveSession(record);
48
+ return result;
49
+ }
50
+ export async function RunLogin(hostArg) {
51
+ const prompter = new Prompter();
52
+ try {
53
+ if (process.env.ELWIS_SESSION_JWT) {
54
+ process.stderr.write('Note: ELWIS_SESSION_JWT is set in this environment and takes priority over saved logins, the server will keep using that token until it is removed.\n');
55
+ }
56
+ const result = await LoginFlow(prompter, hostArg);
57
+ stdout.write(`\nSigned in as ${result.email}${result.tenantId ? ` (tenant ${result.tenantId})` : ''}.\n`);
58
+ stdout.write("You'll stay signed in about as long as a normal ELWIS browser login (typically two weeks). When it runs out, just sign in again.\n");
59
+ }
60
+ finally {
61
+ prompter.close();
62
+ }
63
+ }
64
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/cli/login.ts"],"names":[],"mappings":"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"}