pi-mcp-adapter 2.3.3 → 2.3.4
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/CHANGELOG.md +6 -0
- package/mcp-auth-flow.ts +3 -2
- package/mcp-callback-server.ts +67 -39
- package/mcp-oauth-provider.ts +26 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.3.4] - 2026-04-12
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- OAuth callback handling now allows dynamic-registration flows to fall back to a free local port when the preferred callback port is busy, while keeping pre-registered clients on their exact configured redirect port.
|
|
14
|
+
- Documented the new callback-port behavior and added focused auth-flow regression coverage.
|
|
15
|
+
|
|
10
16
|
## [2.3.3] - 2026-04-12
|
|
11
17
|
|
|
12
18
|
### Fixed
|
package/mcp-auth-flow.ts
CHANGED
|
@@ -100,8 +100,9 @@ export async function startAuth(
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
// Start the callback server
|
|
104
|
-
|
|
103
|
+
// Start the callback server.
|
|
104
|
+
// Pre-registered OAuth clients require an exact redirect URI, so enforce strict port binding.
|
|
105
|
+
await ensureCallbackServer({ strictPort: Boolean(config.clientId) })
|
|
105
106
|
|
|
106
107
|
// Generate and store OAuth state BEFORE creating the provider
|
|
107
108
|
// The SDK will call provider.state() to read this value
|
package/mcp-callback-server.ts
CHANGED
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { createServer, type Server, type IncomingMessage, type ServerResponse } from "http"
|
|
9
|
-
import {
|
|
10
|
-
|
|
9
|
+
import {
|
|
10
|
+
OAUTH_CALLBACK_PATH,
|
|
11
|
+
getConfiguredOAuthCallbackPort,
|
|
12
|
+
getOAuthCallbackPort,
|
|
13
|
+
setOAuthCallbackPort,
|
|
14
|
+
} from "./mcp-oauth-provider.js"
|
|
11
15
|
|
|
12
16
|
// HTML templates for callback responses
|
|
13
17
|
const HTML_SUCCESS = `<!DOCTYPE html>
|
|
@@ -65,29 +69,10 @@ const pendingAuths = new Map<string, PendingAuth>()
|
|
|
65
69
|
/** Timeout for callback completion (5 minutes) */
|
|
66
70
|
const CALLBACK_TIMEOUT_MS = 5 * 60 * 1000
|
|
67
71
|
|
|
68
|
-
|
|
69
|
-
* Check if the callback port is already in use.
|
|
70
|
-
* Used to detect if another instance is running the server.
|
|
71
|
-
*/
|
|
72
|
-
async function isPortInUse(): Promise<boolean> {
|
|
73
|
-
return new Promise((resolve) => {
|
|
74
|
-
const socket = connect({ host: "127.0.0.1", port: OAUTH_CALLBACK_PORT })
|
|
75
|
-
socket.setTimeout(1000)
|
|
76
|
-
|
|
77
|
-
socket.on("connect", () => {
|
|
78
|
-
socket.end()
|
|
79
|
-
resolve(true)
|
|
80
|
-
})
|
|
72
|
+
const MAX_PORT_SCAN_ATTEMPTS = 25
|
|
81
73
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
socket.on("timeout", () => {
|
|
87
|
-
socket.destroy()
|
|
88
|
-
resolve(false)
|
|
89
|
-
})
|
|
90
|
-
})
|
|
74
|
+
interface EnsureCallbackServerOptions {
|
|
75
|
+
strictPort?: boolean
|
|
91
76
|
}
|
|
92
77
|
|
|
93
78
|
/**
|
|
@@ -160,27 +145,68 @@ function handleRequest(req: IncomingMessage, res: ServerResponse): void {
|
|
|
160
145
|
|
|
161
146
|
/**
|
|
162
147
|
* Ensure the callback server is running.
|
|
163
|
-
* If
|
|
148
|
+
* If strictPort is true, requires binding on the configured callback port.
|
|
149
|
+
* If strictPort is false, scans forward for an available local port.
|
|
164
150
|
*/
|
|
165
|
-
export async function ensureCallbackServer(): Promise<void> {
|
|
166
|
-
|
|
151
|
+
export async function ensureCallbackServer(options: EnsureCallbackServerOptions = {}): Promise<void> {
|
|
152
|
+
const configuredPort = getConfiguredOAuthCallbackPort()
|
|
153
|
+
const strictPort = options.strictPort === true
|
|
167
154
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
155
|
+
if (server) {
|
|
156
|
+
if (!strictPort || getOAuthCallbackPort() === configuredPort) return
|
|
157
|
+
|
|
158
|
+
throw new Error(
|
|
159
|
+
`OAuth callback server is already running on port ${getOAuthCallbackPort()}, but strict callback port ${configuredPort} is required`
|
|
160
|
+
)
|
|
171
161
|
}
|
|
172
162
|
|
|
173
|
-
|
|
163
|
+
const preferredPort = configuredPort
|
|
164
|
+
const maxAttempts = strictPort ? 1 : MAX_PORT_SCAN_ATTEMPTS
|
|
165
|
+
let lastError: Error | undefined
|
|
174
166
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
})
|
|
167
|
+
for (let offset = 0; offset < maxAttempts; offset++) {
|
|
168
|
+
const candidatePort = preferredPort + offset
|
|
169
|
+
const candidateServer = createServer(handleRequest)
|
|
179
170
|
|
|
180
|
-
|
|
181
|
-
resolve
|
|
182
|
-
|
|
183
|
-
|
|
171
|
+
try {
|
|
172
|
+
await new Promise<void>((resolve, reject) => {
|
|
173
|
+
candidateServer.once("error", (err) => {
|
|
174
|
+
reject(err)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
candidateServer.listen(candidatePort, "127.0.0.1", () => {
|
|
178
|
+
resolve()
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
server = candidateServer
|
|
183
|
+
setOAuthCallbackPort(candidatePort)
|
|
184
|
+
return
|
|
185
|
+
} catch (error) {
|
|
186
|
+
const nodeError = error as NodeJS.ErrnoException
|
|
187
|
+
await new Promise<void>((resolve) => {
|
|
188
|
+
candidateServer.close(() => resolve())
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
if (nodeError.code !== "EADDRINUSE") {
|
|
192
|
+
throw error
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
lastError = error instanceof Error ? error : new Error(String(error))
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (strictPort) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`OAuth callback port ${preferredPort} is already in use. Pre-registered OAuth clients require an exact redirect URI; set MCP_OAUTH_CALLBACK_PORT to your registered port or free port ${preferredPort}`,
|
|
202
|
+
{ cause: lastError }
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
throw new Error(
|
|
207
|
+
`OAuth callback port ${preferredPort} is already in use and no free port was found in range ${preferredPort}-${preferredPort + MAX_PORT_SCAN_ATTEMPTS - 1}`,
|
|
208
|
+
{ cause: lastError }
|
|
209
|
+
)
|
|
184
210
|
}
|
|
185
211
|
|
|
186
212
|
/**
|
|
@@ -225,6 +251,8 @@ export async function stopCallbackServer(): Promise<void> {
|
|
|
225
251
|
server = undefined
|
|
226
252
|
}
|
|
227
253
|
|
|
254
|
+
setOAuthCallbackPort(getConfiguredOAuthCallbackPort())
|
|
255
|
+
|
|
228
256
|
// Reject all pending auths (defer to allow any pending operations to complete)
|
|
229
257
|
const pendingList = Array.from(pendingAuths.entries())
|
|
230
258
|
pendingAuths.clear()
|
package/mcp-oauth-provider.ts
CHANGED
|
@@ -27,9 +27,32 @@ import {
|
|
|
27
27
|
} from "./mcp-auth.js"
|
|
28
28
|
|
|
29
29
|
// Callback server configuration
|
|
30
|
-
const
|
|
30
|
+
const DEFAULT_OAUTH_CALLBACK_PORT = 19876
|
|
31
31
|
const OAUTH_CALLBACK_PATH = "/mcp/oauth/callback"
|
|
32
32
|
|
|
33
|
+
let configuredOAuthCallbackPort = DEFAULT_OAUTH_CALLBACK_PORT
|
|
34
|
+
|
|
35
|
+
if (process.env.MCP_OAUTH_CALLBACK_PORT) {
|
|
36
|
+
const parsedPort = Number.parseInt(process.env.MCP_OAUTH_CALLBACK_PORT, 10)
|
|
37
|
+
if (Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535) {
|
|
38
|
+
configuredOAuthCallbackPort = parsedPort
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let oauthCallbackPort = configuredOAuthCallbackPort
|
|
43
|
+
|
|
44
|
+
export function getConfiguredOAuthCallbackPort(): number {
|
|
45
|
+
return configuredOAuthCallbackPort
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getOAuthCallbackPort(): number {
|
|
49
|
+
return oauthCallbackPort
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function setOAuthCallbackPort(port: number): void {
|
|
53
|
+
oauthCallbackPort = port
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
/** Configuration options for OAuth */
|
|
34
57
|
export interface McpOAuthConfig {
|
|
35
58
|
grantType?: "authorization_code" | "client_credentials"
|
|
@@ -65,7 +88,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
65
88
|
*/
|
|
66
89
|
get redirectUrl(): string | undefined {
|
|
67
90
|
if (this.usesClientCredentials) return undefined
|
|
68
|
-
return `http://127.0.0.1:${
|
|
91
|
+
return `http://127.0.0.1:${getOAuthCallbackPort()}${OAUTH_CALLBACK_PATH}`
|
|
69
92
|
}
|
|
70
93
|
|
|
71
94
|
/**
|
|
@@ -262,4 +285,4 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
262
285
|
}
|
|
263
286
|
}
|
|
264
287
|
|
|
265
|
-
export {
|
|
288
|
+
export { DEFAULT_OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH }
|