Rewrite proxy.ts for Deno
This commit is contained in:
parent
61a1f4c3cc
commit
b0216017a2
8 changed files with 1494 additions and 76 deletions
|
@ -12,18 +12,18 @@ Here is a plan to transform your Node.js CLI package into a Deno CLI project, fo
|
||||||
- [x] Review `compilerOptions`. Deno uses these, but ensure they align with Deno's defaults or project needs. Remove `"types": ["node"]` as Deno handles Node types via `node:` specifiers.
|
- [x] Review `compilerOptions`. Deno uses these, but ensure they align with Deno's defaults or project needs. Remove `"types": ["node"]` as Deno handles Node types via `node:` specifiers.
|
||||||
- [x] Remove `"unstable": ["sloppy-imports"]` and plan to add explicit file extensions to imports.
|
- [x] Remove `"unstable": ["sloppy-imports"]` and plan to add explicit file extensions to imports.
|
||||||
3. **Adapt Code in `src/`:**
|
3. **Adapt Code in `src/`:**
|
||||||
- [ ] **Imports:**
|
- [x] **Imports:**
|
||||||
- [ ] Prefix all Node.js built-in module imports with `node:` (e.g., `import { EventEmitter } from 'node:events';`).
|
- [x] Prefix all Node.js built-in module imports with `node:` (e.g., `import { EventEmitter } from 'node:events';`).
|
||||||
- [ ] Update imports for external npm packages to match the `npm:` specifiers defined in `deno.json` or directly use `npm:` specifiers in the import statement.
|
- [x] Update imports for external npm packages to match the `npm:` specifiers defined in `deno.json` or directly use `npm:` specifiers in the import statement.
|
||||||
- [ ] Append the `.ts` (or `.js` if applicable) extension to all relative file imports within the `src/` directory (e.g., `import { ... } from './lib/utils.ts';`).
|
- [x] Append the `.ts` (or `.js` if applicable) extension to all relative file imports within the `src/` directory (e.g., `import { ... } from './lib/utils.ts';`).
|
||||||
- [ ] **Node Globals/APIs:**
|
- [x] **Node Globals/APIs:**
|
||||||
- [ ] Replace `process.argv` with `Deno.args`. Note that `Deno.args` does *not* include the script name, so adjustments to slicing (like `.slice(2)`) might be needed or removed.
|
- [x] Replace `process.argv` with `Deno.args`. Note that `Deno.args` does *not* include the script name, so adjustments to slicing (like `.slice(2)`) might be needed or removed.
|
||||||
- [ ] Replace `process.exit()` with `Deno.exit()`.
|
- [x] Replace `process.exit()` with `Deno.exit()`.
|
||||||
- [ ] Replace or refactor any other Node-specific APIs that don't have direct Deno equivalents or aren't polyfilled via the `node:` specifier (e.g., check compatibility of `StdioServerTransport` if it relies heavily on Node streams internally, although the `npm:` specifier should handle much of this).
|
- [x] Replace or refactor any other Node-specific APIs that don't have direct Deno equivalents or aren't polyfilled via the `node:` specifier (e.g., check compatibility of `StdioServerTransport` if it relies heavily on Node streams internally, although the `npm:` specifier should handle much of this).
|
||||||
4. **Cleanup Project Root:**
|
4. **Cleanup Project Root:**
|
||||||
- [ ] Delete `pnpm-lock.yaml` and `node_modules` (if present).
|
- [x] Delete `pnpm-lock.yaml` and `node_modules` (if present).
|
||||||
- [ ] Decide whether to keep `package.json`. It's not used by Deno for dependencies but can be useful for metadata (name, version, description). If kept, ensure it doesn't cause confusion.
|
- [x] Decide whether to keep `package.json`. It's not used by Deno for dependencies but can be useful for metadata (name, version, description). If kept, ensure it doesn't cause confusion.
|
||||||
- [ ] Remove `tsconfig.json` if all necessary compiler options are migrated to `deno.json`. Linters/editors might still pick it up, so consider keeping it for tooling compatibility if needed, but `deno.json` takes precedence for Deno itself.
|
- [x] Remove `tsconfig.json` if all necessary compiler options are migrated to `deno.json`. Linters/editors might still pick it up, so consider keeping it for tooling compatibility if needed, but `deno.json` takes precedence for Deno itself.
|
||||||
5. **Testing:**
|
5. **Testing:**
|
||||||
- [ ] Run the main task using `deno task start <args...>`.
|
- [ ] Run the main task using `deno task start <args...>`.
|
||||||
- [ ] Thoroughly test the CLI's functionality to ensure it behaves identically to the original Node.js version. Pay close attention to areas involving file system access, network requests, environment variables, and process management.
|
- [ ] Thoroughly test the CLI's functionality to ensure it behaves identically to the original Node.js version. Pay close attention to areas involving file system access, network requests, environment variables, and process management.
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { checkLockfile, createLockfile, deleteLockfile, getConfigFilePath, LockfileData } from './mcp-auth-config'
|
import { checkLockfile, createLockfile, deleteLockfile, getConfigFilePath, type LockfileData } from './mcp-auth-config.ts'
|
||||||
import { EventEmitter } from 'events'
|
import type { EventEmitter } from 'node:events'
|
||||||
import { Server } from 'http'
|
import type { Server } from 'node:http'
|
||||||
import express from 'express'
|
import express from 'npm:express'
|
||||||
import { AddressInfo } from 'net'
|
import type { AddressInfo } from 'node:net'
|
||||||
import { log, setupOAuthCallbackServerWithLongPoll } from './utils'
|
import { log, setupOAuthCallbackServerWithLongPoll } from './utils.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a process with the given PID is running
|
* Checks if a process with the given PID is running
|
||||||
|
@ -12,10 +12,37 @@ import { log, setupOAuthCallbackServerWithLongPoll } from './utils'
|
||||||
*/
|
*/
|
||||||
export async function isPidRunning(pid: number): Promise<boolean> {
|
export async function isPidRunning(pid: number): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
process.kill(pid, 0) // Doesn't kill the process, just checks if it exists
|
// Deno doesn't have a direct equivalent to process.kill(pid, 0)
|
||||||
return true
|
// On non-Windows platforms, we can try to use kill system call to check
|
||||||
|
if (Deno.build.os !== 'windows') {
|
||||||
|
try {
|
||||||
|
// Using Deno.run to check if process exists
|
||||||
|
const command = new Deno.Command('kill', {
|
||||||
|
args: ['-0', pid.toString()],
|
||||||
|
stdout: 'null',
|
||||||
|
stderr: 'null',
|
||||||
|
});
|
||||||
|
const { success } = await command.output();
|
||||||
|
return success;
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// On Windows, use tasklist to check if process exists
|
||||||
|
try {
|
||||||
|
const command = new Deno.Command('tasklist', {
|
||||||
|
args: ['/FI', `PID eq ${pid}`, '/NH'],
|
||||||
|
stdout: 'piped',
|
||||||
|
});
|
||||||
|
const { stdout } = await command.output();
|
||||||
|
const output = new TextDecoder().decode(stdout);
|
||||||
|
return output.includes(pid.toString());
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,11 +98,12 @@ export async function waitForAuthentication(port: number): Promise<boolean> {
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
// Auth completed, but we don't return the code anymore
|
// Auth completed, but we don't return the code anymore
|
||||||
log(`Authentication completed by other instance`)
|
log('Authentication completed by other instance')
|
||||||
return true
|
return true
|
||||||
} else if (response.status === 202) {
|
}
|
||||||
|
if (response.status === 202) {
|
||||||
// Continue polling
|
// Continue polling
|
||||||
log(`Authentication still in progress`)
|
log('Authentication still in progress')
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
} else {
|
} else {
|
||||||
log(`Unexpected response status: ${response.status}`)
|
log(`Unexpected response status: ${response.status}`)
|
||||||
|
@ -101,7 +129,7 @@ export async function coordinateAuth(
|
||||||
events: EventEmitter,
|
events: EventEmitter,
|
||||||
): Promise<{ server: Server; waitForAuthCode: () => Promise<string>; skipBrowserAuth: boolean }> {
|
): Promise<{ server: Server; waitForAuthCode: () => Promise<string>; skipBrowserAuth: boolean }> {
|
||||||
// Check for a lockfile (disabled on Windows for the time being)
|
// Check for a lockfile (disabled on Windows for the time being)
|
||||||
const lockData = process.platform === 'win32' ? null : await checkLockfile(serverUrlHash)
|
const lockData = Deno.build.os === 'windows' ? null : await checkLockfile(serverUrlHash)
|
||||||
|
|
||||||
// If there's a valid lockfile, try to use the existing auth process
|
// If there's a valid lockfile, try to use the existing auth process
|
||||||
if (lockData && (await isLockValid(lockData))) {
|
if (lockData && (await isLockValid(lockData))) {
|
||||||
|
@ -128,9 +156,8 @@ export async function coordinateAuth(
|
||||||
waitForAuthCode: dummyWaitForAuthCode,
|
waitForAuthCode: dummyWaitForAuthCode,
|
||||||
skipBrowserAuth: true,
|
skipBrowserAuth: true,
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log('Taking over authentication process...')
|
|
||||||
}
|
}
|
||||||
|
log('Taking over authentication process...')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`Error waiting for authentication: ${error}`)
|
log(`Error waiting for authentication: ${error}`)
|
||||||
}
|
}
|
||||||
|
@ -144,7 +171,7 @@ export async function coordinateAuth(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create our own lockfile
|
// Create our own lockfile
|
||||||
const { server, waitForAuthCode, authCompletedPromise } = setupOAuthCallbackServerWithLongPoll({
|
const { server, waitForAuthCode, authCompletedPromise: _ } = setupOAuthCallbackServerWithLongPoll({
|
||||||
port: callbackPort,
|
port: callbackPort,
|
||||||
path: '/oauth/callback',
|
path: '/oauth/callback',
|
||||||
events,
|
events,
|
||||||
|
@ -154,8 +181,8 @@ export async function coordinateAuth(
|
||||||
const address = server.address() as AddressInfo
|
const address = server.address() as AddressInfo
|
||||||
const actualPort = address.port
|
const actualPort = address.port
|
||||||
|
|
||||||
log(`Creating lockfile for server ${serverUrlHash} with process ${process.pid} on port ${actualPort}`)
|
log(`Creating lockfile for server ${serverUrlHash} with process ${Deno.pid} on port ${actualPort}`)
|
||||||
await createLockfile(serverUrlHash, process.pid, actualPort)
|
await createLockfile(serverUrlHash, Deno.pid, actualPort)
|
||||||
|
|
||||||
// Make sure lockfile is deleted on process exit
|
// Make sure lockfile is deleted on process exit
|
||||||
const cleanupHandler = async () => {
|
const cleanupHandler = async () => {
|
||||||
|
@ -167,18 +194,29 @@ export async function coordinateAuth(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process.once('exit', () => {
|
// Setup exit handlers for Deno
|
||||||
|
// Note: Deno doesn't have process.once but we can use addEventListener
|
||||||
|
// Use unload event instead of beforeunload signal
|
||||||
|
addEventListener("unload", () => {
|
||||||
try {
|
try {
|
||||||
// Synchronous version for 'exit' event since we can't use async here
|
// Synchronous cleanup
|
||||||
const configPath = getConfigFilePath(serverUrlHash, 'lock.json')
|
const configPath = getConfigFilePath(serverUrlHash, 'lock.json')
|
||||||
require('fs').unlinkSync(configPath)
|
// Use Deno's synchronous file API
|
||||||
} catch {}
|
try {
|
||||||
})
|
Deno.removeSync(configPath);
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore errors
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore errors during exit
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Also handle SIGINT separately
|
// Also handle SIGINT separately
|
||||||
process.once('SIGINT', async () => {
|
Deno.addSignalListener("SIGINT", async () => {
|
||||||
await cleanupHandler()
|
await cleanupHandler();
|
||||||
})
|
Deno.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
server,
|
server,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import path from 'path'
|
import path from 'node:path'
|
||||||
import os from 'os'
|
import os from 'node:os'
|
||||||
import fs from 'fs/promises'
|
import { log, MCP_REMOTE_VERSION } from './utils.ts'
|
||||||
import { log, MCP_REMOTE_VERSION } from './utils'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MCP Remote Authentication Configuration
|
* MCP Remote Authentication Configuration
|
||||||
|
@ -82,7 +81,7 @@ export async function deleteLockfile(serverUrlHash: string): Promise<void> {
|
||||||
* @returns The path to the configuration directory
|
* @returns The path to the configuration directory
|
||||||
*/
|
*/
|
||||||
export function getConfigDir(): string {
|
export function getConfigDir(): string {
|
||||||
const baseConfigDir = process.env.MCP_REMOTE_CONFIG_DIR || path.join(os.homedir(), '.mcp-auth')
|
const baseConfigDir = Deno.env.get('MCP_REMOTE_CONFIG_DIR') || path.join(os.homedir(), '.mcp-auth')
|
||||||
// Add a version subdirectory so we don't need to worry about backwards/forwards compatibility yet
|
// Add a version subdirectory so we don't need to worry about backwards/forwards compatibility yet
|
||||||
return path.join(baseConfigDir, `mcp-remote-${MCP_REMOTE_VERSION}`)
|
return path.join(baseConfigDir, `mcp-remote-${MCP_REMOTE_VERSION}`)
|
||||||
}
|
}
|
||||||
|
@ -93,7 +92,7 @@ export function getConfigDir(): string {
|
||||||
export async function ensureConfigDir(): Promise<void> {
|
export async function ensureConfigDir(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const configDir = getConfigDir()
|
const configDir = getConfigDir()
|
||||||
await fs.mkdir(configDir, { recursive: true })
|
await Deno.mkdir(configDir, { recursive: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log('Error creating config directory:', error)
|
log('Error creating config directory:', error)
|
||||||
throw error
|
throw error
|
||||||
|
@ -119,10 +118,10 @@ export function getConfigFilePath(serverUrlHash: string, filename: string): stri
|
||||||
export async function deleteConfigFile(serverUrlHash: string, filename: string): Promise<void> {
|
export async function deleteConfigFile(serverUrlHash: string, filename: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const filePath = getConfigFilePath(serverUrlHash, filename)
|
const filePath = getConfigFilePath(serverUrlHash, filename)
|
||||||
await fs.unlink(filePath)
|
await Deno.remove(filePath)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore if file doesn't exist
|
// Ignore if file doesn't exist
|
||||||
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
if ((error as Deno.errors.NotFound).name !== 'NotFound') {
|
||||||
log(`Error deleting ${filename}:`, error)
|
log(`Error deleting ${filename}:`, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,12 +139,12 @@ export async function readJsonFile<T>(serverUrlHash: string, filename: string, s
|
||||||
await ensureConfigDir()
|
await ensureConfigDir()
|
||||||
|
|
||||||
const filePath = getConfigFilePath(serverUrlHash, filename)
|
const filePath = getConfigFilePath(serverUrlHash, filename)
|
||||||
const content = await fs.readFile(filePath, 'utf-8')
|
const content = await Deno.readTextFile(filePath)
|
||||||
const result = await schema.parseAsync(JSON.parse(content))
|
const result = await schema.parseAsync(JSON.parse(content))
|
||||||
// console.log({ filename: result })
|
// console.log({ filename: result })
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
if (error instanceof Deno.errors.NotFound) {
|
||||||
// console.log(`File ${filename} does not exist`)
|
// console.log(`File ${filename} does not exist`)
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -164,7 +163,7 @@ export async function writeJsonFile(serverUrlHash: string, filename: string, dat
|
||||||
try {
|
try {
|
||||||
await ensureConfigDir()
|
await ensureConfigDir()
|
||||||
const filePath = getConfigFilePath(serverUrlHash, filename)
|
const filePath = getConfigFilePath(serverUrlHash, filename)
|
||||||
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8')
|
await Deno.writeTextFile(filePath, JSON.stringify(data, null, 2))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`Error writing ${filename}:`, error)
|
log(`Error writing ${filename}:`, error)
|
||||||
throw error
|
throw error
|
||||||
|
@ -182,7 +181,7 @@ export async function readTextFile(serverUrlHash: string, filename: string, erro
|
||||||
try {
|
try {
|
||||||
await ensureConfigDir()
|
await ensureConfigDir()
|
||||||
const filePath = getConfigFilePath(serverUrlHash, filename)
|
const filePath = getConfigFilePath(serverUrlHash, filename)
|
||||||
return await fs.readFile(filePath, 'utf-8')
|
return await Deno.readTextFile(filePath)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(errorMessage || `Error reading ${filename}`)
|
throw new Error(errorMessage || `Error reading ${filename}`)
|
||||||
}
|
}
|
||||||
|
@ -198,7 +197,7 @@ export async function writeTextFile(serverUrlHash: string, filename: string, tex
|
||||||
try {
|
try {
|
||||||
await ensureConfigDir()
|
await ensureConfigDir()
|
||||||
const filePath = getConfigFilePath(serverUrlHash, filename)
|
const filePath = getConfigFilePath(serverUrlHash, filename)
|
||||||
await fs.writeFile(filePath, text, 'utf-8')
|
await Deno.writeTextFile(filePath, text)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`Error writing ${filename}:`, error)
|
log(`Error writing ${filename}:`, error)
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import open from 'open'
|
import open from 'npm:open'
|
||||||
import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'
|
import { OAuthClientProvider } from 'npm:@modelcontextprotocol/sdk/client/auth.js'
|
||||||
import {
|
import {
|
||||||
OAuthClientInformation,
|
OAuthClientInformation,
|
||||||
OAuthClientInformationFull,
|
OAuthClientInformationFull,
|
||||||
OAuthClientInformationSchema,
|
OAuthClientInformationSchema,
|
||||||
OAuthTokens,
|
OAuthTokens,
|
||||||
OAuthTokensSchema,
|
OAuthTokensSchema,
|
||||||
} from '@modelcontextprotocol/sdk/shared/auth.js'
|
} from 'npm:@modelcontextprotocol/sdk/shared/auth.js'
|
||||||
import type { OAuthProviderOptions } from './types'
|
import type { OAuthProviderOptions } from './types.ts'
|
||||||
import { readJsonFile, writeJsonFile, readTextFile, writeTextFile } from './mcp-auth-config'
|
import { readJsonFile, writeJsonFile, readTextFile, writeTextFile } from './mcp-auth-config.ts'
|
||||||
import { getServerUrlHash, log, MCP_REMOTE_VERSION } from './utils'
|
import { getServerUrlHash, log, MCP_REMOTE_VERSION } from './utils.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the OAuthClientProvider interface for Node.js environments.
|
* Implements the OAuthClientProvider interface for Node.js environments.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { EventEmitter } from 'events'
|
import type { EventEmitter } from 'node:events'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for creating an OAuth client provider
|
* Options for creating an OAuth client provider
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { OAuthClientProvider, UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js'
|
import { type OAuthClientProvider, UnauthorizedError } from 'npm:@modelcontextprotocol/sdk/client/auth.js'
|
||||||
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
|
import { SSEClientTransport } from 'npm:@modelcontextprotocol/sdk/client/sse.js'
|
||||||
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
|
import type { Transport } from 'npm:@modelcontextprotocol/sdk/shared/transport.js'
|
||||||
import { OAuthCallbackServerOptions } from './types'
|
import type { OAuthCallbackServerOptions } from './types.ts'
|
||||||
import express from 'express'
|
import express from 'npm:express'
|
||||||
import net from 'net'
|
import net from 'node:net'
|
||||||
import crypto from 'crypto'
|
import crypto from 'node:crypto'
|
||||||
|
|
||||||
// Package version from package.json
|
// Package version from deno.json (set a constant for now)
|
||||||
export const MCP_REMOTE_VERSION = require('../../package.json').version
|
export const MCP_REMOTE_VERSION = '1.0.0' // TODO: Find better way to get version in Deno
|
||||||
|
|
||||||
const pid = process.pid
|
const pid = Deno.pid
|
||||||
export function log(str: string, ...rest: unknown[]) {
|
export function log(str: string, ...rest: unknown[]) {
|
||||||
// Using stderr so that it doesn't interfere with stdout
|
// Using stderr so that it doesn't interfere with stdout
|
||||||
console.error(`[${pid}] ${str}`, ...rest)
|
console.error(`[${pid}] ${str}`, ...rest)
|
||||||
|
|
19
src/proxy.ts
19
src/proxy.ts
|
@ -1,19 +1,20 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
/// <reference lib="deno.ns" />
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MCP Proxy with OAuth support
|
* MCP Proxy with OAuth support
|
||||||
* A bidirectional proxy between a local STDIO MCP server and a remote SSE server with OAuth authentication.
|
* A bidirectional proxy between a local STDIO MCP server and a remote SSE server with OAuth authentication.
|
||||||
*
|
*
|
||||||
* Run with: npx tsx proxy.ts https://example.remote/server [callback-port]
|
* Run with: deno run --allow-net --allow-env --allow-read --allow-run src/proxy.ts https://example.remote/server [callback-port]
|
||||||
*
|
*
|
||||||
* If callback-port is not specified, an available port will be automatically selected.
|
* If callback-port is not specified, an available port will be automatically selected.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events'
|
import { EventEmitter } from 'node:events'
|
||||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
import { StdioServerTransport } from 'npm:@modelcontextprotocol/sdk/server/stdio.js'
|
||||||
import { connectToRemoteServer, log, mcpProxy, parseCommandLineArgs, setupSignalHandlers, getServerUrlHash } from './lib/utils'
|
import { connectToRemoteServer, log, mcpProxy, parseCommandLineArgs, setupSignalHandlers, getServerUrlHash } from './lib/utils.ts'
|
||||||
import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider'
|
import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider.ts'
|
||||||
import { coordinateAuth } from './lib/coordination'
|
import { coordinateAuth } from './lib/coordination.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main function to run the proxy
|
* Main function to run the proxy
|
||||||
|
@ -94,16 +95,16 @@ to the CA certificate file. If using claude_desktop_config.json, this might look
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
server.close()
|
server.close()
|
||||||
process.exit(1)
|
Deno.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse command-line arguments and run the proxy
|
// Parse command-line arguments and run the proxy
|
||||||
parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts <https://server-url> [callback-port]')
|
parseCommandLineArgs(Deno.args, 3334, 'Usage: deno run src/proxy.ts <https://server-url> [callback-port]')
|
||||||
.then(({ serverUrl, callbackPort, headers }) => {
|
.then(({ serverUrl, callbackPort, headers }) => {
|
||||||
return runProxy(serverUrl, callbackPort, headers)
|
return runProxy(serverUrl, callbackPort, headers)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
log('Fatal error:', error)
|
log('Fatal error:', error)
|
||||||
process.exit(1)
|
Deno.exit(1)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue