Skip to content

ddg-search-mcp - #10

Open
onokonem wants to merge 2 commits into
mainfrom
mcp-opsx-staged
Open

ddg-search-mcp#10
onokonem wants to merge 2 commits into
mainfrom
mcp-opsx-staged

Conversation

@onokonem

@onokonem onokonem commented Mar 5, 2026

Copy link
Copy Markdown
Member

No description provided.

@onokonem onokonem self-assigned this Mar 5, 2026
Add MCP configuration management

Add MCP logging infrastructure

Add MCP signal handling for graceful shutdown

Add OpenSpec change documentation for MCP server

Update Go dependencies for MCP server

Add MCP server tests

Update MCP server implementation

Update OpenSpec tasks documentation

Refactor Perplexity client implementation

Update MCP server tools implementation

Update MCP server core

Update command implementations

Update OpenSpec documentation

Fix SSE endpoint routing and add streaming support

Add HTTP SSE transport tests

Update Perplexity integration

Update E2E tests and main command

Add SSE test plan and update tasks

docs: update manual test plans for /mcp endpoint migration

Update stage8, stage9, and stage10 manual test plans to reflect
the new Streamable HTTP transport endpoint (/mcp) instead of the
deprecated /sse and /message endpoints.

Changes:
- stage8-manual-test-plan.md: Updated all test cases to use /mcp endpoint
  with POST for JSON-RPC requests and GET with Accept: text/event-stream
  for SSE connections. Removed session ID handling as it's no longer
  needed in the new transport.
- stage9-manual-test-plan.md: Updated TLS/mTLS test cases to use /mcp
  endpoint with GET and Accept: text/event-stream header.
- stage10-manual-test-plan.md: Verified as already correct for new transport.

Fix README inconsistencies

Archive ddg-search-mcp-server change and sync specs

Add TLS tests and archive ddg-search-mcp-server change

Update MCP server implementation

Update mise configuration

Update config test file

Update dump test file

Refactor search test files to internal
@onokonem onokonem changed the title Mcp opsx staged ddg-search-mcp Mar 5, 2026
@AnyCPU

AnyCPU commented Mar 5, 2026

Copy link
Copy Markdown
  1. Remove duplicate formatPerplexityResults — use Markdown() instead
  2. Extract shared extractArgs helper (3x copy-paste)
  3. Remove pre-handler logToolCall + guard debug logging
  4. Remove dead debugWriter and apiKey fields in perplexity client
  5. Remove unused SearchOptions type + tests in perplexity
  6. Remove double SetContext(ctx)
  7. Make config loading conditional in main.go
  8. Remove nonfunctional Context() in ShutdownHandler
  9. Fix misleading doc comment on ReloadableConfig.Get()
  10. Wire up SearchConfig defaults from appConfig in HandleSearch

Comment thread internal/mcp/server.go
streamableHTTPServer *server.StreamableHTTPServer
logger *slog.Logger
config *Config
appConfig any // Holds full application configuration (mcpconfig.Config)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

appConfig any breaks type safety.

The entire TLS configuration path in buildTLSConfig() (lines 437-503) uses a chain of interface type assertions (tlsConfigGetter, tlsFields) to extract config fields.

Similarly, ReloadTLS() (lines 164-213) does the same.

The Server struct doesn't know what its config is, yet deeply depends on
its shape.

Recommendation: Define an explicit interface that mcpconfig.Config satisfies:

type AppConfig interface {k
  GetTLSConfig() *TLSSettings // concrete type, not any
}

Or simply accept *mcpconfig.Config directly — these packages are in the same module.

}

// GetEnabled returns whether TLS is enabled.
func (t *TLSConfig) GetEnabled() bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This adds 6 getter methods (GetEnabled, GetCertFile, GetKeyFile, GetMinVersion, GetMTLSEnabled, GetMTLSCAFile) on an already-exported struct with exported fields.

These exist solely to satisfy the inline interfaces in server.go. If the types were used directly, these would be unnecessary.

serverCtx, serverCancel := context.WithCancel(context.Background())

go func() {
defer serverCancel()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The serverCancel is only called when Serve returns.

But in waitForShutdown (line 173), when shutdown signal arrives, nothing cancels serverCtx — the code just waits on shutdown.Wait().

The HTTP server is never told to stop. The Serve method does

select { case <-ctx.Done(): ... }

but ctx is serverCtx, which is never cancelled from outside.

Recommendation: Cancel serverCtx when shutdown is initiated.

case <-shutdown.ShutdownChan():
logger.Info("Shutting down...")
case err := <-serverErr:
if err != nil && err.Error() != "MCP server error: context canceled" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is fragile string matching. Use errors.Is(err, context.Canceled) or a sentinel error.

defer h.mu.Unlock()

select {
case <-h.shutdownChan:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Both branches return context.Background(). This method never returns a context that gets cancelled on shutdown.

If the intent is to provide a cancellable context, it should use context.WithCancel tied to the shutdown channel.

Cancelling serverCtx on shutdown solves the shutdown problem without needing this method.

v.SetConfigFile(configFilePath)
} else {
// File doesn't exist, skip config file loading
skipConfigFile = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When DDG_SEARCH_CONFIG_FILE points to a non-existent file, the code silently skips it and uses defaults.

If a user explicitly sets config file, they expect that file to be used. Silently falling back to defaults hides misconfiguration

logger *slog.Logger,
) {
go reloadableCfg.WatchSignalsWithCallback(shutdown.ShutdownChan(), func() {
currentCfg := reloadableCfg.Get()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

After SIGHUP reload, the callback reads currentCfg but never updates the server’s appConfig pointer.

Since tools and ReloadTLS() read configuration from mcpServer state, they keep using the initial config loaded at startup, so runtime config changes (e.g., Perplexity enable/token, search defaults, TLS file path changes) are silently ignored.

func (r *ReloadableConfig) Reload() error {
r.logger.Info("Reloading configuration")

newCfg, err := Load()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This reads default locations/environment, even when startup used --config and LoadFromFile(...).

In that case, SIGHUP reload pulls a different config source, so services launched with a custom config file can unexpectedly revert to defaults or unrelated settings.

Also note: --log-level override is currently startup-only and will also be lost on reload.

Comment thread internal/mcp/server.go
// Build TLS config
config := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12, // Minimum TLS 1.2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This hardcodes MinVersion: tls.VersionTLS12 and never uses the configured server.tls.min_version value, even though the interface exposes GetMinVersion().

// Make the request
resp, err := c.Do(ctx, c.httpClient.R().
SetBody(reqBody).
SetContext(ctx).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can be removed because Do() at client.go:63 calls req.SetContext(ctx) again.

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.

3 participants