Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 35 additions & 23 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,16 @@ cmd/mcp-front/ # Main application entry point
### Documentation Standards

**Write precise, technical language:**

- ❌ "When Claude connects to MCP Front, it includes a bearer token in the Authorization header"
- ✅ "An MCP client can connect to MCP Front with a bearer token"
- ❌ "Users log in with their Google account"
- ❌ "Users log in with their Google account"
- ✅ "Claude redirects users to Google for authentication"
- ❌ "Claude establishes SSE connection"
- ✅ "Claude connects via SSE"

**Key clarifications:**

- **Claude.ai only supports OAuth** - Bearer tokens are for development/alternative clients only
- **Avoid redundant implementation details** - "bearer token" implies Authorization header
- **Use precise actors** - "MCP client" not "user" in technical contexts
Expand All @@ -184,11 +186,10 @@ cmd/mcp-front/ # Main application entry point
### Refactoring Guidelines

When refactoring for better design:

1. **Identify the core issue** - Don't just patch symptoms
2. **Use proper dependency injection** - Pass dependencies to constructors
3. **Extract interfaces to break circular dependencies** - Put in `internal/interfaces`
4. **Test the refactoring** - Ensure all tests still pass
5. **Update documentation** - Keep CLAUDE.md current with best practices
3. **Test the refactoring** - Ensure all tests still pass

### Security Boundaries

Expand All @@ -200,30 +201,32 @@ When refactoring for better design:
## Quick Reference Commands

```bash
# Build
go build -o mcp-front ./cmd/mcp-front
# Build everything
make build

# Test
go test ./...
go test ./integration -v
# Format everything
make format

# Lint
staticcheck ./...
# Lint everything
make lint

# Run locally
# Test mcp-front
go test ./internal/... -v
go test ./integration -v

# Run mcp-front locally
./mcp-front -config config.json

# Documentation site (from root)
make doc # Start dev server
make format # Format docs with Prettier
make build # Build static site
# Start docs dev server
make doc
```

## Documentation Site Guidelines

### Design Philosophy

The documentation site follows terse, to-the-point prose style (like early Stripe or Stainless docs):

- No bullet lists or tables in content
- Conversational yet technical tone
- Developer-to-developer communication
Expand All @@ -239,6 +242,7 @@ The documentation site follows terse, to-the-point prose style (like early Strip
### Technical Implementation

**Structure** (docs-site/):

```
src/
├── components/
Expand All @@ -252,6 +256,7 @@ src/
```

**Key Features**:

- Starlight theme with custom components and CSS overrides
- Proper light/dark mode with automatic logo switching
- 12-second animation cycle for subtle mascot behavior
Expand All @@ -260,26 +265,29 @@ src/
### Animation Details

The animated logo creates a face-like character:

- **Eyes**: Left/right translation (1px) with synchronized movement
- **Nose**: Subtle rotation (-1deg/+1deg) following eye direction
- **Nose**: Subtle rotation (-1deg/+1deg) following eye direction
- **Blinking**: Vertical scale (scaleY 0.1) with step-like timing for natural effect
- **Timing**: 12-second cycle for easter egg discovery, not attention-grabbing

### Color Management

**CSS Custom Properties**:

```css
:root {
--sl-color-accent: #FF6B6B; /* Light mode */
--sl-color-accent: #ff6b6b; /* Light mode */
}

[data-theme='dark'] {
--sl-color-accent: #FF6B6B; /* Buttons */
--sl-color-text-accent: #333333; /* Text on red backgrounds */
[data-theme="dark"] {
--sl-color-accent: #ff6b6b; /* Buttons */
--sl-color-text-accent: #333333; /* Text on red backgrounds */
}
```

**Specific Overrides**:

- GitHub icon: White in dark mode
- Sidebar selection: Readable contrast
- Anchor links: Light gray in dark mode
Expand All @@ -289,26 +297,30 @@ The animated logo creates a face-like character:
### Content Guidelines

**Configuration Examples**:

- Always use `{"$env": "VAR"}` syntax, never bash `$VAR`
- Match actual Go implementation exactly
- Use realistic service names (e.g., "linear" not "database")
- Include all required fields (version, transportType, etc.)

**Writing Style**:

- Flowing prose, not lists
- Explain the "why" not just "how"
- Explain the "why" not just "how"
- Assume developer audience
- Keep it concise but complete

### Pull Request Guidelines

**PR Titles**: Use clear, descriptive titles focused on the change impact, not just restating commit messages. Examples:

- ❌ "feat: add message endpoint support for SSE MCP servers"
- ✅ "Add SSE message endpoint support"
- ❌ "fix: implement session-specific tool registration for stdio clients"
- ❌ "fix: implement session-specific tool registration for stdio clients"
- ✅ "Fix stdio session tool handler conflicts"

**PR Descriptions**: Write terse prose for humans, not documentation. Avoid bullet lists unless they add genuine value. Focus on the problem solved and solution approach:

- Explain what was broken and how it's fixed
- Use conversational, developer-to-developer tone
- Skip implementation details unless critical for review
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ format:
go fmt ./...
cd docs-site && npm run format

lint:
staticcheck ./...
golangci-lint run ./...

build:
go build -o mcp-front ./cmd/mcp-front
cd docs-site && npm run build

.PHONY: doc format build
.PHONY: doc format build lint
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Main endpoint for MCP protocol communication over Server-Sent Events.
The request flow:

1. Claude connects via SSE
2. MCP Front validates auth token
2. MCP Front validates auth token
3. MCP Front connects to MCP server
4. Bidirectional message streaming

Expand Down
4 changes: 2 additions & 2 deletions docs-site/src/content/docs/examples/oauth-google.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ The `allowedDomains` field is your primary security control. Only email addresse
How it works:

- john@company.com ✓ Allowed
- jane@subsidiary.com ✓ Allowed
- jane@subsidiary.com ✓ Allowed
- hacker@gmail.com ✗ Rejected

This is perfect for companies using Google Workspace, as it automatically grants access to all employees while blocking external users.
Expand Down Expand Up @@ -139,7 +139,7 @@ OAuth 2.1 requires HTTPS in production:

Options for HTTPS:

- **Load Balancer**: Terminate SSL at the load balancer
- **Load Balancer**: Terminate SSL at the load balancer
- **Reverse Proxy**: Use nginx/caddy with Let's Encrypt
- **Managed hosting**: Deploy to any platform with automatic HTTPS

Expand Down
2 changes: 1 addition & 1 deletion integration/config/config.inline-test.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
},
"command": "sleep",
"args": ["10"],
"timeout": 100000000
"timeout": "100ms"
}
]
}
Expand Down
4 changes: 2 additions & 2 deletions integration/oauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@ func TestToolAdvertisementWithUserTokens(t *testing.T) {
assert.Equal(t, "token_required", errorInfo["code"], "Error code should be token_required")

errorMessage := errorInfo["message"].(string)
assert.Contains(t, errorMessage, "Token Required", "Error should mention token required")
assert.Contains(t, errorMessage, "token required", "Error should mention token required")
assert.Contains(t, errorMessage, "/my/tokens", "Error should mention token setup URL")
assert.Contains(t, errorMessage, "Test Service", "Error should mention service name")

Expand All @@ -909,7 +909,7 @@ func TestToolAdvertisementWithUserTokens(t *testing.T) {
// Verify instructions
instructions := errData["instructions"].(map[string]interface{})
assert.Contains(t, instructions["ai"].(string), "CRITICAL", "Should have AI instructions")
assert.Contains(t, instructions["human"].(string), "Token Required", "Should have human instructions")
assert.Contains(t, instructions["human"].(string), "token required", "Should have human instructions")
})

t.Run("ToolInvocationSucceedsWithUserToken", func(t *testing.T) {
Expand Down
87 changes: 5 additions & 82 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,83 +134,6 @@ func DefaultTransportCreator(conf *config.MCPClientConfig) (MCPClientInterface,
return nil, errors.New("invalid client type: must have either command or url")
}

// AddToMCPServer connects the client to an MCP server
func (c *Client) AddToMCPServer(ctx context.Context, clientInfo mcp.Implementation, mcpServer *server.MCPServer) error {
return c.AddToMCPServerWithTokenCheck(ctx, clientInfo, mcpServer, "", false, nil, "", "", nil)
}

// AddToMCPServerWithTokenCheck connects the client to an MCP server with optional token checking
func (c *Client) AddToMCPServerWithTokenCheck(
ctx context.Context,
clientInfo mcp.Implementation,
mcpServer *server.MCPServer,
userEmail string,
requiresToken bool,
tokenStore storage.UserTokenStore,
serverName string,
setupBaseURL string,
tokenSetup *config.TokenSetupConfig,
) error {
return c.AddToMCPServerWithSession(ctx, clientInfo, mcpServer, userEmail, requiresToken, tokenStore, serverName, setupBaseURL, tokenSetup, nil)
}

// AddToMCPServerWithSession connects the client to an MCP server with optional session-specific tools
func (c *Client) AddToMCPServerWithSession(
ctx context.Context,
clientInfo mcp.Implementation,
mcpServer *server.MCPServer,
userEmail string,
requiresToken bool,
tokenStore storage.UserTokenStore,
serverName string,
setupBaseURL string,
tokenSetup *config.TokenSetupConfig,
session server.ClientSession,
) error {
if c.needManualStart {
err := c.client.Start(ctx)
if err != nil {
return err
}
}
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = clientInfo
initRequest.Params.Capabilities = mcp.ClientCapabilities{
Experimental: make(map[string]interface{}),
Roots: nil,
Sampling: nil,
}
_, err := c.client.Initialize(ctx, initRequest)
if err != nil {
return err
}
internal.Logf("<%s> Successfully initialized MCP client", c.name)

// Start capability discovery
internal.LogInfoWithFields("client", "Starting MCP capability discovery", map[string]interface{}{
"server": c.name,
})

err = c.addToolsToServer(ctx, mcpServer, userEmail, requiresToken, tokenStore, serverName, setupBaseURL, tokenSetup, session)
if err != nil {
return err
}
_ = c.addPromptsToServer(ctx, mcpServer)
_ = c.addResourcesToServer(ctx, mcpServer)
_ = c.addResourceTemplatesToServer(ctx, mcpServer)

internal.LogInfoWithFields("client", "MCP capability discovery completed", map[string]interface{}{
"server": c.name,
"userTokenRequired": requiresToken,
})

if c.needPing {
go c.startPingTask(ctx)
}
return nil
}

// startPingTask runs a goroutine that pings the MCP server every 30 seconds.
// The goroutine lifecycle is tied to the provided context:
// - For stdio clients: context is cancelled when the request ends, stopping pings
Expand Down Expand Up @@ -513,7 +436,7 @@ func (c *Client) wrapToolHandler(
serverName,
setupBaseURL,
tokenSetup,
"Configuration error: This service requires user tokens but OAuth is not properly configured.",
"configuration error: this service requires user tokens but OAuth is not properly configured.",
)

errorJSON, _ := json.Marshal(errorData)
Expand All @@ -528,17 +451,17 @@ func (c *Client) wrapToolHandler(
var errorMessage string
if tokenSetup != nil {
errorMessage = fmt.Sprintf(
"Token Required: %s requires a user token to access the API. "+
"Please visit %s to set up your %s token. %s",
"token required: %s requires a user token to access the API. "+
"please visit %s to set up your %s token. %s",
tokenSetup.DisplayName,
tokenSetupURL,
tokenSetup.DisplayName,
tokenSetup.Instructions,
)
} else {
errorMessage = fmt.Sprintf(
"Token Required: This service requires a user token. "+
"Please visit %s to configure your token.",
"token required: this service requires a user token. "+
"please visit %s to configure your token.",
tokenSetupURL,
)
}
Expand Down
Loading
Loading