@proof-holdings/mcp-server 1.0.0 → 1.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 +1 -1
- package/README.md +223 -216
- package/dist/authBoundary.d.ts +182 -0
- package/dist/authBoundary.d.ts.map +1 -0
- package/dist/authBoundary.js +328 -0
- package/dist/authBoundary.js.map +1 -0
- package/dist/factory.d.ts +33 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +123 -0
- package/dist/factory.js.map +1 -0
- package/dist/http.d.ts +63 -3
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +183 -4
- package/dist/http.js.map +1 -1
- package/dist/remote.d.ts +111 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +787 -0
- package/dist/remote.js.map +1 -0
- package/dist/server.js +11 -57
- package/dist/server.js.map +1 -1
- package/dist/tools/{projects.d.ts → accounts.d.ts} +1 -1
- package/dist/tools/accounts.d.ts.map +1 -0
- package/dist/tools/accounts.js +70 -0
- package/dist/tools/accounts.js.map +1 -0
- package/dist/tools/api-keys.d.ts.map +1 -1
- package/dist/tools/api-keys.js +14 -5
- package/dist/tools/api-keys.js.map +1 -1
- package/dist/tools/auth-flows.d.ts +4 -0
- package/dist/tools/auth-flows.d.ts.map +1 -0
- package/dist/tools/auth-flows.js +34 -0
- package/dist/tools/auth-flows.js.map +1 -0
- package/dist/tools/authorizations.d.ts +4 -0
- package/dist/tools/authorizations.d.ts.map +1 -0
- package/dist/tools/authorizations.js +111 -0
- package/dist/tools/authorizations.js.map +1 -0
- package/dist/tools/circles.d.ts +4 -0
- package/dist/tools/circles.d.ts.map +1 -0
- package/dist/tools/circles.js +215 -0
- package/dist/tools/circles.js.map +1 -0
- package/dist/tools/confirmations.d.ts +4 -0
- package/dist/tools/confirmations.d.ts.map +1 -0
- package/dist/tools/confirmations.js +86 -0
- package/dist/tools/confirmations.js.map +1 -0
- package/dist/tools/delegation-verify-outcomes.d.ts +23 -0
- package/dist/tools/delegation-verify-outcomes.d.ts.map +1 -0
- package/dist/tools/delegation-verify-outcomes.js +51 -0
- package/dist/tools/delegation-verify-outcomes.js.map +1 -0
- package/dist/tools/delegation-verify.d.ts +24 -0
- package/dist/tools/delegation-verify.d.ts.map +1 -0
- package/dist/tools/delegation-verify.js +192 -0
- package/dist/tools/delegation-verify.js.map +1 -0
- package/dist/tools/delegations.d.ts +4 -0
- package/dist/tools/delegations.d.ts.map +1 -0
- package/dist/tools/delegations.js +84 -0
- package/dist/tools/delegations.js.map +1 -0
- package/dist/tools/domains.d.ts.map +1 -1
- package/dist/tools/domains.js +1 -2
- package/dist/tools/domains.js.map +1 -1
- package/dist/tools/hitl-keys.d.ts +4 -0
- package/dist/tools/hitl-keys.d.ts.map +1 -0
- package/dist/tools/hitl-keys.js +52 -0
- package/dist/tools/hitl-keys.js.map +1 -0
- package/dist/tools/hitl.d.ts +4 -0
- package/dist/tools/hitl.d.ts.map +1 -0
- package/dist/tools/hitl.js +151 -0
- package/dist/tools/hitl.js.map +1 -0
- package/dist/tools/phones.js +1 -1
- package/dist/tools/phones.js.map +1 -1
- package/dist/tools/profiles.d.ts.map +1 -1
- package/dist/tools/profiles.js +73 -0
- package/dist/tools/profiles.js.map +1 -1
- package/dist/tools/proof-me.d.ts +4 -0
- package/dist/tools/proof-me.d.ts.map +1 -0
- package/dist/tools/proof-me.js +107 -0
- package/dist/tools/proof-me.js.map +1 -0
- package/dist/tools/proofs.d.ts.map +1 -1
- package/dist/tools/proofs.js +9 -6
- package/dist/tools/proofs.js.map +1 -1
- package/dist/tools/render-auth-link.d.ts +3 -0
- package/dist/tools/render-auth-link.d.ts.map +1 -0
- package/dist/tools/render-auth-link.js +30 -0
- package/dist/tools/render-auth-link.js.map +1 -0
- package/dist/tools/sessions.js +5 -5
- package/dist/tools/sessions.js.map +1 -1
- package/dist/tools/settings.d.ts.map +1 -1
- package/dist/tools/settings.js +69 -0
- package/dist/tools/settings.js.map +1 -1
- package/dist/tools/twofa.d.ts.map +1 -1
- package/dist/tools/twofa.js +16 -3
- package/dist/tools/twofa.js.map +1 -1
- package/dist/tools/user-requests.d.ts.map +1 -1
- package/dist/tools/user-requests.js +1 -2
- package/dist/tools/user-requests.js.map +1 -1
- package/dist/tools/verification-requests.d.ts.map +1 -1
- package/dist/tools/verification-requests.js +40 -13
- package/dist/tools/verification-requests.js.map +1 -1
- package/dist/tools/verifications.d.ts.map +1 -1
- package/dist/tools/verifications.js +59 -12
- package/dist/tools/verifications.js.map +1 -1
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +114 -5
- package/dist/types.js.map +1 -1
- package/package.json +11 -5
- package/dist/tools/projects.d.ts.map +0 -1
- package/dist/tools/projects.js +0 -159
- package/dist/tools/projects.js.map +0 -1
package/dist/factory.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { HttpClient } from './http.js';
|
|
3
|
+
import { accessNoticeFor, serverInstructions } from './authBoundary.js';
|
|
4
|
+
import { registerTools as registerVerifications } from './tools/verifications.js';
|
|
5
|
+
import { registerTools as registerVerificationRequests } from './tools/verification-requests.js';
|
|
6
|
+
import { registerTools as registerProofs } from './tools/proofs.js';
|
|
7
|
+
import { registerTools as registerSessions } from './tools/sessions.js';
|
|
8
|
+
import { registerTools as registerAssets } from './tools/assets.js';
|
|
9
|
+
import { registerTools as registerAuth } from './tools/auth.js';
|
|
10
|
+
import { registerTools as registerSettings } from './tools/settings.js';
|
|
11
|
+
import { registerTools as registerTemplates } from './tools/templates.js';
|
|
12
|
+
import { registerTools as registerWebhookDeliveries } from './tools/webhook-deliveries.js';
|
|
13
|
+
import { registerTools as registerBilling } from './tools/billing.js';
|
|
14
|
+
import { registerTools as registerProfiles } from './tools/profiles.js';
|
|
15
|
+
import { registerTools as registerEmails } from './tools/emails.js';
|
|
16
|
+
import { registerTools as registerPhones } from './tools/phones.js';
|
|
17
|
+
import { registerTools as registerApiKeys } from './tools/api-keys.js';
|
|
18
|
+
import { registerTools as registerTwofa } from './tools/twofa.js';
|
|
19
|
+
import { registerTools as registerDomains } from './tools/domains.js';
|
|
20
|
+
import { registerTools as registerDnsCredentials } from './tools/dns-credentials.js';
|
|
21
|
+
import { registerTools as registerUserRequests } from './tools/user-requests.js';
|
|
22
|
+
import { registerTools as registerUserDomainVerify } from './tools/user-domain-verify.js';
|
|
23
|
+
import { registerTools as registerPublicProfiles } from './tools/public-profiles.js';
|
|
24
|
+
import { registerTools as registerAuthorizations } from './tools/authorizations.js';
|
|
25
|
+
import { registerTools as registerConfirmations } from './tools/confirmations.js';
|
|
26
|
+
import { registerTools as registerHitl } from './tools/hitl.js';
|
|
27
|
+
import { registerTools as registerHitlKeys } from './tools/hitl-keys.js';
|
|
28
|
+
import { registerTools as registerProofMe } from './tools/proof-me.js';
|
|
29
|
+
import { registerTools as registerCircles } from './tools/circles.js';
|
|
30
|
+
import { registerTools as registerDelegations } from './tools/delegations.js';
|
|
31
|
+
import { registerTools as registerDelegationVerify } from './tools/delegation-verify.js';
|
|
32
|
+
import { registerTools as registerAuthFlows } from './tools/auth-flows.js';
|
|
33
|
+
import { registerTools as registerAccounts } from './tools/accounts.js';
|
|
34
|
+
import { registerTools as registerRenderAuthLink } from './tools/render-auth-link.js';
|
|
35
|
+
/**
|
|
36
|
+
* Builds one MCP server with its own HTTP client.
|
|
37
|
+
*
|
|
38
|
+
* Why a factory at all (`h-mcp-remote`, SC-4): the stdio entrypoint serves exactly one user, so one
|
|
39
|
+
* `HttpClient` per process is correct there. The remote server serves many, and `HttpClient` keeps
|
|
40
|
+
* `sessionToken` / `refreshToken` as INSTANCE fields written by `captureSetCookie` — sharing one
|
|
41
|
+
* across connections would hand the next user the previous user's logged-in session. So the unit of
|
|
42
|
+
* isolation is the connection, and this function is that unit.
|
|
43
|
+
*
|
|
44
|
+
* The registration list below is the ONLY copy in the package: `server.ts` calls this rather than
|
|
45
|
+
* repeating it. A second copy would work on the day it was written and drift one module at a time
|
|
46
|
+
* afterwards, with both paths green — `__tests__/factory.test.ts` pins both the list's completeness
|
|
47
|
+
* against `src/tools` and the absence of a second copy in `server.ts`.
|
|
48
|
+
*/
|
|
49
|
+
export function createProofServer(options) {
|
|
50
|
+
const http = new HttpClient({
|
|
51
|
+
apiKey: options.apiKey,
|
|
52
|
+
baseUrl: options.baseUrl,
|
|
53
|
+
remote: options.remote,
|
|
54
|
+
});
|
|
55
|
+
const access = { remote: options.remote, hasCredential: Boolean(options.apiKey) };
|
|
56
|
+
const server = new McpServer({
|
|
57
|
+
name: 'proof-holdings',
|
|
58
|
+
// The version every client reads back from `initialize`, and the one a user quotes in a bug
|
|
59
|
+
// report. A LITERAL rather than a read of `mcp/package.json` — a CHOICE, not a constraint;
|
|
60
|
+
// the manifest is reachable from here. What makes the literal safe is the pin below, never
|
|
61
|
+
// that choice. Held equal to the manifest by
|
|
62
|
+
// `src/__tests__/drift/delegation-publication.test.ts` (h-npm-registry-release). If this is
|
|
63
|
+
// ever replaced by a real read, delete the pin in the same change: a pin over a derived
|
|
64
|
+
// value guards nothing.
|
|
65
|
+
version: '1.1.0',
|
|
66
|
+
}, { instructions: serverInstructions(access) });
|
|
67
|
+
const bound = server.tool.bind(server);
|
|
68
|
+
/**
|
|
69
|
+
* The registrar every module below is handed, wrapping `server.tool` so the access notice is
|
|
70
|
+
* appended in ONE place (`h-fix-mcp-agent-facing-text`, SC-1).
|
|
71
|
+
*
|
|
72
|
+
* Editing 176 descriptions by hand would have put the same sentence in 176 places, drifting one
|
|
73
|
+
* tool at a time, and each copy would have had to know the transport it was registered on —
|
|
74
|
+
* which a description literal cannot. Here the classification in `authBoundary.ts` is read once,
|
|
75
|
+
* beside the connection that determines it. The signature is `server.tool`'s own, and only the
|
|
76
|
+
* description argument is touched: a registration whose second argument is not a string is passed
|
|
77
|
+
* through untouched rather than being coerced, because the SDK accepts several call shapes and a
|
|
78
|
+
* wrapper that assumes one would corrupt the others.
|
|
79
|
+
*/
|
|
80
|
+
const tool = ((...args) => {
|
|
81
|
+
const [name, description] = args;
|
|
82
|
+
if (typeof name === 'string' && typeof description === 'string') {
|
|
83
|
+
const rest = args.slice(2);
|
|
84
|
+
return bound(name, description + accessNoticeFor(name, access), ...rest);
|
|
85
|
+
}
|
|
86
|
+
return bound(...args);
|
|
87
|
+
});
|
|
88
|
+
registerVerifications(tool, http);
|
|
89
|
+
registerVerificationRequests(tool, http);
|
|
90
|
+
registerProofs(tool, http);
|
|
91
|
+
registerSessions(tool, http);
|
|
92
|
+
registerAssets(tool, http);
|
|
93
|
+
registerAuth(tool, http);
|
|
94
|
+
registerSettings(tool, http);
|
|
95
|
+
registerTemplates(tool, http);
|
|
96
|
+
registerWebhookDeliveries(tool, http);
|
|
97
|
+
registerBilling(tool, http);
|
|
98
|
+
registerProfiles(tool, http);
|
|
99
|
+
registerEmails(tool, http);
|
|
100
|
+
registerPhones(tool, http);
|
|
101
|
+
registerApiKeys(tool, http);
|
|
102
|
+
registerTwofa(tool, http);
|
|
103
|
+
registerDomains(tool, http);
|
|
104
|
+
registerDnsCredentials(tool, http);
|
|
105
|
+
registerUserRequests(tool, http);
|
|
106
|
+
registerUserDomainVerify(tool, http);
|
|
107
|
+
registerPublicProfiles(tool, http);
|
|
108
|
+
registerAuthorizations(tool, http);
|
|
109
|
+
registerConfirmations(tool, http);
|
|
110
|
+
registerHitl(tool, http);
|
|
111
|
+
registerHitlKeys(tool, http);
|
|
112
|
+
registerProofMe(tool, http);
|
|
113
|
+
registerCircles(tool, http);
|
|
114
|
+
registerDelegations(tool, http);
|
|
115
|
+
// Takes the base URL rather than the HTTP client: verifying a third party's delegation runs
|
|
116
|
+
// against PUBLIC surfaces only and carries no API key.
|
|
117
|
+
registerDelegationVerify(tool, { baseUrl: options.baseUrl });
|
|
118
|
+
registerAuthFlows(tool, http);
|
|
119
|
+
registerAccounts(tool, http);
|
|
120
|
+
registerRenderAuthLink(tool);
|
|
121
|
+
return { server, http };
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
CHANGED
|
@@ -1,26 +1,86 @@
|
|
|
1
1
|
export interface HttpClientConfig {
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* API key (Bearer). OPTIONAL — the server also runs in public mode, where the bootstrap
|
|
4
|
+
* and verification surfaces work with no credential at all and every other tool answers
|
|
5
|
+
* `api_key_required` instead of the process exiting at startup.
|
|
6
|
+
*/
|
|
7
|
+
apiKey?: string;
|
|
3
8
|
baseUrl: string;
|
|
4
9
|
timeout?: number;
|
|
5
10
|
maxRetries?: number;
|
|
11
|
+
/**
|
|
12
|
+
* True when this client serves the REMOTE transport (`src/remote.ts`) rather than a local stdio
|
|
13
|
+
* process. It changes exactly one thing — what the "you need a key" refusal tells the user to do
|
|
14
|
+
* — because the stdio instruction (set `PROOF_API_KEY`, restart the server) is impossible on a
|
|
15
|
+
* server somebody else runs.
|
|
16
|
+
*/
|
|
17
|
+
remote?: boolean;
|
|
6
18
|
}
|
|
7
19
|
type QueryValue = string | number | boolean | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Endpoints the client may call with NO credential — the public mode surface.
|
|
22
|
+
*
|
|
23
|
+
* Stored as (method, prefix) PAIRS, not bare prefixes, and the reason is load-bearing:
|
|
24
|
+
* `/api/v1/auth/sessions` is the prefix of two public routes (create a login session, poll one)
|
|
25
|
+
* AND of two `jwt+api_key` ones (`GET /auth/sessions` lists sessions, `DELETE /auth/sessions/:id`
|
|
26
|
+
* revokes one). A prefix-only list would mark those two public, so a keyless call would travel to
|
|
27
|
+
* the network and come back `invalid_token` instead of getting the structured hint here.
|
|
28
|
+
*
|
|
29
|
+
* The drift rule `validateMcpPublicPathCoverage` (scripts/validate-api-map.ts) requires every
|
|
30
|
+
* `auth: public` + `mcp: available` endpoint in docs/api-map.yaml to appear below. That rule is a
|
|
31
|
+
* LOWER BOUND, not the definition: the two poll routes are carried here but not required by it,
|
|
32
|
+
* because the map holds them `planned` (they are reached through `wait_for_login` /
|
|
33
|
+
* `wait_for_account_creation` rather than by a tool of their own). Removing them would leave both
|
|
34
|
+
* wait tools broken in public mode while every check stayed green.
|
|
35
|
+
*/
|
|
36
|
+
export declare const PUBLIC_PATHS: ReadonlyArray<{
|
|
37
|
+
method: string;
|
|
38
|
+
prefix: string;
|
|
39
|
+
}>;
|
|
8
40
|
export declare class HttpClient {
|
|
9
41
|
readonly baseUrl: string;
|
|
10
|
-
private readonly apiKey
|
|
42
|
+
private readonly apiKey?;
|
|
11
43
|
private readonly timeout;
|
|
12
44
|
private readonly maxRetries;
|
|
45
|
+
private readonly remote;
|
|
46
|
+
private sessionToken?;
|
|
47
|
+
private refreshToken?;
|
|
48
|
+
private refreshing?;
|
|
13
49
|
constructor(config: HttpClientConfig);
|
|
14
50
|
request<T>(method: string, path: string, options?: {
|
|
15
51
|
body?: unknown;
|
|
16
52
|
query?: Record<string, QueryValue>;
|
|
53
|
+
headers?: Record<string, string>;
|
|
54
|
+
/** Internal: set on the post-refresh retry to prevent an infinite refresh loop. */
|
|
55
|
+
_refreshed?: boolean;
|
|
17
56
|
}): Promise<T>;
|
|
57
|
+
/**
|
|
58
|
+
* True once a login flow has put a session on this client (`captureSetCookie`).
|
|
59
|
+
*
|
|
60
|
+
* Boolean only, never the token: the remote server needs to know whether this CONNECTION has
|
|
61
|
+
* been signed in — the bootstrap road `start_login` opens — and nothing else. Without it a caller
|
|
62
|
+
* with no API key looks identical before and after signing in, and the tools behind
|
|
63
|
+
* `SESSION_AUTH_PREFIXES` are refused to a user who just logged in.
|
|
64
|
+
*/
|
|
65
|
+
hasSessionToken(): boolean;
|
|
18
66
|
get<T>(path: string, query?: Record<string, QueryValue>): Promise<T>;
|
|
19
67
|
post<T>(path: string, body?: unknown): Promise<T>;
|
|
20
68
|
put<T>(path: string, body?: unknown): Promise<T>;
|
|
21
69
|
patch<T>(path: string, body?: unknown): Promise<T>;
|
|
22
|
-
del<T>(path: string): Promise<T>;
|
|
70
|
+
del<T>(path: string, body?: unknown): Promise<T>;
|
|
23
71
|
private buildUrl;
|
|
72
|
+
/** True for endpoints behind `jwtAuth` — the user JWT session surface. */
|
|
73
|
+
private usesSessionAuth;
|
|
74
|
+
/** Capture the `token` / `refresh_token` cookies the login + refresh flows set via Set-Cookie. */
|
|
75
|
+
private captureSetCookie;
|
|
76
|
+
/**
|
|
77
|
+
* Exchange the refresh token for a rotated access token. Returns true on success.
|
|
78
|
+
* Concurrent callers share one in-flight refresh so a single-use refresh token is
|
|
79
|
+
* only spent once (otherwise the loser would post a consumed token and wipe the
|
|
80
|
+
* freshly-rotated session).
|
|
81
|
+
*/
|
|
82
|
+
private refreshSession;
|
|
83
|
+
private doRefresh;
|
|
24
84
|
private backoff;
|
|
25
85
|
private sleep;
|
|
26
86
|
}
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,KAAK,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAwBxD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAO1E,CAAC;AAQF,qBAAa,UAAU;IACrB,SAAgB,OAAO,EAAE,MAAM,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,UAAU,CAAC,CAAmB;gBAE1B,MAAM,EAAE,gBAAgB;IAQ9B,OAAO,CAAC,CAAC,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,mFAAmF;QACnF,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,GACA,OAAO,CAAC,CAAC,CAAC;IA2Hb;;;;;;;OAOG;IACH,eAAe,IAAI,OAAO;IAI1B,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIpE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAIjD,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAIhD,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAIlD,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAIhD,OAAO,CAAC,QAAQ;IAYhB,0EAA0E;IAC1E,OAAO,CAAC,eAAe;IAIvB,kGAAkG;IAClG,OAAO,CAAC,gBAAgB;IAexB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;YASR,SAAS;IA6BvB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,KAAK;CAGd;AAED,qBAAa,QAAS,SAAQ,KAAK;aAGf,IAAI,EAAE,MAAM;aACZ,UAAU,EAAE,MAAM;aAClB,OAAO,CAAC,EAAE,OAAO;gBAHjC,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,OAAO,YAAA;CAMpC"}
|
package/dist/http.js
CHANGED
|
@@ -1,16 +1,103 @@
|
|
|
1
|
-
|
|
1
|
+
// The version this client sends in its own `User-Agent` (`proof-mcp/<version>`), i.e. what the
|
|
2
|
+
// issuer's logs attribute a request to. A LITERAL for the same reason `factory.ts`'s `serverInfo`
|
|
3
|
+
// one is, and safe for the same reason: the pin, not the choice. Held equal to `mcp/package.json`
|
|
4
|
+
// by `src/__tests__/drift/delegation-publication.test.ts` (h-npm-registry-release).
|
|
5
|
+
//
|
|
6
|
+
// Of the carriers a version bump has to move, this is the one it misses: a bare constant at the
|
|
7
|
+
// top of a module about HTTP, where nothing about editing the manifest leads a reader.
|
|
8
|
+
const VERSION = '1.1.0';
|
|
9
|
+
// Path prefixes protected by `jwtAuth` ONLY (pure JWT surface — these 401 with an API key).
|
|
10
|
+
// Deliberately excludes the dual-auth (`jwtOrApiKeyAuth`) endpoints that already work with the
|
|
11
|
+
// API key and enforce its scopes — `/me/{profiles,settings,usage,export,assets}`,
|
|
12
|
+
// `/profiles/me`, `/profiles/me/assets`, `/profiles/profiles/:id/proofs`, and `/auth/sessions`
|
|
13
|
+
// (list/revoke). Sending the JWT there would silently switch the principal from the API-key
|
|
14
|
+
// tenant to the logged-in user and bypass scope checks. The two `/profiles/me/*` entries
|
|
15
|
+
// below are the jwt-only exceptions inside that otherwise dual-auth family.
|
|
16
|
+
const SESSION_AUTH_PREFIXES = [
|
|
17
|
+
'/api/v1/me/emails',
|
|
18
|
+
'/api/v1/me/phones',
|
|
19
|
+
'/api/v1/me/api-keys',
|
|
20
|
+
'/api/v1/me/2fa',
|
|
21
|
+
'/api/v1/me/domains',
|
|
22
|
+
'/api/v1/me/dns-credentials',
|
|
23
|
+
'/api/v1/me/dns-providers',
|
|
24
|
+
'/api/v1/me/verification-requests',
|
|
25
|
+
'/api/v1/me/verify/',
|
|
26
|
+
'/api/v1/me/account',
|
|
27
|
+
'/api/v1/profiles/me/username',
|
|
28
|
+
'/api/v1/profiles/me/proofs',
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Endpoints the client may call with NO credential — the public mode surface.
|
|
32
|
+
*
|
|
33
|
+
* Stored as (method, prefix) PAIRS, not bare prefixes, and the reason is load-bearing:
|
|
34
|
+
* `/api/v1/auth/sessions` is the prefix of two public routes (create a login session, poll one)
|
|
35
|
+
* AND of two `jwt+api_key` ones (`GET /auth/sessions` lists sessions, `DELETE /auth/sessions/:id`
|
|
36
|
+
* revokes one). A prefix-only list would mark those two public, so a keyless call would travel to
|
|
37
|
+
* the network and come back `invalid_token` instead of getting the structured hint here.
|
|
38
|
+
*
|
|
39
|
+
* The drift rule `validateMcpPublicPathCoverage` (scripts/validate-api-map.ts) requires every
|
|
40
|
+
* `auth: public` + `mcp: available` endpoint in docs/api-map.yaml to appear below. That rule is a
|
|
41
|
+
* LOWER BOUND, not the definition: the two poll routes are carried here but not required by it,
|
|
42
|
+
* because the map holds them `planned` (they are reached through `wait_for_login` /
|
|
43
|
+
* `wait_for_account_creation` rather than by a tool of their own). Removing them would leave both
|
|
44
|
+
* wait tools broken in public mode while every check stayed green.
|
|
45
|
+
*/
|
|
46
|
+
export const PUBLIC_PATHS = [
|
|
47
|
+
{ method: 'POST', prefix: '/api/v1/proofs/validate' },
|
|
48
|
+
{ method: 'GET', prefix: '/api/v1/proofs/revoked' },
|
|
49
|
+
{ method: 'POST', prefix: '/api/v1/auth/sessions' },
|
|
50
|
+
{ method: 'GET', prefix: '/api/v1/auth/sessions/' },
|
|
51
|
+
{ method: 'POST', prefix: '/api/v1/accounts' },
|
|
52
|
+
{ method: 'GET', prefix: '/api/v1/accounts/' },
|
|
53
|
+
];
|
|
54
|
+
/** True when (method, path) is on the public surface above. Method is compared case-insensitively. */
|
|
55
|
+
function isPublicPath(method, path) {
|
|
56
|
+
const verb = method.toUpperCase();
|
|
57
|
+
return PUBLIC_PATHS.some((p) => p.method === verb && path.startsWith(p.prefix));
|
|
58
|
+
}
|
|
2
59
|
export class HttpClient {
|
|
3
60
|
baseUrl;
|
|
4
61
|
apiKey;
|
|
5
62
|
timeout;
|
|
6
63
|
maxRetries;
|
|
64
|
+
remote;
|
|
65
|
+
sessionToken;
|
|
66
|
+
refreshToken;
|
|
67
|
+
refreshing;
|
|
7
68
|
constructor(config) {
|
|
8
69
|
this.apiKey = config.apiKey;
|
|
9
70
|
this.baseUrl = config.baseUrl.replace(/\/$/, '');
|
|
10
71
|
this.timeout = config.timeout ?? 30_000;
|
|
11
72
|
this.maxRetries = config.maxRetries ?? 2;
|
|
73
|
+
this.remote = config.remote ?? false;
|
|
12
74
|
}
|
|
13
75
|
async request(method, path, options) {
|
|
76
|
+
const useSession = this.usesSessionAuth(path) && !!this.sessionToken;
|
|
77
|
+
const bearer = useSession ? this.sessionToken : this.apiKey;
|
|
78
|
+
// Public mode: answer before the network rather than after it. A credential-less call to a
|
|
79
|
+
// keyed route would otherwise spend a round trip to return `invalid_token`, which reads to an
|
|
80
|
+
// agent as "the key is wrong" rather than "there is no key yet".
|
|
81
|
+
if (!bearer && !isPublicPath(method, path)) {
|
|
82
|
+
// The wording matters as much as the code, and it has to be true for the transport the caller
|
|
83
|
+
// is actually on. It once said "run create_account to get a key from inside this session",
|
|
84
|
+
// which an agent can follow forever without succeeding on stdio: the key is read from the
|
|
85
|
+
// environment and has no runtime setter. But the fix was written for stdio alone, and on the
|
|
86
|
+
// REMOTE server the resulting instruction is not merely unhelpful — it is impossible, since
|
|
87
|
+
// nobody can set an env var on somebody else's pod or restart it. A signed-in remote user
|
|
88
|
+
// calling a tool outside the session surface is exactly who reads this.
|
|
89
|
+
throw new ApiError(this.remote
|
|
90
|
+
? 'This operation needs a Proof account with an API key. Connect this server to your account ' +
|
|
91
|
+
'(your client\'s sign-in / "Authenticate" step), then call the tool again. Signing in with ' +
|
|
92
|
+
'start_login alone covers your own contact and account records — emails, phones, domains, ' +
|
|
93
|
+
'API keys, 2FA, DNS credentials, verification requests, your public profile\'s username ' +
|
|
94
|
+
'and proof list; account-wide reads such as the platform summary, settings and usage ' +
|
|
95
|
+
'need the key that connecting grants.'
|
|
96
|
+
: 'This operation needs a Proof API key, which must be present in PROOF_API_KEY when the server starts. ' +
|
|
97
|
+
'Without one you can still create an account or sign in (create_account, start_login) and check ' +
|
|
98
|
+
'proofs and delegations. If you are signed in, create_api_key works over that session — but the new ' +
|
|
99
|
+
'key takes effect only after it is put in PROOF_API_KEY and this MCP server is restarted.', 'api_key_required', 401);
|
|
100
|
+
}
|
|
14
101
|
const url = this.buildUrl(path, options?.query);
|
|
15
102
|
let lastError;
|
|
16
103
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
@@ -20,14 +107,17 @@ export class HttpClient {
|
|
|
20
107
|
const response = await fetch(url, {
|
|
21
108
|
method,
|
|
22
109
|
headers: {
|
|
110
|
+
...options?.headers,
|
|
23
111
|
'Content-Type': 'application/json',
|
|
24
|
-
'Authorization': `Bearer ${
|
|
112
|
+
...(bearer ? { 'Authorization': `Bearer ${bearer}` } : {}),
|
|
25
113
|
'User-Agent': `proof-mcp/${VERSION}`,
|
|
26
114
|
},
|
|
27
115
|
body: options?.body ? JSON.stringify(options.body) : undefined,
|
|
28
116
|
signal: controller.signal,
|
|
29
117
|
});
|
|
30
118
|
clearTimeout(timeoutId);
|
|
119
|
+
// Capture the JWT session cookie set by the login / account-creation flows
|
|
120
|
+
this.captureSetCookie(response);
|
|
31
121
|
if (response.status === 429 && attempt < this.maxRetries) {
|
|
32
122
|
const retryAfter = response.headers.get('Retry-After');
|
|
33
123
|
const parsed = retryAfter ? parseInt(retryAfter, 10) : NaN;
|
|
@@ -44,6 +134,15 @@ export class HttpClient {
|
|
|
44
134
|
const apiError = body.error;
|
|
45
135
|
const code = apiError?.code ?? `http_${response.status}`;
|
|
46
136
|
const message = apiError?.message ?? `Request failed with status ${response.status}`;
|
|
137
|
+
// Expired session JWT: refresh once and retry the original request.
|
|
138
|
+
if (response.status === 401 &&
|
|
139
|
+
code === 'token_expired' &&
|
|
140
|
+
useSession &&
|
|
141
|
+
!options?._refreshed &&
|
|
142
|
+
this.refreshToken &&
|
|
143
|
+
(await this.refreshSession())) {
|
|
144
|
+
return this.request(method, path, { ...options, _refreshed: true });
|
|
145
|
+
}
|
|
47
146
|
throw new ApiError(message, code, response.status, apiError?.details);
|
|
48
147
|
}
|
|
49
148
|
return body;
|
|
@@ -68,6 +167,17 @@ export class HttpClient {
|
|
|
68
167
|
}
|
|
69
168
|
throw new ApiError(lastError?.message ?? 'Network request failed', 'network_error', 0);
|
|
70
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* True once a login flow has put a session on this client (`captureSetCookie`).
|
|
172
|
+
*
|
|
173
|
+
* Boolean only, never the token: the remote server needs to know whether this CONNECTION has
|
|
174
|
+
* been signed in — the bootstrap road `start_login` opens — and nothing else. Without it a caller
|
|
175
|
+
* with no API key looks identical before and after signing in, and the tools behind
|
|
176
|
+
* `SESSION_AUTH_PREFIXES` are refused to a user who just logged in.
|
|
177
|
+
*/
|
|
178
|
+
hasSessionToken() {
|
|
179
|
+
return !!this.sessionToken;
|
|
180
|
+
}
|
|
71
181
|
get(path, query) {
|
|
72
182
|
return this.request('GET', path, { query });
|
|
73
183
|
}
|
|
@@ -80,8 +190,8 @@ export class HttpClient {
|
|
|
80
190
|
patch(path, body) {
|
|
81
191
|
return this.request('PATCH', path, { body });
|
|
82
192
|
}
|
|
83
|
-
del(path) {
|
|
84
|
-
return this.request('DELETE', path);
|
|
193
|
+
del(path, body) {
|
|
194
|
+
return this.request('DELETE', path, body ? { body } : undefined);
|
|
85
195
|
}
|
|
86
196
|
buildUrl(path, query) {
|
|
87
197
|
const url = new URL(path, this.baseUrl);
|
|
@@ -94,6 +204,75 @@ export class HttpClient {
|
|
|
94
204
|
}
|
|
95
205
|
return url.toString();
|
|
96
206
|
}
|
|
207
|
+
/** True for endpoints behind `jwtAuth` — the user JWT session surface. */
|
|
208
|
+
usesSessionAuth(path) {
|
|
209
|
+
return SESSION_AUTH_PREFIXES.some((prefix) => path.startsWith(prefix)) || path === '/api/v1/auth/me';
|
|
210
|
+
}
|
|
211
|
+
/** Capture the `token` / `refresh_token` cookies the login + refresh flows set via Set-Cookie. */
|
|
212
|
+
captureSetCookie(response) {
|
|
213
|
+
// getSetCookie() requires Node >= 18.14 / undici; guard so older runtimes don't throw.
|
|
214
|
+
if (typeof response.headers.getSetCookie !== 'function')
|
|
215
|
+
return;
|
|
216
|
+
const cookies = response.headers.getSetCookie();
|
|
217
|
+
for (const cookie of cookies) {
|
|
218
|
+
const pair = cookie.split(';', 1)[0];
|
|
219
|
+
const eq = pair.indexOf('=');
|
|
220
|
+
if (eq === -1)
|
|
221
|
+
continue;
|
|
222
|
+
const name = pair.slice(0, eq).trim();
|
|
223
|
+
const value = pair.slice(eq + 1).trim();
|
|
224
|
+
if (name === 'token')
|
|
225
|
+
this.sessionToken = value;
|
|
226
|
+
else if (name === 'refresh_token')
|
|
227
|
+
this.refreshToken = value;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Exchange the refresh token for a rotated access token. Returns true on success.
|
|
232
|
+
* Concurrent callers share one in-flight refresh so a single-use refresh token is
|
|
233
|
+
* only spent once (otherwise the loser would post a consumed token and wipe the
|
|
234
|
+
* freshly-rotated session).
|
|
235
|
+
*/
|
|
236
|
+
refreshSession() {
|
|
237
|
+
if (!this.refreshToken)
|
|
238
|
+
return Promise.resolve(false);
|
|
239
|
+
if (this.refreshing)
|
|
240
|
+
return this.refreshing;
|
|
241
|
+
this.refreshing = this.doRefresh().finally(() => {
|
|
242
|
+
this.refreshing = undefined;
|
|
243
|
+
});
|
|
244
|
+
return this.refreshing;
|
|
245
|
+
}
|
|
246
|
+
async doRefresh() {
|
|
247
|
+
const controller = new AbortController();
|
|
248
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
249
|
+
try {
|
|
250
|
+
const response = await fetch(this.buildUrl('/api/v1/auth/refresh'), {
|
|
251
|
+
method: 'POST',
|
|
252
|
+
headers: {
|
|
253
|
+
'Content-Type': 'application/json',
|
|
254
|
+
'Cookie': `refresh_token=${this.refreshToken}`,
|
|
255
|
+
'User-Agent': `proof-mcp/${VERSION}`,
|
|
256
|
+
},
|
|
257
|
+
signal: controller.signal,
|
|
258
|
+
});
|
|
259
|
+
if (!response.ok) {
|
|
260
|
+
this.sessionToken = undefined;
|
|
261
|
+
this.refreshToken = undefined;
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
this.captureSetCookie(response);
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
this.sessionToken = undefined;
|
|
269
|
+
this.refreshToken = undefined;
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
clearTimeout(timeoutId);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
97
276
|
backoff(attempt) {
|
|
98
277
|
return Math.min(1000 * Math.pow(2, attempt), 10000);
|
|
99
278
|
}
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/remote.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Remote entrypoint: the Proof MCP server over Streamable HTTP (`h-mcp-remote`, SC-3 + SC-4).
|
|
4
|
+
*
|
|
5
|
+
* Deployed as its own service, NOT as a route inside the existing backend. The reason is
|
|
6
|
+
* structural rather than stylistic: `src/cluster.ts` forks one worker per CPU and round-robins
|
|
7
|
+
* between them, while a Streamable HTTP session lives in the memory of ONE process. Mounting this
|
|
8
|
+
* on the clustered backend would lose sessions by default, not in an edge case. The deployment
|
|
9
|
+
* therefore pins requests to a replica by the REPLICA PREFIX this server puts in every session id
|
|
10
|
+
* it mints (`<replica>.<uuid>`); see `replicaId` below for why hashing the id itself cannot work,
|
|
11
|
+
* and `k8s/mcp-remote/nginx-mcp.conf` for the routing that consumes the prefix. That routing is
|
|
12
|
+
* deliberately NOT in the base nginx configmap — see that directory's README for the ingress it
|
|
13
|
+
* would otherwise take down, and `src/__tests__/drift/mcp-remote-deployment.test.ts`, which asserts
|
|
14
|
+
* the base stays free of it.
|
|
15
|
+
*
|
|
16
|
+
* ANONYMOUS FIRST, KEYED ON REQUEST (`h-mcp-oauth-remote-wiring`). Every connection starts with the
|
|
17
|
+
* keyless surface and the ability to log in through the normal flows; a client that has been
|
|
18
|
+
* through the OAuth ceremony (`h-mcp-oauth-server`) presents the API key it was granted, and this
|
|
19
|
+
* server reads it from the `Authorization` header ONLY. A `?api_key=` query parameter is never
|
|
20
|
+
* read: a token in a URL is copied into proxy logs and `Referer`, and the standard clients all send
|
|
21
|
+
* a header. The token is resolved ONCE, at open, by self-inspection (`GET /api/v1/me/api-key`) —
|
|
22
|
+
* purely so the refusal is legible; nothing is cached from it, and the key travels to the backend
|
|
23
|
+
* on every call, which is what makes a revocation take effect immediately rather than at session
|
|
24
|
+
* TTL. Where the 401 lands is a measurement, not a preference: on the TOOL CALL that needs an
|
|
25
|
+
* account, because a 401 on `initialize` reached the human as a connection timeout and cost the
|
|
26
|
+
* anonymous connection entirely (`docs/mcp-oauth-client-probe.md`).
|
|
27
|
+
*
|
|
28
|
+
* Each connection needs its OWN `HttpClient` for two reasons now: `captureSetCookie` writes a login
|
|
29
|
+
* session onto the instance, and the API key of one connection must never be reachable from
|
|
30
|
+
* another. The boundary between keyless and keyed tools lives in `./authBoundary.js`.
|
|
31
|
+
*/
|
|
32
|
+
import { type Server } from 'node:http';
|
|
33
|
+
import { HttpClient } from './http.js';
|
|
34
|
+
export interface RemoteServerOptions {
|
|
35
|
+
baseUrl: string;
|
|
36
|
+
/** Maximum concurrent MCP sessions. Each one registers the full tool set, so this is a memory bound. */
|
|
37
|
+
maxSessions?: number;
|
|
38
|
+
/** Idle time after which a session is closed and dropped. */
|
|
39
|
+
sessionTtlMs?: number;
|
|
40
|
+
sweepIntervalMs?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Which replica this process is. It is PREFIXED to every session id it mints, and the proxy
|
|
43
|
+
* routes on that prefix.
|
|
44
|
+
*
|
|
45
|
+
* This is not decoration, it is the only thing that makes sticky routing possible. Hashing the
|
|
46
|
+
* session id — the obvious reading of "sticky by mcp-session-id" — cannot work: the replica is
|
|
47
|
+
* chosen when the connection OPENS, and at that moment no session id exists yet, so the hash of
|
|
48
|
+
* the id minted afterwards lands wherever it lands. Measured on a two-replica nginx stand, that
|
|
49
|
+
* arrangement sent 45 of 60 follow-up requests to a replica that had never heard of the session.
|
|
50
|
+
* Carrying the replica in the id turns the routing decision into a lookup instead of a guess.
|
|
51
|
+
*/
|
|
52
|
+
replicaId?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface RemoteServer {
|
|
55
|
+
httpServer: Server;
|
|
56
|
+
sessionCount(): number;
|
|
57
|
+
/** Test seam: the live per-session HTTP clients, to assert they are distinct instances. */
|
|
58
|
+
debugClients(): HttpClient[];
|
|
59
|
+
close(): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Ceiling on the burst, expressed FLEET-WIDE because the bucket it spends is fleet-wide.
|
|
63
|
+
*
|
|
64
|
+
* The burst below is sized off `maxSessions`, which is per replica — and the bucket it lands in is
|
|
65
|
+
* `ipRateLimit(300, 60, 'me')` keyed on the pod's egress IP, i.e. shared by every replica. So the
|
|
66
|
+
* figure that matters is `replicas × burst`, and stating the burst per replica beside a rate stated
|
|
67
|
+
* fleet-wide is how those two drift apart: at `replicas: 3` and the default cap, an unbounded burst
|
|
68
|
+
* is 300 — the entire minute's bucket, spent at exactly the moment those sessions also begin their
|
|
69
|
+
* ordinary `/me/*` calls through it. `src/__tests__/drift/mcp-remote-deployment.test.ts` derives
|
|
70
|
+
* `replicas × min(MCP_MAX_SESSIONS, RESOLVE_BURST_CEILING)` — the EFFECTIVE burst, not this
|
|
71
|
+
* constant alone — against the backend's own literal, so SCALING THE FLEET reddens it instead of
|
|
72
|
+
* surfacing as 429s in production. Raising THIS constant is caught elsewhere and deliberately so:
|
|
73
|
+
* while the manifest cap sits at or below the ceiling, the effective burst does not move, and the
|
|
74
|
+
* unit case in `mcp/__tests__/remote.test.ts` that runs a cap ABOVE the ceiling is what holds it.
|
|
75
|
+
*/
|
|
76
|
+
export declare const RESOLVE_BURST_CEILING = 100;
|
|
77
|
+
/**
|
|
78
|
+
* A token bucket over the self-inspection above. Process-local on purpose: what it protects is this
|
|
79
|
+
* process's own outbound channel, and a shared counter would need a store this service does not
|
|
80
|
+
* have and must not grow for a defensive bound.
|
|
81
|
+
*
|
|
82
|
+
* Exported for its own test. The arithmetic below has a failure mode no HTTP-level case can reach
|
|
83
|
+
* cheaply — a clock that steps BACKWARDS — and the previous version's clock seam was a default
|
|
84
|
+
* parameter on an unexported function, i.e. unreachable from a test even in principle. That is the
|
|
85
|
+
* same shape `positiveIntEnv` above was fixed for once already.
|
|
86
|
+
*/
|
|
87
|
+
export declare function createResolveBudget(maxSessions: number, now?: () => number): {
|
|
88
|
+
/** True when a self-inspection may be sent; consumes one token. */
|
|
89
|
+
take(): boolean;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* The replica's own index: `MCP_REPLICA_ID` if set, otherwise the ordinal a StatefulSet puts at the
|
|
93
|
+
* end of the pod hostname (`mcp-remote-1` → `1`). Falls back to `0` for a single process, which is
|
|
94
|
+
* what the stdio-style local run and the tests get.
|
|
95
|
+
*/
|
|
96
|
+
/**
|
|
97
|
+
* Reads a positive-integer setting, falling back to the built-in default on anything else.
|
|
98
|
+
*
|
|
99
|
+
* A typo must not disable the bound it configures. `Number('abc')` is NaN, and both
|
|
100
|
+
* `size >= NaN` and `lastSeen < NaN` are false — so an unparseable value would fail OPEN on exactly
|
|
101
|
+
* the two limits this server relies on, with the pod still passing every probe. INTEGER rather than
|
|
102
|
+
* merely finite-and-positive, because `MCP_SESSION_TTL_MS=0.5` would sweep every session on the
|
|
103
|
+
* first tick and `MCP_MAX_SESSIONS=0.5` would silently mean one.
|
|
104
|
+
*
|
|
105
|
+
* Exported and at module scope so it can be tested — the previous version was a closure inside the
|
|
106
|
+
* entrypoint guard, unreachable from a test even in principle, which left the fix one edit wide.
|
|
107
|
+
*/
|
|
108
|
+
export declare function positiveIntEnv(name: string, env?: NodeJS.ProcessEnv): number | undefined;
|
|
109
|
+
export declare function resolveReplicaId(explicit?: string, hostname?: string): string;
|
|
110
|
+
export declare function createRemoteServer(options: RemoteServerOptions): RemoteServer;
|
|
111
|
+
//# sourceMappingURL=remote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AAiBjG,OAAO,EAAY,UAAU,EAAE,MAAM,WAAW,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,wGAAwG;IACxG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,IAAI,MAAM,CAAC;IACvB,2FAA2F;IAC3F,YAAY,IAAI,UAAU,EAAE,CAAC;IAC7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA2RD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAkBzC;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,MAAgC;IAMhG,mEAAmE;YAC3D,OAAO;EAclB;AAoGD;;;;GAIG;AACH;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAarG;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAsB,GAAG,MAAM,CAI5F;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAwT7E"}
|