Skip to content

[ENG-623] feat: remote mcp#150

Merged
miguelangaranocurrents merged 6 commits into
mainfrom
feat/remote-mcp
Jul 14, 2026
Merged

[ENG-623] feat: remote mcp#150
miguelangaranocurrents merged 6 commits into
mainfrom
feat/remote-mcp

Conversation

@miguelangaranocurrents

@miguelangaranocurrents miguelangaranocurrents commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Remote MCP HTTP server + container publish

Summary

Adds a Streamable HTTP transport so the Currents MCP server can run as a hosted remote endpoint (e.g. for Claude web/mobile connectors), while keeping the existing stdio transport unchanged for local clients.

The same Publish NPM Package workflow now also builds and pushes a Docker image to GHCR. For beta and latest channels, a successful push triggers deployment in the private infrastructure repo via repository_dispatch.

Motivation

Today the server only speaks stdio — fine for Claude Desktop and Cursor, but remote connectors need an HTTPS URL. The goal is one package, one set of tools, two entry points: stdio for local use, HTTP for hosted use.

Authentication for the hosted path is intentionally not implemented in the MCP server. Callers pass their Currents API key as a Bearer token; the server forwards it to the Currents API, which remains the sole auth authority.

Architecture

flowchart TD
  stdio["stdio entry"] -->|"CURRENTS_API_KEY env"| ctx[requestContext]
  http["HTTP entry"] -->|"Authorization Bearer header"| ctx
  ctx --> req["getApiKey in request.ts"]
  req --> tools["tool handlers unchanged"]
  tools --> api["Currents API"]
Loading
Transport Entry API key
stdio dist/index.mjs CURRENTS_API_KEY env var (required at startup)
HTTP dist/http.mjs Authorization: Bearer <key> per request

Key design decisions

Per-request context via AsyncLocalStorage — The HTTP server is multi-tenant: concurrent requests may carry different API keys. Rather than threading a key through every tool handler, getApiKey() in request.ts reads from request-scoped context (HTTP) or falls back to the env var (stdio). Tool handlers and their signatures are untouched.

Stateless HTTP — Each POST /mcp creates a fresh McpServer + StreamableHTTPServerTransport with sessionIdGenerator: undefined. No session state, no cross-request key bleed. GET/DELETE on /mcp return 405.

Server factorycreateMcpServer() replaces the module-level singleton so both transports can instantiate servers independently.

What to review

Runtime (mcp-server/src/)

File Review focus
lib/context.ts ALS store shape; env fallback for stdio
lib/request.ts Four auth header sites now call getApiKey()
server.ts Factory extraction; stdio path still gated on env key
http.ts Stateless transport wiring, Bearer passthrough, /healthz, error handling

Packaging and Docker

File Review focus
package.json New start:http script and currents-mcp-http bin; stdio bin unchanged
tsdown.config.ts http build entry
Dockerfile Now runs HTTP server; CURRENTS_API_URL fixed to include /v1; no baked-in API key
.dockerignore Build context trimming

CI/CD (.github/workflows/publish.yaml)

New publish-image job runs after npm publish:

  1. Build + push ghcr.io/currents-dev/currents-mcp:<version> and :<channel>
  2. For beta / latest only: resolve GHCR digest, fire repository_dispatch (mcp-image-published) to currents-dev/currents

Worth checking: dispatch runs after push (digest must exist), failures are not swallowed, and alpha / semver-only publishes do not dispatch.

Tests

  • context.test.ts — concurrent ALS isolation
  • http.test.ts — Bearer parsing and outbound auth header passthrough
  • server.test.ts — updated to call factory explicitly
  • Existing suite should pass unchanged

Docs

  • README.md — remote endpoint usage for end users (client config, Bearer auth, local run)
  • .github/workflows/README.md — GHCR publish + DISPATCH_TOKEN secret

Backward compatibility

  • stdio clients (npx @currents/mcp, Claude Desktop config) are unaffected
  • All MCP tool names, schemas, and handlers are unchanged
  • npm publish channels and the Create Release workflow are unchanged
  • No AWS or infra config added to this repo

Test plan

# Unit tests
cd mcp-server && npm ci && npm run test:run

# HTTP locally
npm run build && PORT=3000 npm run start:http
curl http://localhost:3000/healthz

curl -s -N -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $CURRENTS_API_KEY" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"0"}}}'
# expect serverInfo.name == "currents"

# Docker
docker build -t currents-mcp . && docker run --rm -p 3000:3000 currents-mcp

# stdio regression
CURRENTS_API_KEY=$CURRENTS_API_KEY node dist/index.mjs

After merge, before first beta/latest publish:

  1. Add DISPATCH_TOKEN repo secret (fine-grained PAT, Contents read/write on currents-dev/currents)
  2. Confirm Actions workflow permissions allow GITHUB_TOKEN to write packages

Deploy integration

GHCR channel tag Downstream action
latest Production deploy
beta Staging deploy
alpha, semver, oldversion Image published only; no auto-deploy

Dispatch payload: { "tag": "<channel>", "digest": "sha256:..." }.

Summary by CodeRabbit

  • New Features
    • Added a hosted HTTP MCP endpoint with GET /healthz and POST /mcp, secured via Bearer-token Authorization.
    • Added currents-mcp-http plus a start:http command.
  • Documentation
    • Documented hosted endpoint usage and local/Docker run instructions in the README.
    • Updated workflow documentation for publishing the package and container image.
  • Tests
    • Added tests for HTTP API key parsing and per-request key isolation.
  • Refactor
    • Updated server startup to create a fresh MCP server instance with tool registration.
  • Chores
    • Hardened Docker build, updated container runtime to use the HTTP entrypoint, and added a comprehensive .dockerignore.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1228870b-3bea-4c93-83c0-157441c69f19

📥 Commits

Reviewing files that changed from the base of the PR and between a47ce87 and 38f3060.

📒 Files selected for processing (3)
  • Dockerfile
  • mcp-server/src/http.ts
  • mcp-server/src/lib/request.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • Dockerfile
  • mcp-server/src/lib/request.ts
  • mcp-server/src/http.ts

📝 Walkthrough

Walkthrough

Adds a hosted HTTP MCP transport with per-request Bearer-token API key propagation, refactors server creation into a factory, updates packaging and Docker runtime configuration, and adds GHCR image publishing with deployment notifications.

Changes

HTTP MCP Transport and Publish Pipeline

Layer / File(s) Summary
Per-request API key context and request wiring
mcp-server/src/lib/context.ts, mcp-server/src/lib/context.test.ts, mcp-server/src/lib/request.ts
Adds AsyncLocalStorage-based API key resolution, updates API helpers to use request-scoped keys, and tests fallback and concurrent isolation.
McpServer factory and HTTP entrypoint
mcp-server/src/server.ts, mcp-server/src/http.ts, mcp-server/src/http.test.ts, mcp-server/src/server.test.ts
Adds createMcpServer(), HTTP routing and JSON-RPC handling, per-request transports, health checks, cleanup, and API key propagation.
Build, packaging, and Docker runtime
mcp-server/tsdown.config.ts, mcp-server/package.json, Dockerfile, .dockerignore
Adds the HTTP build target and executable, switches the container to dist/http.mjs on port 3000, updates the API URL, ignores install scripts, and excludes image-context artifacts.
GHCR publishing workflow and documentation
.github/workflows/publish.yaml, .github/workflows/README.md, README.md
Builds and pushes version/channel-tagged images, resolves digests, dispatches downstream events for latest and beta, and documents publishing and hosted endpoint usage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HTTPServer
  participant MCPServer
  participant CurrentsAPI

  Client->>HTTPServer: POST /mcp with Bearer token
  HTTPServer->>MCPServer: Create transport and server
  MCPServer->>CurrentsAPI: Request with request-scoped Bearer token
  CurrentsAPI-->>MCPServer: API response
  MCPServer-->>Client: JSON-RPC response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding remote MCP support.
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/remote-mcp

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: 1

🧹 Nitpick comments (4)
mcp-server/src/http.ts (1)

24-31: 💤 Low value

Consider handling edge cases in Bearer token extraction.

The current implementation returns the raw header value for invalid Bearer formats (e.g., "Bearer" with no token returns "Bearer"). While this aligns with the design philosophy of forwarding tokens to the Currents API for validation, it may pass through malformed headers.

Consider either:

  • Returning undefined for invalid Bearer headers
  • Adding validation to ensure a non-empty token exists after "Bearer"
  • Documenting the intentional passthrough behavior
🛡️ Example validation approach
 export function extractApiKey(req: IncomingMessage): string | undefined {
   const auth = req.headers.authorization?.trim();
   if (!auth) {
     return undefined;
   }
   const match = /^Bearer\s+(.+)$/i.exec(auth);
-  return (match ? match[1] : auth).trim();
+  if (match) {
+    const token = match[1].trim();
+    return token || undefined; // reject empty tokens
+  }
+  // Accept raw tokens without Bearer prefix
+  return auth.trim() || undefined;
 }
🤖 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 `@mcp-server/src/http.ts` around lines 24 - 31, The extractApiKey function in
the Bearer token extraction logic does not validate that a valid token actually
exists after the "Bearer" prefix. Currently, malformed headers like "Bearer"
with no token are passed through as-is (returning "Bearer" itself). Update the
function to either return undefined when the Bearer prefix is present but no
token follows it, or add validation to ensure the matched group contains a
non-empty token before returning it. This prevents passing malformed
authentication headers downstream while maintaining the passthrough behavior for
non-Bearer authorization schemes.
README.md (1)

115-117: 💤 Low value

Add language specifier to fenced code block.

The code block showing the Authorization header format is missing a language specifier. While not critical, adding http or text improves rendering and satisfies linting rules.

📝 Suggested fix
-```
+```http
 Authorization: Bearer <your-currents-api-key>
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

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

In @README.md around lines 115 - 117, The fenced code block displaying the
Authorization header format is missing a language specifier on the opening
backticks. Add a language identifier such as http or text immediately after
the opening triple backticks (change tohttp) to improve markdown
rendering and satisfy linting requirements.


</details>

<!-- cr-comment:v1:c93917ece7dab6edc19f8b7d -->

_Source: Linters/SAST tools_

</blockquote></details>
<details>
<summary>.github/workflows/publish.yaml (2)</summary><blockquote>

`102-104`: _⚡ Quick win_

**Consider disabling credential persistence for security.**

The checkout action does not set `persist-credentials: false`, allowing the GitHub token to persist in the repository's git config. If subsequent steps are compromised, the token could be misused.





<details>
<summary>🔒 Suggested hardening</summary>

```diff
       - uses: actions/checkout@v4
         with:
           ref: ${{ github.event.pull_request.head.sha || github.ref }}
+          persist-credentials: false
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

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

In @.github/workflows/publish.yaml around lines 102 - 104, Add the
`persist-credentials: false` parameter to the `with` section of the
actions/checkout@v4 action to prevent the GitHub token from persisting in the
git config. This ensures that if any subsequent workflow steps are compromised,
the token cannot be misused by an attacker.
```

</details>

<!-- cr-comment:v1:f2f5c547d3fbe8c377f2d297 -->

_Source: Linters/SAST tools_

---

`102-102`: _⚖️ Poor tradeoff_

**Consider pinning actions to commit SHAs for supply-chain security.**

The workflow uses semantic version tags (`@v3`, `@v4`, `@v5`) rather than immutable commit SHAs. While tags are more readable, they are mutable and could be compromised. Pinning to SHAs with a comment noting the version improves supply-chain security.





Example:
```yaml
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
```


Also applies to: 112-112, 114-114, 122-122, 129-129

<details>
<summary>🤖 Prompt for AI Agents</summary>

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

In @.github/workflows/publish.yaml at line 102, Replace all GitHub Actions
version tags with immutable commit SHAs for supply-chain security in the
publish.yaml workflow. In `.github/workflows/publish.yaml` at lines 102, 112,
114, 122, and 129, change each action reference from semantic version tags (such
as `@v4`, `@v3`, `@v5`) to specific commit SHAs by looking up the commit hash for each
action version and including the version as a comment for readability. For
example, replace `actions/checkout@v4` with the format
`actions/checkout@<commit-sha> # v4.1.1` where the commit SHA is the immutable
hash corresponding to that version tag.
```

</details>

<!-- cr-comment:v1:00b88ca52a278690646004a0 -->

_Source: Linters/SAST tools_

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

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 @.github/workflows/publish.yaml:

  • Line 20: The packages: write permission at the workflow level (line 20)
    should be removed and kept only at the job level where it is actually needed.
    Delete the packages: write line from the workflow-level permissions section,
    since the publish-image job already correctly declares this permission at
    lines 97-99. This ensures the permission is scoped only to the job that requires
    it, adhering to the principle of least privilege.

Nitpick comments:
In @.github/workflows/publish.yaml:

  • Around line 102-104: Add the persist-credentials: false parameter to the
    with section of the actions/checkout@v4 action to prevent the GitHub token
    from persisting in the git config. This ensures that if any subsequent workflow
    steps are compromised, the token cannot be misused by an attacker.
  • Line 102: Replace all GitHub Actions version tags with immutable commit SHAs
    for supply-chain security in the publish.yaml workflow. In
    .github/workflows/publish.yaml at lines 102, 112, 114, 122, and 129, change
    each action reference from semantic version tags (such as @v4, @v3, @v5) to
    specific commit SHAs by looking up the commit hash for each action version and
    including the version as a comment for readability. For example, replace
    actions/checkout@v4 with the format actions/checkout@<commit-sha> # v4.1.1
    where the commit SHA is the immutable hash corresponding to that version tag.

In @mcp-server/src/http.ts:

  • Around line 24-31: The extractApiKey function in the Bearer token extraction
    logic does not validate that a valid token actually exists after the "Bearer"
    prefix. Currently, malformed headers like "Bearer" with no token are passed
    through as-is (returning "Bearer" itself). Update the function to either return
    undefined when the Bearer prefix is present but no token follows it, or add
    validation to ensure the matched group contains a non-empty token before
    returning it. This prevents passing malformed authentication headers downstream
    while maintaining the passthrough behavior for non-Bearer authorization schemes.

In @README.md:

  • Around line 115-117: The fenced code block displaying the Authorization header
    format is missing a language specifier on the opening backticks. Add a language
    identifier such as http or text immediately after the opening triple
    backticks (change tohttp) to improve markdown rendering and satisfy
    linting requirements.

</details>

<details>
<summary>🪄 Autofix (Beta)</summary>

Fix all unresolved CodeRabbit comments on this PR:

- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Organization UI

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `6b031673-0034-4f74-9c2d-18c9294ed71a`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e42e9fccd302f94942fdc6aac42ba5769a61008e and 9a997ccec09a30dc4b83690d5056b6098780be87.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `mcp-server/package-lock.json` is excluded by `!**/package-lock.json`

</details>

<details>
<summary>📒 Files selected for processing (14)</summary>

* `.dockerignore`
* `.github/workflows/README.md`
* `.github/workflows/publish.yaml`
* `Dockerfile`
* `README.md`
* `mcp-server/package.json`
* `mcp-server/src/http.test.ts`
* `mcp-server/src/http.ts`
* `mcp-server/src/lib/context.test.ts`
* `mcp-server/src/lib/context.ts`
* `mcp-server/src/lib/request.ts`
* `mcp-server/src/server.test.ts`
* `mcp-server/src/server.ts`
* `mcp-server/tsdown.config.ts`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread .github/workflows/publish.yaml Outdated
@maxigimenez maxigimenez requested review from vCaisim and removed request for maxigimenez July 13, 2026 10:48

@vCaisim vCaisim left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@miguelangaranocurrents looks great, left 2 optional nits, approved.

Comment thread mcp-server/src/lib/request.ts Outdated
Comment thread mcp-server/src/http.ts Outdated
cursoragent and others added 2 commits July 14, 2026 06:13
Co-authored-by: miguelangaranocurrents <miguelangaranocurrents@users.noreply.github.com>
Co-authored-by: miguelangaranocurrents <miguelangaranocurrents@users.noreply.github.com>
@miguelangaranocurrents miguelangaranocurrents merged commit 3e76b01 into main Jul 14, 2026
5 checks passed
@miguelangaranocurrents miguelangaranocurrents deleted the feat/remote-mcp branch July 14, 2026 06:16
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.

4 participants