poe-code 3.0.432 → 3.0.434

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.432",
3
+ "version": "3.0.434",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -219,6 +219,18 @@ export async function authorizeBearerRequest(req, options) {
219
219
  authorizationServers: options.authorizationServers.map(toUrlString),
220
220
  requiredScopes,
221
221
  });
222
+ const verifiedScopes = new Set(verifiedToken.scopes);
223
+ if (requiredScopes.some((scope) => !verifiedScopes.has(scope))) {
224
+ return {
225
+ ok: false,
226
+ statusCode: 403,
227
+ challenge: createBearerChallenge(req, {
228
+ error: "insufficient_scope",
229
+ errorDescription: "insufficient scope",
230
+ scope: requiredScopes,
231
+ }, options.protectedResourcePath, options.trustedProxy),
232
+ };
233
+ }
222
234
  const auth = toRequestAuthInfo(verifiedToken, toUrlString(options.resource));
223
235
  req.auth = auth;
224
236
  return {
@@ -62,6 +62,10 @@ export function createExpressOAuthHandlers(options) {
62
62
  path,
63
63
  }),
64
64
  mcpMiddleware: async (req, res, next) => {
65
+ if (req.method === "OPTIONS") {
66
+ await mcpMiddleware(req, res, next);
67
+ return;
68
+ }
65
69
  const authorization = await authorizeBearerRequest(req, {
66
70
  ...options.oauth,
67
71
  protectedResourcePath: path,
@@ -106,7 +106,7 @@ export function createHttpServer(options) {
106
106
  request: {}
107
107
  };
108
108
  const authorizeHttpRequest = async (req, res, protectedResourcePath) => {
109
- if (options.oauth === undefined) {
109
+ if (options.oauth === undefined || req.method === "OPTIONS") {
110
110
  return true;
111
111
  }
112
112
  const authenticatedRequest = req;
@@ -99,12 +99,15 @@ export declare class StreamableHttpTransport {
99
99
  private readonly trustedProxy;
100
100
  private readonly sessionMessages;
101
101
  private readonly sseStreams;
102
+ private readonly sseExpiryTimers;
102
103
  private readonly sseEventHistory;
103
104
  private readonly responseRequestIds;
104
105
  private readonly responseOrigins;
105
106
  private nextNotificationEventId;
106
107
  private nextRequestId;
107
108
  private activeToolCalls;
109
+ private closed;
110
+ private sessionExpiryInterval;
108
111
  private sseKeepAliveInterval;
109
112
  constructor(server: Server, options?: StreamableHttpTransportOptions, runWithRequestContext?: RequestContextRunner);
110
113
  handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void>;
@@ -128,6 +131,8 @@ export declare class StreamableHttpTransport {
128
131
  private createLocalMessageSession;
129
132
  private ensureLocalMessageSession;
130
133
  private closeStreamsForSession;
134
+ private scheduleSseExpiry;
135
+ private clearSseExpiryTimer;
131
136
  private startSseKeepAlive;
132
137
  private stopSseKeepAliveIfIdle;
133
138
  private stopSseKeepAlive;
@@ -39,12 +39,15 @@ export class StreamableHttpTransport {
39
39
  trustedProxy;
40
40
  sessionMessages = new Map();
41
41
  sseStreams = new Map();
42
+ sseExpiryTimers = new Map();
42
43
  sseEventHistory = new Map();
43
44
  responseRequestIds = new WeakMap();
44
45
  responseOrigins = new WeakMap();
45
46
  nextNotificationEventId = 1;
46
47
  nextRequestId = 1;
47
48
  activeToolCalls = 0;
49
+ closed = false;
50
+ sessionExpiryInterval;
48
51
  sseKeepAliveInterval;
49
52
  constructor(server, options = {}, runWithRequestContext = async (_req, callback) => callback()) {
50
53
  this.server = server;
@@ -73,6 +76,17 @@ export class StreamableHttpTransport {
73
76
  this.requestIdGenerator = options.requestIdGenerator ?? (() => `req-${this.nextRequestId++}`);
74
77
  this.observability = options.observability ?? {};
75
78
  this.trustedProxy = options.trustedProxy ?? false;
79
+ if (this.sessionTtlMs !== undefined) {
80
+ this.sessionExpiryInterval = setInterval(() => {
81
+ try {
82
+ this.purgeExpiredSessions();
83
+ }
84
+ catch {
85
+ return;
86
+ }
87
+ }, Math.min(this.sessionTtlMs, 60_000));
88
+ this.sessionExpiryInterval.unref();
89
+ }
76
90
  }
77
91
  async handleRequest(req, res) {
78
92
  const startedAt = Date.now();
@@ -90,12 +104,10 @@ export class StreamableHttpTransport {
90
104
  sessionId: this.readSessionId(req)
91
105
  });
92
106
  try {
93
- this.purgeExpiredSessions();
94
- }
95
- catch {
96
- // Expiry cleanup must not make otherwise valid requests fail.
97
- }
98
- try {
107
+ if (this.closed) {
108
+ this.respondWithStatus(res, 503);
109
+ return;
110
+ }
99
111
  if (!this.acceptsHost(req) || !this.acceptsOrigin(req)) {
100
112
  this.respondWithStatus(res, 403);
101
113
  return;
@@ -148,7 +160,16 @@ export class StreamableHttpTransport {
148
160
  }
149
161
  }
150
162
  async close() {
163
+ this.closed = true;
164
+ if (this.sessionExpiryInterval !== undefined) {
165
+ clearInterval(this.sessionExpiryInterval);
166
+ this.sessionExpiryInterval = undefined;
167
+ }
151
168
  this.stopSseKeepAlive();
169
+ for (const timer of this.sseExpiryTimers.values()) {
170
+ clearTimeout(timer);
171
+ }
172
+ this.sseExpiryTimers.clear();
152
173
  for (const sessionId of [...this.sseStreams.keys()]) {
153
174
  this.closeStreamsForSession(sessionId);
154
175
  }
@@ -395,6 +416,7 @@ export class StreamableHttpTransport {
395
416
  streamCount: streams.size
396
417
  });
397
418
  const cleanup = () => {
419
+ this.clearSseExpiryTimer(res);
398
420
  const activeStreams = this.sseStreams.get(sessionId);
399
421
  if (activeStreams === undefined) {
400
422
  return;
@@ -415,6 +437,10 @@ export class StreamableHttpTransport {
415
437
  req.on("close", cleanup);
416
438
  res.on("close", cleanup);
417
439
  res.on("finish", cleanup);
440
+ const expiresAt = req.auth?.expiresAt;
441
+ if (expiresAt !== undefined) {
442
+ this.scheduleSseExpiry(res, expiresAt);
443
+ }
418
444
  res.writeHead(200, this.withSessionHeader(SSE_HEADERS, sessionId, res));
419
445
  this.replaySseEvents(req, res, sessionId);
420
446
  res.flushHeaders();
@@ -589,6 +615,7 @@ export class StreamableHttpTransport {
589
615
  return;
590
616
  }
591
617
  for (const response of streams) {
618
+ this.clearSseExpiryTimer(response);
592
619
  if (!response.writableEnded) {
593
620
  response.end();
594
621
  }
@@ -596,6 +623,25 @@ export class StreamableHttpTransport {
596
623
  this.sseStreams.delete(sessionId);
597
624
  this.stopSseKeepAliveIfIdle();
598
625
  }
626
+ scheduleSseExpiry(response, expiresAt) {
627
+ const remainingMs = Math.max(0, expiresAt * 1_000 - Date.now());
628
+ const timer = setTimeout(() => {
629
+ this.sseExpiryTimers.delete(response);
630
+ if (!response.writableEnded) {
631
+ response.end();
632
+ }
633
+ }, remainingMs);
634
+ timer.unref();
635
+ this.sseExpiryTimers.set(response, timer);
636
+ }
637
+ clearSseExpiryTimer(response) {
638
+ const timer = this.sseExpiryTimers.get(response);
639
+ if (timer === undefined) {
640
+ return;
641
+ }
642
+ clearTimeout(timer);
643
+ this.sseExpiryTimers.delete(response);
644
+ }
599
645
  startSseKeepAlive() {
600
646
  if (this.sseKeepAliveMs === 0 || this.sseKeepAliveInterval !== undefined) {
601
647
  return;
@@ -739,7 +785,7 @@ export class StreamableHttpTransport {
739
785
  if (host === undefined || host.length === 0) {
740
786
  return true;
741
787
  }
742
- return this.allowedHosts.has(this.normalizeHost(host));
788
+ return this.allowedHosts.has(this.normalizeHost(this.readRequestHost(req)));
743
789
  }
744
790
  normalizeHost(host) {
745
791
  const normalized = host.trim().toLowerCase();