Skip to content

growth(day-20): @byok-relay/mcp — MCP server for Claude Desktop + Claude Code - #58

Open
alokit-bot wants to merge 3 commits into
mainfrom
feat/growth-2026-06-29
Open

growth(day-20): @byok-relay/mcp — MCP server for Claude Desktop + Claude Code#58
alokit-bot wants to merge 3 commits into
mainfrom
feat/growth-2026-06-29

Conversation

@alokit-bot

@alokit-bot alokit-bot commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds packages/mcp/ — a complete MCP server (@byok-relay/mcp) that exposes byok-relay as 6 tools for Claude Desktop, Claude Code, Cursor, Windsurf, and any MCP-compatible client.

Why

The MCP ecosystem is growing fast. Every Claude Desktop and Claude Code user is a potential byok-relay user — an MCP server gets byok-relay in front of them at the moment they need BYOK AI in a frontend app, with zero friction (npx -y @byok-relay/mcp, no install step). Once listed on mcp.so and in the Anthropic MCP registry, it becomes a self-sustaining discovery channel.

Tools exposed

Tool Description
byok_relay_health Liveness + readiness check
byok_relay_register Register and get relay token (shown once)
byok_relay_store_key Store provider API key (AES-256-GCM encrypted)
byok_relay_request Forward any provider API request
byok_relay_chat Chat completions with unified model routing
byok_relay_stats Usage stats per user / app_id

Claude Desktop setup (zero-install)

{
  "mcpServers": {
    "byok-relay": {
      "command": "npx",
      "args": ["-y", "@byok-relay/mcp"],
      "env": {
        "RELAY_URL": "https://relay.byokrelay.com",
        "RELAY_TOKEN": "<your-relay-token>"
      }
    }
  }
}

Other changes

  • llms.txt: MCP Server section + npm link so AI agents discover the MCP path
  • README.md: MCP badge in header + "Use from Claude Desktop" section before Deploy
  • package.json: workspaces includes packages/* (alongside existing packages/client)

Metrics

stars=51 forks=0 views=17 clones=116 (2026-06-29)

Next step (Avi)

Once merged: publish @byok-relay/mcp to npm with cd packages/mcp && npm publish --access public, then submit to mcp.so and Anthropic MCP registry.

Summary by CodeRabbit

  • New Features

    • Added a preconfigured Codespaces/devcontainer setup for quick local development.
    • Exposed the service OpenAPI spec directly at JSON and YAML endpoints.
    • Added an MCP server package so the relay can be used from supported desktop AI tools.
  • Documentation

    • Expanded the README and API docs with instant-start, OpenAPI, and MCP setup guidance.
    • Added a dedicated MCP README with setup, usage examples, and environment options.

Add .devcontainer/devcontainer.json:
- Uses mcr.microsoft.com/devcontainers/javascript-node:1-20-bookworm
- postCreateCommand runs on-create.sh (npm install + auto-generates .env)
- Forwards port 3000 with label 'byok-relay API'
- VS Code extensions: eslint, prettier, yaml, json
- Opens README.md on Codespace start

Add .devcontainer/on-create.sh:
- npm install
- Creates .env with safe dev-only defaults if not present
- Prints clear 'what next' instructions on completion

Update README.md:
- Add 'Open in GitHub Codespaces' badge in header (next to skills.sh)
- Add 'Try it instantly' section before Quickstart with Codespaces CTA
  and one-sentence explain (deps installed, dev .env pre-configured)

Enables: zero-friction 'try it' path for developers evaluating the project.
GitHub shows Codespaces tab in the Code dropdown once devcontainer.json exists.

Metrics 2026-06-27: stars=51 forks=0 watchers=1 clones=258 views=38
…endpoints

- openapi.json: full OpenAPI 3.0 spec covering all 7 endpoints (health, users,
  keys CRUD, relay, openapi.json, openapi.yaml) with request/response schemas,
  security schemes (RelayToken + AppSecret), provider enum, and worked examples
- GET /openapi.json: serves spec as JSON (require-cached, hot-reload safe)
- GET /openapi.yaml: serves spec as YAML (js-yaml dump from JSON spec)
- llms.txt: API Reference section with managed relay spec URLs
- README.md: OpenAPI 3.0 badge in header + spec link at top of API section
- deps: js-yaml added for YAML serialisation endpoint

AI coding agents can now fetch /openapi.json to discover the full API surface
programmatically. Postman/Insomnia/RapidAPI users can import the spec directly.
Serves spec at /openapi.json and /openapi.yaml at runtime on both managed relay
and self-hosted instances.

Metrics: stars=51 forks=0 watchers=1 clones(14d)=259 views(14d)=40
…ude Code

Adds packages/mcp/ — a full MCP server that exposes byok-relay as 6 tools
for Claude Desktop, Claude Code, Cursor, Windsurf and any MCP client.

Tools:
  byok_relay_health   — check relay server health (liveness + readiness)
  byok_relay_register — register and receive a relay token
  byok_relay_store_key — store a provider API key (encrypted at rest)
  byok_relay_request  — forward any provider API request through the relay
  byok_relay_chat     — chat completions with unified model routing
  byok_relay_stats    — usage statistics per user / app_id

Install via Claude Desktop claude_desktop_config.json:
  npx -y @byok-relay/mcp (no install step, zero-friction)

Also:
  - llms.txt: MCP Server section + @byok-relay/mcp npm link
  - README.md: MCP badge in header + 'Use from Claude Desktop' section
  - package.json: workspaces includes packages/*

metrics/history.csv: 2026-06-29 stars=51 forks=0 views=17 clones=116
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an OpenAPI 3.0 spec file (openapi.json) served via new GET /openapi.json and GET /openapi.yaml routes using js-yaml. Introduces a new @byok-relay/mcp npm workspace package implementing an MCP stdio server that exposes relay operations as tools. Adds a Codespaces devcontainer configuration with a setup script. Updates README and llms.txt with new sections.

Changes

OpenAPI Spec and Server Routes

Layer / File(s) Summary
OpenAPI spec definition
openapi.json
Full OpenAPI 3.0.3 spec defining metadata, servers, tags, all endpoint paths (/health, /users, /keys, /relay), and reusable components (security schemes, provider enum, error schemas).
OpenAPI routes in main server
package.json, src/index.js
Adds js-yaml dependency, fs/path imports, and GET /openapi.json / GET /openapi.yaml routes with require-cache clearing and 404 fallback.

MCP Server Package

Layer / File(s) Summary
MCP package manifest and workspace wiring
package.json, packages/mcp/package.json
Adds workspaces: ["packages/*"] to root package.json; creates packages/mcp/package.json with entry point, MCP SDK dependency, engine constraint, and publish config.
MCP server implementation
packages/mcp/src/index.js
Implements relayFetch helper, TOOLS definitions for six relay tools, handleTool switch dispatcher, and main() bootstrapping Server with StdioServerTransport.
MCP docs and README references
packages/mcp/README.md, README.md, llms.txt
Adds full @byok-relay/mcp package docs (tools table, quick start, env vars, self-hosting); updates root README and llms.txt with MCP sections and OpenAPI links.

Codespaces Devcontainer

Layer / File(s) Summary
Devcontainer config and setup script
.devcontainer/devcontainer.json, .devcontainer/on-create.sh
Creates devcontainer.json with Node.js 20 Bookworm image, port 3000 forwarding, and VS Code extensions; on-create.sh runs npm install and creates a default .env.
README Codespaces section
README.md
Adds OpenAPI badges, "Try it instantly" Codespaces section, and OpenAPI spec links to the API section.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • avikalpg

Poem

🐰 A relay, a spec, and a tool—
The bunny has hopped through it all!
MCP hops to Claude's ear,
OpenAPI rings clear,
And Codespaces answers the call! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the new @byok-relay/mcp MCP server and its Claude Desktop/Claude Code focus.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/growth-2026-06-29

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.devcontainer/on-create.sh:
- Around line 12-18: The .env bootstrap in on-create.sh is writing a committed
default for ENCRYPTION_SECRET, which should instead be unique per Codespace.
Update the setup flow to generate a fresh random value at creation time rather
than hardcoding the secret, and keep the existing .env generation behavior
otherwise. Use the on-create.sh script as the place to change this, and ensure
the value remains compatible with the encryption logic used by src/index.js and
src/db.js.

In `@llms.txt`:
- Line 108: The `llms.txt` entry currently points to a local relative path that
won’t resolve outside a checkout. Update the `See ... for full setup guide.`
reference to use an absolute README URL instead, and make sure the link still
points to the `packages/mcp/README.md` documentation target.

In `@openapi.json`:
- Around line 308-312: The binary request schema is describing providers that
are not actually allowed by the published contract. Update the
`application/octet-stream` description in the OpenAPI spec so it matches the
supported `provider` enum and does not mention ElevenLabs or Deepgram unless
those providers are added to the enum; keep the wording aligned with the request
body definition around the `provider` field and the binary payload schema.
- Around line 7-10: The license metadata in the OpenAPI spec is inconsistent
with the package manifest, so update the license object in openapi.json to match
the package.json declaration. Locate the existing license block under the
top-level OpenAPI metadata and change the advertised license name (and URL if
needed) so both sources present the same SPDX license for the artifact.
- Around line 230-246: The relay path contract is modeled as a single OpenAPI
path parameter, but the documented examples rely on multi-segment values like
v1/chat/completions. Update the /relay/{provider}/{path} operation in
openapi.json to either redesign the route shape or explicitly document that
{path} must be percent-encoded and make all examples and descriptions
consistent; use the relayRequest operation and path parameter definition as the
main places to fix.
- Around line 96-97: The API description for the registration endpoint currently
tells integrators to store the relay token in localStorage, which is unsafe
guidance for a bearer-equivalent credential. Update the affected OpenAPI
descriptions to use storage-agnostic wording or recommend a safer client-managed
pattern instead, and make sure the same change is applied to every duplicated
registration description referenced by the reviewer, including the one in the
registration operation docs and any other matching entries.

In `@packages/mcp/README.md`:
- Around line 96-98: The fenced code block in README.md is missing a language
hint, which triggers markdownlint MD040. Update the markdown fence around the
“Ask Claude: ...” example to use a text language identifier so the block is
explicitly labeled; this applies to the README example snippet only.

In `@packages/mcp/src/index.js`:
- Around line 124-146: The byok_relay_request schema in inputSchema is missing
support for the passthrough headers required by documented relay calls, so add a
headers field and wire it through the relay request handling. Also make body
optional for non-POST methods in the schema and validation logic, since GET
requests should not require it. Update the byok_relay_request path in
packages/mcp/src/index.js so Anthropic and openai-compatible flows can express
the documented relay calls using the existing provider, path, and method fields
plus the new headers support.
- Around line 33-45: Update the registration flow in relayFetch so the POST
/users call can use APP_SECRET instead of always sending RELAY_TOKEN. Add a way
for byok_relay_register (and any helper it uses) to pass the operator credential
explicitly, and have relayFetch choose the correct Authorization header based on
that call site while leaving normal relay requests on RELAY_TOKEN. Keep the
change localized around relayFetch and byok_relay_register so the registration
path can succeed on APP_SECRET-gated relays.
- Around line 39-51: The relayFetch helper currently waits on fetch with no
timeout, so slow or half-open relay responses can hang requests indefinitely.
Update relayFetch to enforce a finite timeout around the fetch call, using an
AbortController or equivalent timeout mechanism, and make sure the timeout is
applied to the fetch invocation while preserving the existing headers, response
parsing, and returned { status, ok, body } shape.

In `@src/index.js`:
- Around line 141-142: Update the OpenAPI load handlers in the relevant route
logic so the `catch` blocks no longer always return 404 for `OpenAPI spec not
found`; use the existing loader/response path to distinguish missing-file errors
from parse or serialization failures. In the handler(s) around the OpenAPI spec
lookup, return 404 only when the failure is specifically “file not found,” and
return a 500 with an appropriate error response for all other exceptions. Apply
the same fix to both affected catch sites so the behavior is consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d58645c-d220-48e3-b57d-caa327b3a9da

📥 Commits

Reviewing files that changed from the base of the PR and between f0e1a43 and e04c65f.

⛔ Files ignored due to path filters (3)
  • metrics/history.csv is excluded by !**/*.csv
  • package-lock.json is excluded by !**/package-lock.json
  • packages/mcp/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • .devcontainer/devcontainer.json
  • .devcontainer/on-create.sh
  • README.md
  • llms.txt
  • openapi.json
  • package.json
  • packages/mcp/README.md
  • packages/mcp/package.json
  • packages/mcp/src/index.js
  • src/index.js

Comment on lines +12 to +18
cat > .env <<'EOF'
# ── Dev-only defaults — NOT for production ──────────────────────────────
# Generate real secrets with: openssl rand -hex 32

# Required: 32+ char secret used to AES-256-GCM encrypt stored API keys
ENCRYPTION_SECRET=dev-only-change-this-before-any-real-use-32chars

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Generate ENCRYPTION_SECRET per Codespace, not from a committed default.

src/index.js:12 and src/db.js:118-126 make this value the root secret for encrypting stored provider keys. Writing the same known secret into every generated .env means anyone with a copy of the encrypted data can decrypt it with the published default. Generate a random secret during setup instead.

Suggested fix
 if [ ! -f .env ]; then
   echo "🔧 Creating .env with dev-only defaults..."
-  cat > .env <<'EOF'
+  ENCRYPTION_SECRET="$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")"
+  cat > .env <<EOF
 # ── Dev-only defaults — NOT for production ──────────────────────────────
 # Generate real secrets with: openssl rand -hex 32
 
 # Required: 32+ char secret used to AES-256-GCM encrypt stored API keys
-ENCRYPTION_SECRET=dev-only-change-this-before-any-real-use-32chars
+ENCRYPTION_SECRET=${ENCRYPTION_SECRET}
 
 # Optional: if set, POST /users requires Authorization: Bearer <APP_SECRET>
 # APP_SECRET=
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cat > .env <<'EOF'
# ── Dev-only defaults — NOT for production ──────────────────────────────
# Generate real secrets with: openssl rand -hex 32
# Required: 32+ char secret used to AES-256-GCM encrypt stored API keys
ENCRYPTION_SECRET=dev-only-change-this-before-any-real-use-32chars
if [ ! -f .env ]; then
echo "🔧 Creating .env with dev-only defaults..."
ENCRYPTION_SECRET="$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")"
cat > .env <<EOF
# ── Dev-only defaults — NOT for production ──────────────────────────────
# Generate real secrets with: openssl rand -hex 32
# Required: 32+ char secret used to AES-256-GCM encrypt stored API keys
ENCRYPTION_SECRET=${ENCRYPTION_SECRET}
# Optional: if set, POST /users requires Authorization: Bearer <APP_SECRET>
# APP_SECRET=
EOF
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.devcontainer/on-create.sh around lines 12 - 18, The .env bootstrap in
on-create.sh is writing a committed default for ENCRYPTION_SECRET, which should
instead be unique per Codespace. Update the setup flow to generate a fresh
random value at creation time rather than hardcoding the secret, and keep the
existing .env generation behavior otherwise. Use the on-create.sh script as the
place to change this, and ensure the value remains compatible with the
encryption logic used by src/index.js and src/db.js.

Comment thread llms.txt

Available MCP tools: `byok_relay_health`, `byok_relay_register`, `byok_relay_store_key`, `byok_relay_request`, `byok_relay_chat`, `byok_relay_stats`.

See `packages/mcp/README.md` for full setup guide.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use an absolute MCP README link here.

llms.txt is consumed outside a local checkout, so packages/mcp/README.md is not a resolvable path for most agents. Point this to the GitHub or npm README instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@llms.txt` at line 108, The `llms.txt` entry currently points to a local
relative path that won’t resolve outside a checkout. Update the `See ... for
full setup guide.` reference to use an absolute README URL instead, and make
sure the link still points to the `packages/mcp/README.md` documentation target.

Comment thread openapi.json
Comment on lines +7 to +10
"license": {
"name": "MIT",
"url": "https://github.com/avikalpg/byok-relay/blob/main/LICENSE"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Align the published license metadata with the package manifest.

This spec advertises MIT, but package.json Line 23 now says Apache-2.0. Shipping conflicting license metadata for the same artifact is a contract/compliance problem for downstream consumers and generated docs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openapi.json` around lines 7 - 10, The license metadata in the OpenAPI spec
is inconsistent with the package manifest, so update the license object in
openapi.json to match the package.json declaration. Locate the existing license
block under the top-level OpenAPI metadata and change the advertised license
name (and URL if needed) so both sources present the same SPDX license for the
artifact.

Comment thread openapi.json
Comment on lines +96 to +97
"description": "Creates a user record for an app_id and returns a relay token. Store the token in localStorage — it is the credential for all key and relay operations.\n\nIf the operator has set APP_SECRET, supply `Authorization: Bearer <APP_SECRET>`. Otherwise registration is open (dev mode).\n\nRate limit: 10 registrations/hour per IP.",
"security": [{ "AppSecret": [] }, {}],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Don’t tell integrators to store the relay token in localStorage.

This token is the caller’s bearer-equivalent credential, so recommending localStorage makes it trivially recoverable by any XSS in the consuming app. Please switch this to storage-agnostic wording or recommend a safer pattern.

Also applies to: 373-380

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openapi.json` around lines 96 - 97, The API description for the registration
endpoint currently tells integrators to store the relay token in localStorage,
which is unsafe guidance for a bearer-equivalent credential. Update the affected
OpenAPI descriptions to use storage-agnostic wording or recommend a safer
client-managed pattern instead, and make sure the same change is applied to
every duplicated registration description referenced by the reviewer, including
the one in the registration operation docs and any other matching entries.

Comment thread openapi.json
Comment on lines +230 to +246
"/relay/{provider}/{path}": {
"post": {
"tags": ["Relay"],
"operationId": "relayRequest",
"summary": "Forward a request to an AI provider",
"description": "Forwards the request body to the AI provider using the user's stored API key. The relay injects the key — your frontend never sees it.\n\n**Path routing:** `{path}` is forwarded verbatim to the provider base URL.\n- Anthropic: `POST /relay/anthropic/v1/messages`\n- OpenAI: `POST /relay/openai/v1/chat/completions`\n- Google Gemini: `POST /relay/google/v1beta/models/gemini-2.0-flash:generateContent`\n\n**Streaming:** set `stream: true` in the request body to receive a Server-Sent Events (SSE) stream piped directly from the provider.\n\n**openai-compatible:** pass `x-relay-base-url` header with the target base URL (SSRF-validated, HTTPS only).\n\nRate limit: 20 AI requests/min per relay token.",
"security": [{ "RelayToken": [] }],
"parameters": [
{ "$ref": "#/components/parameters/provider" },
{
"name": "path",
"in": "path",
"required": true,
"schema": { "type": "string" },
"description": "Provider-specific path (e.g. v1/messages, v1/chat/completions)",
"example": "v1/messages"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

{path} cannot represent the documented relay subpaths.

The examples here use raw multi-segment values like v1/chat/completions, but /relay/{provider}/{path} only models a single path segment in OpenAPI. Generated clients will not infer the required slash-encoding, so this contract is inaccurate as written. Please either redesign this part of the API shape or explicitly require percent-encoded slashes and update every example accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openapi.json` around lines 230 - 246, The relay path contract is modeled as a
single OpenAPI path parameter, but the documented examples rely on multi-segment
values like v1/chat/completions. Update the /relay/{provider}/{path} operation
in openapi.json to either redesign the route shape or explicitly document that
{path} must be percent-encoded and make all examples and descriptions
consistent; use the relayRequest operation and path parameter definition as the
main places to fix.

Comment thread packages/mcp/README.md
Comment on lines +96 to +98
```
Ask Claude: "Use byok_relay_store_key to store my OpenAI key sk-... for provider openai"
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language to this fenced block.

This currently trips markdownlint (MD040).

Suggested fix
-```
+```text
 Ask Claude: "Use byok_relay_store_key to store my OpenAI key sk-... for provider openai"
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 96-96: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mcp/README.md` around lines 96 - 98, The fenced code block in
README.md is missing a language hint, which triggers markdownlint MD040. Update
the markdown fence around the “Ask Claude: ...” example to use a text language
identifier so the block is explicitly labeled; this applies to the README
example snippet only.

Source: Linters/SAST tools

Comment thread packages/mcp/src/index.js
Comment on lines +33 to +45
const RELAY_URL = (process.env.RELAY_URL || 'https://relay.byokrelay.com').replace(/\/$/, '');
const RELAY_TOKEN = process.env.RELAY_TOKEN || '';
const APP_ID = process.env.APP_ID || 'mcp-client';

// ─── helpers ─────────────────────────────────────────────────────────────────

async function relayFetch(path, options = {}) {
const fetch = globalThis.fetch || (await import('node-fetch')).default;
const url = `${RELAY_URL}${path}`;
const headers = {
'Content-Type': 'application/json',
...(RELAY_TOKEN ? { Authorization: `Bearer ${RELAY_TOKEN}` } : {}),
...(options.headers || {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Let registration use APP_SECRET instead of the user relay token.

relayFetch() injects RELAY_TOKEN into every request, but POST /users is the one endpoint that may require the operator's APP_SECRET instead. On an APP_SECRET-gated relay, byok_relay_register cannot succeed because there is no way to send the correct credential.

Suggested fix
 const RELAY_URL = (process.env.RELAY_URL || 'https://relay.byokrelay.com').replace(/\/$/, '');
 const RELAY_TOKEN = process.env.RELAY_TOKEN || '';
+const APP_SECRET = process.env.APP_SECRET || '';
 const APP_ID = process.env.APP_ID || 'mcp-client';
@@
       const appId = args.app_id || APP_ID;
       const { status, body } = await relayFetch('/users', {
+        headers: APP_SECRET ? { Authorization: `Bearer ${APP_SECRET}` } : {},
         method: 'POST',
         body: JSON.stringify({ app_id: appId }),
       });

Also applies to: 217-221

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mcp/src/index.js` around lines 33 - 45, Update the registration flow
in relayFetch so the POST /users call can use APP_SECRET instead of always
sending RELAY_TOKEN. Add a way for byok_relay_register (and any helper it uses)
to pass the operator credential explicitly, and have relayFetch choose the
correct Authorization header based on that call site while leaving normal relay
requests on RELAY_TOKEN. Keep the change localized around relayFetch and
byok_relay_register so the registration path can succeed on APP_SECRET-gated
relays.

Comment thread packages/mcp/src/index.js
Comment on lines +39 to +51
async function relayFetch(path, options = {}) {
const fetch = globalThis.fetch || (await import('node-fetch')).default;
const url = `${RELAY_URL}${path}`;
const headers = {
'Content-Type': 'application/json',
...(RELAY_TOKEN ? { Authorization: `Bearer ${RELAY_TOKEN}` } : {}),
...(options.headers || {}),
};
const res = await fetch(url, { ...options, headers });
const text = await res.text();
let body;
try { body = JSON.parse(text); } catch { body = text; }
return { status: res.status, ok: res.ok, body };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a timeout to relayFetch.

Every tool call blocks on this helper, and fetch has no default timeout. A slow or half-open relay/SSE response will leave the MCP request hanging indefinitely.

Suggested fix
 async function relayFetch(path, options = {}) {
   const fetch = globalThis.fetch || (await import('node-fetch')).default;
   const url = `${RELAY_URL}${path}`;
   const headers = {
     'Content-Type': 'application/json',
     ...(RELAY_TOKEN ? { Authorization: `Bearer ${RELAY_TOKEN}` } : {}),
     ...(options.headers || {}),
   };
-  const res = await fetch(url, { ...options, headers });
-  const text = await res.text();
+  const controller = new AbortController();
+  const timeout = setTimeout(() => controller.abort(), 30000);
+  let res;
+  let text;
+  try {
+    res = await fetch(url, { ...options, headers, signal: controller.signal });
+    text = await res.text();
+  } finally {
+    clearTimeout(timeout);
+  }
   let body;
   try { body = JSON.parse(text); } catch { body = text; }
   return { status: res.status, ok: res.ok, body };
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function relayFetch(path, options = {}) {
const fetch = globalThis.fetch || (await import('node-fetch')).default;
const url = `${RELAY_URL}${path}`;
const headers = {
'Content-Type': 'application/json',
...(RELAY_TOKEN ? { Authorization: `Bearer ${RELAY_TOKEN}` } : {}),
...(options.headers || {}),
};
const res = await fetch(url, { ...options, headers });
const text = await res.text();
let body;
try { body = JSON.parse(text); } catch { body = text; }
return { status: res.status, ok: res.ok, body };
async function relayFetch(path, options = {}) {
const fetch = globalThis.fetch || (await import('node-fetch')).default;
const url = `${RELAY_URL}${path}`;
const headers = {
'Content-Type': 'application/json',
...(RELAY_TOKEN ? { Authorization: `Bearer ${RELAY_TOKEN}` } : {}),
...(options.headers || {}),
};
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000);
let res;
let text;
try {
res = await fetch(url, { ...options, headers, signal: controller.signal });
text = await res.text();
} finally {
clearTimeout(timeout);
}
let body;
try { body = JSON.parse(text); } catch { body = text; }
return { status: res.status, ok: res.ok, body };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mcp/src/index.js` around lines 39 - 51, The relayFetch helper
currently waits on fetch with no timeout, so slow or half-open relay responses
can hang requests indefinitely. Update relayFetch to enforce a finite timeout
around the fetch call, using an AbortController or equivalent timeout mechanism,
and make sure the timeout is applied to the fetch invocation while preserving
the existing headers, response parsing, and returned { status, ok, body } shape.

Comment thread packages/mcp/src/index.js
Comment on lines +124 to +146
inputSchema: {
type: 'object',
required: ['provider', 'path', 'body'],
properties: {
provider: {
type: 'string',
enum: ['openai', 'anthropic', 'google', 'mistral', 'openai-compatible'],
description: 'AI provider to route to.',
},
path: {
type: 'string',
description: 'Provider API path (e.g. "/v1/chat/completions" for OpenAI).',
},
body: {
type: 'object',
description: 'Request body as a JSON object (will be forwarded verbatim).',
},
method: {
type: 'string',
enum: ['POST', 'GET', 'DELETE'],
description: 'HTTP method. Default: POST.',
},
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

byok_relay_request can't express documented provider calls.

The relay API depends on passthrough headers such as anthropic-version and x-relay-base-url, but this tool exposes no headers field and still marks body as required even for GET. Anthropic and openai-compatible requests therefore cannot be represented from MCP despite being documented relay flows.

Suggested fix
     inputSchema: {
       type: 'object',
-      required: ['provider', 'path', 'body'],
+      required: ['provider', 'path'],
       properties: {
         provider: {
           type: 'string',
           enum: ['openai', 'anthropic', 'google', 'mistral', 'openai-compatible'],
           description: 'AI provider to route to.',
@@
         body: {
           type: 'object',
           description: 'Request body as a JSON object (will be forwarded verbatim).',
         },
+        headers: {
+          type: 'object',
+          description: 'Additional relay/provider headers to forward.',
+          additionalProperties: { type: 'string' },
+        },
         method: {
           type: 'string',
           enum: ['POST', 'GET', 'DELETE'],
           description: 'HTTP method. Default: POST.',
         },
@@
       const method = args.method || 'POST';
       const relayPath = `/relay/${args.provider}${args.path.startsWith('/') ? args.path : '/' + args.path}`;
       const fetchOpts = {
         method,
-        ...(method !== 'GET' && args.body ? { body: JSON.stringify(args.body) } : {}),
+        ...(args.headers ? { headers: args.headers } : {}),
+        ...(method !== 'GET' && args.body != null ? { body: JSON.stringify(args.body) } : {}),
       };

Also applies to: 260-266

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mcp/src/index.js` around lines 124 - 146, The byok_relay_request
schema in inputSchema is missing support for the passthrough headers required by
documented relay calls, so add a headers field and wire it through the relay
request handling. Also make body optional for non-POST methods in the schema and
validation logic, since GET requests should not require it. Update the
byok_relay_request path in packages/mcp/src/index.js so Anthropic and
openai-compatible flows can express the documented relay calls using the
existing provider, path, and method fields plus the new headers support.

Comment thread src/index.js
Comment on lines +141 to +142
} catch {
res.status(404).json({ error: 'OpenAPI spec not found' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Don’t collapse every OpenAPI load failure into a 404.

These catch blocks also hide parse errors and serialization failures as “not found”, which makes runtime debugging much harder and returns the wrong status to clients. Please return 404 only for missing-file cases and surface other failures as 500s.

Also applies to: 157-158

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.js` around lines 141 - 142, Update the OpenAPI load handlers in the
relevant route logic so the `catch` blocks no longer always return 404 for
`OpenAPI spec not found`; use the existing loader/response path to distinguish
missing-file errors from parse or serialization failures. In the handler(s)
around the OpenAPI spec lookup, return 404 only when the failure is specifically
“file not found,” and return a 500 with an appropriate error response for all
other exceptions. Apply the same fix to both affected catch sites so the
behavior is consistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant