3d4e739529
Context7 provides up-to-date library documentation for LLMs via a remote endpoint — no local binary needed. Always registered since it has no PATH dependency. Also switches generated config from .json to .jsonc so we can include a comment about the optional API key for higher rate limits. The existing-config check now detects both file extensions.
184 lines
5.9 KiB
Python
Executable File
184 lines
5.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Generate opencode.json from environment variables on first container start.
|
|
|
|
Safety guarantees:
|
|
- NEVER overwrites an existing opencode.json. If the file is present
|
|
(whether bind-mounted from the host, persisted in a named volume, or
|
|
previously generated), this script exits immediately without writing.
|
|
- Requires OPENCODE_PROVIDER to be set. Without it, no file is written.
|
|
|
|
Environment variables:
|
|
OPENCODE_PROVIDER Required. One of: anthropic, openai, amazon-bedrock.
|
|
OPENCODE_MODEL Optional. Overrides the provider default model.
|
|
AWS_REGION Bedrock only. Default: us-east-1.
|
|
AWS_PROFILE Bedrock only. Default: default.
|
|
|
|
MCP servers are auto-registered for tools detected on PATH:
|
|
- mempalace (if installed) — enabled
|
|
- gitea-mcp (if installed) — registered but disabled by default
|
|
|
|
Output path: $HOME/.config/opencode/opencode.json
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Default model per provider. Update here when upstream changes.
|
|
DEFAULT_MODELS: dict[str, str] = {
|
|
"anthropic": "anthropic/claude-sonnet-4-6",
|
|
"openai": "openai/gpt-5.4",
|
|
"amazon-bedrock": (
|
|
"amazon-bedrock/global.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
|
),
|
|
}
|
|
|
|
# Fallback when OPENCODE_PROVIDER is set but not recognized.
|
|
FALLBACK_MODEL = DEFAULT_MODELS["anthropic"]
|
|
|
|
SCHEMA_URL = "https://opencode.ai/config.json"
|
|
|
|
|
|
def build_config(provider: str, model: str) -> dict:
|
|
"""Build the base opencode.json structure for a provider."""
|
|
config: dict = {
|
|
"$schema": SCHEMA_URL,
|
|
"model": model,
|
|
"share": "disabled",
|
|
"autoupdate": False,
|
|
}
|
|
|
|
if provider == "amazon-bedrock":
|
|
config["provider"] = {
|
|
"amazon-bedrock": {
|
|
"options": {
|
|
"region": os.environ.get("AWS_REGION", "us-east-1"),
|
|
"profile": os.environ.get("AWS_PROFILE", "default"),
|
|
}
|
|
}
|
|
}
|
|
|
|
return config
|
|
|
|
|
|
def register_mcp_servers(config: dict) -> list[str]:
|
|
"""Auto-register MCP servers for tools detected on PATH.
|
|
|
|
Returns the list of server names that were added. The "mcp" key
|
|
is only added to the config when at least one server is registered.
|
|
"""
|
|
servers: dict[str, dict] = {}
|
|
|
|
# MemPalace — local-first AI memory (if installed).
|
|
# `mempalace-mcp` is the entry-point binary shipped by the mempalace
|
|
# Python package. `uv tool install mempalace` places it on PATH as a
|
|
# shim whose shebang points at the isolated venv's Python, so system
|
|
# `python3 -m mempalace.mcp_server` (which would fail — system
|
|
# python3 can't import from the uv venv) is unnecessary here.
|
|
if shutil.which("mempalace-mcp"):
|
|
servers["mempalace"] = {
|
|
"type": "local",
|
|
"command": ["mempalace-mcp"],
|
|
}
|
|
|
|
# Gitea — self-hosted Git forge API (if installed).
|
|
# Disabled by default; user must set GITEA_ACCESS_TOKEN + GITEA_HOST
|
|
# and flip enabled=true in their config.
|
|
if shutil.which("gitea-mcp"):
|
|
servers["gitea"] = {
|
|
"type": "local",
|
|
"command": ["gitea-mcp", "-t", "stdio"],
|
|
"enabled": False,
|
|
}
|
|
|
|
# Context7 — up-to-date library documentation for LLMs (remote).
|
|
# Free tier works without an API key; set CONTEXT7_API_KEY for higher
|
|
# rate limits. No local binary needed — purely a remote MCP endpoint.
|
|
servers["context7"] = {
|
|
"type": "remote",
|
|
"url": "https://mcp.context7.com/mcp",
|
|
}
|
|
|
|
if servers:
|
|
config["mcp"] = servers
|
|
|
|
return list(servers.keys())
|
|
|
|
|
|
def main() -> int:
|
|
provider = os.environ.get("OPENCODE_PROVIDER", "").strip()
|
|
if not provider:
|
|
# No provider set — nothing to do. Not an error.
|
|
return 0
|
|
|
|
home = Path(os.environ.get("HOME", "/home/developer"))
|
|
config_dir = home / ".config" / "opencode"
|
|
config_file = config_dir / "opencode.jsonc"
|
|
config_file_legacy = config_dir / "opencode.json"
|
|
|
|
# CRITICAL: never overwrite an existing config. Users may have
|
|
# bind-mounted their host config directory, or their config may be
|
|
# persisted in a named volume from a previous run.
|
|
# Check both .json and .jsonc variants.
|
|
if config_file.exists() or config_file_legacy.exists():
|
|
existing = config_file if config_file.exists() else config_file_legacy
|
|
print(
|
|
f"Existing config found at {existing} — "
|
|
"skipping generation.",
|
|
file=sys.stderr,
|
|
)
|
|
return 0
|
|
|
|
if provider not in DEFAULT_MODELS:
|
|
print(
|
|
f"WARNING: unknown OPENCODE_PROVIDER={provider!r}, "
|
|
f"falling back to default model {FALLBACK_MODEL!r}.",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
model = os.environ.get("OPENCODE_MODEL", "").strip() or DEFAULT_MODELS.get(
|
|
provider, FALLBACK_MODEL
|
|
)
|
|
|
|
print(f"Generating opencode config for provider: {provider}", file=sys.stderr)
|
|
|
|
config = build_config(provider, model)
|
|
added = register_mcp_servers(config)
|
|
|
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Write as JSONC so we can include helpful comments.
|
|
content = json.dumps(config, indent=2)
|
|
|
|
# Insert a comment about Context7 API key after the context7 url line.
|
|
context7_comment = (
|
|
' "url": "https://mcp.context7.com/mcp"\n'
|
|
" // For higher rate limits, sign up at https://context7.com/dashboard\n"
|
|
' // and add: "headers": { "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" }'
|
|
)
|
|
content = content.replace(
|
|
' "url": "https://mcp.context7.com/mcp"',
|
|
context7_comment,
|
|
)
|
|
|
|
with config_file.open("w") as f:
|
|
f.write(content)
|
|
f.write("\n")
|
|
|
|
if added:
|
|
print(
|
|
f"MCP servers registered in opencode config: {', '.join(added)}.",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|