Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions cmd/mcpproxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
bbolterrors "go.etcd.io/bbolt/errors"
"go.uber.org/zap"

"github.com/smart-mcp-proxy/mcpproxy-go/internal/branding"
clioutput "github.com/smart-mcp-proxy/mcpproxy-go/internal/cli/output"
"github.com/smart-mcp-proxy/mcpproxy-go/internal/cliclient"
"github.com/smart-mcp-proxy/mcpproxy-go/internal/config"
Expand Down Expand Up @@ -98,8 +99,15 @@ func main() {
config.SetRegistriesInitCallback(registries.SetRegistriesFromConfig)

rootCmd := &cobra.Command{
Use: "mcpproxy",
Short: "Smart MCP Proxy - Intelligent tool discovery and proxying for Model Context Protocol servers",
Use: "mcpproxy",
Short: "Smart MCP Proxy - Intelligent tool discovery and proxying for Model Context Protocol servers",
// Long is shown in `mcpproxy --help`. It carries the project links
// (discussion #948) so the way back to the homepage/repo/docs is always
// one --help away, no matter how the binary was installed.
Long: "Smart MCP Proxy - Intelligent tool discovery and proxying for Model Context Protocol servers.\n\n" +
"Homepage: " + branding.Homepage + "\n" +
"GitHub: " + branding.Repo + "\n" +
"Docs: " + branding.Docs,
Version: version,
}
rootCmd.SetVersionTemplate(versionLine())
Expand Down
9 changes: 7 additions & 2 deletions cmd/mcpproxy/version_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/spf13/cobra"

"github.com/smart-mcp-proxy/mcpproxy-go/internal/branding"
clioutput "github.com/smart-mcp-proxy/mcpproxy-go/internal/cli/output"
)

Expand All @@ -19,9 +20,13 @@ var (
)

// versionLine returns the human-readable version string shared by
// `mcpproxy version` and the root command's --version flag.
// `mcpproxy version` and the root command's --version flag. It carries the
// project links (discussion #948) so a user who found the binary via Homebrew /
// a plugin / an AI recommendation can always find the homepage and repo.
func versionLine() string {
return fmt.Sprintf("MCPProxy %s (%s) %s/%s\n", version, Edition, runtime.GOOS, runtime.GOARCH)
return fmt.Sprintf("MCPProxy %s (%s) %s/%s\nHomepage: %s\nGitHub: %s\nDocs: %s\n",
version, Edition, runtime.GOOS, runtime.GOARCH,
branding.Homepage, branding.Repo, branding.Docs)
}

// VersionInfo is the machine-readable version payload for -o json/yaml.
Expand Down
14 changes: 11 additions & 3 deletions cmd/mcpproxy/version_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,17 @@ func TestVersionCommandTableOutput(t *testing.T) {
t.Fatalf("Execute() error: %v", err)
}

want := fmt.Sprintf("MCPProxy %s (%s) %s/%s\n", version, Edition, runtime.GOOS, runtime.GOARCH)
if got := buf.String(); got != want {
t.Errorf("table output mismatch:\n got: %q\nwant: %q", got, want)
got := buf.String()
// First line is the canonical version string.
wantFirst := fmt.Sprintf("MCPProxy %s (%s) %s/%s", version, Edition, runtime.GOOS, runtime.GOARCH)
if !strings.HasPrefix(got, wantFirst+"\n") {
t.Errorf("table output should start with %q, got: %q", wantFirst, got)
}
// Discussion #948: the version output carries the project links.
for _, link := range []string{"https://mcpproxy.app", "https://github.com/smart-mcp-proxy/mcpproxy-go", "https://docs.mcpproxy.app"} {
if !strings.Contains(got, link) {
t.Errorf("version output must contain project link %q, got: %q", link, got)
}
}
}

Expand Down
6 changes: 5 additions & 1 deletion frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
state so the content fluidly reclaims space when the sidebar shrinks
to its icon rail. -->
<div
class="drawer-content grid grid-rows-[auto_1fr] h-screen bg-base-200 transition-[padding] duration-200 ease-out"
class="drawer-content grid grid-rows-[auto_1fr_auto] h-screen bg-base-200 transition-[padding] duration-200 ease-out"
:class="systemStore.sidebarCollapsed ? 'lg:pl-14' : 'lg:pl-64'"
>
<!-- Top Header -->
Expand All @@ -16,6 +16,9 @@
<main class="overflow-y-auto p-6">
<router-view />
</main>

<!-- Persistent footer with project links (discussion #948) -->
<AppFooter />
</div>

<!-- Sidebar -->
Expand Down Expand Up @@ -43,6 +46,7 @@
import { onMounted, onUnmounted, reactive, ref } from 'vue'
import SidebarNav from '@/components/SidebarNav.vue'
import TopHeader from '@/components/TopHeader.vue'
import AppFooter from '@/components/AppFooter.vue'
import ToastContainer from '@/components/ToastContainer.vue'
import ConnectionStatus from '@/components/ConnectionStatus.vue'
import AuthErrorModal from '@/components/AuthErrorModal.vue'
Expand Down
40 changes: 40 additions & 0 deletions frontend/src/components/AppFooter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!--
Persistent footer shown on every page (discussion #948). Gives a running-app
user the way back to the project — homepage, source, docs, discussions — plus
the running version. Previously the only outbound links were buried on the
settings page.
-->
<template>
<footer
class="shrink-0 border-t border-base-300 bg-base-100 px-6 py-2 text-xs text-base-content/60"
>
<div class="flex flex-wrap items-center justify-center gap-x-2 gap-y-1">
<span class="font-medium text-base-content/70">
MCPProxy<span v-if="displayVersion">&nbsp;{{ displayVersion }}</span>
</span>
<span aria-hidden="true">·</span>
<a :href="links.homepage" target="_blank" rel="noopener noreferrer" class="link link-hover">Homepage</a>
<span aria-hidden="true">·</span>
<a :href="links.github" target="_blank" rel="noopener noreferrer" class="link link-hover">GitHub</a>
<span aria-hidden="true">·</span>
<a :href="links.docs" target="_blank" rel="noopener noreferrer" class="link link-hover">Docs</a>
<span aria-hidden="true">·</span>
<a :href="links.discussions" target="_blank" rel="noopener noreferrer" class="link link-hover">Discussions</a>
</div>
</footer>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useSystemStore } from '@/stores/system'
import { PROJECT_LINKS as links } from '@/config/links'

const systemStore = useSystemStore()
// The store version already carries a leading "v" (e.g. "v0.58.1"); normalise to
// a single "v" so the footer never shows "vv0.58.1".
const displayVersion = computed(() => {
const v = systemStore.version
if (!v) return ''
return v.startsWith('v') ? v : `v${v}`
})
</script>
10 changes: 10 additions & 0 deletions frontend/src/config/links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Canonical project links (discussion #948). Kept in sync with the Go
// (internal/branding) and Swift (ProjectLinks) copies. Users arriving via
// Homebrew / plugins / AI recommendations often never see the repo, so every
// surface of the running app must carry the way back to the project.
export const PROJECT_LINKS = {
homepage: 'https://mcpproxy.app',
github: 'https://github.com/smart-mcp-proxy/mcpproxy-go',
docs: 'https://docs.mcpproxy.app',
discussions: 'https://github.com/smart-mcp-proxy/mcpproxy-go/discussions',
} as const
30 changes: 30 additions & 0 deletions internal/branding/branding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Package branding holds the canonical, user-facing project identifiers and
// URLs (homepage, repo, docs, issues) so every surface that points a user back
// to the project — CLI --version/--help, the MCP serverInfo instructions, docs
// links in errors — uses one source of truth. The macOS tray (Swift) and Web UI
// (TypeScript) keep their own copies of these strings; keep all three in sync.
//
// Added for discussion #948: users arriving via Homebrew / plugins / AI
// recommendations often never see the GitHub repo, so the running app must
// carry the way back to the project.
package branding

const (
// ProductName is the single product name used everywhere.
ProductName = "MCPProxy"

// Homepage is the marketing/landing site.
Homepage = "https://mcpproxy.app"

// Repo is the GitHub source repository.
Repo = "https://github.com/smart-mcp-proxy/mcpproxy-go"

// Docs is the documentation site.
Docs = "https://docs.mcpproxy.app"

// Issues is the GitHub issue tracker (for "Report an issue").
Issues = "https://github.com/smart-mcp-proxy/mcpproxy-go/issues"

// Discussions is the GitHub Discussions board.
Discussions = "https://github.com/smart-mcp-proxy/mcpproxy-go/discussions"
)
6 changes: 5 additions & 1 deletion internal/server/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"unicode"

"github.com/smart-mcp-proxy/mcpproxy-go/internal/auth"
"github.com/smart-mcp-proxy/mcpproxy-go/internal/branding"
"github.com/smart-mcp-proxy/mcpproxy-go/internal/cache"
"github.com/smart-mcp-proxy/mcpproxy-go/internal/config"
"github.com/smart-mcp-proxy/mcpproxy-go/internal/contracts"
Expand Down Expand Up @@ -75,7 +76,10 @@ const (
"When 'code_execution' is exposed, you may instead orchestrate several discovered tools in a single step with JavaScript. " +
"When upstream tools are listed directly (named 'server__tool'), just call them by name. " +
"Do NOT use 'search_servers' to find existing tools — it searches EXTERNAL registries for adding NEW servers only. " +
"Use 'upstream_servers' with operation 'list' to see currently connected servers and their status."
"Use 'upstream_servers' with operation 'list' to see currently connected servers and their status. " +
// Discussion #948: carry the project links at the protocol level so an
// agent (and anyone reading its logs) can always find the project.
"ABOUT: MCPProxy homepage " + branding.Homepage + ", source " + branding.Repo + ", docs " + branding.Docs + "."

// Connection status constants
statusError = "error"
Expand Down
10 changes: 10 additions & 0 deletions internal/server/mcp_instructions_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package server

import (
"github.com/smart-mcp-proxy/mcpproxy-go/internal/branding"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -36,3 +37,12 @@ func TestDefaultInstructions_ContainsKeyTerms(t *testing.T) {
assert.Contains(t, defaultInstructions, "code_execution")
assert.Contains(t, defaultInstructions, "server__tool")
}

// TestServer_DefaultInstructions_CarriesProjectLinks verifies discussion #948:
// the protocol-level serverInfo instructions carry the homepage/repo/docs so an
// agent (and anyone reading its logs) can find the project.
func TestServer_DefaultInstructions_CarriesProjectLinks(t *testing.T) {
assert.Contains(t, defaultInstructions, branding.Homepage)
assert.Contains(t, defaultInstructions, branding.Repo)
assert.Contains(t, defaultInstructions, branding.Docs)
}
59 changes: 59 additions & 0 deletions native/macos/MCPProxy/MCPProxy/MCPProxyApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1326,7 +1326,24 @@ final class AppController: NSObject, NSApplicationDelegate, NSWindowDelegate, NS
menu.addItem(stop)
}

// Help / project links (discussion #948): a running-app user must always
// have a way back to the homepage, docs, and issue tracker.
menu.addItem(.separator())

let docsItem = NSMenuItem(title: "Documentation", action: #selector(openDocumentation), keyEquivalent: "")
docsItem.target = self
menu.addItem(docsItem)

let issueItem = NSMenuItem(title: "Report an Issue…", action: #selector(reportIssue), keyEquivalent: "")
issueItem.target = self
menu.addItem(issueItem)

let aboutItem = NSMenuItem(title: "About MCPProxy", action: #selector(showAboutPanel), keyEquivalent: "")
aboutItem.target = self
menu.addItem(aboutItem)

// Quit
menu.addItem(.separator())
let quit = NSMenuItem(title: "Quit MCPProxy", action: #selector(quitApp), keyEquivalent: "q")
quit.target = self
menu.addItem(quit)
Expand Down Expand Up @@ -1620,6 +1637,48 @@ final class AppController: NSObject, NSApplicationDelegate, NSWindowDelegate, NS
NSWorkspace.shared.open(InstancePaths.configFileURL)
}

// MARK: - Project links (discussion #948)

@objc private func openDocumentation() {
NSWorkspace.shared.open(ProjectLinks.docs)
}

@objc private func reportIssue() {
NSWorkspace.shared.open(ProjectLinks.issues)
}

/// Shows the standard About panel (app name + version) with a credits block
/// that links back to the homepage, source, and docs — so "About MCPProxy"
/// carries the way back to the project, not just a version string.
@objc private func showAboutPanel() {
NSApp.activate(ignoringOtherApps: true)

let credits = NSMutableAttributedString()
let body: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 11),
.foregroundColor: NSColor.secondaryLabelColor,
]
credits.append(NSAttributedString(
string: "Smart MCP proxy — intelligent tool discovery, token savings, and security quarantine.\n\n",
attributes: body))
appendLink(to: credits, label: "Homepage", url: ProjectLinks.homepage)
credits.append(NSAttributedString(string: " ", attributes: body))
appendLink(to: credits, label: "GitHub", url: ProjectLinks.github)
credits.append(NSAttributedString(string: " ", attributes: body))
appendLink(to: credits, label: "Documentation", url: ProjectLinks.docs)

NSApp.orderFrontStandardAboutPanel(options: [
.credits: credits
])
}

private func appendLink(to string: NSMutableAttributedString, label: String, url: URL) {
string.append(NSAttributedString(string: label, attributes: [
.link: url,
.font: NSFont.systemFont(ofSize: 11),
]))
}

@objc private func openLogsDirectory() {
let home = FileManager.default.homeDirectoryForCurrentUser
NSWorkspace.shared.open(home.appendingPathComponent("Library/Logs/mcpproxy"))
Expand Down
15 changes: 15 additions & 0 deletions native/macos/MCPProxy/MCPProxy/ProjectLinks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

/// Canonical, user-facing project links (discussion #948). Kept in sync with the
/// Go (`internal/branding`) and Web UI (`frontend/src/config/links.ts`) copies.
///
/// Users arrive via Homebrew / plugins / AI recommendations and often never see
/// the GitHub repo, so the running app — the tray especially, its main surface —
/// must carry the way back to the project.
enum ProjectLinks {
static let homepage = URL(string: "https://mcpproxy.app")!
static let github = URL(string: "https://github.com/smart-mcp-proxy/mcpproxy-go")!
static let docs = URL(string: "https://docs.mcpproxy.app")!
static let issues = URL(string: "https://github.com/smart-mcp-proxy/mcpproxy-go/issues")!
static let discussions = URL(string: "https://github.com/smart-mcp-proxy/mcpproxy-go/discussions")!
}
Loading