From 2caf3b3223cf6456ab483e0e2bc9352119ee0626 Mon Sep 17 00:00:00 2001 From: Furox-Art <177975472+Furox-Art@users.noreply.github.com> Date: Sat, 10 Oct 2026 19:50:06 +0300 Subject: [PATCH 1/2] feat: show build version and git commit in UI and startup log Closes #1000 - packages/backend/src/common/app-version.ts: single source of truth exporting APP_VERSION, APP_COMMIT (APP_COMMIT || SENTRY_RELEASE || dev) and APP_BUILD_DATE (APP_BUILD_DATE || null), moving readAppVersion() out of mcp-endpoint.controller.ts - packages/backend/src/tracing.ts + instrument.ts: use APP_VERSION as the service.version / Sentry release fallback instead of npm_package_version - packages/backend/src/main.ts: log version, commit, build date and mode at startup - packages/backend/src/health/version.controller.ts: new GET /api/version behind AuthGuard('jwt') returning { version, commit, buildDate, deploymentMode }; registered in health.module.ts - packages/frontend/src/components/footer.tsx: show v{version} for signed-in users, with full version+commit in the tooltip - Dockerfile: APP_COMMIT and APP_BUILD_DATE build args to ENV, plus org.opencontainers.image.revision and .version OCI labels - app-version.spec.ts: unit tests for env precedence and dev fallback - docs: deployment.md documents the build args; observability.md notes the service.version source change --- .github/workflows/docker-publish.yml | 2 + Dockerfile | 10 +++- docs/deployment.md | 13 ++++ docs/operations/observability.md | 3 + .../backend/src/common/app-version.spec.ts | 51 ++++++++++++++++ packages/backend/src/common/app-version.ts | 45 ++++++++++++++ packages/backend/src/health/health.module.ts | 3 +- .../backend/src/health/version.controller.ts | 20 +++++++ packages/backend/src/instrument.ts | 3 +- packages/backend/src/main.ts | 4 ++ .../src/mcp-server/mcp-endpoint.controller.ts | 35 +---------- packages/backend/src/tracing.ts | 4 +- .../frontend/src/app/settings/admin/page.tsx | 38 ++++++++++++ packages/frontend/src/components/footer.tsx | 60 ++++++++++++++++++- packages/frontend/src/lib/api.ts | 5 ++ 15 files changed, 256 insertions(+), 40 deletions(-) create mode 100644 packages/backend/src/common/app-version.spec.ts create mode 100644 packages/backend/src/common/app-version.ts create mode 100644 packages/backend/src/health/version.controller.ts diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index f24862443..72e243628 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -69,6 +69,8 @@ jobs: push: true build-args: | SENTRY_RELEASE=${{ github.sha }} + APP_COMMIT=${{ github.sha }} + APP_BUILD_DATE=${{ github.event.head_commit.timestamp }} # Only the source map upload uses it; nothing Sentry-related that # decides where an instance reports is baked into the public image. secrets: | diff --git a/Dockerfile b/Dockerfile index e503ca13c..59f6be47a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -120,6 +120,12 @@ ENV NEXT_TELEMETRY_DISABLED=1 # Release reported by backend and frontend when an operator sets SENTRY_DSN. ARG SENTRY_RELEASE= ENV SENTRY_RELEASE=$SENTRY_RELEASE +# Build provenance: commit and build date, shown in the UI and startup log. +ARG APP_VERSION= +ARG APP_COMMIT= +ENV APP_COMMIT=$APP_COMMIT +ARG APP_BUILD_DATE= +ENV APP_BUILD_DATE=$APP_BUILD_DATE RUN addgroup --system --gid 1001 appuser && \ adduser --system --uid 1001 appuser @@ -153,7 +159,9 @@ RUN chmod +x ./start.sh RUN mkdir -p /app/diagnostics && chown appuser:appuser /app/diagnostics ENV HEAP_SNAPSHOT_DIR=/app/diagnostics -LABEL org.opencontainers.image.title="AnythingMCP" \ +LABEL org.opencontainers.image.revision="${APP_COMMIT}" \ + org.opencontainers.image.version="${APP_VERSION}" \ + org.opencontainers.image.title="AnythingMCP" \ org.opencontainers.image.description="Convert any API into an MCP server — REST, SOAP, GraphQL, Database, MCP Bridge. Self-hosted MCP middleware." \ org.opencontainers.image.url="https://github.com/HelpCode-ai/anythingmcp" \ org.opencontainers.image.source="https://github.com/HelpCode-ai/anythingmcp" \ diff --git a/docs/deployment.md b/docs/deployment.md index fafbdd84e..f129f8472 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -2,6 +2,18 @@ > Docker setup, production deployment, local development, reverse proxy, and environment configuration. +## Build provenance + +When building the image yourself, pass the commit and build date so the UI and startup log show real values instead of `dev`: + +```bash +docker build \ + --build-arg APP_COMMIT=$(git rev-parse --short HEAD) \ + --build-arg APP_BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \ + -t anythingmcp:local . +``` + + [Back to README](../README.md) --- @@ -431,3 +443,4 @@ With both of these in place, AnythingMCP makes no outbound connections to `anyth --- [Back to README](../README.md) | [API Reference](api-reference.md) | [Integration Guides](../README.md#use-it-from-claude-chatgpt-copilot-and-gemini) + diff --git a/docs/operations/observability.md b/docs/operations/observability.md index fbfb44c91..8a53494b2 100644 --- a/docs/operations/observability.md +++ b/docs/operations/observability.md @@ -65,6 +65,8 @@ Service identity: |---|---| | `OTEL_SERVICE_NAME` | `anythingmcp-backend` | | `OTEL_SERVICE_VERSION` | `npm_package_version` | + +The `service.version` value now comes from `APP_VERSION` (resolved from `package.json`), with `APP_COMMIT` and `APP_BUILD_DATE` available as build-time env vars. | `OTEL_DEPLOYMENT_ENVIRONMENT` | `NODE_ENV` | Auth: standard `OTEL_EXPORTER_OTLP_HEADERS` (e.g. `authorization=Bearer …`). @@ -193,3 +195,4 @@ When a customer reports a problem, the **first** thing to ask is "what's the `X- - No PII in error reports: the organization id is the only identifier, and no IP address is sent. If a self-hoster wants any of these enabled, every knob is a documented env var. If we add one that isn't, that's a bug — file it. + diff --git a/packages/backend/src/common/app-version.spec.ts b/packages/backend/src/common/app-version.spec.ts new file mode 100644 index 000000000..7523ce9aa --- /dev/null +++ b/packages/backend/src/common/app-version.spec.ts @@ -0,0 +1,51 @@ +describe('app-version', () => { + const originalEnv = { ...process.env }; + + beforeEach(() => { + jest.resetModules(); + }); + + afterEach(() => { + process.env = { ...originalEnv }; + jest.resetModules(); + }); + + it('APP_COMMIT falls back to dev when unset', () => { + delete process.env.APP_COMMIT; + delete process.env.SENTRY_RELEASE; + const mod = require('./app-version'); + expect(mod.APP_COMMIT).toBe('dev'); + }); + + it('APP_COMMIT prefers APP_COMMIT over SENTRY_RELEASE', () => { + process.env.APP_COMMIT = 'abc1234'; + process.env.SENTRY_RELEASE = 'def5678'; + const mod = require('./app-version'); + expect(mod.APP_COMMIT).toBe('abc1234'); + }); + + it('APP_COMMIT uses SENTRY_RELEASE when APP_COMMIT is unset', () => { + delete process.env.APP_COMMIT; + process.env.SENTRY_RELEASE = 'def5678'; + const mod = require('./app-version'); + expect(mod.APP_COMMIT).toBe('def5678'); + }); + + it('APP_BUILD_DATE defaults to null', () => { + delete process.env.APP_BUILD_DATE; + const mod = require('./app-version'); + expect(mod.APP_BUILD_DATE).toBeNull(); + }); + + it('APP_BUILD_DATE reads from env', () => { + process.env.APP_BUILD_DATE = '2026-10-10T12:00:00Z'; + const mod = require('./app-version'); + expect(mod.APP_BUILD_DATE).toBe('2026-10-10T12:00:00Z'); + }); + + it('APP_VERSION is a non-empty string', () => { + const mod = require('./app-version'); + expect(mod.APP_VERSION).toBeTruthy(); + expect(typeof mod.APP_VERSION).toBe('string'); + }); +}); diff --git a/packages/backend/src/common/app-version.ts b/packages/backend/src/common/app-version.ts new file mode 100644 index 000000000..dcd3d608c --- /dev/null +++ b/packages/backend/src/common/app-version.ts @@ -0,0 +1,45 @@ +/** + * Single source of truth for the backend version, commit and build date. + * + * The version is resolved by walking up from this file rather than with a + * fixed relative path: tsc emits to `dist/src/...`, so the package.json sits + * one level further up in the build than in the source tree, and the runtime + * image moves it again (`/app/backend/package.json` against + * `/app/backend/dist/src/common/`). A hardcoded `../../package.json` is + * correct in exactly one of those three layouts, and being wrong throws + * MODULE_NOT_FOUND at import time — which takes the whole backend down for a + * string that appears in one handshake field. Hence also the try/catch: this + * can degrade, it cannot fail to boot. + */ + +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; + +function readPackageVersion(): string { + let dir = __dirname; + for (let i = 0; i < 6; i++) { + try { + const pkg = JSON.parse( + readFileSync(join(dir, 'package.json'), 'utf8'), + ) as { name?: string; version?: string }; + if (pkg.name && pkg.version) return pkg.version; + } catch { + /* keep walking */ + } + const parent = dirname(dir); + if (parent === dir) break; + dir = parent; + } + return '0.0.0'; +} + +/** Package version, read from the nearest package.json. */ +export const APP_VERSION: string = readPackageVersion(); + +/** Short commit SHA, or 'dev' when unset (local builds, source runs). */ +export const APP_COMMIT: string = + process.env.APP_COMMIT || process.env.SENTRY_RELEASE || 'dev'; + +/** Build date (ISO), or null when unset. */ +export const APP_BUILD_DATE: string | null = + process.env.APP_BUILD_DATE || null; diff --git a/packages/backend/src/health/health.module.ts b/packages/backend/src/health/health.module.ts index 42c720a21..4da365948 100644 --- a/packages/backend/src/health/health.module.ts +++ b/packages/backend/src/health/health.module.ts @@ -1,13 +1,14 @@ import { Module } from '@nestjs/common'; import { TerminusModule } from '@nestjs/terminus'; import { HealthController } from './health.controller'; +import { VersionController } from './version.controller'; import { SentryVerifyController } from './sentry-verify.controller'; import { UsersModule } from '../users/users.module'; import { ProcessVitalsService } from '../common/process-vitals.service'; @Module({ imports: [TerminusModule, UsersModule], - controllers: [HealthController, SentryVerifyController], + controllers: [HealthController, SentryVerifyController, VersionController], providers: [ProcessVitalsService], }) export class HealthModule {} diff --git a/packages/backend/src/health/version.controller.ts b/packages/backend/src/health/version.controller.ts new file mode 100644 index 000000000..9cc23da48 --- /dev/null +++ b/packages/backend/src/health/version.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { AuthGuard } from '@nestjs/passport'; +import { APP_VERSION, APP_COMMIT, APP_BUILD_DATE } from '../common/app-version'; + +@ApiTags('Version') +@Controller('api/version') +@UseGuards(AuthGuard('jwt')) +export class VersionController { + @Get() + @ApiBearerAuth() + getVersion() { + return { + version: APP_VERSION, + commit: APP_COMMIT, + buildDate: APP_BUILD_DATE, + deploymentMode: process.env.DEPLOYMENT_MODE || 'self-hosted', + }; + } +} diff --git a/packages/backend/src/instrument.ts b/packages/backend/src/instrument.ts index 3c478c572..d18334c87 100644 --- a/packages/backend/src/instrument.ts +++ b/packages/backend/src/instrument.ts @@ -11,6 +11,7 @@ * ./common/sentry-scrub.ts for the exact rules. */ import * as Sentry from '@sentry/nestjs'; +import { APP_VERSION } from './common/app-version'; import { scrubBreadcrumb, scrubEvent } from './common/sentry-scrub'; import { sampleRateFor } from './common/sentry-sampler'; @@ -28,7 +29,7 @@ if (dsn) { process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV || 'development', - release: process.env.SENTRY_RELEASE || process.env.npm_package_version, + release: process.env.SENTRY_RELEASE || APP_VERSION, // Tracing is opt-in on top of error reporting because it adds overhead. // SENTRY_TRACES_SAMPLE_RATE applies to HTTP requests; MCP calls take diff --git a/packages/backend/src/main.ts b/packages/backend/src/main.ts index d0dfbe497..4fc08829d 100644 --- a/packages/backend/src/main.ts +++ b/packages/backend/src/main.ts @@ -18,6 +18,7 @@ import { startTracing } from './tracing'; startTracing(); import { NestFactory } from '@nestjs/core'; +import { APP_VERSION, APP_COMMIT, APP_BUILD_DATE } from './common/app-version'; import { Logger, ValidationPipe } from '@nestjs/common'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { ConfigService } from '@nestjs/config'; @@ -206,6 +207,9 @@ async function bootstrap() { }); } + const mode = process.env.DEPLOYMENT_MODE || 'self-hosted'; + const shortCommit = /^[0-9a-f]{40}$/.test(APP_COMMIT) ? APP_COMMIT.slice(0, 7) : APP_COMMIT; + logger.log(`AnythingMCP v${APP_VERSION} (commit ${shortCommit}, built ${APP_BUILD_DATE ?? 'unknown'}, mode ${mode})`); logger.log(`AnythingMCP backend running on: http://localhost:${port}`); logger.log(`Swagger docs: http://localhost:${port}/api/docs`); logger.log(`MCP endpoint (global): http://localhost:${port}/mcp`); diff --git a/packages/backend/src/mcp-server/mcp-endpoint.controller.ts b/packages/backend/src/mcp-server/mcp-endpoint.controller.ts index df070ae3c..4cc088637 100644 --- a/packages/backend/src/mcp-server/mcp-endpoint.controller.ts +++ b/packages/backend/src/mcp-server/mcp-endpoint.controller.ts @@ -1,3 +1,4 @@ +import { APP_VERSION } from '../common/app-version'; import { Controller, Post, @@ -77,40 +78,6 @@ import { planServerResources, } from './mcp-resources'; import { jsonSchemaToZodShape, stripEnvVarParams } from './tool-schema.util'; -import { readFileSync } from 'node:fs'; -import { dirname, join } from 'node:path'; - -/** - * Backend version, reported as the demo server's version (was a hardcoded 1.0.0). - * - * Resolved by walking up from this file rather than with a fixed relative path: - * tsc emits to `dist/src/...`, so the package.json sits one level further up in - * the build than in the source tree, and the runtime image moves it again - * (`/app/backend/package.json` against `/app/backend/dist/src/mcp-server/`). - * A hardcoded `../../package.json` is correct in exactly one of those three - * layouts, and being wrong throws MODULE_NOT_FOUND at import time — which takes - * the whole backend down for a string that appears in one handshake field. - * Hence also the try/catch: this can degrade, it cannot fail to boot. - */ -function readAppVersion(): string { - let dir = __dirname; - for (let i = 0; i < 6; i++) { - try { - const pkg = JSON.parse( - readFileSync(join(dir, 'package.json'), 'utf8'), - ) as { name?: string; version?: string }; - if (pkg.name && pkg.version) return pkg.version; - } catch { - /* keep walking */ - } - const parent = dirname(dir); - if (parent === dir) break; - dir = parent; - } - return '0.0.0'; -} - -const APP_VERSION: string = readAppVersion(); function trimSlash(url: string | undefined): string { return (url || '').trim().replace(/\/+$/, ''); diff --git a/packages/backend/src/tracing.ts b/packages/backend/src/tracing.ts index e0a11fa5f..ed6aef82c 100644 --- a/packages/backend/src/tracing.ts +++ b/packages/backend/src/tracing.ts @@ -13,6 +13,7 @@ * (Sentry for error correlation, OTLP for an in-house collector). */ import { NodeSDK } from '@opentelemetry/sdk-node'; +import { APP_VERSION } from './common/app-version'; import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { resourceFromAttributes } from '@opentelemetry/resources'; @@ -32,8 +33,7 @@ export function startTracing(): void { [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'anythingmcp-backend', [ATTR_SERVICE_VERSION]: process.env.OTEL_SERVICE_VERSION || - process.env.npm_package_version || - '0.0.0', + APP_VERSION, [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: process.env.OTEL_DEPLOYMENT_ENVIRONMENT || process.env.NODE_ENV || diff --git a/packages/frontend/src/app/settings/admin/page.tsx b/packages/frontend/src/app/settings/admin/page.tsx index e947259c2..f87a965b2 100644 --- a/packages/frontend/src/app/settings/admin/page.tsx +++ b/packages/frontend/src/app/settings/admin/page.tsx @@ -30,6 +30,8 @@ export default function SettingsAdminPage() { // Footer links const [footerLinks, setFooterLinks] = useState>([]); + const [versionInfo, setVersionInfo] = useState<{ version: string; commit: string; buildDate: string | null; deploymentMode: string } | null>(null); + const [versionMsg, setVersionMsg] = useState(''); const [footerMsg, setFooterMsg] = useState(''); // Connector-failure alert webhook @@ -63,6 +65,7 @@ export default function SettingsAdminPage() { }).catch(() => {}); adminSettings.getFooterLinks(token).then(setFooterLinks).catch(() => {}); + versionApi.get(token).then(setVersionInfo).catch(() => {}); adminSettings.getAlertWebhook(token).then((data) => { if (!('url' in data)) return; @@ -526,6 +529,41 @@ export default function SettingsAdminPage() { )} + + {/* About this instance */} + +

About this instance

+ {versionInfo && ( +
+

+ Version {versionInfo.version} · commit{' '} + + {/^[0-9a-f]{40}$/.test(versionInfo.commit) ? versionInfo.commit.slice(0, 7) : versionInfo.commit} + + {versionInfo.buildDate && <> · built {versionInfo.buildDate.slice(0, 10)}} +

+ + {versionMsg && ( +

+ {versionMsg} +

+ )} +
+ )} +
); } diff --git a/packages/frontend/src/components/footer.tsx b/packages/frontend/src/components/footer.tsx index db94c3fbb..153d4f818 100644 --- a/packages/frontend/src/components/footer.tsx +++ b/packages/frontend/src/components/footer.tsx @@ -2,7 +2,8 @@ import { useState, useEffect } from 'react'; import { useTheme } from '@/lib/theme-context'; -import { siteSettings } from '@/lib/api'; +import { siteSettings, versionApi } from '@/lib/api'; +import { useAuth } from '@/lib/auth-context'; function SunIcon({ size = 16 }: { size?: number }) { return ( @@ -37,14 +38,65 @@ interface FooterLink { url: string; } +interface VersionInfo { + version: string; + commit: string; +} + +/** Fetch the version once per session; every mount shares the same promise. */ +let versionPromise: Promise | null = null; + +function getVersionOnce(token: string): Promise { + if (!versionPromise) { + versionPromise = versionApi + .get(token) + .then((v) => ({ version: v.version, commit: v.commit })) + .catch(() => null); + } + return versionPromise; +} + +/** Shorten a 40-char SHA for display; pass anything else through. */ +function shortSha(c: string): string { + return /^[0-9a-f]{40}$/.test(c) ? c.slice(0, 7) : c; +} + +function VersionBadge({ version }: { version: VersionInfo }) { + const sha = shortSha(version.commit); + return ( + + v{version.version} ·{' '} + {/^[0-9a-f]{7}$/.test(sha) && version.commit.length === 40 ? ( + + {sha} + + ) : ( + sha + )} + + ); +} + export function Footer() { const { theme, setTheme } = useTheme(); + const { token } = useAuth(); const [links, setLinks] = useState([]); + const [version, setVersion] = useState<{ version: string; commit: string } | null>(null); useEffect(() => { siteSettings.footerLinks().then(setLinks).catch(() => {}); }, []); + useEffect(() => { + if (!token) return; + getVersionOnce(token).then(setVersion).catch(() => {}); + }, [token]); + const themeOptions = [ { value: 'light' as const, label: 'Light', icon: SunIcon }, { value: 'dark' as const, label: 'Dark', icon: MoonIcon }, @@ -60,6 +112,12 @@ export function Footer() { © {new Date().getFullYear()} AnythingMCP · Powered by helpcode.ai + {version && ( + <> + · + + + )} {links.length > 0 && ( <> · diff --git a/packages/frontend/src/lib/api.ts b/packages/frontend/src/lib/api.ts index ab2b4244a..972952183 100644 --- a/packages/frontend/src/lib/api.ts +++ b/packages/frontend/src/lib/api.ts @@ -1013,6 +1013,11 @@ export const siteSettings = { request>('/api/site-settings/footer-links'), }; +export const versionApi = { + get: (token: string) => + request<{ version: string; commit: string; buildDate: string | null; deploymentMode: string }>('/api/version', { token, skipAutoLogout: true }), +}; + // Admin Settings export const adminSettings = { getSmtp: (token: string) => From eb6b61a2d0789df652b74dff6d1e90b0c0ac732d Mon Sep 17 00:00:00 2001 From: Furox-Art Date: Tue, 13 Oct 2026 20:16:40 +0300 Subject: [PATCH 2/2] fix: address review feedback on version display PR - admin/page.tsx: import versionApi (TS2304 at line 68) - admin/page.tsx: use copyText from @/lib/clipboard for the Copy button (navigator.clipboard is undefined on plain-HTTP origins); show "Version info unavailable" when the fetch fails - docker-publish.yml: APP_BUILD_DATE from steps.meta labels instead of github.event.head_commit.timestamp (empty on workflow_dispatch) - observability.md: OTEL_SERVICE_VERSION default is APP_VERSION from package.json; sentence stays below the table - deployment.md: Build provenance is a ### subsection under Production Deployment (after "With Docker (Manual)"); use git rev-parse HEAD (40-char SHA) instead of --short HEAD so the footer link works - footer.tsx: reset versionPromise on failed fetch so a mid-session token expiry doesn't hide the version until page reload --- .github/workflows/docker-publish.yml | 2 +- docs/deployment.md | 25 +++++++++---------- docs/operations/observability.md | 2 +- .../frontend/src/app/settings/admin/page.tsx | 14 +++++------ packages/frontend/src/components/footer.tsx | 7 +++++- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 72e243628..b435eba7a 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -70,7 +70,7 @@ jobs: build-args: | SENTRY_RELEASE=${{ github.sha }} APP_COMMIT=${{ github.sha }} - APP_BUILD_DATE=${{ github.event.head_commit.timestamp }} + APP_BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} # Only the source map upload uses it; nothing Sentry-related that # decides where an instance reports is baked into the public image. secrets: | diff --git a/docs/deployment.md b/docs/deployment.md index f129f8472..45b53f9b1 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -2,18 +2,6 @@ > Docker setup, production deployment, local development, reverse proxy, and environment configuration. -## Build provenance - -When building the image yourself, pass the commit and build date so the UI and startup log show real values instead of `dev`: - -```bash -docker build \ - --build-arg APP_COMMIT=$(git rev-parse --short HEAD) \ - --build-arg APP_BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \ - -t anythingmcp:local . -``` - - [Back to README](../README.md) --- @@ -211,6 +199,18 @@ cd packages/frontend && npm start --- +### Build provenance + +When building the image yourself, pass the commit and build date so the UI and startup log show real values instead of `dev`: + +```bash +docker build \ + --build-arg APP_COMMIT=$(git rev-parse HEAD) \ + --build-arg APP_BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \ + -t anythingmcp:local . +``` + + ## Reverse Proxy & HTTPS ### Caddy (Recommended — Automatic SSL) @@ -443,4 +443,3 @@ With both of these in place, AnythingMCP makes no outbound connections to `anyth --- [Back to README](../README.md) | [API Reference](api-reference.md) | [Integration Guides](../README.md#use-it-from-claude-chatgpt-copilot-and-gemini) - diff --git a/docs/operations/observability.md b/docs/operations/observability.md index 8a53494b2..3e0314025 100644 --- a/docs/operations/observability.md +++ b/docs/operations/observability.md @@ -64,7 +64,7 @@ Service identity: | Env | Default | |---|---| | `OTEL_SERVICE_NAME` | `anythingmcp-backend` | -| `OTEL_SERVICE_VERSION` | `npm_package_version` | +| `OTEL_SERVICE_VERSION` | `APP_VERSION` (from `package.json`) | The `service.version` value now comes from `APP_VERSION` (resolved from `package.json`), with `APP_COMMIT` and `APP_BUILD_DATE` available as build-time env vars. | `OTEL_DEPLOYMENT_ENVIRONMENT` | `NODE_ENV` | diff --git a/packages/frontend/src/app/settings/admin/page.tsx b/packages/frontend/src/app/settings/admin/page.tsx index f87a965b2..7a69130a6 100644 --- a/packages/frontend/src/app/settings/admin/page.tsx +++ b/packages/frontend/src/app/settings/admin/page.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react'; import Link from 'next/link'; import { useAuth } from '@/lib/auth-context'; -import { adminSettings, type AlertWebhookInput } from '@/lib/api'; +import { adminSettings, versionApi, type AlertWebhookInput } from '@/lib/api'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { AppSelect } from '@/components/ui/select'; @@ -533,6 +533,9 @@ export default function SettingsAdminPage() { {/* About this instance */}

About this instance

+ {!versionInfo && ( +

Version info unavailable.

+ )} {versionInfo && (

@@ -545,13 +548,10 @@ export default function SettingsAdminPage() {