@myspec/mcp-server 0.4.0-next.93 → 0.4.0-next.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -4
- package/dist/index.js +47 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,9 +101,17 @@ claude mcp add --scope user myspec \
|
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
Nothing else is required — no `login`, no files. **For a non-production token,
|
|
104
|
-
set `MYSPEC_USER_AUTH_URL` too** (e.g. `https://dev-auth.myspec.dev`)
|
|
105
|
-
|
|
106
|
-
token
|
|
104
|
+
set `MYSPEC_USER_AUTH_URL` too** (e.g. `https://dev-auth.myspec.dev`) unless
|
|
105
|
+
`~/.myspec/settings.json` already names that environment, which the server reads
|
|
106
|
+
even with no `oauth_creds.json` beside it; a dev token against the production
|
|
107
|
+
default fails with a 401 that reads like a bad token until you read the host it
|
|
108
|
+
names.
|
|
109
|
+
|
|
110
|
+
That cuts both ways: set it explicitly for a **production** token on a machine
|
|
111
|
+
that was ever signed in to dev. `login` writes `settings.json` and `logout` does
|
|
112
|
+
not remove it, so the leftover `userAuthUrl` decides the exchange either way.
|
|
113
|
+
The 401 names the host it asked, so the wrong environment is visible rather than
|
|
114
|
+
looking like a rejected token.
|
|
107
115
|
|
|
108
116
|
The server writes `~/.myspec/settings.json` so restarts skip endpoint discovery.
|
|
109
117
|
It never writes the token to disk, so unsetting the variable removes the
|
|
@@ -272,7 +280,7 @@ Local state is stored under `~/.myspec/` (alongside the on-disk cache root), spl
|
|
|
272
280
|
| Variable | Purpose |
|
|
273
281
|
|---|---|
|
|
274
282
|
| `MYSPEC_API_TOKEN` | Long-lived MySpec API token (`msp_pat_…`) for unattended use. Takes precedence over anything in `~/.myspec/oauth_creds.json` and is never written to disk. Needs 0.3.0+. |
|
|
275
|
-
| `MYSPEC_USER_AUTH_URL` | user-auth base URL (
|
|
283
|
+
| `MYSPEC_USER_AUTH_URL` | user-auth base URL (`--user-auth-url` > this > `userAuthUrl` in `~/.myspec/settings.json` > `https://auth.myspec.dev`). The webapp URL is derived from it; the platform / ai-agent URLs are discovered via the webapp. |
|
|
276
284
|
| `MYSPEC_DOWNLOAD_ROOT` | Absolute path used by `read_spec_file` as its on-disk cache root (default: `~/.myspec`). Tools never write outside this root. |
|
|
277
285
|
| `MYSPEC_AI_AGENT_WS_URL` | ai-agent WebSocket URL for `reverse`; skips the webapp discovery call |
|
|
278
286
|
|
package/dist/index.js
CHANGED
|
@@ -360,6 +360,10 @@ async function loadSettings(target) {
|
|
|
360
360
|
throw err;
|
|
361
361
|
}
|
|
362
362
|
}
|
|
363
|
+
async function readSettingsUserAuthUrl(settingsPath = defaultSettingsPath()) {
|
|
364
|
+
const settings = await loadSettings(settingsPath);
|
|
365
|
+
return settings?.userAuthUrl;
|
|
366
|
+
}
|
|
363
367
|
async function writeJsonFile(target, data, opts) {
|
|
364
368
|
const dir = path.dirname(target);
|
|
365
369
|
await fs.mkdir(dir, { recursive: true, mode: 448 });
|
|
@@ -726,7 +730,7 @@ var TokenManager = class {
|
|
|
726
730
|
if (response.status === 401 || response.status === 403) {
|
|
727
731
|
const reason = response.status === 403 ? "the token owner no longer has access to its organization" : "the token is invalid, disabled, or expired";
|
|
728
732
|
const rejection = new ApiTokenRejectedError(
|
|
729
|
-
`API token exchange failed: ${reason}.
|
|
733
|
+
`API token exchange failed at ${url}: ${reason}. Check that URL names the environment the token was created in. With MYSPEC_API_TOKEN it comes from MYSPEC_USER_AUTH_URL (or --user-auth-url), then userAuthUrl in ~/.myspec/settings.json, defaulting to https://auth.myspec.dev; with an apiToken in oauth_creds.json it comes from that settings.json value alone, which only \`npx @myspec/mcp-server login --user-auth-url <url>\` rewrites. If the host is right, create a new API token in MySpec and set it as MYSPEC_API_TOKEN (or write it to the apiToken field of your credentials file).`
|
|
730
734
|
);
|
|
731
735
|
this.apiTokenRejection = rejection;
|
|
732
736
|
throw rejection;
|
|
@@ -736,7 +740,7 @@ var TokenManager = class {
|
|
|
736
740
|
throw new HttpStatusError(
|
|
737
741
|
response.status,
|
|
738
742
|
body,
|
|
739
|
-
`Token exchange failed: ${url} returned 404, so no exchange endpoint is there. Check that URL names the environment the token was created in. With MYSPEC_API_TOKEN it comes from MYSPEC_USER_AUTH_URL (or --user-auth-url), defaulting to https://auth.myspec.dev. Otherwise it comes from
|
|
743
|
+
`Token exchange failed: ${url} returned 404, so no exchange endpoint is there. Check that URL names the environment the token was created in. With MYSPEC_API_TOKEN it comes from MYSPEC_USER_AUTH_URL (or --user-auth-url), then userAuthUrl in ~/.myspec/settings.json, defaulting to https://auth.myspec.dev. Otherwise it comes from that same settings.json value, which only \`npx @myspec/mcp-server login --user-auth-url <url>\` rewrites \u2014 the flag and the variable do not redirect a file-configured exchange on their own, and userAuthUrl in oauth_creds.json is ignored entirely.`
|
|
740
744
|
);
|
|
741
745
|
}
|
|
742
746
|
if (!response.ok) {
|
|
@@ -5487,21 +5491,59 @@ async function resolveReverseAgentUrl(sources) {
|
|
|
5487
5491
|
throw err;
|
|
5488
5492
|
}
|
|
5489
5493
|
}
|
|
5494
|
+
function ignoringSettings(settingsPath, reason) {
|
|
5495
|
+
return `myspec-mcp: ignoring the userAuthUrl in ${settingsPath} for the MYSPEC_API_TOKEN exchange URL \u2014 ${reason}.`;
|
|
5496
|
+
}
|
|
5497
|
+
function usableAuthUrl(candidate, settingsPath, warn) {
|
|
5498
|
+
if (candidate === void 0) {
|
|
5499
|
+
return void 0;
|
|
5500
|
+
}
|
|
5501
|
+
try {
|
|
5502
|
+
resolveConfig({ env: {}, storedUserAuthUrl: candidate });
|
|
5503
|
+
return candidate;
|
|
5504
|
+
} catch (err) {
|
|
5505
|
+
if (!(err instanceof ConfigError)) {
|
|
5506
|
+
throw err;
|
|
5507
|
+
}
|
|
5508
|
+
warn(ignoringSettings(settingsPath, err.message));
|
|
5509
|
+
return void 0;
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5490
5512
|
async function buildCredentialsStore(opts = {}) {
|
|
5491
5513
|
const env = opts.env ?? process.env;
|
|
5492
|
-
const
|
|
5514
|
+
const settingsPath = opts.settingsPath ?? defaultSettingsPath();
|
|
5515
|
+
const fileStore = opts.fileStore ?? createFileCredentialsStore({ settingsPath });
|
|
5516
|
+
const warn = opts.warn ?? ((message) => {
|
|
5517
|
+
process.stderr.write(message + "\n");
|
|
5518
|
+
});
|
|
5493
5519
|
const usingEnvApiToken = hasEnvApiToken(env);
|
|
5494
5520
|
const tolerate = opts.toleratesUnreadableFile ?? usingEnvApiToken;
|
|
5495
5521
|
const stored = tolerate ? await fileStore.load().catch(() => null) : await fileStore.load();
|
|
5522
|
+
const overridden = (opts.cliUserAuthUrl ?? env.MYSPEC_USER_AUTH_URL) !== void 0;
|
|
5523
|
+
const fromSettings = usingEnvApiToken && !overridden && stored?.userAuthUrl === void 0 ? await readSettingsUserAuthUrl(settingsPath).catch((err) => {
|
|
5524
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5525
|
+
warn(ignoringSettings(settingsPath, `it could not be read (${message})`));
|
|
5526
|
+
return void 0;
|
|
5527
|
+
}) : void 0;
|
|
5496
5528
|
const config = resolveConfig({
|
|
5497
5529
|
env,
|
|
5498
5530
|
cliUserAuthUrl: opts.cliUserAuthUrl,
|
|
5499
|
-
|
|
5531
|
+
// Validated on its own before it joins the chain, for the same reason the
|
|
5532
|
+
// unreadable file above is forgiven: parseSettings only requires a
|
|
5533
|
+
// non-empty string, so a hand-written `htps://…` or a bare host in a
|
|
5534
|
+
// settings.json that is not even the credential would otherwise turn a
|
|
5535
|
+
// pure-env install that used to start on the prod default into a
|
|
5536
|
+
// ConfigError exit — and one that never names the file it came from.
|
|
5537
|
+
storedUserAuthUrl: stored?.userAuthUrl ?? usableAuthUrl(fromSettings, settingsPath, warn)
|
|
5500
5538
|
});
|
|
5501
5539
|
return {
|
|
5502
5540
|
store: createEnvApiTokenStore({
|
|
5503
5541
|
fileStore,
|
|
5504
|
-
|
|
5542
|
+
// `warn` rather than the default sink, so the "cannot be sent as a
|
|
5543
|
+
// header" warning lands wherever the caller's other warnings do.
|
|
5544
|
+
envConfig: readEnvApiToken(config.userAuthUrl, env, warn),
|
|
5545
|
+
settingsPath,
|
|
5546
|
+
warn
|
|
5505
5547
|
}),
|
|
5506
5548
|
config,
|
|
5507
5549
|
usingEnvApiToken
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myspec/mcp-server",
|
|
3
|
-
"version": "0.4.0-next.
|
|
3
|
+
"version": "0.4.0-next.95",
|
|
4
4
|
"description": "MySpec MCP server — exposes MySpec platform projects, files and attachments to MCP-aware clients via OAuth-authenticated access tokens.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|