@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 +201 -0
- package/README.md +178 -0
- package/dist/auth.js +228 -0
- package/dist/client.js +115 -0
- package/dist/config.js +63 -0
- package/dist/index.js +70 -0
- package/dist/tools.js +485 -0
- package/package.json +52 -0
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
|
+
}
|