Skip to content

[DOJ-2175/2177/2178] feat: rewrite MarketplaceService with real API calls#6

Open
dbejarano820 wants to merge 4 commits into
mainfrom
feat/DOJ-2175-service-rewrite
Open

[DOJ-2175/2177/2178] feat: rewrite MarketplaceService with real API calls#6
dbejarano820 wants to merge 4 commits into
mainfrom
feat/DOJ-2175-service-rewrite

Conversation

@dbejarano820
Copy link
Copy Markdown
Contributor

Summary

  • DOJ-2177: Wires per-request API key injection from HTTP transport to MarketplaceService via setApiKey(). Removes apiKey parameter threading from install() and private helpers — all methods now use the instance field.
  • DOJ-2175: Adds private callApi() method that POSTs to the marketplace-mcp-api Edge Function. Replaces all stub service methods (search, listCategories, getDetails, getItemDetail, uninstall, listInstalled) with real API calls. Updates MarketplaceCategory type to { name, slug, description }.
  • DOJ-2178: Implements marketplace_list_categories with real data — maps Edge Function's ['skill', 'plugin', 'tool'] response to full category objects with display names and descriptions.

Linear

Closes DOJ-2175, DOJ-2177, DOJ-2178

Test plan

  • npm test passes (24 tests)
  • npm run typecheck passes
  • npm run build succeeds
  • HTTP transport injects API key per-request
  • Service methods call real Edge Function endpoints

🤖 Generated with Claude Code

dbejarano820 and others added 2 commits February 23, 2026 13:24
Add apiKey field and setApiKey() method to MarketplaceService so
the HTTP transport can inject the caller's Bearer token per request.
Remove the apiKey parameter from install() — all methods now use the
instance field, falling back to DOJO_API_KEY env var at construction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add private callApi() method that POSTs to the marketplace-mcp-api
Edge Function. Replace search, listCategories, getDetails, getItemDetail,
uninstall, and listInstalled stubs with real callApi() invocations.
Private helpers (resolveSlug, getVersionRecord, incrementDownloadCount)
now use this.apiKey instead of parameter threading. Update tests to
mock fetch for all API-calling methods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 23, 2026

Greptile Summary

Replaces MarketplaceService stub methods with real Edge Function API calls via a new callApi() helper. Adds MethodOptions parameter to all public methods for per-request API key injection. Updates MarketplaceCategory type from {id, name, itemCount} to {name, slug, description}. Adds 30s timeouts to all fetch calls.

Critical issue: DOJ-2177's per-request API key injection is incomplete. http.ts:27 stores authCtx.apiKey in res.locals.dojoApiKey, but tool handlers never retrieve it or pass it to service methods via the options parameter. All service calls use this.apiKey (the instance default from env var) instead of the per-request key, defeating the race condition fix.

  • Implemented real API calls for search, listCategories, getDetails, getItemDetail, uninstall, listInstalled
  • Added AbortSignal.timeout(30_000) to prevent hanging requests
  • Tests updated to mock API responses and verify correct action payloads
  • Empty string guard added for category slug capitalization (line 254)

Confidence Score: 1/5

  • Critical bug in HTTP mode: per-request credentials are validated but never used
  • DOJ-2177 claims to wire per-request credential injection, but the stored value is never retrieved. Tool handlers call service methods without passing the per-request value via options parameter, so all requests use the shared instance field. This creates the exact race condition DOJ-2177 intended to fix. HTTP mode will fail with auth errors or leak credentials between concurrent requests.
  • src/transports/http.ts must inject the validated credential into the service instance after line 27, or tool handlers must be refactored to pass it via MCP request metadata

Important Files Changed

Filename Overview
src/transports/http.ts Critical bug: per-request API key stored but never used, defeating the race condition fix
src/services/marketplace.service.ts Replaces stub methods with real API calls, adds MethodOptions parameter, adds timeouts and empty string guards
src/index.ts Passes service parameter to startHttpServer for per-request API key injection

Sequence Diagram

sequenceDiagram
    participant Client
    participant HTTP as HTTP Middleware
    participant Service as MarketplaceService
    participant API as Edge Function

    Client->>HTTP: POST /mcp + Authorization header
    HTTP->>HTTP: Extract & validate API key
    HTTP->>HTTP: Store in res.locals.dojoApiKey
    Note over HTTP,Service: ❌ API key NOT passed to service
    HTTP->>Service: Call tool handler
    Service->>Service: Use this.apiKey (default/env)
    Service->>API: POST with wrong API key
    API-->>Service: Response
    Service-->>HTTP: Result
    HTTP-->>Client: Response
Loading

Last reviewed commit: a4f85da

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread src/services/marketplace.service.ts Outdated

return data.map((slug) => {
const display = CATEGORY_DISPLAY[slug] ?? {
name: slug.charAt(0).toUpperCase() + slug.slice(1),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Empty string handling - if slug is an empty string, .charAt(0) returns empty string and capitalization fails silently

Suggested change
name: slug.charAt(0).toUpperCase() + slug.slice(1),
name: slug ? slug.charAt(0).toUpperCase() + slug.slice(1) : "Unknown",
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/services/marketplace.service.ts
Line: 244

Comment:
Empty string handling - if `slug` is an empty string, `.charAt(0)` returns empty string and capitalization fails silently

```suggestion
        name: slug ? slug.charAt(0).toUpperCase() + slug.slice(1) : "Unknown",
```

How can I resolve this? If you propose a fix, please make it concise.

dbejarano820 and others added 2 commits February 23, 2026 16:17
- Add per-call apiKey parameter to all public MarketplaceService methods
  to prevent API key leakage between concurrent HTTP requests
- Add AbortSignal.timeout(30s) to resolveSlug, getVersionRecord, and
  incrementDownloadCount fetch calls
- Add empty string guard in listCategories slug mapping

[DOJ-2175]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace `declare global { namespace Express }` with `res.locals`
to satisfy @typescript-eslint/no-namespace ESLint rule.

[DOJ-2175]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dbejarano820
Copy link
Copy Markdown
Contributor Author

@greptile review again

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment thread src/transports/http.ts
export async function startHttpServer(server: McpServer): Promise<void> {
export async function startHttpServer(
server: McpServer,
_service: MarketplaceService,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

_service parameter is marked as unused but the race condition fix is incomplete

The underscore prefix indicates this parameter is intentionally unused, but DOJ-2177's goal was to inject the per-request API key. Since tool handlers don't access res.locals.dojoApiKey, calling _service.setApiKey(authCtx.apiKey) here would fix the race condition (though still has timing issues if multiple tools are called in parallel within one request).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/transports/http.ts
Line: 11

Comment:
`_service` parameter is marked as unused but the race condition fix is incomplete

The underscore prefix indicates this parameter is intentionally unused, but DOJ-2177's goal was to inject the per-request API key. Since tool handlers don't access `res.locals.dojoApiKey`, calling `_service.setApiKey(authCtx.apiKey)` here would fix the race condition (though still has timing issues if multiple tools are called in parallel within one request).

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/transports/http.ts
return;
}
// Store on res.locals — no shared-state mutation, no type augmentation
res.locals.dojoApiKey = authCtx.apiKey;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

stored value is never used — tool handlers don't pass it to service methods

The value stored on line 27 is not retrieved or passed to MarketplaceService methods. Tool handlers in browse.tools.ts and install.tools.ts call service methods like service.search(args) without passing the per-request value via options, so they'll use the default instance field instead.

Fix: call _service.setApiKey(authCtx.apiKey) after line 27 to inject the per-request value into the service instance.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/transports/http.ts
Line: 27

Comment:
stored value is never used — tool handlers don't pass it to service methods

The value stored on line 27 is not retrieved or passed to `MarketplaceService` methods. Tool handlers in `browse.tools.ts` and `install.tools.ts` call service methods like `service.search(args)` without passing the per-request value via options, so they'll use the default instance field instead.

Fix: call `_service.setApiKey(authCtx.apiKey)` after line 27 to inject the per-request value into the service instance.

How can I resolve this? If you propose a fix, please make it concise.


return data.map((slug) => {
const display = CATEGORY_DISPLAY[slug] ?? {
name: slug ? slug.charAt(0).toUpperCase() + slug.slice(1) : "Unknown",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

empty string guard added (already noted in previous review thread)

The ternary slug ? ... : "Unknown" handles the empty string case mentioned in the previous review. Consider whether "Unknown" is the right fallback or if empty strings should be filtered from the API response entirely.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/services/marketplace.service.ts
Line: 254

Comment:
empty string guard added (already noted in previous review thread)

The ternary `slug ? ... : "Unknown"` handles the empty string case mentioned in the previous review. Consider whether "Unknown" is the right fallback or if empty strings should be filtered from the API response entirely.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

status: response.status,
statusText: response.statusText,
});
const error = await response.json().catch(() => ({}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

error parsing assumes JSON — will throw if Edge Function returns HTML or plain text on error

.json().catch(() => ({})) falls back to empty object, so the error message becomes generic API error: {status} instead of showing the actual error text. Consider .text() fallback for better error messages.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/services/marketplace.service.ts
Line: 200

Comment:
error parsing assumes JSON — will throw if Edge Function returns HTML or plain text on error

`.json().catch(() => ({}))` falls back to empty object, so the error message becomes generic `API error: {status}` instead of showing the actual error text. Consider `.text()` fallback for better error messages.

How can I resolve this? If you propose a fix, please make it concise.

@dbejarano820
Copy link
Copy Markdown
Contributor Author

🤖 This PR has had no activity for 3 days.

If it's still needed, please:

  • Resolve any conflicts
  • Address review feedback
  • Push an update

If it's no longer needed, please close it.

— Dojito (automated PR gardener)

@dbejarano820
Copy link
Copy Markdown
Contributor Author

@greptileai what is missing for this to be a 5/5 PR?

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 27, 2026

Looking at the PR and review comments, here's what's missing for 5/5:

Critical Issue:
The per-request API key injection (DOJ-2177) is incomplete. The code stores authCtx.apiKey in res.locals.dojoApiKey (http.ts:27) but never uses it. Tool handlers call service methods without passing the per-request key, so all requests share the same instance field — the race condition still exists.

Fix: Call service.setApiKey(authCtx.apiKey) in the middleware after line 27, or pass { apiKey: res.locals.dojoApiKey } from tool handlers to service methods.

Secondary Issues:

  1. Error handling (marketplace.service.ts:200): Falls back to empty object when response isn't JSON, losing error details. Use .text() fallback for better error messages.

  2. Empty string guard (marketplace.service.ts:254): Works but consider filtering empty slugs at the API level rather than mapping them to "Unknown".

Test Plan:
The two unchecked items won't pass until the API key injection is fixed:

  • ☐ HTTP transport injects API key per-request
  • ☐ Service methods call real Edge Function endpoints

Once the API key wiring is complete and those items are verified, this will be a solid 5/5.

@dbejarano820
Copy link
Copy Markdown
Contributor Author

🤖 This PR has had no activity for 7 days.

If it's still needed, please:

  • Resolve any conflicts
  • Address review feedback
  • Push an update

If it's no longer needed, please close it.

— Dojito (automated PR gardener)

@dbejarano820
Copy link
Copy Markdown
Contributor Author

🤖 This PR has had no activity for 10 days.

If it's still needed, please:

  • Resolve any conflicts
  • Address review feedback
  • Push an update

If it's no longer needed, please close it.

— Dojito (automated PR gardener)

@dbejarano820
Copy link
Copy Markdown
Contributor Author

🤖 This PR has had no activity for 15 days.

If it's still needed, please:

  • Resolve any conflicts
  • Address review feedback
  • Push an update

If it's no longer needed, please close it.

— Dojito (automated PR gardener)

1 similar comment
@dbejarano820
Copy link
Copy Markdown
Contributor Author

🤖 This PR has had no activity for 15 days.

If it's still needed, please:

  • Resolve any conflicts
  • Address review feedback
  • Push an update

If it's no longer needed, please close it.

— Dojito (automated PR gardener)

@dbejarano820
Copy link
Copy Markdown
Contributor Author

🤖 This PR has had no activity for 3 days.

If it's still needed, please:

  • Address Greptile review feedback (score: 1/5)
  • Push an update

If it's no longer needed, please close it.

— Dojito (automated PR gardener)

@dbejarano820
Copy link
Copy Markdown
Contributor Author

🤖 This PR has had no activity for 3 days.

If it's still needed, please:

  • Address Greptile review feedback
  • Push an update

If it's no longer needed, please close it.

— Dojito (automated PR gardener)

@dbejarano820
Copy link
Copy Markdown
Contributor Author

🤖 This PR has had no activity for 24+ days.

If it's still needed, please:

  • Resolve any conflicts
  • Address review feedback
  • Push an update

If it's no longer needed, please close it.

— Dojito (automated PR gardener)

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