poe-code 3.0.300 → 3.0.302

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-code",
3
- "version": "3.0.300",
3
+ "version": "3.0.302",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,6 +42,9 @@ function parsePort(value) {
42
42
  if (value === undefined) {
43
43
  return 0;
44
44
  }
45
+ if (!isDecimalInteger(value)) {
46
+ throw new Error("--port must be an integer between 0 and 65535.");
47
+ }
45
48
  const port = Number(value);
46
49
  if (!Number.isInteger(port) || port < 0 || port > 65535) {
47
50
  throw new Error("--port must be an integer between 0 and 65535.");
@@ -52,12 +55,18 @@ function parsePositiveInteger(value, flagName) {
52
55
  if (value === undefined) {
53
56
  return 60;
54
57
  }
58
+ if (!isDecimalInteger(value)) {
59
+ throw new Error(`${flagName} must be a positive integer.`);
60
+ }
55
61
  const parsed = Number(value);
56
62
  if (!Number.isInteger(parsed) || parsed <= 0) {
57
63
  throw new Error(`${flagName} must be a positive integer.`);
58
64
  }
59
65
  return parsed;
60
66
  }
67
+ function isDecimalInteger(value) {
68
+ return value.length > 0 && [...value].every((character) => character >= "0" && character <= "9");
69
+ }
61
70
  function parseAbsoluteUrl(value, flagName) {
62
71
  if (value === undefined) {
63
72
  return undefined;
@@ -88,8 +97,10 @@ function parseScopes(value) {
88
97
  }
89
98
  const scopes = value
90
99
  .split(",")
91
- .map((scope) => scope.trim())
92
- .filter((scope) => scope.length > 0);
100
+ .map((scope) => scope.trim());
101
+ if (scopes.some((scope) => scope.length === 0)) {
102
+ throw new Error("--scopes must not contain empty entries.");
103
+ }
93
104
  if (scopes.length === 0) {
94
105
  throw new Error("--scopes must include at least one non-empty scope.");
95
106
  }
@@ -178,20 +189,27 @@ export async function runCli(args = process.argv.slice(2), dependencies = {}) {
178
189
  port: parsed.port,
179
190
  hostname: parsed.hostname,
180
191
  };
181
- const server = createMcpOAuthTestServer(serverOptions);
182
- const handle = await server.listen(listenOptions);
183
- stdout.write(`${packageInfo.name} ${packageInfo.version}\n`);
184
- stdout.write(`MCP URL: ${handle.mcpUrl}\n`);
185
- stdout.write(`PRM URL: ${handle.prmUrl}\n`);
186
- stdout.write(`AS issuer: ${handle.oauth.issuer}\n`);
187
- stdout.write(`Resource: ${handle.resource}\n`);
188
- if (parsed.printTestToken) {
189
- const token = await handle.oauth.issueTokenFor({
190
- clientId: "demo-client",
191
- resource: handle.resource,
192
- scopes: parsed.scopes ?? ["mcp.read"],
193
- });
194
- stdout.write(`Test bearer token: ${token}\n`);
192
+ let handle;
193
+ try {
194
+ const server = createMcpOAuthTestServer(serverOptions);
195
+ handle = await server.listen(listenOptions);
196
+ stdout.write(`${packageInfo.name} ${packageInfo.version}\n`);
197
+ stdout.write(`MCP URL: ${handle.mcpUrl}\n`);
198
+ stdout.write(`PRM URL: ${handle.prmUrl}\n`);
199
+ stdout.write(`AS issuer: ${handle.oauth.issuer}\n`);
200
+ stdout.write(`Resource: ${handle.resource}\n`);
201
+ if (parsed.printTestToken) {
202
+ const token = await handle.oauth.issueTokenFor({
203
+ clientId: "demo-client",
204
+ resource: handle.resource,
205
+ scopes: parsed.scopes ?? ["mcp.read"],
206
+ });
207
+ stdout.write(`Test bearer token: ${token}\n`);
208
+ }
209
+ }
210
+ catch (error) {
211
+ stderr.write(`${error instanceof Error ? error.message : String(error)}\n\n${HELP_TEXT}\n`);
212
+ return 1;
195
213
  }
196
214
  await (dependencies.waitForShutdown ?? waitForShutdown)(handle.close);
197
215
  return 0;
@@ -85,6 +85,9 @@ function normalizeScopes(scopes) {
85
85
  if (normalizedScopes.some((scope) => scope.trim().length === 0)) {
86
86
  throw new Error("scopes must contain non-empty values");
87
87
  }
88
+ if (normalizedScopes.some((scope) => scope.trim() !== scope || scope.includes(" "))) {
89
+ throw new Error("scope entries must not contain spaces");
90
+ }
88
91
  return normalizedScopes;
89
92
  }
90
93
  function normalizeTtlSeconds(ttlSeconds) {
@@ -94,6 +97,13 @@ function normalizeTtlSeconds(ttlSeconds) {
94
97
  }
95
98
  return normalizedTtlSeconds;
96
99
  }
100
+ function normalizeListenPort(port) {
101
+ const normalizedPort = port ?? 0;
102
+ if (!Number.isInteger(normalizedPort) || normalizedPort < 0 || normalizedPort > 65535) {
103
+ throw new Error("port must be an integer between 0 and 65535");
104
+ }
105
+ return normalizedPort;
106
+ }
97
107
  function closeServer(server) {
98
108
  return new Promise((resolve, reject) => {
99
109
  server.close((error) => {
@@ -135,9 +145,9 @@ export function createMcpOAuthTestServer(options = {}) {
135
145
  if (currentHandle !== null || listenPending) {
136
146
  throw new Error("MCP OAuth test server is already listening");
137
147
  }
138
- listenPending = true;
139
148
  const hostname = listenOptions.hostname ?? "127.0.0.1";
140
- const requestedPort = listenOptions.port ?? 0;
149
+ const requestedPort = normalizeListenPort(listenOptions.port);
150
+ listenPending = true;
141
151
  let lastError;
142
152
  for (let attempt = 0; attempt < 10; attempt += 1) {
143
153
  try {
@@ -66,6 +66,19 @@ function formatScope(scope) {
66
66
  }
67
67
  return scope.join(" ");
68
68
  }
69
+ function hasBearerTokenWhitespace(value) {
70
+ for (const character of value) {
71
+ if (character === " "
72
+ || character === "\t"
73
+ || character === "\n"
74
+ || character === "\r"
75
+ || character === "\f"
76
+ || character === "\v") {
77
+ return true;
78
+ }
79
+ }
80
+ return false;
81
+ }
69
82
  function readBearerToken(req) {
70
83
  const authorization = readSingleHeaderValue(req.headers.authorization);
71
84
  if (authorization === undefined || authorization.length === 0) {
@@ -80,7 +93,7 @@ function readBearerToken(req) {
80
93
  }
81
94
  const scheme = authorization.slice(0, separatorIndex);
82
95
  const token = authorization.slice(separatorIndex + 1).trim();
83
- if (scheme.toLowerCase() !== "bearer" || token.length === 0 || token.includes(" ")) {
96
+ if (scheme.toLowerCase() !== "bearer" || token.length === 0 || hasBearerTokenWhitespace(token)) {
84
97
  return {
85
98
  kind: "malformed",
86
99
  errorDescription: "malformed bearer token",
@@ -153,7 +166,14 @@ function toRequestAuthInfo(verifiedToken, resource) {
153
166
  };
154
167
  }
155
168
  export function getProtectedResourceMetadataUrl(req, protectedResourcePath, trustedProxy = false) {
156
- return new URL(`${PROTECTED_RESOURCE_METADATA_PATH}${normalizeProtectedResourcePath(protectedResourcePath)}`, `${getRequestProtocol(req, trustedProxy)}://${getRequestHost(req, trustedProxy)}`).toString();
169
+ const path = `${PROTECTED_RESOURCE_METADATA_PATH}${normalizeProtectedResourcePath(protectedResourcePath)}`;
170
+ const protocol = getRequestProtocol(req, trustedProxy);
171
+ try {
172
+ return new URL(path, `${protocol}://${getRequestHost(req, trustedProxy)}`).toString();
173
+ }
174
+ catch {
175
+ return new URL(path, `${protocol}://127.0.0.1`).toString();
176
+ }
157
177
  }
158
178
  export function createBearerChallenge(req, options = {}, protectedResourcePath, trustedProxy = false) {
159
179
  const parts = [
@@ -73,7 +73,7 @@ function parsePort(value) {
73
73
  if (value === undefined) {
74
74
  return 3000;
75
75
  }
76
- const port = Number(value);
76
+ const port = parseDecimalInteger(value, "--port");
77
77
  if (!Number.isInteger(port) || port < 0 || port > 65535) {
78
78
  throw new Error("--port must be an integer between 0 and 65535.");
79
79
  }
@@ -95,11 +95,24 @@ function parseOrigin(value, flagName) {
95
95
  throw new Error(`${flagName} must be an absolute URL.`);
96
96
  }
97
97
  }
98
+ function parseDecimalInteger(value, flagName) {
99
+ const trimmed = value.trim();
100
+ if (trimmed.length === 0) {
101
+ throw new Error(`${flagName} must be an integer.`);
102
+ }
103
+ for (const character of trimmed) {
104
+ const codePoint = character.codePointAt(0);
105
+ if (codePoint === undefined || codePoint < 48 || codePoint > 57) {
106
+ throw new Error(`${flagName} must be an integer.`);
107
+ }
108
+ }
109
+ return Number(trimmed);
110
+ }
98
111
  function parseOptionalInteger(value, flagName, minimum) {
99
112
  if (value === undefined) {
100
113
  return undefined;
101
114
  }
102
- const parsed = Number(value);
115
+ const parsed = parseDecimalInteger(value, flagName);
103
116
  if (!Number.isInteger(parsed) || parsed < minimum) {
104
117
  throw new Error(`${flagName} must be an integer greater than or equal to ${minimum}.`);
105
118
  }
@@ -116,6 +129,23 @@ function hasConfiguredOAuthFlag(values) {
116
129
  values["oauth-verifier-export"],
117
130
  ].some((value) => value !== undefined);
118
131
  }
132
+ function parseRepeatableStrings(value, flagName) {
133
+ if (!Array.isArray(value) || value.length === 0) {
134
+ return undefined;
135
+ }
136
+ const normalized = [];
137
+ for (const item of value) {
138
+ if (typeof item !== "string") {
139
+ throw new Error(`${flagName} must be provided as a string.`);
140
+ }
141
+ const trimmed = item.trim();
142
+ if (trimmed.length === 0) {
143
+ throw new Error(`${flagName} must not be blank.`);
144
+ }
145
+ normalized.push(trimmed);
146
+ }
147
+ return normalized;
148
+ }
119
149
  function parseCliOAuthOptions(values) {
120
150
  const resource = values["oauth-resource"];
121
151
  const authorizationServers = values["oauth-authorization-server"];
@@ -134,21 +164,15 @@ function parseCliOAuthOptions(values) {
134
164
  if (typeof verifierModule !== "string" || verifierModule.length === 0) {
135
165
  throw new Error("--oauth-verifier-module is required when --oauth-resource is set.");
136
166
  }
137
- const supportedScopes = values["oauth-supported-scope"];
138
- const requiredScopes = values["oauth-required-scope"];
139
- const bearerMethods = values["oauth-bearer-method"];
167
+ const supportedScopes = parseRepeatableStrings(values["oauth-supported-scope"], "--oauth-supported-scope");
168
+ const requiredScopes = parseRepeatableStrings(values["oauth-required-scope"], "--oauth-required-scope");
169
+ const bearerMethods = parseRepeatableStrings(values["oauth-bearer-method"], "--oauth-bearer-method");
140
170
  return {
141
171
  resource: parseAbsoluteUrl(resource, "--oauth-resource"),
142
172
  authorizationServers: authorizationServers.map((value) => parseAbsoluteUrl(value, "--oauth-authorization-server")),
143
- ...(Array.isArray(requiredScopes) && requiredScopes.length > 0
144
- ? { requiredScopes: [...requiredScopes] }
145
- : {}),
146
- ...(Array.isArray(supportedScopes) && supportedScopes.length > 0
147
- ? { scopesSupported: [...supportedScopes] }
148
- : {}),
149
- ...(Array.isArray(bearerMethods) && bearerMethods.length > 0
150
- ? { bearerMethodsSupported: [...bearerMethods] }
151
- : {}),
173
+ ...(requiredScopes === undefined ? {} : { requiredScopes }),
174
+ ...(supportedScopes === undefined ? {} : { scopesSupported: supportedScopes }),
175
+ ...(bearerMethods === undefined ? {} : { bearerMethodsSupported: bearerMethods }),
152
176
  verifierModule,
153
177
  verifierExport: typeof verifierExport === "string" && verifierExport.length > 0
154
178
  ? verifierExport
@@ -134,6 +134,7 @@ export declare class StreamableHttpTransport {
134
134
  private acceptsHost;
135
135
  private normalizeHost;
136
136
  private acceptsConfiguredResponse;
137
+ private acceptsResponseType;
137
138
  private isValidNewSessionId;
138
139
  private isRequest;
139
140
  private isToolCallOk;
@@ -8,6 +8,15 @@ const ALLOWED_METHODS = "POST, GET, DELETE, OPTIONS";
8
8
  const MCP_SESSION_ID_HEADER = "Mcp-Session-Id";
9
9
  const LOCAL_HOSTS = ["localhost", "127.0.0.1", "::1"];
10
10
  const DEFAULT_ALLOWED_HEADERS = "Accept, Authorization, Content-Type, Mcp-Session-Id, MCP-Protocol-Version";
11
+ function validateOptionalIntegerOption(name, value, minimum) {
12
+ if (value === undefined) {
13
+ return undefined;
14
+ }
15
+ if (!Number.isInteger(value) || value < minimum) {
16
+ throw new Error(`${name} must be an integer greater than or equal to ${minimum}.`);
17
+ }
18
+ return value;
19
+ }
11
20
  export class StreamableHttpTransport {
12
21
  server;
13
22
  runWithRequestContext;
@@ -44,13 +53,14 @@ export class StreamableHttpTransport {
44
53
  this.enableJsonResponse = options.enableJsonResponse ?? false;
45
54
  this.allowedOrigins = new Set(options.allowedOrigins ?? []);
46
55
  this.allowedHosts = new Set((options.allowedHosts ?? LOCAL_HOSTS).map((host) => this.normalizeHost(host)));
47
- this.maxRequestBytes = options.maxRequestBytes;
48
- this.maxBatchSize = options.maxBatchSize;
49
- this.maxSessions = options.maxSessions;
50
- this.sessionTtlMs = options.sessionTtlMs;
51
- this.maxStreamsPerSession = options.maxStreamsPerSession;
52
- this.maxSseEventHistory = options.maxSseEventHistory ?? 100;
53
- this.maxConcurrentToolCalls = options.maxConcurrentToolCalls;
56
+ this.maxRequestBytes = validateOptionalIntegerOption("maxRequestBytes", options.maxRequestBytes, 1);
57
+ this.maxBatchSize = validateOptionalIntegerOption("maxBatchSize", options.maxBatchSize, 1);
58
+ this.maxSessions = validateOptionalIntegerOption("maxSessions", options.maxSessions, 1);
59
+ this.sessionTtlMs = validateOptionalIntegerOption("sessionTtlMs", options.sessionTtlMs, 1);
60
+ this.maxStreamsPerSession = validateOptionalIntegerOption("maxStreamsPerSession", options.maxStreamsPerSession, 1);
61
+ this.maxSseEventHistory =
62
+ validateOptionalIntegerOption("maxSseEventHistory", options.maxSseEventHistory, 0) ?? 100;
63
+ this.maxConcurrentToolCalls = validateOptionalIntegerOption("maxConcurrentToolCalls", options.maxConcurrentToolCalls, 1);
54
64
  this.sessionStore = options.sessionStore ?? createSessionStore();
55
65
  this.requestIdGenerator =
56
66
  options.requestIdGenerator ?? (() => `req-${this.nextRequestId++}`);
@@ -328,6 +338,10 @@ export class StreamableHttpTransport {
328
338
  });
329
339
  return;
330
340
  }
341
+ if (!this.acceptsResponseType(req, "text/event-stream")) {
342
+ this.respondWithStatus(res, 406);
343
+ return;
344
+ }
331
345
  const sessionId = this.readSessionId(req);
332
346
  if (sessionId === undefined) {
333
347
  this.respondWithStatus(res, 400);
@@ -673,11 +687,14 @@ export class StreamableHttpTransport {
673
687
  : normalized;
674
688
  }
675
689
  acceptsConfiguredResponse(req) {
690
+ const expectedType = this.enableJsonResponse ? "application/json" : "text/event-stream";
691
+ return this.acceptsResponseType(req, expectedType);
692
+ }
693
+ acceptsResponseType(req, expectedType) {
676
694
  const accept = req.headers.accept;
677
695
  if (accept === undefined) {
678
696
  return true;
679
697
  }
680
- const expectedType = this.enableJsonResponse ? "application/json" : "text/event-stream";
681
698
  const value = Array.isArray(accept) ? accept.join(",") : accept;
682
699
  return value
683
700
  .split(",")