@typeship-ax/mcp 0.8.0 → 0.9.1
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/AGENTS.md +31 -0
- package/README.md +66 -9
- package/api.json +1617 -712
- package/api.md +8292 -382
- package/dist/api-identity.d.ts +40 -0
- package/dist/api-identity.d.ts.map +1 -0
- package/dist/api-identity.js +128 -0
- package/dist/auth-profiles.d.ts +30 -0
- package/dist/auth-profiles.d.ts.map +1 -0
- package/dist/auth-profiles.js +138 -0
- package/dist/core/http.d.ts +17 -2
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +78 -17
- package/dist/credential-storage.d.ts +24 -0
- package/dist/credential-storage.d.ts.map +1 -0
- package/dist/credential-storage.js +207 -0
- package/dist/docs.d.ts +25 -0
- package/dist/docs.d.ts.map +1 -1
- package/dist/docs.js +144 -0
- package/dist/errors.d.ts +18 -10
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +24 -14
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/mcp-authorization.d.ts +52 -0
- package/dist/mcp-authorization.d.ts.map +1 -0
- package/dist/mcp-authorization.js +232 -0
- package/dist/mcp-protocol.d.ts +51 -2
- package/dist/mcp-protocol.d.ts.map +1 -1
- package/dist/mcp-protocol.js +249 -37
- package/dist/mcp.d.ts +21 -3
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +185 -68
- package/dist/named-credentials.d.ts +21 -0
- package/dist/named-credentials.d.ts.map +1 -0
- package/dist/named-credentials.js +86 -0
- package/dist/oauth-request.d.ts +21 -0
- package/dist/oauth-request.d.ts.map +1 -0
- package/dist/oauth-request.js +119 -0
- package/dist/oauth-session.d.ts +106 -0
- package/dist/oauth-session.d.ts.map +1 -0
- package/dist/oauth-session.js +244 -0
- package/dist/ops.d.ts +14 -1
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +30 -30
- package/dist/resources/account.d.ts +2 -2
- package/dist/resources/account.d.ts.map +1 -1
- package/dist/resources/account.js +1 -0
- package/dist/resources/api-keys.d.ts +3 -3
- package/dist/resources/api-keys.d.ts.map +1 -1
- package/dist/resources/api-keys.js +2 -0
- package/dist/resources/definition-revisions.d.ts +5 -5
- package/dist/resources/definition-revisions.d.ts.map +1 -1
- package/dist/resources/definition-revisions.js +4 -0
- package/dist/resources/definitions.d.ts +15 -4
- package/dist/resources/definitions.d.ts.map +1 -1
- package/dist/resources/definitions.js +11 -2
- package/dist/resources/generate.d.ts +14 -3
- package/dist/resources/generate.d.ts.map +1 -1
- package/dist/resources/generate.js +10 -2
- package/dist/resources/generations.d.ts +3 -3
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +2 -0
- package/dist/resources/projects.d.ts +56 -20
- package/dist/resources/projects.d.ts.map +1 -1
- package/dist/resources/projects.js +40 -4
- package/dist/resources/targets.d.ts +20 -9
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +14 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +38 -22
- package/dist/types.d.ts +385 -119
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/dist/worker.js +4 -4
- package/package.json +11 -1
- package/server.json +42 -0
- package/src/api-identity.ts +98 -0
- package/src/auth-profiles.ts +114 -0
- package/src/core/http.ts +88 -19
- package/src/credential-storage.ts +183 -0
- package/src/docs.ts +138 -0
- package/src/errors.ts +26 -15
- package/src/index.ts +29 -4
- package/src/mcp-authorization.ts +211 -0
- package/src/mcp-protocol.ts +287 -38
- package/src/mcp.ts +186 -72
- package/src/named-credentials.ts +74 -0
- package/src/oauth-request.ts +90 -0
- package/src/oauth-session.ts +258 -0
- package/src/ops.ts +44 -31
- package/src/resources/account.ts +3 -0
- package/src/resources/api-keys.ts +5 -0
- package/src/resources/definition-revisions.ts +9 -0
- package/src/resources/definitions.ts +25 -0
- package/src/resources/generate.ts +23 -0
- package/src/resources/generations.ts +5 -0
- package/src/resources/projects.ts +95 -7
- package/src/resources/targets.ts +32 -0
- package/src/schemas.ts +38 -22
- package/src/types.ts +404 -119
- package/src/worker.ts +4 -4
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export class OAuthResponseError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code) {
|
|
4
|
+
const messages = {
|
|
5
|
+
request_failed: "OAuth request failed. Check the configured provider endpoint.",
|
|
6
|
+
timed_out: "OAuth request timed out. Run login again to retry.",
|
|
7
|
+
cancelled: "OAuth request was cancelled.",
|
|
8
|
+
response_too_large: "OAuth provider response exceeded 1 MiB.",
|
|
9
|
+
invalid_response: "OAuth provider returned an invalid JSON response.",
|
|
10
|
+
};
|
|
11
|
+
super(messages[code]);
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.name = "OAuthResponseError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Error bodies and transport causes may contain credentials and are discarded. */
|
|
17
|
+
export function oauthJsonRequest(url, init = {}, timeoutMs = 30_000) {
|
|
18
|
+
return boundedRequest(url, init, timeoutMs, "json");
|
|
19
|
+
}
|
|
20
|
+
/** Device polling needs four protocol errors. Return only their exact names,
|
|
21
|
+
* never descriptions, error URIs, arbitrary error codes or other body fields. */
|
|
22
|
+
export function oauthDeviceRequest(url, init = {}, timeoutMs = 30_000) {
|
|
23
|
+
return boundedRequest(url, init, timeoutMs, "device");
|
|
24
|
+
}
|
|
25
|
+
/** Revocation may return an empty success body. Do not wait for or parse it. */
|
|
26
|
+
export function oauthStatusRequest(url, init = {}, timeoutMs = 30_000) {
|
|
27
|
+
return boundedRequest(url, init, timeoutMs, "status");
|
|
28
|
+
}
|
|
29
|
+
/** Bound the entire exchange, including decoded response bytes. */
|
|
30
|
+
async function boundedRequest(url, init, timeoutMs, mode) {
|
|
31
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 30_000)
|
|
32
|
+
throw new OAuthResponseError("request_failed");
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
let reader;
|
|
35
|
+
let timer;
|
|
36
|
+
let interrupted;
|
|
37
|
+
let rejectDeadline;
|
|
38
|
+
const deadline = new Promise((_, reject) => { rejectDeadline = reject; });
|
|
39
|
+
const stop = (code) => { interrupted ??= code; rejectDeadline(new OAuthResponseError(interrupted)); controller.abort(); };
|
|
40
|
+
const cancel = () => stop("cancelled");
|
|
41
|
+
init.signal?.addEventListener("abort", cancel, { once: true });
|
|
42
|
+
timer = setTimeout(() => stop("timed_out"), timeoutMs);
|
|
43
|
+
try {
|
|
44
|
+
if (init.signal?.aborted)
|
|
45
|
+
throw new OAuthResponseError("cancelled");
|
|
46
|
+
const request = { ...init, redirect: "error", credentials: "omit", cache: "no-store", signal: controller.signal };
|
|
47
|
+
const response = await Promise.race([
|
|
48
|
+
fetch(url, request).then((value) => {
|
|
49
|
+
// Also dispose a late response from a transport that ignored abort.
|
|
50
|
+
if (controller.signal.aborted)
|
|
51
|
+
void value.body?.cancel().catch(() => { });
|
|
52
|
+
return value;
|
|
53
|
+
}), deadline,
|
|
54
|
+
]);
|
|
55
|
+
if (response.redirected) {
|
|
56
|
+
void response.body?.cancel().catch(() => { });
|
|
57
|
+
throw new OAuthResponseError("request_failed");
|
|
58
|
+
}
|
|
59
|
+
if (mode === "status" || response.status !== 200 && !(mode === "device" && response.status === 400)) {
|
|
60
|
+
void response.body?.cancel().catch(() => { });
|
|
61
|
+
return { status: response.status };
|
|
62
|
+
}
|
|
63
|
+
if (!response.body)
|
|
64
|
+
throw new OAuthResponseError("invalid_response");
|
|
65
|
+
reader = response.body.getReader();
|
|
66
|
+
let size = 0, text = "";
|
|
67
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
68
|
+
while (true) {
|
|
69
|
+
const chunk = await Promise.race([reader.read(), deadline]);
|
|
70
|
+
if (chunk.done)
|
|
71
|
+
break;
|
|
72
|
+
size += chunk.value.byteLength;
|
|
73
|
+
if (size > 1_048_576)
|
|
74
|
+
throw new OAuthResponseError("response_too_large");
|
|
75
|
+
try {
|
|
76
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new OAuthResponseError("invalid_response");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
text += decoder.decode();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new OAuthResponseError("invalid_response");
|
|
87
|
+
}
|
|
88
|
+
let data;
|
|
89
|
+
try {
|
|
90
|
+
data = JSON.parse(text);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new OAuthResponseError("invalid_response");
|
|
94
|
+
}
|
|
95
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
96
|
+
throw new OAuthResponseError("invalid_response");
|
|
97
|
+
if (response.status !== 200) {
|
|
98
|
+
const error = data.error;
|
|
99
|
+
return { status: response.status, ...(["authorization_pending", "slow_down", "access_denied", "expired_token"].includes(String(error)) && typeof error === "string" ? { error: error } : {}) };
|
|
100
|
+
}
|
|
101
|
+
return { status: response.status, data: data };
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (interrupted)
|
|
105
|
+
throw new OAuthResponseError(interrupted);
|
|
106
|
+
if (error instanceof OAuthResponseError)
|
|
107
|
+
throw error;
|
|
108
|
+
throw new OAuthResponseError("request_failed");
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
init.signal?.removeEventListener("abort", cancel);
|
|
113
|
+
controller.abort();
|
|
114
|
+
if (reader) {
|
|
115
|
+
void reader.cancel().catch(() => { });
|
|
116
|
+
reader.releaseLock();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { type IdentityConfiguration, type VerifiedIdentity } from "./api-identity.js";
|
|
2
|
+
export interface StoredOAuthSession {
|
|
3
|
+
accessToken: string;
|
|
4
|
+
refreshToken?: string;
|
|
5
|
+
expiresAt?: number;
|
|
6
|
+
tokenUrl?: string;
|
|
7
|
+
issuer?: string;
|
|
8
|
+
clientId?: string;
|
|
9
|
+
revocationUrl?: string;
|
|
10
|
+
apiBaseUrl?: string;
|
|
11
|
+
configuredClientId?: string | null;
|
|
12
|
+
scopes?: string[];
|
|
13
|
+
/** New login identity, independent of rotating tokens. */
|
|
14
|
+
sessionId: string;
|
|
15
|
+
binding: string;
|
|
16
|
+
/** Persisted before exchange: an interrupted exchange must not replay a rotating token. */
|
|
17
|
+
refreshPending?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface CredentialDestination {
|
|
20
|
+
apiBaseUrl: string;
|
|
21
|
+
environment?: string;
|
|
22
|
+
profile?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function assertCredentialDestination(credentials: StoredCredentials, destination: CredentialDestination): void;
|
|
25
|
+
export interface StoredCredentials {
|
|
26
|
+
destination?: CredentialDestination;
|
|
27
|
+
named?: Record<string, string | {
|
|
28
|
+
username: string;
|
|
29
|
+
password: string;
|
|
30
|
+
}>;
|
|
31
|
+
scalars?: Record<string, string>;
|
|
32
|
+
basic?: {
|
|
33
|
+
username: string;
|
|
34
|
+
password: string;
|
|
35
|
+
};
|
|
36
|
+
oauth?: StoredOAuthSession;
|
|
37
|
+
identity?: VerifiedIdentity & {
|
|
38
|
+
binding: string;
|
|
39
|
+
};
|
|
40
|
+
minted?: {
|
|
41
|
+
via: "browser";
|
|
42
|
+
key_name: string;
|
|
43
|
+
org_id?: string;
|
|
44
|
+
revocationUrl?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface SessionConfiguration extends CredentialDestination {
|
|
48
|
+
identity?: IdentityConfiguration;
|
|
49
|
+
verifyIdentity?: (accessToken: string) => Promise<VerifiedIdentity>;
|
|
50
|
+
clientId?: string | null;
|
|
51
|
+
issuer?: string | null;
|
|
52
|
+
tokenUrl?: string | null;
|
|
53
|
+
discoveryUrl?: string | null;
|
|
54
|
+
authorizationUrl?: string | null;
|
|
55
|
+
scopes?: string[];
|
|
56
|
+
audience?: string;
|
|
57
|
+
resource?: string;
|
|
58
|
+
organizationParameter?: "organization" | "organization_id";
|
|
59
|
+
}
|
|
60
|
+
/** Bind minimal identity evidence to this credential set and its configured read. */
|
|
61
|
+
export declare function credentialIdentityBinding(credentials: StoredCredentials, identity: IdentityConfiguration): string;
|
|
62
|
+
export declare function assertStoredIdentity(credentials: StoredCredentials, identity?: IdentityConfiguration): void;
|
|
63
|
+
/** Bind the login to effective nonsecret settings. Scope ordering is irrelevant. */
|
|
64
|
+
export declare function sessionBinding(config: SessionConfiguration): string;
|
|
65
|
+
interface LockedCredentials {
|
|
66
|
+
read(): StoredCredentials | null;
|
|
67
|
+
write(credentials: StoredCredentials | null): void;
|
|
68
|
+
}
|
|
69
|
+
export interface CredentialStore {
|
|
70
|
+
read(): StoredCredentials | null;
|
|
71
|
+
withLock<T>(work: (store: LockedCredentials) => Promise<T>): Promise<T>;
|
|
72
|
+
}
|
|
73
|
+
export declare class CredentialStorageError extends Error {
|
|
74
|
+
}
|
|
75
|
+
export interface CredentialCodec {
|
|
76
|
+
readonly name: string;
|
|
77
|
+
prepare(): void;
|
|
78
|
+
encode(plaintext: string): string;
|
|
79
|
+
decode(encoded: string): string;
|
|
80
|
+
}
|
|
81
|
+
/** Private file adapter. All writers (login, refresh and logout) use the same
|
|
82
|
+
* cross-process lock. Credential storage can be replaced independently of OAuth. */
|
|
83
|
+
export declare class FileCredentialStore implements CredentialStore {
|
|
84
|
+
readonly path: string;
|
|
85
|
+
private readonly lockTimeoutMs;
|
|
86
|
+
private readonly codec?;
|
|
87
|
+
constructor(path: string, lockTimeoutMs?: number, codec?: CredentialCodec | undefined);
|
|
88
|
+
get backend(): string;
|
|
89
|
+
/** Check storage access before starting an interactive login. */
|
|
90
|
+
prepare(): Promise<void>;
|
|
91
|
+
read(): StoredCredentials | null;
|
|
92
|
+
private write;
|
|
93
|
+
withLock<T>(work: (store: LockedCredentials) => Promise<T>): Promise<T>;
|
|
94
|
+
private recoverDeadOwner;
|
|
95
|
+
update(change: (current: StoredCredentials) => StoredCredentials): Promise<void>;
|
|
96
|
+
/** Explicit local-only logout can remove an unreadable encrypted session. */
|
|
97
|
+
clear(): Promise<boolean>;
|
|
98
|
+
/** Remove under the refresh lock, then revoke the latest token outside it. */
|
|
99
|
+
take(): Promise<StoredCredentials | null>;
|
|
100
|
+
}
|
|
101
|
+
/** Re-read on every request attempt; only one process may rotate the token.
|
|
102
|
+
* A failed or interrupted exchange requires login, since the server may have
|
|
103
|
+
* consumed its refresh token even when no response reached this process. */
|
|
104
|
+
export declare function oauthSessionToken(store: CredentialStore, config: SessionConfiguration, sessionId: string, tokenParams?: Record<string, string>): Promise<string>;
|
|
105
|
+
export {};
|
|
106
|
+
//# sourceMappingURL=oauth-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-session.d.ts","sourceRoot":"","sources":["../src/oauth-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { closeSync, existsSync, linkSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { assertApiIdentity } from "./api-identity.js";
|
|
5
|
+
import { oauthJsonRequest } from "./oauth-request.js";
|
|
6
|
+
export function assertCredentialDestination(credentials, destination) {
|
|
7
|
+
const saved = credentials.destination;
|
|
8
|
+
if (!saved)
|
|
9
|
+
throw new Error("Saved credentials have no API and profile binding. Log in again before using them.");
|
|
10
|
+
if (new URL(saved.apiBaseUrl).href !== new URL(destination.apiBaseUrl).href)
|
|
11
|
+
throw new Error("The API destination changed. Log in again before using saved credentials.");
|
|
12
|
+
if ((saved.environment ?? null) !== (destination.environment ?? null) || (saved.profile ?? null) !== (destination.profile ?? null))
|
|
13
|
+
throw new Error("The credential environment or profile changed. Log in again before using saved credentials.");
|
|
14
|
+
}
|
|
15
|
+
/** Bind minimal identity evidence to this credential set and its configured read. */
|
|
16
|
+
export function credentialIdentityBinding(credentials, identity) {
|
|
17
|
+
return createHash("sha256").update(JSON.stringify({
|
|
18
|
+
destination: credentials.destination, scalars: credentials.scalars, named: credentials.named, basic: credentials.basic,
|
|
19
|
+
oauth: credentials.oauth ? { sessionId: credentials.oauth.sessionId, binding: credentials.oauth.binding } : undefined,
|
|
20
|
+
operation: identity.operation, fields: Object.entries(identity.fields).sort(([a], [b]) => a.localeCompare(b)),
|
|
21
|
+
})).digest("hex");
|
|
22
|
+
}
|
|
23
|
+
export function assertStoredIdentity(credentials, identity) {
|
|
24
|
+
if (!identity)
|
|
25
|
+
return;
|
|
26
|
+
if (!credentials.identity?.values || credentials.identity.binding !== credentialIdentityBinding(credentials, identity) || Object.keys(identity.fields).some((kind) => !Object.hasOwn(credentials.identity.values, kind))) {
|
|
27
|
+
throw new Error("The saved login has no matching API identity verification. Sign in again to verify the current account and organization.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Bind the login to effective nonsecret settings. Scope ordering is irrelevant. */
|
|
31
|
+
export function sessionBinding(config) {
|
|
32
|
+
const url = (value) => value ? new URL(value).href : null;
|
|
33
|
+
return createHash("sha256").update(JSON.stringify({
|
|
34
|
+
apiBaseUrl: url(config.apiBaseUrl), environment: config.environment ?? null, profile: config.profile ?? null,
|
|
35
|
+
clientId: config.clientId ?? null, issuer: config.issuer ?? null,
|
|
36
|
+
tokenUrl: url(config.tokenUrl), discoveryUrl: url(config.discoveryUrl), authorizationUrl: url(config.authorizationUrl),
|
|
37
|
+
scopes: [...new Set(config.scopes ?? [])].sort(), audience: config.audience ?? null, resource: config.resource ?? null,
|
|
38
|
+
organizationParameter: config.organizationParameter ?? null,
|
|
39
|
+
identity: config.identity ? { operation: config.identity.operation, fields: Object.entries(config.identity.fields).sort(([a], [b]) => a.localeCompare(b)) } : null,
|
|
40
|
+
})).digest("hex");
|
|
41
|
+
}
|
|
42
|
+
function code(error) { return error?.code; }
|
|
43
|
+
function alive(pid) {
|
|
44
|
+
try {
|
|
45
|
+
process.kill(pid, 0);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return code(error) !== "ESRCH";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export class CredentialStorageError extends Error {
|
|
53
|
+
}
|
|
54
|
+
/** Private file adapter. All writers (login, refresh and logout) use the same
|
|
55
|
+
* cross-process lock. Credential storage can be replaced independently of OAuth. */
|
|
56
|
+
export class FileCredentialStore {
|
|
57
|
+
path;
|
|
58
|
+
lockTimeoutMs;
|
|
59
|
+
codec;
|
|
60
|
+
constructor(path, lockTimeoutMs = 40_000, codec) {
|
|
61
|
+
this.path = path;
|
|
62
|
+
this.lockTimeoutMs = lockTimeoutMs;
|
|
63
|
+
this.codec = codec;
|
|
64
|
+
}
|
|
65
|
+
get backend() { return this.codec?.name ?? "private plaintext file"; }
|
|
66
|
+
/** Check storage access before starting an interactive login. */
|
|
67
|
+
async prepare() { await this.withLock(async () => { this.codec?.prepare(); }); }
|
|
68
|
+
read() {
|
|
69
|
+
try {
|
|
70
|
+
const encoded = readFileSync(this.path, "utf8");
|
|
71
|
+
const data = JSON.parse(this.codec ? this.codec.decode(encoded) : encoded);
|
|
72
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
73
|
+
throw new Error();
|
|
74
|
+
return data;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (code(error) === "ENOENT")
|
|
78
|
+
return null;
|
|
79
|
+
if (error instanceof CredentialStorageError)
|
|
80
|
+
throw error;
|
|
81
|
+
throw new Error("Cannot read saved credentials. Check the credential store before logging in again.");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
write(credentials) {
|
|
85
|
+
if (credentials === null) {
|
|
86
|
+
rmSync(this.path, { force: true });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const temporary = this.path + "." + randomBytes(16).toString("hex") + ".tmp";
|
|
90
|
+
try {
|
|
91
|
+
const plaintext = JSON.stringify(credentials, null, 2);
|
|
92
|
+
writeFileSync(temporary, (this.codec ? this.codec.encode(plaintext) : plaintext) + "\n", { mode: 0o600, flag: "wx", flush: true });
|
|
93
|
+
renameSync(temporary, this.path);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
rmSync(temporary, { force: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async withLock(work) {
|
|
100
|
+
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
101
|
+
const lock = this.path + ".lock";
|
|
102
|
+
const owner = { pid: process.pid, nonce: randomBytes(16).toString("hex") };
|
|
103
|
+
const claim = lock + "." + owner.nonce;
|
|
104
|
+
// Hard-link a complete owner record: no empty-file window if the process dies.
|
|
105
|
+
writeFileSync(claim, JSON.stringify(owner), { mode: 0o600, flag: "wx" });
|
|
106
|
+
let acquired = false;
|
|
107
|
+
const deadline = Date.now() + this.lockTimeoutMs;
|
|
108
|
+
try {
|
|
109
|
+
while (!acquired) {
|
|
110
|
+
try {
|
|
111
|
+
linkSync(claim, lock);
|
|
112
|
+
acquired = true;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (code(error) !== "EEXIST")
|
|
116
|
+
throw new Error("Cannot lock the credential store.");
|
|
117
|
+
this.recoverDeadOwner(lock);
|
|
118
|
+
if (Date.now() >= deadline)
|
|
119
|
+
throw new Error("The credential store is busy. Wait for the other login or refresh to finish and retry. If its process stopped, inspect " + lock + ".");
|
|
120
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return await work({ read: () => this.read(), write: (value) => this.write(value) });
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
if (acquired)
|
|
127
|
+
rmSync(lock, { force: true });
|
|
128
|
+
rmSync(claim, { force: true });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
recoverDeadOwner(lock) {
|
|
132
|
+
let previous;
|
|
133
|
+
try {
|
|
134
|
+
previous = JSON.parse(readFileSync(lock, "utf8"));
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!Number.isSafeInteger(previous?.pid) || previous.pid <= 0 || typeof previous.nonce !== "string" || !/^[a-f0-9]{32}$/.test(previous.nonce) || alive(previous.pid))
|
|
140
|
+
return;
|
|
141
|
+
// One reclaimer per dead owner. Keep this empty marker: deleting it lets a
|
|
142
|
+
// delayed contender remove a later owner's lock (the ABA race). A double
|
|
143
|
+
// crash during reclamation fails closed with the bounded busy diagnostic.
|
|
144
|
+
const marker = lock + ".reaped-" + previous.nonce;
|
|
145
|
+
try {
|
|
146
|
+
closeSync(openSync(marker, "wx", 0o600));
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const current = JSON.parse(readFileSync(lock, "utf8"));
|
|
153
|
+
if (current.nonce === previous.nonce)
|
|
154
|
+
rmSync(lock);
|
|
155
|
+
}
|
|
156
|
+
catch { /* another process already completed recovery */ }
|
|
157
|
+
}
|
|
158
|
+
async update(change) {
|
|
159
|
+
await this.withLock(async (store) => { store.write(change(store.read() ?? {})); });
|
|
160
|
+
}
|
|
161
|
+
/** Explicit local-only logout can remove an unreadable encrypted session. */
|
|
162
|
+
async clear() {
|
|
163
|
+
return this.withLock(async (store) => { const existed = existsSync(this.path); store.write(null); return existed; });
|
|
164
|
+
}
|
|
165
|
+
/** Remove under the refresh lock, then revoke the latest token outside it. */
|
|
166
|
+
async take() {
|
|
167
|
+
return this.withLock(async (store) => { const current = store.read(); store.write(null); return current; });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function validSession(credentials, config, sessionId, allowPending = false) {
|
|
171
|
+
const session = credentials?.oauth;
|
|
172
|
+
if (!session || !session.sessionId || session.sessionId !== sessionId)
|
|
173
|
+
throw new Error("The saved OAuth session changed or was logged out. Run login again and retry the request.");
|
|
174
|
+
if (session.apiBaseUrl && new URL(session.apiBaseUrl).href !== new URL(config.apiBaseUrl).href)
|
|
175
|
+
throw new Error("The API destination changed. Log in again.");
|
|
176
|
+
if (session.issuer && config.issuer && session.issuer !== config.issuer)
|
|
177
|
+
throw new Error("The configured issuer changed. Log in again.");
|
|
178
|
+
if (session.configuredClientId !== (config.clientId ?? null))
|
|
179
|
+
throw new Error("The OAuth client changed. Log in again.");
|
|
180
|
+
if (!session.binding || session.binding !== sessionBinding(config))
|
|
181
|
+
throw new Error("The API or OAuth configuration changed. Log in again before using saved credentials.");
|
|
182
|
+
if (session.refreshPending && !allowPending)
|
|
183
|
+
throw new Error("The previous OAuth refresh did not finish safely. Log in again; its refresh token will not be reused.");
|
|
184
|
+
if (typeof session.accessToken !== "string" || !session.accessToken)
|
|
185
|
+
throw new Error("The saved OAuth session is invalid. Log in again.");
|
|
186
|
+
assertStoredIdentity(credentials, config.identity);
|
|
187
|
+
return session;
|
|
188
|
+
}
|
|
189
|
+
/** Re-read on every request attempt; only one process may rotate the token.
|
|
190
|
+
* A failed or interrupted exchange requires login, since the server may have
|
|
191
|
+
* consumed its refresh token even when no response reached this process. */
|
|
192
|
+
export async function oauthSessionToken(store, config, sessionId, tokenParams = {}) {
|
|
193
|
+
const fresh = (session) => session.expiresAt === undefined || (Number.isFinite(session.expiresAt) && session.expiresAt > Date.now() + 60_000);
|
|
194
|
+
const first = validSession(store.read(), config, sessionId, true);
|
|
195
|
+
if (!first.refreshPending && fresh(first))
|
|
196
|
+
return first.accessToken;
|
|
197
|
+
return store.withLock(async (locked) => {
|
|
198
|
+
const credentials = locked.read();
|
|
199
|
+
const session = validSession(credentials, config, sessionId);
|
|
200
|
+
if (fresh(session))
|
|
201
|
+
return session.accessToken;
|
|
202
|
+
if (!session.refreshToken || !session.tokenUrl || !session.clientId)
|
|
203
|
+
throw new Error("The OAuth session expired and cannot be refreshed. Log in again.");
|
|
204
|
+
const endpoint = new URL(session.tokenUrl);
|
|
205
|
+
if ((endpoint.protocol !== "https:" && !(endpoint.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(endpoint.hostname))) || endpoint.username || endpoint.password || endpoint.hash)
|
|
206
|
+
throw new Error("The OAuth token endpoint requires HTTPS; loopback HTTP is allowed for development.");
|
|
207
|
+
locked.write({ ...credentials, oauth: { ...session, refreshPending: true } });
|
|
208
|
+
try {
|
|
209
|
+
const response = await oauthJsonRequest(endpoint, {
|
|
210
|
+
method: "POST",
|
|
211
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
212
|
+
body: new URLSearchParams({ ...tokenParams, grant_type: "refresh_token", refresh_token: session.refreshToken, client_id: session.clientId }),
|
|
213
|
+
});
|
|
214
|
+
const body = response.data;
|
|
215
|
+
if (response.status !== 200 || !body || typeof body.access_token !== "string" || !body.access_token || typeof body.token_type !== "string" || body.token_type.toLowerCase() !== "bearer")
|
|
216
|
+
throw new Error();
|
|
217
|
+
if (body.expires_in !== undefined && (typeof body.expires_in !== "number" || !Number.isFinite(body.expires_in) || body.expires_in <= 0))
|
|
218
|
+
throw new Error();
|
|
219
|
+
if (body.refresh_token !== undefined && (typeof body.refresh_token !== "string" || !body.refresh_token))
|
|
220
|
+
throw new Error();
|
|
221
|
+
if (body.scope !== undefined && typeof body.scope !== "string")
|
|
222
|
+
throw new Error();
|
|
223
|
+
const next = {
|
|
224
|
+
...session, accessToken: body.access_token, refreshToken: body.refresh_token ?? session.refreshToken,
|
|
225
|
+
expiresAt: typeof body.expires_in === "number" ? Date.now() + body.expires_in * 1000 : undefined,
|
|
226
|
+
...(typeof body.scope === "string" ? { scopes: body.scope.split(/\s+/).filter(Boolean) } : {}), refreshPending: undefined,
|
|
227
|
+
};
|
|
228
|
+
let identity = credentials?.identity;
|
|
229
|
+
if (config.identity) {
|
|
230
|
+
if (!config.verifyIdentity || !identity)
|
|
231
|
+
throw new Error();
|
|
232
|
+
const verified = await config.verifyIdentity(next.accessToken);
|
|
233
|
+
assertApiIdentity(identity.values, verified.values);
|
|
234
|
+
identity = { ...identity, ...verified };
|
|
235
|
+
}
|
|
236
|
+
locked.write({ ...credentials, oauth: next, ...(identity ? { identity } : {}) });
|
|
237
|
+
return next.accessToken;
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Never expose an error body, a refresh token, or fetch's nested cause.
|
|
241
|
+
throw new Error("The OAuth session could not be refreshed safely. Log in again.");
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
package/dist/ops.d.ts
CHANGED
|
@@ -23,6 +23,16 @@ export interface ParamSpec {
|
|
|
23
23
|
description?: string;
|
|
24
24
|
/** Settable once on the client (globals option); optional per call. */
|
|
25
25
|
global?: boolean;
|
|
26
|
+
/** False opts out. Otherwise exact human references are resolved through
|
|
27
|
+
* this one read operation before the requested call. */
|
|
28
|
+
resolve?: false | {
|
|
29
|
+
via: string;
|
|
30
|
+
match: string[];
|
|
31
|
+
id: string;
|
|
32
|
+
idPattern?: string;
|
|
33
|
+
filterParam?: string;
|
|
34
|
+
inferred?: boolean;
|
|
35
|
+
};
|
|
26
36
|
}
|
|
27
37
|
export interface OpSpec {
|
|
28
38
|
resource: string;
|
|
@@ -85,6 +95,9 @@ export interface OpSpec {
|
|
|
85
95
|
/** The spec's security requirement: "required" fails fast without a
|
|
86
96
|
* credential; "none" works anonymously; "optional" is either. */
|
|
87
97
|
auth: "required" | "optional" | "none";
|
|
98
|
+
security?: Record<string, string[]>[];
|
|
99
|
+
/** Complete credential-option combinations for this operation. */
|
|
100
|
+
credentialOptions?: string[][];
|
|
88
101
|
/** Whether calling this operation only reads, writes, or destroys state. */
|
|
89
102
|
safety: "read" | "write" | "destructive";
|
|
90
103
|
params: ParamSpec[];
|
|
@@ -99,7 +112,7 @@ export declare const OPS: OpSpec[];
|
|
|
99
112
|
/** Operations present in the API Definition but omitted by this
|
|
100
113
|
* generation's operation cap. Kept deliberately small: enough for honest
|
|
101
114
|
* discovery and PLAN_LIMIT errors, never enough to call an absent method. */
|
|
102
|
-
export type OmittedOpSpec = Pick<OpSpec, "resource" | "method" | "command" | "commandAlias" | "tool" | "httpMethod" | "path" | "summary" | "description" | "toolDescription" | "paginated" | "select" | "safety" | "auth" | "params" | "inputSchema">;
|
|
115
|
+
export type OmittedOpSpec = Pick<OpSpec, "resource" | "method" | "command" | "commandAlias" | "tool" | "httpMethod" | "path" | "summary" | "description" | "toolDescription" | "paginated" | "select" | "safety" | "auth" | "security" | "params" | "inputSchema">;
|
|
103
116
|
export declare const OMITTED_OPS: OmittedOpSpec[];
|
|
104
117
|
/** Parameters settable once on the client (globals option): CLI/MCP fill
|
|
105
118
|
* them from flags or environment; the SDK falls back per request. */
|
package/dist/ops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../src/ops.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC3C;;;uDAGmD;IACnD,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAC7E,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,mFAAmF;IACnF,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACvF,yFAAyF;IACzF,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,MAAM,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../src/ops.ts"],"names":[],"mappings":"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"}
|