@runpod/mcp-server 4.8.0 → 4.9.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.
@@ -0,0 +1,58 @@
1
+ import { C as CredentialChecker } from './http-BaACCis6.cjs';
2
+ export { g as getBaseUrl, h as handleMcpRequest } from './http-BaACCis6.cjs';
3
+ import { IncomingMessage, ServerResponse } from 'node:http';
4
+ export { DIRECTORY_PATH } from './specgen/profile.cjs';
5
+ import '@modelcontextprotocol/sdk/types.js';
6
+ import './sink-Cm8lp16D.cjs';
7
+
8
+ interface VerifyResult$1 {
9
+ status: 'valid' | 'invalid' | 'unknown';
10
+ accountId?: string;
11
+ }
12
+ interface AlpIngestOptions {
13
+ /** Test seams. Production uses the shared checker + global fetch + env. */
14
+ verify?: (token: string) => Promise<VerifyResult$1>;
15
+ sinkFetch?: typeof fetch;
16
+ env?: Record<string, string | undefined>;
17
+ }
18
+ declare function handleAlpSubmit(req: IncomingMessage & {
19
+ body?: unknown;
20
+ }, res: ServerResponse & {
21
+ status?: (code: number) => {
22
+ json: (body: unknown) => void;
23
+ };
24
+ }, opts?: AlpIngestOptions): Promise<void>;
25
+
26
+ interface VerifyResult {
27
+ status: 'valid' | 'invalid' | 'unknown';
28
+ accountId?: string;
29
+ }
30
+ interface AlpReadOptions {
31
+ /** Test seams. Production uses the shared checker + global fetch + env. */
32
+ verify?: (token: string) => Promise<VerifyResult>;
33
+ sinkFetch?: typeof fetch;
34
+ env?: Record<string, string | undefined>;
35
+ }
36
+ declare function handleAlpJournalRead(req: IncomingMessage & {
37
+ body?: unknown;
38
+ }, res: ServerResponse, opts?: AlpReadOptions): Promise<void>;
39
+
40
+ type Env = Record<string, string | undefined>;
41
+
42
+ interface SearchKnowledgeOptions {
43
+ /** Test seams. Production uses process.env, global fetch, and real timers. */
44
+ env?: Env;
45
+ fetch?: typeof fetch;
46
+ sleep?: (ms: number) => Promise<void>;
47
+ now?: () => number;
48
+ }
49
+
50
+ interface SearchRouteOptions extends SearchKnowledgeOptions {
51
+ /** Test seam. Production uses the shared credential checker. */
52
+ verify?: CredentialChecker;
53
+ }
54
+ declare function handleSearch(req: IncomingMessage & {
55
+ body?: unknown;
56
+ }, res: ServerResponse, opts?: SearchRouteOptions): Promise<void>;
57
+
58
+ export { handleAlpJournalRead, handleAlpSubmit, handleSearch };
@@ -0,0 +1,58 @@
1
+ import { C as CredentialChecker } from './http-BcFDW7LG.js';
2
+ export { g as getBaseUrl, h as handleMcpRequest } from './http-BcFDW7LG.js';
3
+ import { IncomingMessage, ServerResponse } from 'node:http';
4
+ export { DIRECTORY_PATH } from './specgen/profile.js';
5
+ import '@modelcontextprotocol/sdk/types.js';
6
+ import './sink-Cm8lp16D.js';
7
+
8
+ interface VerifyResult$1 {
9
+ status: 'valid' | 'invalid' | 'unknown';
10
+ accountId?: string;
11
+ }
12
+ interface AlpIngestOptions {
13
+ /** Test seams. Production uses the shared checker + global fetch + env. */
14
+ verify?: (token: string) => Promise<VerifyResult$1>;
15
+ sinkFetch?: typeof fetch;
16
+ env?: Record<string, string | undefined>;
17
+ }
18
+ declare function handleAlpSubmit(req: IncomingMessage & {
19
+ body?: unknown;
20
+ }, res: ServerResponse & {
21
+ status?: (code: number) => {
22
+ json: (body: unknown) => void;
23
+ };
24
+ }, opts?: AlpIngestOptions): Promise<void>;
25
+
26
+ interface VerifyResult {
27
+ status: 'valid' | 'invalid' | 'unknown';
28
+ accountId?: string;
29
+ }
30
+ interface AlpReadOptions {
31
+ /** Test seams. Production uses the shared checker + global fetch + env. */
32
+ verify?: (token: string) => Promise<VerifyResult>;
33
+ sinkFetch?: typeof fetch;
34
+ env?: Record<string, string | undefined>;
35
+ }
36
+ declare function handleAlpJournalRead(req: IncomingMessage & {
37
+ body?: unknown;
38
+ }, res: ServerResponse, opts?: AlpReadOptions): Promise<void>;
39
+
40
+ type Env = Record<string, string | undefined>;
41
+
42
+ interface SearchKnowledgeOptions {
43
+ /** Test seams. Production uses process.env, global fetch, and real timers. */
44
+ env?: Env;
45
+ fetch?: typeof fetch;
46
+ sleep?: (ms: number) => Promise<void>;
47
+ now?: () => number;
48
+ }
49
+
50
+ interface SearchRouteOptions extends SearchKnowledgeOptions {
51
+ /** Test seam. Production uses the shared credential checker. */
52
+ verify?: CredentialChecker;
53
+ }
54
+ declare function handleSearch(req: IncomingMessage & {
55
+ body?: unknown;
56
+ }, res: ServerResponse, opts?: SearchRouteOptions): Promise<void>;
57
+
58
+ export { handleAlpJournalRead, handleAlpSubmit, handleSearch };
@@ -0,0 +1,241 @@
1
+ import {
2
+ SCRUB_VERSION,
3
+ scrubSubmission
4
+ } from "./chunk-H3NZYQIB.mjs";
5
+ import {
6
+ defaultCredentialChecker,
7
+ extractBearerToken,
8
+ getBaseUrl,
9
+ handleAlpJournalRead,
10
+ handleMcpRequest,
11
+ parseSearchRequest,
12
+ searchKnowledge
13
+ } from "./chunk-VKLWTK4O.mjs";
14
+ import {
15
+ ALP_REFERRAL_SOURCES,
16
+ ALP_REFERRAL_USE_CASES,
17
+ ALP_ROUTES,
18
+ ALREADY_SUBMITTED_NOTE,
19
+ isSinkUrl
20
+ } from "./chunk-577IYCJT.mjs";
21
+ import {
22
+ DIRECTORY_PATH
23
+ } from "./chunk-6IETGSON.mjs";
24
+
25
+ // src/alp/ingest.ts
26
+ var MAX_CONTENT_CHARS = 2e4;
27
+ var MAX_REFERRAL_CONTENT_CHARS = 200;
28
+ var MAX_FIELD_CHARS = 500;
29
+ var SINK_TIMEOUT_MS = 5e3;
30
+ var ALP_SEVERITIES = ["blocked", "degraded", "cosmetic"];
31
+ function notRecorded(reason) {
32
+ return {
33
+ recorded: false,
34
+ note: `Not recorded (${reason}). Do not retry \u2014 continue your task.`
35
+ };
36
+ }
37
+ async function handleAlpSubmit(req, res, opts = {}) {
38
+ const env = opts.env ?? process.env;
39
+ const send = (code, body2) => {
40
+ res.writeHead(code, { "Content-Type": "application/json" });
41
+ res.end(JSON.stringify(body2));
42
+ };
43
+ const auth = req.headers.authorization;
44
+ const token = auth?.startsWith("Bearer ") ? auth.slice(7).trim() : void 0;
45
+ if (!token) {
46
+ send(401, {
47
+ recorded: false,
48
+ error: "Authenticate with your Runpod API key as a Bearer token."
49
+ });
50
+ return;
51
+ }
52
+ const body = req.body && typeof req.body === "object" ? req.body : {};
53
+ const content = typeof body.content === "string" ? body.content : "";
54
+ const referral = body.route === "referral";
55
+ const rawNoContext = body.noContext;
56
+ const noContextAnswer = referral && (rawNoContext === true || rawNoContext === 1 || typeof rawNoContext === "string" && rawNoContext.trim().toLowerCase() === "true");
57
+ const useCase = referral && !noContextAnswer && ALP_REFERRAL_USE_CASES.includes(body.useCase) ? body.useCase : void 0;
58
+ if (!ALP_ROUTES.includes(body.route) || content.trim().length === 0 && !noContextAnswer && !useCase) {
59
+ send(400, {
60
+ recorded: false,
61
+ error: `Body must carry route (${ALP_ROUTES.join("|")}) and a non-empty content string (or, on referral, a useCase or noContext true).`
62
+ });
63
+ return;
64
+ }
65
+ const verify = opts.verify ?? defaultCredentialChecker.verify;
66
+ const verdict = await verify(token);
67
+ if (verdict.status === "invalid") {
68
+ send(401, {
69
+ recorded: false,
70
+ error: "This Runpod API key is not valid. Re-authenticate and retry once."
71
+ });
72
+ return;
73
+ }
74
+ if (verdict.status !== "valid" || !verdict.accountId) {
75
+ send(200, notRecorded("identity could not be resolved right now"));
76
+ return;
77
+ }
78
+ const sinkUrl = env.ALP_SINK_URL;
79
+ const sinkSecret = env.ALP_SINK_SECRET;
80
+ if (!sinkUrl || !sinkSecret) {
81
+ send(200, notRecorded("ingest is not configured on this deployment"));
82
+ return;
83
+ }
84
+ if (!isSinkUrl(sinkUrl)) {
85
+ console.warn("alp_sink_misconfigured");
86
+ send(200, notRecorded("ingest is misconfigured on this deployment"));
87
+ return;
88
+ }
89
+ const clean = scrubSubmission({
90
+ content,
91
+ // Feedback/journal/question fields are not stored on the referral route
92
+ // even if a client sends them: the tool does not offer them, and the
93
+ // route's 200-character cap would mean nothing if a transcript could
94
+ // ride in `trigger` or `workaround` under the general cap instead.
95
+ intention: !referral && typeof body.intention === "string" ? body.intention : void 0,
96
+ modelType: typeof body.modelType === "string" ? body.modelType : void 0,
97
+ // Drop an off-enum severity rather than storing it. The whole value of the
98
+ // field is that it can be sorted and counted; one free-text variant
99
+ // ("critical", "high") turns a dimension back into prose, and the loss of
100
+ // one label costs less than that.
101
+ severity: !referral && ALP_SEVERITIES.includes(body.severity) ? body.severity : void 0,
102
+ tool: !referral && typeof body.tool === "string" ? body.tool : void 0,
103
+ workaround: !referral && typeof body.workaround === "string" ? body.workaround : void 0,
104
+ trigger: !referral && typeof body.trigger === "string" ? body.trigger : void 0,
105
+ // Referral-only fields. source follows the severity rule (off-enum is
106
+ // dropped, not stored as prose) and is dropped on a no-context answer,
107
+ // which by definition has no source. noContext is always a boolean on the
108
+ // route so the share of answers with real context is one filter.
109
+ source: referral && !noContextAnswer && ALP_REFERRAL_SOURCES.includes(body.source) ? body.source : void 0,
110
+ noContext: referral ? noContextAnswer : void 0,
111
+ useCase,
112
+ harness: typeof body.harness === "string" ? body.harness : void 0,
113
+ harnessSource: typeof body.harnessSource === "string" ? body.harnessSource : void 0,
114
+ transport: body.transport === "http" || body.transport === "stdio" ? body.transport : void 0,
115
+ redactions: 0,
116
+ scrubVersion: SCRUB_VERSION
117
+ });
118
+ const row = {
119
+ ...clean,
120
+ route: body.route,
121
+ content: clean.content.slice(
122
+ 0,
123
+ referral ? MAX_REFERRAL_CONTENT_CHARS : MAX_CONTENT_CHARS
124
+ ),
125
+ intention: clean.intention?.slice(0, MAX_FIELD_CHARS),
126
+ modelType: clean.modelType?.slice(0, MAX_FIELD_CHARS),
127
+ severity: clean.severity,
128
+ tool: clean.tool?.slice(0, MAX_FIELD_CHARS),
129
+ // Prose fields, so they get the content cap rather than the field cap: a
130
+ // workaround worth reading is a paragraph, not a label.
131
+ workaround: clean.workaround?.slice(0, MAX_CONTENT_CHARS),
132
+ trigger: clean.trigger?.slice(0, MAX_CONTENT_CHARS),
133
+ source: clean.source,
134
+ noContext: clean.noContext,
135
+ useCase: clean.useCase,
136
+ harness: clean.harness?.slice(0, MAX_FIELD_CHARS),
137
+ harnessSource: clean.harnessSource?.slice(0, MAX_FIELD_CHARS),
138
+ transport: clean.transport?.slice(0, MAX_FIELD_CHARS),
139
+ identity: verdict.accountId,
140
+ receivedAt: (/* @__PURE__ */ new Date()).toISOString()
141
+ };
142
+ let storedId;
143
+ try {
144
+ const sinkFetch = opts.sinkFetch ?? fetch;
145
+ const response = await sinkFetch(sinkUrl, {
146
+ method: "POST",
147
+ headers: {
148
+ "Content-Type": "application/json",
149
+ "X-ALP-Secret": sinkSecret
150
+ },
151
+ body: JSON.stringify(row),
152
+ signal: AbortSignal.timeout(SINK_TIMEOUT_MS)
153
+ });
154
+ if (!response.ok) {
155
+ console.warn("alp_sink_error", { status: response.status });
156
+ send(200, notRecorded("the store did not accept the write"));
157
+ return;
158
+ }
159
+ const sinkBody = await response.json().catch(() => null);
160
+ if (sinkBody?.ok !== true || typeof sinkBody.id !== "string") {
161
+ console.warn("alp_sink_unrecognized", { status: response.status });
162
+ send(200, notRecorded("the store did not confirm the write"));
163
+ return;
164
+ }
165
+ if (sinkBody.duplicate === true) {
166
+ console.log(
167
+ "alp_submit_duplicate",
168
+ JSON.stringify({
169
+ route: body.route,
170
+ transport: row.transport,
171
+ row: sinkBody.id
172
+ })
173
+ );
174
+ send(200, {
175
+ recorded: false,
176
+ reason: "already_submitted",
177
+ note: ALREADY_SUBMITTED_NOTE
178
+ });
179
+ return;
180
+ }
181
+ storedId = sinkBody.id;
182
+ } catch {
183
+ console.warn("alp_sink_unreachable");
184
+ send(200, notRecorded("the store is unreachable right now"));
185
+ return;
186
+ }
187
+ console.log(
188
+ "alp_submit",
189
+ JSON.stringify({
190
+ route: body.route,
191
+ transport: row.transport,
192
+ redactions: row.redactions,
193
+ row: storedId
194
+ })
195
+ );
196
+ send(200, { recorded: true });
197
+ }
198
+
199
+ // src/search/route.ts
200
+ async function handleSearch(req, res, opts = {}) {
201
+ const send = (code, body2) => {
202
+ res.writeHead(code, { "Content-Type": "application/json" });
203
+ res.end(JSON.stringify(body2));
204
+ };
205
+ const body = req.body && typeof req.body === "object" && !Array.isArray(req.body) ? req.body : {};
206
+ const parsed = parseSearchRequest(body);
207
+ const token = extractBearerToken(req);
208
+ if (!token) {
209
+ send(401, {
210
+ error: "Authenticate with your Runpod API key as a Bearer token."
211
+ });
212
+ return;
213
+ }
214
+ if ("error" in parsed) {
215
+ send(400, { error: parsed.error });
216
+ return;
217
+ }
218
+ const verdict = await (opts.verify ?? defaultCredentialChecker.verify)(token);
219
+ if (verdict.status === "invalid") {
220
+ send(401, { error: "This Runpod API key is not valid." });
221
+ return;
222
+ }
223
+ if (verdict.status !== "valid") {
224
+ send(503, {
225
+ error: "Could not verify your Runpod API key right now.",
226
+ hint: "Retry the same call in a few seconds."
227
+ });
228
+ return;
229
+ }
230
+ const outcome = await searchKnowledge(parsed.request, opts);
231
+ send(outcome.status, outcome.body);
232
+ }
233
+ export {
234
+ DIRECTORY_PATH,
235
+ getBaseUrl,
236
+ handleAlpJournalRead,
237
+ handleAlpSubmit,
238
+ handleMcpRequest,
239
+ handleSearch
240
+ };
241
+ //# sourceMappingURL=hosted.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/alp/ingest.ts","../src/search/route.ts"],"sourcesContent":["// ALP ingest endpoint: POST /api/alp/submit (see docs/agent-learning-protocol.md).\n//\n// The shared write path for the four hosted ALP write tools.\n// Local stdio does not register these tools.\n//\n// Behavior per request: authenticate → resolve the durable identity (never\n// key on the API key; see the design doc) → courtesy scrub → forward to the\n// private sink with the shared server secret. The sink (a Convex HTTP action\n// in convex/ in this repo) owns storage, the authoritative scrub, and everything\n// downstream. When no sink is configured this endpoint answers honestly that\n// nothing was recorded — it never pretends.\n//\n// FAIL-SOFT CONTRACT: whatever goes wrong (no sink, sink down, identity\n// unresolvable), the response is a calm 200 with { recorded: false } and an\n// instruction not to retry. ALP is a side quest; it must never derail the\n// agent's actual task or invite retry loops. Only a missing/invalid\n// credential is a real 401 — that is actionable by re-authenticating.\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { defaultCredentialChecker } from '../http.js';\nimport { scrubSubmission, SCRUB_VERSION } from './scrub.js';\nimport {\n ALP_REFERRAL_SOURCES,\n ALP_REFERRAL_USE_CASES,\n ALP_ROUTES,\n type AlpRoute,\n ALREADY_SUBMITTED_NOTE,\n isSinkUrl,\n type AlpReferralSource,\n type AlpReferralUseCase,\n} from './sink.js';\n\nexport { ALP_ROUTES, type AlpRoute } from './sink.js';\n\nconst MAX_CONTENT_CHARS = 20_000;\n// The referral note is a short generic description in the agent's own words,\n// never the user's (CON-1762), so it gets a cap only a phrase fits in.\nexport const MAX_REFERRAL_CONTENT_CHARS = 200;\nconst MAX_FIELD_CHARS = 500;\nconst SINK_TIMEOUT_MS = 5_000;\n\nexport const ALP_SEVERITIES = ['blocked', 'degraded', 'cosmetic'] as const;\nexport type AlpSeverity = (typeof ALP_SEVERITIES)[number];\n\nexport interface AlpSubmitBody {\n route: AlpRoute;\n content: string;\n intention?: string;\n modelType?: string;\n /** Triage dimension, feedback route. Constrained so it stays sortable. */\n severity?: AlpSeverity;\n /** The tool the entry is about, as it appears in the agent's tool list. */\n tool?: string;\n /** Feedback route: what the agent did instead. Absent means fully blocked. */\n workaround?: string;\n /** Journal route: the situation in which to recall the entry. */\n trigger?: string;\n /** Referral route: why Runpod came up. Constrained so it stays countable. */\n source?: AlpReferralSource;\n /** Referral route: the kind of work that brought the user to Runpod. */\n useCase?: AlpReferralUseCase;\n /** Referral route: the agent had no context on how the user found Runpod. */\n noContext?: boolean;\n harness?: string;\n harnessSource?: 'client_info' | 'user_agent';\n transport?: 'stdio' | 'http';\n}\n\ninterface VerifyResult {\n status: 'valid' | 'invalid' | 'unknown';\n accountId?: string;\n}\n\nexport interface AlpIngestOptions {\n /** Test seams. Production uses the shared checker + global fetch + env. */\n verify?: (token: string) => Promise<VerifyResult>;\n sinkFetch?: typeof fetch;\n env?: Record<string, string | undefined>;\n}\n\nfunction notRecorded(reason: string): Record<string, unknown> {\n return {\n recorded: false,\n note: `Not recorded (${reason}). Do not retry — continue your task.`,\n };\n}\n\nexport async function handleAlpSubmit(\n req: IncomingMessage & { body?: unknown },\n res: ServerResponse & {\n status?: (code: number) => { json: (body: unknown) => void };\n },\n opts: AlpIngestOptions = {}\n): Promise<void> {\n const env = opts.env ?? process.env;\n const send = (code: number, body: unknown) => {\n res.writeHead(code, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n };\n\n const auth = req.headers.authorization;\n const token = auth?.startsWith('Bearer ') ? auth.slice(7).trim() : undefined;\n if (!token) {\n send(401, {\n recorded: false,\n error: 'Authenticate with your Runpod API key as a Bearer token.',\n });\n return;\n }\n\n const body = (\n req.body && typeof req.body === 'object' ? req.body : {}\n ) as Partial<AlpSubmitBody>;\n const content = typeof body.content === 'string' ? body.content : '';\n const referral = body.route === 'referral';\n // Referral route only: content is an optional note, so an answer with a\n // use case, or a no-context answer, is complete without it. A 400 there\n // would leave the account's one slot open and the ask repeating every\n // session. The flag is normalized leniently (true, 1,\n // any casing of \"true\") because the first answer closes the slot and a\n // sloppy no-context call must not be stored as a real one.\n const rawNoContext = body.noContext as unknown;\n const noContextAnswer =\n referral &&\n (rawNoContext === true ||\n rawNoContext === 1 ||\n (typeof rawNoContext === 'string' &&\n rawNoContext.trim().toLowerCase() === 'true'));\n const useCase =\n referral &&\n !noContextAnswer &&\n ALP_REFERRAL_USE_CASES.includes(body.useCase as AlpReferralUseCase)\n ? (body.useCase as AlpReferralUseCase)\n : undefined;\n if (\n !ALP_ROUTES.includes(body.route as AlpRoute) ||\n (content.trim().length === 0 && !noContextAnswer && !useCase)\n ) {\n send(400, {\n recorded: false,\n error: `Body must carry route (${ALP_ROUTES.join('|')}) and a non-empty content string (or, on referral, a useCase or noContext true).`,\n });\n return;\n }\n\n // Resolve the durable identity. A dead key is a real 401 (re-auth fixes\n // it); an unreachable auth backend is fail-soft (we cannot key the row).\n const verify = opts.verify ?? defaultCredentialChecker.verify;\n const verdict = await verify(token);\n if (verdict.status === 'invalid') {\n send(401, {\n recorded: false,\n error:\n 'This Runpod API key is not valid. Re-authenticate and retry once.',\n });\n return;\n }\n if (verdict.status !== 'valid' || !verdict.accountId) {\n send(200, notRecorded('identity could not be resolved right now'));\n return;\n }\n\n const sinkUrl = env.ALP_SINK_URL;\n const sinkSecret = env.ALP_SINK_SECRET;\n if (!sinkUrl || !sinkSecret) {\n send(200, notRecorded('ingest is not configured on this deployment'));\n return;\n }\n // Refuse a sink URL that is not shaped like the sink. The sink is always a\n // Convex HTTP action, so anything else is a config error, and a loud log\n // line beats inferring it from a table later. Note the limit of this check:\n // it cannot tell one Convex deployment from another, so it catches a\n // malformed or foreign URL, never a valid URL aimed at the wrong\n // environment. Separating the per-environment secrets is what covers that.\n if (!isSinkUrl(sinkUrl)) {\n console.warn('alp_sink_misconfigured');\n send(200, notRecorded('ingest is misconfigured on this deployment'));\n return;\n }\n\n // Scrub before truncation so cutting a token cannot hide its shape.\n const clean = scrubSubmission({\n content,\n // Feedback/journal/question fields are not stored on the referral route\n // even if a client sends them: the tool does not offer them, and the\n // route's 200-character cap would mean nothing if a transcript could\n // ride in `trigger` or `workaround` under the general cap instead.\n intention:\n !referral && typeof body.intention === 'string'\n ? body.intention\n : undefined,\n modelType: typeof body.modelType === 'string' ? body.modelType : undefined,\n // Drop an off-enum severity rather than storing it. The whole value of the\n // field is that it can be sorted and counted; one free-text variant\n // (\"critical\", \"high\") turns a dimension back into prose, and the loss of\n // one label costs less than that.\n severity:\n !referral && ALP_SEVERITIES.includes(body.severity as AlpSeverity)\n ? (body.severity as AlpSeverity)\n : undefined,\n tool: !referral && typeof body.tool === 'string' ? body.tool : undefined,\n workaround:\n !referral && typeof body.workaround === 'string'\n ? body.workaround\n : undefined,\n trigger:\n !referral && typeof body.trigger === 'string' ? body.trigger : undefined,\n // Referral-only fields. source follows the severity rule (off-enum is\n // dropped, not stored as prose) and is dropped on a no-context answer,\n // which by definition has no source. noContext is always a boolean on the\n // route so the share of answers with real context is one filter.\n source:\n referral &&\n !noContextAnswer &&\n ALP_REFERRAL_SOURCES.includes(body.source as AlpReferralSource)\n ? (body.source as AlpReferralSource)\n : undefined,\n noContext: referral ? noContextAnswer : undefined,\n useCase,\n harness: typeof body.harness === 'string' ? body.harness : undefined,\n harnessSource:\n typeof body.harnessSource === 'string' ? body.harnessSource : undefined,\n transport:\n body.transport === 'http' || body.transport === 'stdio'\n ? body.transport\n : undefined,\n redactions: 0,\n scrubVersion: SCRUB_VERSION,\n });\n const row = {\n ...clean,\n route: body.route,\n content: clean.content.slice(\n 0,\n referral ? MAX_REFERRAL_CONTENT_CHARS : MAX_CONTENT_CHARS\n ),\n intention: clean.intention?.slice(0, MAX_FIELD_CHARS),\n modelType: clean.modelType?.slice(0, MAX_FIELD_CHARS),\n severity: clean.severity,\n tool: clean.tool?.slice(0, MAX_FIELD_CHARS),\n // Prose fields, so they get the content cap rather than the field cap: a\n // workaround worth reading is a paragraph, not a label.\n workaround: clean.workaround?.slice(0, MAX_CONTENT_CHARS),\n trigger: clean.trigger?.slice(0, MAX_CONTENT_CHARS),\n source: clean.source,\n noContext: clean.noContext,\n useCase: clean.useCase,\n harness: clean.harness?.slice(0, MAX_FIELD_CHARS),\n harnessSource: clean.harnessSource?.slice(0, MAX_FIELD_CHARS),\n transport: clean.transport?.slice(0, MAX_FIELD_CHARS),\n identity: verdict.accountId,\n receivedAt: new Date().toISOString(),\n };\n\n // Awaited, not fire-and-forget: Vercel can freeze the function the moment\n // the response returns, so an unawaited write may silently vanish.\n let storedId: string;\n try {\n const sinkFetch = opts.sinkFetch ?? fetch;\n const response = await sinkFetch(sinkUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-ALP-Secret': sinkSecret,\n },\n body: JSON.stringify(row),\n signal: AbortSignal.timeout(SINK_TIMEOUT_MS),\n });\n if (!response.ok) {\n console.warn('alp_sink_error', { status: response.status });\n send(200, notRecorded('the store did not accept the write'));\n return;\n }\n // A 200 is not enough. Any misconfigured sink host answers 200 to a POST\n // (a static page, a redirect target, another app's root), and treating\n // that as success reports recorded: true while nothing is stored — a\n // silent data loss that no log line and no ack can distinguish from a\n // real write. This is defense in depth, not a fix for an observed\n // incident: require the sink's own contract, { ok: true, id }, so that\n // \"recorded\" can only mean a row exists.\n const sinkBody = (await response.json().catch(() => null)) as {\n ok?: boolean;\n id?: string;\n duplicate?: boolean;\n } | null;\n if (sinkBody?.ok !== true || typeof sinkBody.id !== 'string') {\n console.warn('alp_sink_unrecognized', { status: response.status });\n send(200, notRecorded('the store did not confirm the write'));\n return;\n }\n if (sinkBody.duplicate === true) {\n // A once-per-account route already holds this identity's answer; the\n // sink kept the existing row and stored nothing. Named as its own\n // reason rather than folded into the generic not-recorded note: that\n // note reads as a transient failure, and this must not invite a later\n // session to try again.\n console.log(\n 'alp_submit_duplicate',\n JSON.stringify({\n route: body.route,\n transport: row.transport,\n row: sinkBody.id,\n })\n );\n send(200, {\n recorded: false,\n reason: 'already_submitted',\n note: ALREADY_SUBMITTED_NOTE,\n });\n return;\n }\n storedId = sinkBody.id;\n } catch {\n console.warn('alp_sink_unreachable');\n send(200, notRecorded('the store is unreachable right now'));\n return;\n }\n\n // One log line, same privacy rules as tool_call: never the content.\n // `row` is the sink's own document id: the one field here that cannot exist\n // unless a write actually happened. Everything else was known before the\n // request went out, which is why the old log line looked healthy while\n // nothing was being stored.\n console.log(\n 'alp_submit',\n JSON.stringify({\n route: body.route,\n transport: row.transport,\n redactions: row.redactions,\n row: storedId,\n })\n );\n send(200, { recorded: true });\n}\n","// POST /api/search on the hosted server: the stdio package's path to\n// searchKnowledge (src/search/knowledge.ts), authenticated with the caller's\n// own Runpod API key. Kept apart from knowledge.ts so the stdio bundle, which\n// imports the tool, does not pull in the HTTP shell.\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { CredentialChecker } from '../_shared/credential-check.js';\nimport { defaultCredentialChecker, extractBearerToken } from '../http.js';\nimport {\n parseSearchRequest,\n searchKnowledge,\n type SearchKnowledgeOptions,\n} from './knowledge.js';\n\nexport interface SearchRouteOptions extends SearchKnowledgeOptions {\n /** Test seam. Production uses the shared credential checker. */\n verify?: CredentialChecker;\n}\n\nexport async function handleSearch(\n req: IncomingMessage & { body?: unknown },\n res: ServerResponse,\n opts: SearchRouteOptions = {}\n): Promise<void> {\n const send = (code: number, body: unknown) => {\n res.writeHead(code, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n };\n\n // Input first: it is free, and the credential probe is not.\n const body = (\n req.body && typeof req.body === 'object' && !Array.isArray(req.body)\n ? req.body\n : {}\n ) as Record<string, unknown>;\n const parsed = parseSearchRequest(body);\n\n const token = extractBearerToken(req);\n if (!token) {\n send(401, {\n error: 'Authenticate with your Runpod API key as a Bearer token.',\n });\n return;\n }\n if ('error' in parsed) {\n send(400, { error: parsed.error });\n return;\n }\n // Only a key the API confirms is served. Every call spends the service\n // key's search capacity, so an unreachable auth backend fails closed.\n const verdict = await (opts.verify ?? defaultCredentialChecker.verify)(token);\n if (verdict.status === 'invalid') {\n send(401, { error: 'This Runpod API key is not valid.' });\n return;\n }\n if (verdict.status !== 'valid') {\n send(503, {\n error: 'Could not verify your Runpod API key right now.',\n hint: 'Retry the same call in a few seconds.',\n });\n return;\n }\n const outcome = await searchKnowledge(parsed.request, opts);\n send(outcome.status, outcome.body);\n}\n"],"mappings":"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","names":["body","body"]}
@@ -0,0 +1,75 @@
1
+ import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
2
+ import { h as OncePerAccountRoute } from './sink-Cm8lp16D.cjs';
3
+ import { ServerProfile } from './specgen/profile.cjs';
4
+ import { IncomingMessage, ServerResponse } from 'node:http';
5
+
6
+ type CredentialVerdict = {
7
+ status: 'valid';
8
+ accountId?: string;
9
+ } | {
10
+ status: 'invalid';
11
+ reason: string;
12
+ } | {
13
+ status: 'unknown';
14
+ };
15
+ type CredentialChecker = (token: string) => Promise<CredentialVerdict>;
16
+ interface CredentialCheckerHandle {
17
+ verify: CredentialChecker;
18
+ invalidate: (token: string) => void;
19
+ }
20
+
21
+ interface HandleMcpRequestOptions {
22
+ serverVersion?: string;
23
+ verifyCredential?: CredentialChecker;
24
+ invalidateCredential?: (token: string) => void;
25
+ /** Once-per-account lookup behind the referral ask (src/alp/answered.ts).
26
+ * Resolve, never reject: a rejection is treated as an unknown verdict. */
27
+ hasAnswered?: (identity: string, route: OncePerAccountRoute) => Promise<boolean | null>;
28
+ }
29
+ interface RpcSummary {
30
+ rpc?: string;
31
+ client?: string;
32
+ }
33
+
34
+ /**
35
+ * Extract the bearer token from an HTTP request's Authorization header.
36
+ * Returns null if the header is missing or malformed.
37
+ */
38
+ declare function extractBearerToken(req: IncomingMessage): string | null;
39
+ declare function getBaseUrl(req: {
40
+ headers: {
41
+ host?: string;
42
+ 'x-forwarded-proto'?: string | string[];
43
+ };
44
+ }): string;
45
+ declare function requestProfile(req: {
46
+ url?: string;
47
+ }): ServerProfile;
48
+ declare const defaultCredentialChecker: CredentialCheckerHandle;
49
+ declare function parseMcpMessages(body: unknown): JSONRPCMessage[];
50
+ declare function bodyIsCheckable(body: unknown): boolean;
51
+ declare function prefersJsonResponse(messages: JSONRPCMessage[]): boolean;
52
+ declare function requestLogSampleRate(env?: NodeJS.ProcessEnv): number;
53
+ declare function summarizeRpc(messages: JSONRPCMessage[]): RpcSummary;
54
+ /**
55
+ * Handle an incoming HTTP request as a streamable MCP session.
56
+ *
57
+ * Each request gets its own McpServer + transport. The caller's Bearer token
58
+ * is forwarded directly to the Runpod API as the credential — it may be a
59
+ * Runpod API key (set manually by the caller) or a token obtained through the
60
+ * OAuth "Sign in with Runpod" flow. The server holds no credential of its own
61
+ * and never shares one across users.
62
+ *
63
+ * The token is pre-flight verified (cached) before the MCP request is
64
+ * handled: a dead credential — e.g. an OAuth-minted key revoked mid-session —
65
+ * must surface as an HTTP 401 + WWW-Authenticate so
66
+ * OAuth-capable clients re-run their auth flow automatically. Without this,
67
+ * the upstream 401 is wrapped into a 200 JSON-RPC tool error and the client
68
+ * never re-authenticates. Set MCP_SKIP_CREDENTIAL_CHECK=true to disable.
69
+ *
70
+ * The gate also self-disables when the REST hosts are overridden without a
71
+ * matching RUNPOD_AUTHED_GRAPHQL_URL — see credentialCheckMayBeWrongEnvironment.
72
+ */
73
+ declare function handleMcpRequest(req: IncomingMessage, res: ServerResponse, opts?: HandleMcpRequestOptions): Promise<void>;
74
+
75
+ export { type CredentialChecker as C, prefersJsonResponse as a, bodyIsCheckable as b, requestLogSampleRate as c, defaultCredentialChecker as d, extractBearerToken as e, getBaseUrl as g, handleMcpRequest as h, parseMcpMessages as p, requestProfile as r, summarizeRpc as s };
@@ -0,0 +1,75 @@
1
+ import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
2
+ import { h as OncePerAccountRoute } from './sink-Cm8lp16D.js';
3
+ import { ServerProfile } from './specgen/profile.js';
4
+ import { IncomingMessage, ServerResponse } from 'node:http';
5
+
6
+ type CredentialVerdict = {
7
+ status: 'valid';
8
+ accountId?: string;
9
+ } | {
10
+ status: 'invalid';
11
+ reason: string;
12
+ } | {
13
+ status: 'unknown';
14
+ };
15
+ type CredentialChecker = (token: string) => Promise<CredentialVerdict>;
16
+ interface CredentialCheckerHandle {
17
+ verify: CredentialChecker;
18
+ invalidate: (token: string) => void;
19
+ }
20
+
21
+ interface HandleMcpRequestOptions {
22
+ serverVersion?: string;
23
+ verifyCredential?: CredentialChecker;
24
+ invalidateCredential?: (token: string) => void;
25
+ /** Once-per-account lookup behind the referral ask (src/alp/answered.ts).
26
+ * Resolve, never reject: a rejection is treated as an unknown verdict. */
27
+ hasAnswered?: (identity: string, route: OncePerAccountRoute) => Promise<boolean | null>;
28
+ }
29
+ interface RpcSummary {
30
+ rpc?: string;
31
+ client?: string;
32
+ }
33
+
34
+ /**
35
+ * Extract the bearer token from an HTTP request's Authorization header.
36
+ * Returns null if the header is missing or malformed.
37
+ */
38
+ declare function extractBearerToken(req: IncomingMessage): string | null;
39
+ declare function getBaseUrl(req: {
40
+ headers: {
41
+ host?: string;
42
+ 'x-forwarded-proto'?: string | string[];
43
+ };
44
+ }): string;
45
+ declare function requestProfile(req: {
46
+ url?: string;
47
+ }): ServerProfile;
48
+ declare const defaultCredentialChecker: CredentialCheckerHandle;
49
+ declare function parseMcpMessages(body: unknown): JSONRPCMessage[];
50
+ declare function bodyIsCheckable(body: unknown): boolean;
51
+ declare function prefersJsonResponse(messages: JSONRPCMessage[]): boolean;
52
+ declare function requestLogSampleRate(env?: NodeJS.ProcessEnv): number;
53
+ declare function summarizeRpc(messages: JSONRPCMessage[]): RpcSummary;
54
+ /**
55
+ * Handle an incoming HTTP request as a streamable MCP session.
56
+ *
57
+ * Each request gets its own McpServer + transport. The caller's Bearer token
58
+ * is forwarded directly to the Runpod API as the credential — it may be a
59
+ * Runpod API key (set manually by the caller) or a token obtained through the
60
+ * OAuth "Sign in with Runpod" flow. The server holds no credential of its own
61
+ * and never shares one across users.
62
+ *
63
+ * The token is pre-flight verified (cached) before the MCP request is
64
+ * handled: a dead credential — e.g. an OAuth-minted key revoked mid-session —
65
+ * must surface as an HTTP 401 + WWW-Authenticate so
66
+ * OAuth-capable clients re-run their auth flow automatically. Without this,
67
+ * the upstream 401 is wrapped into a 200 JSON-RPC tool error and the client
68
+ * never re-authenticates. Set MCP_SKIP_CREDENTIAL_CHECK=true to disable.
69
+ *
70
+ * The gate also self-disables when the REST hosts are overridden without a
71
+ * matching RUNPOD_AUTHED_GRAPHQL_URL — see credentialCheckMayBeWrongEnvironment.
72
+ */
73
+ declare function handleMcpRequest(req: IncomingMessage, res: ServerResponse, opts?: HandleMcpRequestOptions): Promise<void>;
74
+
75
+ export { type CredentialChecker as C, prefersJsonResponse as a, bodyIsCheckable as b, requestLogSampleRate as c, defaultCredentialChecker as d, extractBearerToken as e, getBaseUrl as g, handleMcpRequest as h, parseMcpMessages as p, requestProfile as r, summarizeRpc as s };
package/dist/http.cjs CHANGED
@@ -9179,7 +9179,7 @@ function words2(name) {
9179
9179
 
9180
9180
  // src/server.ts
9181
9181
  var SERVER_NAME = "Runpod API Server";
9182
- var SERVER_VERSION = true ? "4.8.0" : "dev";
9182
+ var SERVER_VERSION = true ? "4.9.0" : "dev";
9183
9183
 
9184
9184
  // src/specgen/analytics.ts
9185
9185
  var import_node_crypto = require("crypto");
package/dist/http.d.cts CHANGED
@@ -1,77 +1,5 @@
1
- import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
2
- import { ServerProfile } from './specgen/profile.cjs';
3
- import { IncomingMessage, ServerResponse } from 'node:http';
4
-
5
- type CredentialVerdict = {
6
- status: 'valid';
7
- accountId?: string;
8
- } | {
9
- status: 'invalid';
10
- reason: string;
11
- } | {
12
- status: 'unknown';
13
- };
14
- type CredentialChecker = (token: string) => Promise<CredentialVerdict>;
15
- interface CredentialCheckerHandle {
16
- verify: CredentialChecker;
17
- invalidate: (token: string) => void;
18
- }
19
-
20
- declare const ONCE_PER_ACCOUNT_ROUTES: readonly ["referral"];
21
- type OncePerAccountRoute = (typeof ONCE_PER_ACCOUNT_ROUTES)[number];
22
-
23
- interface HandleMcpRequestOptions {
24
- serverVersion?: string;
25
- verifyCredential?: CredentialChecker;
26
- invalidateCredential?: (token: string) => void;
27
- /** Once-per-account lookup behind the referral ask (src/alp/answered.ts).
28
- * Resolve, never reject: a rejection is treated as an unknown verdict. */
29
- hasAnswered?: (identity: string, route: OncePerAccountRoute) => Promise<boolean | null>;
30
- }
31
- interface RpcSummary {
32
- rpc?: string;
33
- client?: string;
34
- }
35
-
36
- /**
37
- * Extract the bearer token from an HTTP request's Authorization header.
38
- * Returns null if the header is missing or malformed.
39
- */
40
- declare function extractBearerToken(req: IncomingMessage): string | null;
41
- declare function getBaseUrl(req: {
42
- headers: {
43
- host?: string;
44
- 'x-forwarded-proto'?: string | string[];
45
- };
46
- }): string;
47
- declare function requestProfile(req: {
48
- url?: string;
49
- }): ServerProfile;
50
- declare const defaultCredentialChecker: CredentialCheckerHandle;
51
- declare function parseMcpMessages(body: unknown): JSONRPCMessage[];
52
- declare function bodyIsCheckable(body: unknown): boolean;
53
- declare function prefersJsonResponse(messages: JSONRPCMessage[]): boolean;
54
- declare function requestLogSampleRate(env?: NodeJS.ProcessEnv): number;
55
- declare function summarizeRpc(messages: JSONRPCMessage[]): RpcSummary;
56
- /**
57
- * Handle an incoming HTTP request as a streamable MCP session.
58
- *
59
- * Each request gets its own McpServer + transport. The caller's Bearer token
60
- * is forwarded directly to the Runpod API as the credential — it may be a
61
- * Runpod API key (set manually by the caller) or a token obtained through the
62
- * OAuth "Sign in with Runpod" flow. The server holds no credential of its own
63
- * and never shares one across users.
64
- *
65
- * The token is pre-flight verified (cached) before the MCP request is
66
- * handled: a dead credential — e.g. an OAuth-minted key revoked mid-session —
67
- * must surface as an HTTP 401 + WWW-Authenticate so
68
- * OAuth-capable clients re-run their auth flow automatically. Without this,
69
- * the upstream 401 is wrapped into a 200 JSON-RPC tool error and the client
70
- * never re-authenticates. Set MCP_SKIP_CREDENTIAL_CHECK=true to disable.
71
- *
72
- * The gate also self-disables when the REST hosts are overridden without a
73
- * matching RUNPOD_AUTHED_GRAPHQL_URL — see credentialCheckMayBeWrongEnvironment.
74
- */
75
- declare function handleMcpRequest(req: IncomingMessage, res: ServerResponse, opts?: HandleMcpRequestOptions): Promise<void>;
76
-
77
- export { bodyIsCheckable, defaultCredentialChecker, extractBearerToken, getBaseUrl, handleMcpRequest, parseMcpMessages, prefersJsonResponse, requestLogSampleRate, requestProfile, summarizeRpc };
1
+ import '@modelcontextprotocol/sdk/types.js';
2
+ export { b as bodyIsCheckable, d as defaultCredentialChecker, e as extractBearerToken, g as getBaseUrl, h as handleMcpRequest, p as parseMcpMessages, a as prefersJsonResponse, c as requestLogSampleRate, r as requestProfile, s as summarizeRpc } from './http-BaACCis6.cjs';
3
+ import './specgen/profile.cjs';
4
+ import 'node:http';
5
+ import './sink-Cm8lp16D.cjs';