Skip to content
Open
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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ BAZAAR_CATALOG_WRITES_PER_DAY=200
# --- Oracle Relay Example ---
# Lens API base URL consumed by the relay example.
ORACLE_RELAY_API_URL=http://localhost:3002
# Soroban RPC endpoint for transaction submission.
ORACLE_RELAY_RPC_URL=https://soroban-testnet.stellar.org
# Stellar network passphrase for transaction signing.
ORACLE_RELAY_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
# Soroban contract ID for the relay example.
ORACLE_RELAY_CONTRACT_ID=CC...
# Secret key for the relay's source account.
Expand Down Expand Up @@ -207,6 +211,9 @@ FACILITATOR_FEE_STROOPS_MAINNET=50000
# fetches a gated resource. These spend money, so keep them out of any other
# process and fund only what you are willing to lose.
MCP_AGENT_SECRET_KEY_TESTNET=
# WARNING: MCP_AGENT_SECRET_KEY_MAINNET signs transactions on the public network
# and can spend real funds. Keep this out of any other process and fund only
# what you are willing to lose.
MCP_AGENT_SECRET_KEY_MAINNET=
# Ceiling per paid_call, enforced by the x402 fetch wrapper.
MCP_MAX_PAYMENT_PRICE=$1.00
Expand Down
28 changes: 26 additions & 2 deletions src/__tests__/envDrift.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import ts from 'typescript'

const rootDir = resolve(__dirname, '../..')
const srcDir = resolve(rootDir, 'src')
const examplesDir = resolve(rootDir, 'examples')
const scriptsDir = resolve(rootDir, 'scripts')
const envExamplePath = resolve(rootDir, '.env.example')

/**
Expand Down Expand Up @@ -156,7 +158,11 @@ describe('.env.example drift guard', () => {
// vitest's 5s default and makes this guard flaky. Give it real headroom.
it('asserts each process.env read in source appears in .env.example or the allow-list', { timeout: 60_000 }, () => {
const documentedVars = parseEnvExample(envExamplePath)
const sourceFiles = getSourceFiles(srcDir)
const sourceFiles = [
...getSourceFiles(srcDir),
...getSourceFiles(examplesDir),
...getSourceFiles(scriptsDir),
]
const allReads: EnvRead[] = []

for (const file of sourceFiles) {
Expand Down Expand Up @@ -186,7 +192,7 @@ describe('.env.example drift guard', () => {
}
})

it('fails when a new un-documented process.env read is encountered', () => {
it('fails when a new un-documented process.env read is encountered in src/', () => {
const sampleCode = `
export function init() {
const secret = process.env.SOME_BRAND_NEW_SECRET_VAR
Expand All @@ -204,6 +210,24 @@ describe('.env.example drift guard', () => {
expect(missing[0].name).toBe('SOME_BRAND_NEW_SECRET_VAR')
})

it('fails when an un-documented process.env read is encountered in examples/ (#413)', () => {
const sampleExampleCode = `
export function runExample() {
const secret = process.env.MCP_UNDOCUMENTED_TEST_VAR
return secret
}
`
const reads = extractEnvReadsFromSource(sampleExampleCode, 'examples/mcp-server/sample.ts')
const documentedVars = parseEnvExample(envExamplePath)

const missing = reads.filter(
(read) => !documentedVars.has(read.name) && !(read.name in UNDOCUMENTED_ENV_ALLOW_LIST)
)

expect(missing).toHaveLength(1)
expect(missing[0].name).toBe('MCP_UNDOCUMENTED_TEST_VAR')
})

it('requires every allow-list entry to have an explicit rationale', () => {
const entries = Object.entries(UNDOCUMENTED_ENV_ALLOW_LIST)
expect(entries.length).toBeGreaterThan(0)
Expand Down
Loading