@edgegap/mcp 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # edgegap-mcp
2
+
3
+ An MCP server that lets a coding agent take a developer from "I have a game
4
+ server container" to "players are connected to it" without the developer
5
+ reading the API reference.
6
+
7
+ Ten tools, hand-picked. Not generated from the OpenAPI spec — see
8
+ [Scope](#scope) for why.
9
+
10
+ ## Install
11
+
12
+ One line in your MCP client config. Nothing to clone, nothing to build.
13
+
14
+ ```json
15
+ {
16
+ "mcpServers": {
17
+ "edgegap": {
18
+ "command": "npx",
19
+ "args": ["-y", "@edgegap/mcp"]
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ Works in Claude Code, Cursor, Codex, and VS Code. Pin a version in production
26
+ (`@edgegap/mcp@0.1.0`) rather than floating on latest.
27
+
28
+ > **Node version:** the server itself needs Node 18+. Deploying the optional
29
+ > Cloudflare Worker needs Node 22+, because `wrangler` requires it.
30
+
31
+ ## Your token never leaves your machine
32
+
33
+ There is no Edgegap-hosted component. This server runs as a process on your
34
+ own computer, spawned by your editor. The first tool call asks you for a token,
35
+ shows what it authorises, and requires an explicit acknowledgement before
36
+ accepting it. Where that token then lives, exhaustively:
37
+
38
+ - one variable in that process's memory, for the life of your editor session
39
+
40
+ That is the whole list. Not on disk. Not in a config file. Not in logs. Not on
41
+ any Edgegap server — the only thing sent to Edgegap is the API call itself,
42
+ exactly as if you had run `curl`. Closing your editor revokes this server's
43
+ access completely.
44
+
45
+ Generate a token at <https://app.edgegap.com/user-settings?tab=tokens>.
46
+
47
+ Setting `EDGEGAP_API_TOKEN` still works and takes precedence, for CI and for
48
+ clients that cannot show prompts. Do not pass a token as a command-line
49
+ argument — arguments are visible to other processes via `ps`, and the server
50
+ warns if it detects one.
51
+
52
+ **Why this is not hosted.** A hosted server would have to either store your
53
+ token or receive it on every request. "We don't store it" and "we never see it"
54
+ are different claims, and only a local process makes the second one. See
55
+ `worker/DECISION.md` for the full reasoning and the conditions under which a
56
+ hosted version becomes worth building.
57
+
58
+ ## Read this before connecting an agent
59
+
60
+ **The Edgegap API token cannot be scoped.** One token authorises every
61
+ application, every version, every running deployment, and your usage across the
62
+ whole organization. There is no deploy-only token and no per-application token.
63
+
64
+ Consequences worth being deliberate about:
65
+
66
+ - An agent holding this token can stop production deployments, not just the
67
+ test ones it created.
68
+ - Prompt injection reaching the agent — from a repo file, an issue, a fetched
69
+ page — reaches the token too.
70
+ - Anything the agent logs, echoes, or sends to a model provider is a place the
71
+ token could end up. This server does not log it, but it cannot control what
72
+ the rest of the agent does.
73
+
74
+ Recommended setup, in decreasing order of caution:
75
+
76
+ | Situation | Setup |
77
+ | --- | --- |
78
+ | Unattended or autonomous agent | Separate non-production organization, plus `EDGEGAP_READ_ONLY=1` |
79
+ | Supervised agent, live game in the org | `EDGEGAP_APP_ALLOWLIST` scoped to the app being worked on, plus `EDGEGAP_MAX_DURATION_MINUTES` |
80
+ | Solo developer, no production workload | Defaults are fine; revoke the token when finished |
81
+
82
+ The allowlist and read-only flag are enforced in this server, which means they
83
+ protect against an agent that makes a mistake, not against one that has been
84
+ compromised into calling the API directly. They narrow the blast radius; they
85
+ do not remove it.
86
+
87
+ ### Environment variables
88
+
89
+ | Variable | Default | Purpose |
90
+ | --- | --- | --- |
91
+ | `EDGEGAP_API_TOKEN` | *(prompted)* | API token. Optional — omit it and the developer is asked at first use. The `token ` prefix is added for you. |
92
+ | `EDGEGAP_READ_ONLY` | `0` | Set to `1` and the five mutating tools are never registered. The agent cannot see them, so it cannot be talked into calling them. |
93
+ | `EDGEGAP_APP_ALLOWLIST` | *(empty)* | Comma-separated application names. When set, every tool refuses to touch anything else. |
94
+ | `EDGEGAP_MAX_DURATION_MINUTES` | `60` | Ceiling on `max_duration` the agent may set on a version. Caps runaway cost from an unattended agent. |
95
+ | `EDGEGAP_TIMEOUT_MS` | `30000` | Per-request HTTP timeout. |
96
+
97
+ ## The ten tools
98
+
99
+ Ordered along the golden path.
100
+
101
+ | # | Tool | Mutating | What it's for |
102
+ | --- | --- | --- | --- |
103
+ | 1 | `edgegap_list_apps` | | Orient before doing anything. Prevents duplicate applications. |
104
+ | 2 | `edgegap_create_app` | ● | Create the container for versions. |
105
+ | 3 | `edgegap_list_app_versions` | | Find a deployable version, or copy settings from a working one. |
106
+ | 4 | `edgegap_create_app_version` | ● | Register a container image with CPU, memory, and ports. |
107
+ | 5 | `edgegap_deploy` | ● | Start one instance near specified players. |
108
+ | 6 | `edgegap_get_deployment` | | Single status read. |
109
+ | 7 | `edgegap_wait_for_deployment` | | Poll to ready with backoff, then return the connection address. |
110
+ | 8 | `edgegap_list_deployments` | | Find orphaned servers from earlier sessions. |
111
+ | 9 | `edgegap_stop_deployment` | ● | Graceful SIGTERM, one deployment at a time. |
112
+ | 10 | `edgegap_get_deployment_logs` | | Container output and crash exit code after a failure. |
113
+
114
+ ## Design decisions
115
+
116
+ **Curated, not generated.** The Edgegap API has roughly sixty operations.
117
+ Auto-generating one tool per operation puts all sixty descriptions into the
118
+ agent's context on every turn and measurably degrades tool selection. These ten
119
+ cover the path that converts a new developer.
120
+
121
+ **`wait_for_deployment` is a tool, not a loop.** Left to itself an agent will
122
+ call a status endpoint in a tight loop, burn turns, and give up early. Folding
123
+ the polling and backoff into one call removes the most common failure in
124
+ agent-driven deploys.
125
+
126
+ **Errors are written for self-correction.** A 424 comes back saying the image
127
+ could not be pulled and which fields to check. A 422 says to try different
128
+ coordinates or lower the resource request. The agent can act on these without a
129
+ round trip to the human.
130
+
131
+ **Local validation before the wire.** The memory-to-CPU ratio and the missing
132
+ player location are caught here rather than surfacing as an opaque 400.
133
+
134
+ **Bulk operations are deliberately absent.** `stop` takes one `request_id`.
135
+ There is no bulk-stop tool, because an agent with a filter expression and a bug
136
+ can stop a production fleet.
137
+
138
+ ## Scope
139
+
140
+ Not exposed, on purpose: matchmaking, relays, private fleets, smart fleets,
141
+ endpoint storage, ACL/whitelist entries, deployment tags, metrics, container
142
+ registry management, DNS configuration.
143
+
144
+ These are real capabilities, but they belong to studios already operating on
145
+ the platform, not to a developer deploying their first server. Adding them
146
+ would trade the conversion path for surface area.
147
+
148
+ ## Known limitation: asking for the token at all
149
+
150
+ The MCP specification says servers should not use elicitation to collect
151
+ sensitive data, and an API token is sensitive. This server does it anyway,
152
+ because requiring a token in a config file before anything works is the largest
153
+ drop in the onboarding funnel, and the whole point of the server is to remove
154
+ setup friction.
155
+
156
+ That is a deliberate trade rather than a pattern to copy. What makes it
157
+ defensible is the set of mitigations in `src/auth.ts` — memory-only storage,
158
+ plain-language disclosure, required acknowledgement, redaction from all output,
159
+ and the environment variable always winning when present. Removing any of them
160
+ breaks the trade.
161
+
162
+ The real fix is on Edgegap's side: scoped, revocable, deploy-only credentials,
163
+ issued through OAuth rather than pasted as a secret. Until those exist, the
164
+ interactive prompt is a workaround and is labelled as one in the code.
165
+
166
+ ## Development
167
+
168
+ ```bash
169
+ npm run typecheck
170
+ node smoke.mjs # handshake, tool registration, read-only mode
171
+ node guards.mjs # local validation and allowlist enforcement
172
+ node elicit.mjs # token prompt: accept, refuse acknowledgement, decline, no support
173
+ ```
174
+
175
+ None of these make network calls. `elicit.mjs` asserts that the prompt states
176
+ the org-wide scope, that the acknowledgement is required, that the token never
177
+ appears in tool output, and that declining produces a stop-and-report message
178
+ rather than a retry loop.
package/dist/auth.js ADDED
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Token acquisition.
3
+ *
4
+ * The developer is asked for their token at first use rather than having to
5
+ * paste it into a client config file before anything works.
6
+ *
7
+ * WHERE THE TOKEN LIVES, EXHAUSTIVELY:
8
+ *
9
+ * - one variable in this process's memory, for the life of this process
10
+ *
11
+ * That is the complete list. It is never written to disk, never sent to any
12
+ * Edgegap-operated service, never logged, never included in a tool result, and
13
+ * never persisted across restarts. The process runs on the developer's own
14
+ * machine, spawned by their editor, and dies with it. Closing the editor is a
15
+ * complete revocation of this server's access.
16
+ *
17
+ * This is the reason the server is distributed as a local stdio process rather
18
+ * than hosted. A hosted version would put customer credentials in transit
19
+ * through Edgegap infrastructure on every call, which is a different security
20
+ * claim no matter how carefully the hosting is written.
21
+ *
22
+ * IMPORTANT — read before extending this file.
23
+ *
24
+ * The MCP specification says servers should not use elicitation to collect
25
+ * sensitive data, and an API token is sensitive. This implementation exists
26
+ * because the setup friction of a config-file token is the single largest drop
27
+ * in the onboarding funnel, but it is a deliberate trade, not a default to copy.
28
+ * The mitigations below are what make the trade defensible, and removing any of
29
+ * them breaks it:
30
+ *
31
+ * - the environment variable is always preferred when present
32
+ * - the token is held in memory only, for one process lifetime
33
+ * - the elicitation states the token's blast radius in plain language
34
+ * - the developer must tick an acknowledgement before the token is accepted
35
+ * - the token is stripped from every error string before it reaches the model
36
+ *
37
+ * The real fix is scoped, revocable, deploy-only credentials issued by Edgegap.
38
+ * Until those exist, this file is a workaround and should be labelled as one.
39
+ */
40
+ export class TokenUnavailableError extends Error {
41
+ }
42
+ /** Shown to the developer at the moment they are asked to hand over a token. */
43
+ export const PRIVILEGE_WARNING = 'This token grants full access to your entire Edgegap organization: every ' +
44
+ 'application, every version, every running deployment, and your billing-' +
45
+ 'relevant usage. Edgegap does not currently issue scoped or deploy-only ' +
46
+ 'tokens, so it cannot be narrowed.\n\n' +
47
+ 'Whatever agent you are running will be able to use it for anything the API ' +
48
+ 'allows. Only continue if you are supervising this session. For unattended ' +
49
+ 'or autonomous agents, use a token from a separate non-production ' +
50
+ 'organization, and set EDGEGAP_READ_ONLY=1 and EDGEGAP_APP_ALLOWLIST to ' +
51
+ 'limit what the agent can reach.';
52
+ /**
53
+ * Warns if a token was passed as a command-line argument. Process arguments
54
+ * are visible to every other process on the machine via `ps`, and get captured
55
+ * by shell history and crash reporters. Config env vars do not have this
56
+ * problem, and the interactive prompt has it least of all.
57
+ */
58
+ export function warnIfTokenInArgv(argv = process.argv) {
59
+ const looksLikeToken = argv.some((a) => /^--?token[=\s]/i.test(a) || /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(a));
60
+ if (looksLikeToken) {
61
+ process.stderr.write('[edgegap-mcp] WARNING: a token appears to have been passed on the command ' +
62
+ 'line. Command-line arguments are visible to other processes on this machine. ' +
63
+ 'Remove it and let the server prompt you, or use the EDGEGAP_API_TOKEN env var.\n');
64
+ }
65
+ return looksLikeToken;
66
+ }
67
+ export class TokenProvider {
68
+ config;
69
+ cached;
70
+ inFlight;
71
+ server;
72
+ constructor(config) {
73
+ this.config = config;
74
+ this.cached = config.envToken;
75
+ }
76
+ /** Wired up after the server is constructed, since elicitation needs it. */
77
+ attach(server) {
78
+ this.server = server;
79
+ }
80
+ /** The token currently in hand, for redaction purposes. May be undefined. */
81
+ get current() {
82
+ return this.cached;
83
+ }
84
+ /** Drops the cached token so the next call re-prompts. Called on a 401. */
85
+ invalidate() {
86
+ if (this.config.envToken)
87
+ return; // env-supplied tokens are not re-prompted
88
+ this.cached = undefined;
89
+ }
90
+ /**
91
+ * Clears the token from memory. Wired to process exit signals so a token
92
+ * supplied interactively does not outlive the session even briefly in a core
93
+ * dump or a lingering handle.
94
+ */
95
+ scrub() {
96
+ this.cached = undefined;
97
+ }
98
+ /** Registers scrub() against the signals an editor uses to stop the server. */
99
+ installExitHandlers() {
100
+ const clear = () => this.scrub();
101
+ process.once('exit', clear);
102
+ process.once('SIGINT', () => {
103
+ clear();
104
+ process.exit(0);
105
+ });
106
+ process.once('SIGTERM', () => {
107
+ clear();
108
+ process.exit(0);
109
+ });
110
+ }
111
+ /** True the first time a token is obtained, so callers can attach a notice. */
112
+ firstAcquisition = true;
113
+ consumeFirstUseNotice() {
114
+ if (!this.firstAcquisition)
115
+ return undefined;
116
+ this.firstAcquisition = false;
117
+ return ('Token held in memory for this session only — nothing was written to ' +
118
+ 'disk and nothing was sent to Edgegap beyond the API call itself. It ' +
119
+ 'stops being usable when this editor session ends. Reminder: the token ' +
120
+ 'is org-wide and cannot be scoped, so revoke it at ' +
121
+ 'https://app.edgegap.com/user-settings?tab=tokens if it is ever exposed.');
122
+ }
123
+ async get() {
124
+ if (this.cached)
125
+ return this.cached;
126
+ if (this.inFlight)
127
+ return this.inFlight; // collapse concurrent tool calls
128
+ this.inFlight = this.elicit().finally(() => {
129
+ this.inFlight = undefined;
130
+ });
131
+ return this.inFlight;
132
+ }
133
+ async elicit() {
134
+ if (!this.server) {
135
+ throw new TokenUnavailableError('No Edgegap API token available and the server is not ready to ask for one.');
136
+ }
137
+ const capabilities = this.server.getClientCapabilities();
138
+ if (!capabilities?.elicitation) {
139
+ throw new TokenUnavailableError('No Edgegap API token available.\n\n' +
140
+ 'This MCP client does not support interactive prompts, so the token has ' +
141
+ 'to be supplied up front. Add it to your MCP client config:\n\n' +
142
+ ' "env": { "EDGEGAP_API_TOKEN": "your-token" }\n\n' +
143
+ 'Generate a token at https://app.edgegap.com/user-settings?tab=tokens\n\n' +
144
+ PRIVILEGE_WARNING);
145
+ }
146
+ const result = await this.server.elicitInput({
147
+ message: 'Edgegap needs an API token to continue.\n\n' +
148
+ 'Generate one at https://app.edgegap.com/user-settings?tab=tokens\n\n' +
149
+ PRIVILEGE_WARNING,
150
+ requestedSchema: {
151
+ type: 'object',
152
+ properties: {
153
+ api_token: {
154
+ type: 'string',
155
+ title: 'Edgegap API token',
156
+ description: 'Paste the token value. It is held in one variable in memory for ' +
157
+ 'this session, is not saved to disk, is not shown to the model, ' +
158
+ 'and never reaches any Edgegap-operated server.',
159
+ },
160
+ acknowledged: {
161
+ type: 'boolean',
162
+ title: 'I understand this token gives the agent full access to my Edgegap organization',
163
+ description: 'Required. Edgegap cannot currently issue a narrower token, so this ' +
164
+ 'is the only scope available.',
165
+ default: false,
166
+ },
167
+ },
168
+ required: ['api_token', 'acknowledged'],
169
+ },
170
+ });
171
+ if (result.action !== 'accept' || !result.content) {
172
+ throw new TokenUnavailableError('The developer declined to provide an Edgegap API token. Do not retry ' +
173
+ 'automatically. Stop and tell them which operation needed it, so they ' +
174
+ 'can decide whether to continue.');
175
+ }
176
+ const acknowledged = result.content.acknowledged === true;
177
+ if (!acknowledged) {
178
+ throw new TokenUnavailableError('The token privilege acknowledgement was not accepted, so no token was ' +
179
+ 'stored. Nothing has been sent to Edgegap. If the org-wide scope is the ' +
180
+ 'concern, the safer setup is a token from a separate non-production ' +
181
+ 'organization with EDGEGAP_READ_ONLY=1.');
182
+ }
183
+ const raw = String(result.content.api_token ?? '').trim();
184
+ const token = raw.replace(/^token\s+/i, '');
185
+ if (!token) {
186
+ throw new TokenUnavailableError('An empty token was submitted. Nothing was stored.');
187
+ }
188
+ this.cached = token;
189
+ return token;
190
+ }
191
+ }
192
+ /**
193
+ * Credential source for the hosted Worker: the token arrives on the request
194
+ * and lives only as long as this object, which is created per request and
195
+ * garbage collected with it. Nothing is cached across requests, deliberately.
196
+ */
197
+ export class StaticTokenProvider {
198
+ token;
199
+ used = false;
200
+ constructor(token) {
201
+ this.token = token;
202
+ }
203
+ get current() {
204
+ return this.token;
205
+ }
206
+ async get() {
207
+ if (!this.token) {
208
+ throw new TokenUnavailableError('No Edgegap API token on this request. Send it as an Authorization ' +
209
+ 'header on the MCP connection.');
210
+ }
211
+ return this.token;
212
+ }
213
+ invalidate() {
214
+ // Nothing to forget between requests — there is no cache. The next request
215
+ // carries its own token, so a rejected one simply fails and the developer
216
+ // fixes their client config.
217
+ this.token = undefined;
218
+ }
219
+ consumeFirstUseNotice() {
220
+ if (this.used)
221
+ return undefined;
222
+ this.used = true;
223
+ return ('This is the hosted relay: your token is read from the request header, ' +
224
+ 'used for this call, and discarded. It is not stored. It does, however, ' +
225
+ 'pass through Edgegap infrastructure — the local server (npx @edgegap/mcp) ' +
226
+ 'avoids that entirely. Your token is org-wide and cannot be scoped.');
227
+ }
228
+ }