From 6073f26b01e8ed39c09098198b6d84da9562ad26 Mon Sep 17 00:00:00 2001 From: Rares Astilean Date: Sat, 29 Aug 2026 15:59:34 +0300 Subject: [PATCH 1/2] test: cover MCP credential forwarding --- README.md | 8 ++++++-- llms-install.md | 13 +++++++------ tests/api-client.test.mjs | 10 ++++++++++ tests/auth.test.mjs | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 754bfc1..6ba1cef 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ After cloning + building, register the server in your client's MCP config. For C "command": "node", "args": ["/absolute/path/to/lua-platform-mcp/dist/server.js"], "env": { - "LUA_API_KEY": "lk_..." + "LUA_API_KEY": "${env:LUA_API_KEY}" } } } @@ -60,13 +60,17 @@ The server reads your Lua API key in this order: 2. `~/.lua-cli/credentials` (written by `lua auth configure`) 3. `.env` file in CWD -Get a key from [admin.heylua.ai](https://admin.heylua.ai) (sign up + create an agent → Settings → API Keys), or use the Lua CLI: +Credentials are opaque to this MCP server: it selects the first available source and forwards the value unchanged. Never paste a credential into an MCP request or a model conversation. Configure it locally with the Lua CLI, your shell environment, or a local `.env` file instead. + +For local setup, use the Lua CLI: ```bash npm install -g lua-cli lua auth configure ``` +That writes `~/.lua-cli/credentials`. Alternatively, create a credential in [admin.heylua.ai](https://admin.heylua.ai) and set `LUA_API_KEY` in the MCP host's local environment. + ## Verify it works After registering, restart your MCP client and ask: *"What lua-platform tools do you have?"* diff --git a/llms-install.md b/llms-install.md index 17ca7dc..21c2dd7 100644 --- a/llms-install.md +++ b/llms-install.md @@ -38,11 +38,12 @@ This file gives Cline (or any AI agent installing this MCP server on a user's be ``` Use the absolute path — relative paths are unreliable across MCP client implementations. -4. Ask the user to obtain a Lua API key: - - Visit https://admin.heylua.ai - - Sign up (free) and create an agent - - Settings → API Keys → copy the key (`lk_...`) - - Set it as an environment variable: `export LUA_API_KEY=lk_...` (or place in `~/.lua-cli/credentials`) +4. Keep credentials on the user's machine. Do **not** ask the user to paste a secret into this conversation or any MCP request. Tell them to choose one local setup path: + - Run `lua auth configure` after installing `lua-cli`; it writes `~/.lua-cli/credentials`. + - Set `LUA_API_KEY` in the MCP host's local environment. + - Add `LUA_API_KEY` to a local `.env` file in the MCP working directory. + + The server selects sources in that order and forwards either legacy or dotted typed credentials unchanged. If the user needs to create or manage a credential, direct them to https://admin.heylua.ai rather than asking for the secret. 5. Restart Cline. Verify by asking: *"What lua-platform MCP tools are available?"* — expect 5 tools: `list_agents`, `get_agent`, `list_primitive_versions`, `get_deployment_status`, `tail_logs`. @@ -54,7 +55,7 @@ Run from a Cline conversation: ## Troubleshooting -- **Server fails to start**: usually missing `LUA_API_KEY`. Set the env var or write `~/.lua-cli/credentials`. +- **Server fails to start**: usually no local credential source is configured. Set `LUA_API_KEY`, run `lua auth configure`, or add a local `.env` file. - **`list_agents` returns empty**: the API key is valid but the user has no agents yet — direct them to admin.heylua.ai to create one. - **HTTP 401 errors**: the key is invalid or expired. Regenerate at admin.heylua.ai. diff --git a/tests/api-client.test.mjs b/tests/api-client.test.mjs index 98b663b..e17afc1 100644 --- a/tests/api-client.test.mjs +++ b/tests/api-client.test.mjs @@ -45,6 +45,16 @@ describe('apiRequest', () => { expect(fetchFn.calls[0].init.headers.Authorization).toBe('Bearer lk_test_key'); }); + test.each([ + 'lk_legacy_credential', + 'api_123e4567-e89b-12d3-a456-426614174000.abcdefghijklmnopqrstuvwxyz_0123456789-ABCDE', + ])('forwards %s unchanged in the Authorization header', async (credential) => { + process.env.LUA_API_KEY = credential; + const fetchFn = mockFetch(jsonResponse({ ok: true })); + await apiRequest('/agents', { fetchFn }); + expect(fetchFn.calls[0].init.headers.Authorization).toBe(`Bearer ${credential}`); + }); + test('sets Content-Type: application/json', async () => { const fetchFn = mockFetch(jsonResponse({ ok: true })); await apiRequest('/agents', { fetchFn }); diff --git a/tests/auth.test.mjs b/tests/auth.test.mjs index b6e3752..4417e6a 100644 --- a/tests/auth.test.mjs +++ b/tests/auth.test.mjs @@ -138,6 +138,40 @@ describe('Tier 3: .env file (regression test for iteration-8 fix)', () => { }); }); +describe('Credential format opacity', () => { + const legacyCredential = 'lk_legacy_credential'; + const typedCredential = 'api_123e4567-e89b-12d3-a456-426614174000.abcdefghijklmnopqrstuvwxyz_0123456789-ABCDE'; + + test.each([legacyCredential, typedCredential])('forwards %s unchanged from LUA_API_KEY', async (credential) => { + await expect(resolveApiKey({ + env: { LUA_API_KEY: credential }, + credentialsPath: join(tmpDir, 'nonexistent'), + cwd: tmpDir, + })).resolves.toBe(credential); + }); + + test.each([legacyCredential, typedCredential])( + 'forwards %s unchanged from the lua-cli credentials file', + async (credential) => { + writeFileSync(join(tmpDir, 'credentials'), `${credential}\n`); + await expect(resolveApiKey({ + env: {}, + credentialsPath: join(tmpDir, 'credentials'), + cwd: tmpDir, + })).resolves.toBe(credential); + }, + ); + + test.each([legacyCredential, typedCredential])('forwards %s unchanged from the working-directory .env', async (credential) => { + writeFileSync(join(tmpDir, '.env'), `LUA_API_KEY=${credential}\n`); + await expect(resolveApiKey({ + env: {}, + credentialsPath: join(tmpDir, 'nonexistent'), + cwd: tmpDir, + })).resolves.toBe(credential); + }); +}); + describe('No source resolves', () => { test('throws MCP_AUTH_STALE with all three sources mentioned', async () => { await expect(resolveApiKey({ From b17bcb278a096dafeac4ca5f8ad54434a27d30ed Mon Sep 17 00:00:00 2001 From: Rares Astilean Date: Sat, 29 Aug 2026 16:05:12 +0300 Subject: [PATCH 2/2] fix: document Cline credential setup --- README.md | 9 +++------ llms-install.md | 15 ++++++--------- tests/api-client.test.mjs | 2 +- tests/auth.test.mjs | 2 +- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6ba1cef..fff6fa9 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,7 @@ After cloning + building, register the server in your client's MCP config. For C "mcpServers": { "lua-platform": { "command": "node", - "args": ["/absolute/path/to/lua-platform-mcp/dist/server.js"], - "env": { - "LUA_API_KEY": "${env:LUA_API_KEY}" - } + "args": ["/absolute/path/to/lua-platform-mcp/dist/server.js"] } } } @@ -62,14 +59,14 @@ The server reads your Lua API key in this order: Credentials are opaque to this MCP server: it selects the first available source and forwards the value unchanged. Never paste a credential into an MCP request or a model conversation. Configure it locally with the Lua CLI, your shell environment, or a local `.env` file instead. -For local setup, use the Lua CLI: +For the copy-paste-safe local setup, use the Lua CLI before starting your MCP client: ```bash npm install -g lua-cli lua auth configure ``` -That writes `~/.lua-cli/credentials`. Alternatively, create a credential in [admin.heylua.ai](https://admin.heylua.ai) and set `LUA_API_KEY` in the MCP host's local environment. +That writes `~/.lua-cli/credentials`, which the server reads automatically. Alternatively, create a credential in [admin.heylua.ai](https://admin.heylua.ai) and set its real value in the MCP host's local environment. ## Verify it works diff --git a/llms-install.md b/llms-install.md index 21c2dd7..a5ec6dc 100644 --- a/llms-install.md +++ b/llms-install.md @@ -25,21 +25,18 @@ This file gives Cline (or any AI agent installing this MCP server on a user's be 3. Register the server in the MCP config file. For Cline that's `cline_mcp_settings.json` (location varies — typically `~/Library/Application Support/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` on macOS or analogous paths on other OSes). Add or merge: ```json { - "mcpServers": { - "lua-platform": { - "command": "node", - "args": ["/absolute/path/to/lua-platform-mcp/dist/server.js"], - "env": { - "LUA_API_KEY": "${env:LUA_API_KEY}" - } - } + "mcpServers": { + "lua-platform": { + "command": "node", + "args": ["/absolute/path/to/lua-platform-mcp/dist/server.js"] + } } } ``` Use the absolute path — relative paths are unreliable across MCP client implementations. 4. Keep credentials on the user's machine. Do **not** ask the user to paste a secret into this conversation or any MCP request. Tell them to choose one local setup path: - - Run `lua auth configure` after installing `lua-cli`; it writes `~/.lua-cli/credentials`. + - Run `lua auth configure` after installing `lua-cli`; it writes `~/.lua-cli/credentials` and is the copy-paste-safe default for Cline. - Set `LUA_API_KEY` in the MCP host's local environment. - Add `LUA_API_KEY` to a local `.env` file in the MCP working directory. diff --git a/tests/api-client.test.mjs b/tests/api-client.test.mjs index e17afc1..3194163 100644 --- a/tests/api-client.test.mjs +++ b/tests/api-client.test.mjs @@ -46,7 +46,7 @@ describe('apiRequest', () => { }); test.each([ - 'lk_legacy_credential', + 'api_0123456789abcdef0123456789abcdef', 'api_123e4567-e89b-12d3-a456-426614174000.abcdefghijklmnopqrstuvwxyz_0123456789-ABCDE', ])('forwards %s unchanged in the Authorization header', async (credential) => { process.env.LUA_API_KEY = credential; diff --git a/tests/auth.test.mjs b/tests/auth.test.mjs index 4417e6a..97f09d5 100644 --- a/tests/auth.test.mjs +++ b/tests/auth.test.mjs @@ -139,7 +139,7 @@ describe('Tier 3: .env file (regression test for iteration-8 fix)', () => { }); describe('Credential format opacity', () => { - const legacyCredential = 'lk_legacy_credential'; + const legacyCredential = 'api_0123456789abcdef0123456789abcdef'; const typedCredential = 'api_123e4567-e89b-12d3-a456-426614174000.abcdefghijklmnopqrstuvwxyz_0123456789-ABCDE'; test.each([legacyCredential, typedCredential])('forwards %s unchanged from LUA_API_KEY', async (credential) => {