bincode-cli 1.0.1
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/AGENTS.md +27 -0
- package/README.md +15 -0
- package/bin/bincode +98 -0
- package/bunfig.toml +4 -0
- package/package.json +124 -0
- package/parsers-config.ts +239 -0
- package/script/build.ts +167 -0
- package/script/postinstall.mjs +206 -0
- package/script/publish.ts +99 -0
- package/script/schema.ts +47 -0
- package/src/acp/README.md +164 -0
- package/src/acp/agent.ts +1051 -0
- package/src/acp/session.ts +101 -0
- package/src/acp/types.ts +22 -0
- package/src/agent/agent.ts +398 -0
- package/src/agent/generate.txt +75 -0
- package/src/agent/prompt/compaction.txt +12 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/summary.txt +10 -0
- package/src/agent/prompt/title.txt +36 -0
- package/src/auth/bineric-login.ts +506 -0
- package/src/auth/index.ts +70 -0
- package/src/bun/index.ts +114 -0
- package/src/bus/bus-event.ts +43 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +105 -0
- package/src/cli/auth-check.ts +61 -0
- package/src/cli/bootstrap.ts +21 -0
- package/src/cli/cmd/acp.ts +88 -0
- package/src/cli/cmd/agent.ts +256 -0
- package/src/cli/cmd/auth.ts +436 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/debug/config.ts +15 -0
- package/src/cli/cmd/debug/file.ts +91 -0
- package/src/cli/cmd/debug/index.ts +43 -0
- package/src/cli/cmd/debug/lsp.ts +48 -0
- package/src/cli/cmd/debug/ripgrep.ts +83 -0
- package/src/cli/cmd/debug/scrap.ts +15 -0
- package/src/cli/cmd/debug/skill.ts +15 -0
- package/src/cli/cmd/debug/snapshot.ts +48 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1399 -0
- package/src/cli/cmd/import.ts +98 -0
- package/src/cli/cmd/login.ts +112 -0
- package/src/cli/cmd/logout.ts +38 -0
- package/src/cli/cmd/mcp.ts +654 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/pr.ts +112 -0
- package/src/cli/cmd/run.ts +368 -0
- package/src/cli/cmd/serve.ts +31 -0
- package/src/cli/cmd/session.ts +106 -0
- package/src/cli/cmd/stats.ts +298 -0
- package/src/cli/cmd/tui/app.tsx +669 -0
- package/src/cli/cmd/tui/attach.ts +30 -0
- package/src/cli/cmd/tui/component/border.tsx +21 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +123 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +223 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +224 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/logo.tsx +32 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +560 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +1052 -0
- package/src/cli/cmd/tui/context/args.tsx +14 -0
- package/src/cli/cmd/tui/context/directory.ts +13 -0
- package/src/cli/cmd/tui/context/exit.tsx +23 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +101 -0
- package/src/cli/cmd/tui/context/kv.tsx +49 -0
- package/src/cli/cmd/tui/context/local.tsx +339 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +46 -0
- package/src/cli/cmd/tui/context/sdk.tsx +74 -0
- package/src/cli/cmd/tui/context/sync.tsx +372 -0
- package/src/cli/cmd/tui/context/theme/aura.json +69 -0
- package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
- package/src/cli/cmd/tui/context/theme/bincode.json +245 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
- package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
- package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
- package/src/cli/cmd/tui/context/theme/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
- package/src/cli/cmd/tui/context/theme/orng.json +245 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
- package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
- package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
- package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme.tsx +1109 -0
- package/src/cli/cmd/tui/event.ts +40 -0
- package/src/cli/cmd/tui/routes/home.tsx +105 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +141 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +1888 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +321 -0
- package/src/cli/cmd/tui/spawn.ts +60 -0
- package/src/cli/cmd/tui/thread.ts +120 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +330 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +100 -0
- package/src/cli/cmd/tui/util/clipboard.ts +127 -0
- package/src/cli/cmd/tui/util/editor.ts +32 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/worker.ts +63 -0
- package/src/cli/cmd/uninstall.ts +344 -0
- package/src/cli/cmd/upgrade.ts +67 -0
- package/src/cli/cmd/web.ts +84 -0
- package/src/cli/error.ts +55 -0
- package/src/cli/ui.ts +84 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +80 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +97 -0
- package/src/config/config.ts +995 -0
- package/src/config/markdown.ts +41 -0
- package/src/env/index.ts +26 -0
- package/src/file/ignore.ts +83 -0
- package/src/file/index.ts +328 -0
- package/src/file/ripgrep.ts +393 -0
- package/src/file/time.ts +64 -0
- package/src/file/watcher.ts +103 -0
- package/src/flag/flag.ts +46 -0
- package/src/format/formatter.ts +315 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +52 -0
- package/src/id/id.ts +73 -0
- package/src/ide/index.ts +76 -0
- package/src/index.ts +217 -0
- package/src/installation/index.ts +196 -0
- package/src/lsp/client.ts +229 -0
- package/src/lsp/index.ts +485 -0
- package/src/lsp/language.ts +116 -0
- package/src/lsp/server.ts +1895 -0
- package/src/mcp/auth.ts +135 -0
- package/src/mcp/index.ts +654 -0
- package/src/mcp/oauth-callback.ts +200 -0
- package/src/mcp/oauth-provider.ts +154 -0
- package/src/patch/index.ts +622 -0
- package/src/permission/index.ts +199 -0
- package/src/plugin/index.ts +101 -0
- package/src/project/bootstrap.ts +31 -0
- package/src/project/instance.ts +78 -0
- package/src/project/project.ts +221 -0
- package/src/project/state.ts +65 -0
- package/src/project/vcs.ts +76 -0
- package/src/provider/auth.ts +143 -0
- package/src/provider/models-macro.ts +11 -0
- package/src/provider/models.ts +106 -0
- package/src/provider/provider.ts +1071 -0
- package/src/provider/sdk/openai-compatible/src/README.md +5 -0
- package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
- package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +101 -0
- package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
- package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
- package/src/provider/transform.ts +455 -0
- package/src/pty/index.ts +231 -0
- package/src/server/error.ts +36 -0
- package/src/server/project.ts +79 -0
- package/src/server/server.ts +2642 -0
- package/src/server/tui.ts +71 -0
- package/src/session/compaction.ts +223 -0
- package/src/session/index.ts +458 -0
- package/src/session/llm.ts +201 -0
- package/src/session/message-v2.ts +659 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +409 -0
- package/src/session/prompt/anthropic-20250930.txt +166 -0
- package/src/session/prompt/anthropic.txt +104 -0
- package/src/session/prompt/anthropic_spoof.txt +1 -0
- package/src/session/prompt/beast.txt +147 -0
- package/src/session/prompt/build-switch.txt +5 -0
- package/src/session/prompt/codex.txt +318 -0
- package/src/session/prompt/copilot-gpt-5.txt +143 -0
- package/src/session/prompt/gemini.txt +155 -0
- package/src/session/prompt/max-steps.txt +16 -0
- package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
- package/src/session/prompt/plan.txt +26 -0
- package/src/session/prompt/polaris.txt +106 -0
- package/src/session/prompt/qwen.txt +109 -0
- package/src/session/prompt.ts +1446 -0
- package/src/session/retry.ts +86 -0
- package/src/session/revert.ts +108 -0
- package/src/session/status.ts +76 -0
- package/src/session/summary.ts +194 -0
- package/src/session/system.ts +120 -0
- package/src/session/todo.ts +37 -0
- package/src/share/share-next.ts +194 -0
- package/src/share/share.ts +87 -0
- package/src/shell/shell.ts +67 -0
- package/src/skill/index.ts +1 -0
- package/src/skill/skill.ts +83 -0
- package/src/snapshot/index.ts +197 -0
- package/src/storage/storage.ts +226 -0
- package/src/tool/bash.ts +306 -0
- package/src/tool/bash.txt +158 -0
- package/src/tool/batch.ts +175 -0
- package/src/tool/batch.txt +24 -0
- package/src/tool/codesearch.ts +138 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/edit.ts +675 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/glob.ts +65 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +121 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +110 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp-diagnostics.ts +26 -0
- package/src/tool/lsp-diagnostics.txt +1 -0
- package/src/tool/lsp-hover.ts +31 -0
- package/src/tool/lsp-hover.txt +1 -0
- package/src/tool/lsp.ts +87 -0
- package/src/tool/lsp.txt +19 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/patch.ts +233 -0
- package/src/tool/patch.txt +1 -0
- package/src/tool/read.ts +219 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +162 -0
- package/src/tool/skill.ts +100 -0
- package/src/tool/task.ts +136 -0
- package/src/tool/task.txt +60 -0
- package/src/tool/todo.ts +39 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +71 -0
- package/src/tool/webfetch.ts +187 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +150 -0
- package/src/tool/websearch.txt +11 -0
- package/src/tool/write.ts +110 -0
- package/src/tool/write.txt +8 -0
- package/src/util/archive.ts +16 -0
- package/src/util/color.ts +19 -0
- package/src/util/context.ts +25 -0
- package/src/util/defer.ts +12 -0
- package/src/util/eventloop.ts +20 -0
- package/src/util/filesystem.ts +83 -0
- package/src/util/fn.ts +11 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +102 -0
- package/src/util/lazy.ts +11 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +180 -0
- package/src/util/queue.ts +32 -0
- package/src/util/rpc.ts +42 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/wildcard.ts +54 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { Log } from "../util/log"
|
|
2
|
+
import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH } from "./oauth-provider"
|
|
3
|
+
|
|
4
|
+
const log = Log.create({ service: "mcp.oauth-callback" })
|
|
5
|
+
|
|
6
|
+
const HTML_SUCCESS = `<!DOCTYPE html>
|
|
7
|
+
<html>
|
|
8
|
+
<head>
|
|
9
|
+
<title>Bincode - Authorization Successful</title>
|
|
10
|
+
<style>
|
|
11
|
+
body { font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e; color: #eee; }
|
|
12
|
+
.container { text-align: center; padding: 2rem; }
|
|
13
|
+
h1 { color: #4ade80; margin-bottom: 1rem; }
|
|
14
|
+
p { color: #aaa; }
|
|
15
|
+
</style>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<div class="container">
|
|
19
|
+
<h1>Authorization Successful</h1>
|
|
20
|
+
<p>You can close this window and return to Bincode.</p>
|
|
21
|
+
</div>
|
|
22
|
+
<script>setTimeout(() => window.close(), 2000);</script>
|
|
23
|
+
</body>
|
|
24
|
+
</html>`
|
|
25
|
+
|
|
26
|
+
const HTML_ERROR = (error: string) => `<!DOCTYPE html>
|
|
27
|
+
<html>
|
|
28
|
+
<head>
|
|
29
|
+
<title>Bincode - Authorization Failed</title>
|
|
30
|
+
<style>
|
|
31
|
+
body { font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e; color: #eee; }
|
|
32
|
+
.container { text-align: center; padding: 2rem; }
|
|
33
|
+
h1 { color: #f87171; margin-bottom: 1rem; }
|
|
34
|
+
p { color: #aaa; }
|
|
35
|
+
.error { color: #fca5a5; font-family: monospace; margin-top: 1rem; padding: 1rem; background: rgba(248,113,113,0.1); border-radius: 0.5rem; }
|
|
36
|
+
</style>
|
|
37
|
+
</head>
|
|
38
|
+
<body>
|
|
39
|
+
<div class="container">
|
|
40
|
+
<h1>Authorization Failed</h1>
|
|
41
|
+
<p>An error occurred during authorization.</p>
|
|
42
|
+
<div class="error">${error}</div>
|
|
43
|
+
</div>
|
|
44
|
+
</body>
|
|
45
|
+
</html>`
|
|
46
|
+
|
|
47
|
+
interface PendingAuth {
|
|
48
|
+
resolve: (code: string) => void
|
|
49
|
+
reject: (error: Error) => void
|
|
50
|
+
timeout: ReturnType<typeof setTimeout>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export namespace McpOAuthCallback {
|
|
54
|
+
let server: ReturnType<typeof Bun.serve> | undefined
|
|
55
|
+
const pendingAuths = new Map<string, PendingAuth>()
|
|
56
|
+
|
|
57
|
+
const CALLBACK_TIMEOUT_MS = 5 * 60 * 1000 // 5 minutes
|
|
58
|
+
|
|
59
|
+
export async function ensureRunning(): Promise<void> {
|
|
60
|
+
if (server) return
|
|
61
|
+
|
|
62
|
+
const running = await isPortInUse()
|
|
63
|
+
if (running) {
|
|
64
|
+
log.info("oauth callback server already running on another instance", { port: OAUTH_CALLBACK_PORT })
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
server = Bun.serve({
|
|
69
|
+
port: OAUTH_CALLBACK_PORT,
|
|
70
|
+
fetch(req) {
|
|
71
|
+
const url = new URL(req.url)
|
|
72
|
+
|
|
73
|
+
if (url.pathname !== OAUTH_CALLBACK_PATH) {
|
|
74
|
+
return new Response("Not found", { status: 404 })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const code = url.searchParams.get("code")
|
|
78
|
+
const state = url.searchParams.get("state")
|
|
79
|
+
const error = url.searchParams.get("error")
|
|
80
|
+
const errorDescription = url.searchParams.get("error_description")
|
|
81
|
+
|
|
82
|
+
log.info("received oauth callback", { hasCode: !!code, state, error })
|
|
83
|
+
|
|
84
|
+
// Enforce state parameter presence
|
|
85
|
+
if (!state) {
|
|
86
|
+
const errorMsg = "Missing required state parameter - potential CSRF attack"
|
|
87
|
+
log.error("oauth callback missing state parameter", { url: url.toString() })
|
|
88
|
+
return new Response(HTML_ERROR(errorMsg), {
|
|
89
|
+
status: 400,
|
|
90
|
+
headers: { "Content-Type": "text/html" },
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (error) {
|
|
95
|
+
const errorMsg = errorDescription || error
|
|
96
|
+
if (pendingAuths.has(state)) {
|
|
97
|
+
const pending = pendingAuths.get(state)!
|
|
98
|
+
clearTimeout(pending.timeout)
|
|
99
|
+
pendingAuths.delete(state)
|
|
100
|
+
pending.reject(new Error(errorMsg))
|
|
101
|
+
}
|
|
102
|
+
return new Response(HTML_ERROR(errorMsg), {
|
|
103
|
+
headers: { "Content-Type": "text/html" },
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!code) {
|
|
108
|
+
return new Response(HTML_ERROR("No authorization code provided"), {
|
|
109
|
+
status: 400,
|
|
110
|
+
headers: { "Content-Type": "text/html" },
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Validate state parameter
|
|
115
|
+
if (!pendingAuths.has(state)) {
|
|
116
|
+
const errorMsg = "Invalid or expired state parameter - potential CSRF attack"
|
|
117
|
+
log.error("oauth callback with invalid state", { state, pendingStates: Array.from(pendingAuths.keys()) })
|
|
118
|
+
return new Response(HTML_ERROR(errorMsg), {
|
|
119
|
+
status: 400,
|
|
120
|
+
headers: { "Content-Type": "text/html" },
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const pending = pendingAuths.get(state)!
|
|
125
|
+
|
|
126
|
+
clearTimeout(pending.timeout)
|
|
127
|
+
pendingAuths.delete(state)
|
|
128
|
+
pending.resolve(code)
|
|
129
|
+
|
|
130
|
+
return new Response(HTML_SUCCESS, {
|
|
131
|
+
headers: { "Content-Type": "text/html" },
|
|
132
|
+
})
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
log.info("oauth callback server started", { port: OAUTH_CALLBACK_PORT })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function waitForCallback(oauthState: string): Promise<string> {
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
const timeout = setTimeout(() => {
|
|
142
|
+
if (pendingAuths.has(oauthState)) {
|
|
143
|
+
pendingAuths.delete(oauthState)
|
|
144
|
+
reject(new Error("OAuth callback timeout - authorization took too long"))
|
|
145
|
+
}
|
|
146
|
+
}, CALLBACK_TIMEOUT_MS)
|
|
147
|
+
|
|
148
|
+
pendingAuths.set(oauthState, { resolve, reject, timeout })
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function cancelPending(mcpName: string): void {
|
|
153
|
+
const pending = pendingAuths.get(mcpName)
|
|
154
|
+
if (pending) {
|
|
155
|
+
clearTimeout(pending.timeout)
|
|
156
|
+
pendingAuths.delete(mcpName)
|
|
157
|
+
pending.reject(new Error("Authorization cancelled"))
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function isPortInUse(): Promise<boolean> {
|
|
162
|
+
return new Promise((resolve) => {
|
|
163
|
+
Bun.connect({
|
|
164
|
+
hostname: "127.0.0.1",
|
|
165
|
+
port: OAUTH_CALLBACK_PORT,
|
|
166
|
+
socket: {
|
|
167
|
+
open(socket) {
|
|
168
|
+
socket.end()
|
|
169
|
+
resolve(true)
|
|
170
|
+
},
|
|
171
|
+
error() {
|
|
172
|
+
resolve(false)
|
|
173
|
+
},
|
|
174
|
+
data() {},
|
|
175
|
+
close() {},
|
|
176
|
+
},
|
|
177
|
+
}).catch(() => {
|
|
178
|
+
resolve(false)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function stop(): Promise<void> {
|
|
184
|
+
if (server) {
|
|
185
|
+
server.stop()
|
|
186
|
+
server = undefined
|
|
187
|
+
log.info("oauth callback server stopped")
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const [name, pending] of pendingAuths) {
|
|
191
|
+
clearTimeout(pending.timeout)
|
|
192
|
+
pending.reject(new Error("OAuth callback server stopped"))
|
|
193
|
+
}
|
|
194
|
+
pendingAuths.clear()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function isRunning(): boolean {
|
|
198
|
+
return server !== undefined
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js"
|
|
2
|
+
import type {
|
|
3
|
+
OAuthClientMetadata,
|
|
4
|
+
OAuthTokens,
|
|
5
|
+
OAuthClientInformation,
|
|
6
|
+
OAuthClientInformationFull,
|
|
7
|
+
} from "@modelcontextprotocol/sdk/shared/auth.js"
|
|
8
|
+
import { McpAuth } from "./auth"
|
|
9
|
+
import { Log } from "../util/log"
|
|
10
|
+
|
|
11
|
+
const log = Log.create({ service: "mcp.oauth" })
|
|
12
|
+
|
|
13
|
+
const OAUTH_CALLBACK_PORT = 19876
|
|
14
|
+
const OAUTH_CALLBACK_PATH = "/mcp/oauth/callback"
|
|
15
|
+
|
|
16
|
+
export interface McpOAuthConfig {
|
|
17
|
+
clientId?: string
|
|
18
|
+
clientSecret?: string
|
|
19
|
+
scope?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface McpOAuthCallbacks {
|
|
23
|
+
onRedirect: (url: URL) => void | Promise<void>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class McpOAuthProvider implements OAuthClientProvider {
|
|
27
|
+
constructor(
|
|
28
|
+
private mcpName: string,
|
|
29
|
+
private serverUrl: string,
|
|
30
|
+
private config: McpOAuthConfig,
|
|
31
|
+
private callbacks: McpOAuthCallbacks,
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
get redirectUrl(): string {
|
|
35
|
+
return `http://127.0.0.1:${OAUTH_CALLBACK_PORT}${OAUTH_CALLBACK_PATH}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get clientMetadata(): OAuthClientMetadata {
|
|
39
|
+
return {
|
|
40
|
+
redirect_uris: [this.redirectUrl],
|
|
41
|
+
client_name: "Bincode",
|
|
42
|
+
client_uri: "https://bincode.ai",
|
|
43
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
44
|
+
response_types: ["code"],
|
|
45
|
+
token_endpoint_auth_method: this.config.clientSecret ? "client_secret_post" : "none",
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async clientInformation(): Promise<OAuthClientInformation | undefined> {
|
|
50
|
+
// Check config first (pre-registered client)
|
|
51
|
+
if (this.config.clientId) {
|
|
52
|
+
return {
|
|
53
|
+
client_id: this.config.clientId,
|
|
54
|
+
client_secret: this.config.clientSecret,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check stored client info (from dynamic registration)
|
|
59
|
+
// Use getForUrl to validate credentials are for the current server URL
|
|
60
|
+
const entry = await McpAuth.getForUrl(this.mcpName, this.serverUrl)
|
|
61
|
+
if (entry?.clientInfo) {
|
|
62
|
+
// Check if client secret has expired
|
|
63
|
+
if (entry.clientInfo.clientSecretExpiresAt && entry.clientInfo.clientSecretExpiresAt < Date.now() / 1000) {
|
|
64
|
+
log.info("client secret expired, need to re-register", { mcpName: this.mcpName })
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
client_id: entry.clientInfo.clientId,
|
|
69
|
+
client_secret: entry.clientInfo.clientSecret,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// No client info or URL changed - will trigger dynamic registration
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async saveClientInformation(info: OAuthClientInformationFull): Promise<void> {
|
|
78
|
+
await McpAuth.updateClientInfo(
|
|
79
|
+
this.mcpName,
|
|
80
|
+
{
|
|
81
|
+
clientId: info.client_id,
|
|
82
|
+
clientSecret: info.client_secret,
|
|
83
|
+
clientIdIssuedAt: info.client_id_issued_at,
|
|
84
|
+
clientSecretExpiresAt: info.client_secret_expires_at,
|
|
85
|
+
},
|
|
86
|
+
this.serverUrl,
|
|
87
|
+
)
|
|
88
|
+
log.info("saved dynamically registered client", {
|
|
89
|
+
mcpName: this.mcpName,
|
|
90
|
+
clientId: info.client_id,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async tokens(): Promise<OAuthTokens | undefined> {
|
|
95
|
+
// Use getForUrl to validate tokens are for the current server URL
|
|
96
|
+
const entry = await McpAuth.getForUrl(this.mcpName, this.serverUrl)
|
|
97
|
+
if (!entry?.tokens) return undefined
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
access_token: entry.tokens.accessToken,
|
|
101
|
+
token_type: "Bearer",
|
|
102
|
+
refresh_token: entry.tokens.refreshToken,
|
|
103
|
+
expires_in: entry.tokens.expiresAt
|
|
104
|
+
? Math.max(0, Math.floor(entry.tokens.expiresAt - Date.now() / 1000))
|
|
105
|
+
: undefined,
|
|
106
|
+
scope: entry.tokens.scope,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async saveTokens(tokens: OAuthTokens): Promise<void> {
|
|
111
|
+
await McpAuth.updateTokens(
|
|
112
|
+
this.mcpName,
|
|
113
|
+
{
|
|
114
|
+
accessToken: tokens.access_token,
|
|
115
|
+
refreshToken: tokens.refresh_token,
|
|
116
|
+
expiresAt: tokens.expires_in ? Date.now() / 1000 + tokens.expires_in : undefined,
|
|
117
|
+
scope: tokens.scope,
|
|
118
|
+
},
|
|
119
|
+
this.serverUrl,
|
|
120
|
+
)
|
|
121
|
+
log.info("saved oauth tokens", { mcpName: this.mcpName })
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
|
|
125
|
+
log.info("redirecting to authorization", { mcpName: this.mcpName, url: authorizationUrl.toString() })
|
|
126
|
+
await this.callbacks.onRedirect(authorizationUrl)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async saveCodeVerifier(codeVerifier: string): Promise<void> {
|
|
130
|
+
await McpAuth.updateCodeVerifier(this.mcpName, codeVerifier)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async codeVerifier(): Promise<string> {
|
|
134
|
+
const entry = await McpAuth.get(this.mcpName)
|
|
135
|
+
if (!entry?.codeVerifier) {
|
|
136
|
+
throw new Error(`No code verifier saved for MCP server: ${this.mcpName}`)
|
|
137
|
+
}
|
|
138
|
+
return entry.codeVerifier
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async saveState(state: string): Promise<void> {
|
|
142
|
+
await McpAuth.updateOAuthState(this.mcpName, state)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async state(): Promise<string> {
|
|
146
|
+
const entry = await McpAuth.get(this.mcpName)
|
|
147
|
+
if (!entry?.oauthState) {
|
|
148
|
+
throw new Error(`No OAuth state saved for MCP server: ${this.mcpName}`)
|
|
149
|
+
}
|
|
150
|
+
return entry.oauthState
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH }
|