feat: add OAuth 2.0 authentication and README

- Implement RFC 8414 OAuth authorization server metadata
- Add dynamic client registration (RFC 7591)
- Add /authorize and /token endpoints
- Create comprehensive README with setup instructions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude (Lupul Augmentat)
2026-01-27 07:32:42 +01:00
parent 250420e9e2
commit 874e871ec2
2 changed files with 214 additions and 11 deletions

135
README.md Normal file
View File

@@ -0,0 +1,135 @@
# Lupul Augmentat
MCP (Model Context Protocol) server with SSE transport for remote AI agent communication.
## Features
- **SSE Transport**: HTTP-based Server-Sent Events for remote MCP connections
- **OAuth 2.0 Authentication**: RFC 8414 compliant for Claude Code compatibility
- **NATS Messaging**: Internal pub/sub for module communication
- **Agent Presence**: Track online agents and their capabilities
- **Service Discovery**: List local services via systemctl
## Installation
```bash
git clone git@git.runningwolf.com:sebastian/Lupul-Augmentat.git
cd Lupul-Augmentat
npm install
cp .env.example .env
# Edit .env with your configuration
```
## Configuration
Create a `.env` file:
```env
# MCP Server
MCP_HOST=127.0.0.1
MCP_PORT=19017
MCP_LOG_LEVEL=info
# NATS
NATS_URL=nats://localhost:4222
# Security
API_KEY=your-secure-api-key
AUTH_ENABLED=true
```
## Running
### Development
```bash
npm run dev:sse
```
### Production (systemd)
```bash
sudo systemctl start lupul-augmentat
sudo systemctl enable lupul-augmentat
```
## Connecting from Claude Code
Add to your MCP settings:
```json
{
"mcpServers": {
"lupul": {
"type": "sse",
"url": "https://your-server.com/sse"
}
}
}
```
Claude Code will automatically handle OAuth authentication.
## Available Tools
### Agent Management
- `register_agent` - Register yourself with name, role, and capabilities
- `list_agents` - See all online agents
- `list_services` - List services on this machine
### Messaging
- `send_message` - Send message to another agent
- `receive_messages` - Check your inbox
## API Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /sse` | SSE connection (protected) |
| `POST /message` | Send MCP messages (protected) |
| `GET /health` | Health check |
| `GET /.well-known/oauth-authorization-server` | OAuth metadata |
| `GET /.well-known/oauth-protected-resource` | Protected resource metadata |
| `POST /register` | Dynamic client registration |
| `GET /authorize` | OAuth authorization |
| `POST /token` | Token exchange |
## Nginx Configuration
For SSL termination with nginx:
```nginx
location /sse {
proxy_pass http://127.0.0.1:19017/sse;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
chunked_transfer_encoding off;
}
```
## Architecture
```
Claude Code (Mac/Linux)
▼ HTTPS/SSE
┌─────────────────┐
│ nginx (SSL) │
└────────┬────────┘
│ HTTP
┌────────▼────────┐
│ SSE Server │
│ (sse-server.ts)│
└────────┬────────┘
│ NATS
┌────────▼────────┐
│ Tool Modules │
│ (messaging, │
│ file ops...) │
└─────────────────┘
```
## License
MIT

View File

@@ -67,26 +67,94 @@ async function startSSEServer() {
res.send('healthy\n');
});
// OAuth discovery endpoints - return empty/minimal responses
// Claude Code checks these before connecting
// OAuth 2.0 Authorization Server Metadata (RFC 8414)
// This tells Claude Code how to authenticate
const serverUrl = 'https://ultra.runningwolf.com';
app.get('/.well-known/oauth-authorization-server', (_req, res) => {
res.json({
issuer: serverUrl,
authorization_endpoint: `${serverUrl}/authorize`,
token_endpoint: `${serverUrl}/token`,
registration_endpoint: `${serverUrl}/register`,
response_types_supported: ['code'],
grant_types_supported: ['authorization_code'],
code_challenge_methods_supported: ['S256'],
token_endpoint_auth_methods_supported: ['none'],
});
});
// Protected Resource Metadata (RFC 9728)
app.get('/.well-known/oauth-protected-resource', (_req, res) => {
res.status(404).json({ error: 'not_found', error_description: 'OAuth not supported' });
res.json({
resource: serverUrl,
authorization_servers: [serverUrl],
bearer_methods_supported: ['header'],
});
});
app.get('/.well-known/oauth-protected-resource/*', (_req, res) => {
res.status(404).json({ error: 'not_found', error_description: 'OAuth not supported' });
});
app.get('/.well-known/oauth-authorization-server', (_req, res) => {
res.status(404).json({ error: 'not_found', error_description: 'OAuth not supported' });
res.json({
resource: serverUrl,
authorization_servers: [serverUrl],
bearer_methods_supported: ['header'],
});
});
// OpenID Connect discovery (optional, return 404)
app.get('/.well-known/openid-configuration', (_req, res) => {
res.status(404).json({ error: 'not_found', error_description: 'OAuth not supported' });
res.status(404).json({ error: 'not_supported' });
});
app.post('/register', (_req, res) => {
res.status(404).json({ error: 'not_found', error_description: 'OAuth not supported' });
// Dynamic Client Registration (RFC 7591)
// Auto-register any client that asks
app.post('/register', (req, res) => {
const clientId = `client_${Date.now()}`;
res.status(201).json({
client_id: clientId,
client_secret: '', // Public client, no secret needed
client_id_issued_at: Math.floor(Date.now() / 1000),
grant_types: ['authorization_code'],
response_types: ['code'],
token_endpoint_auth_method: 'none',
});
});
// Authorization endpoint - return the API key as code
app.get('/authorize', (req, res) => {
const redirectUri = req.query.redirect_uri as string;
const state = req.query.state as string;
if (!redirectUri) {
res.status(400).json({ error: 'invalid_request', error_description: 'Missing redirect_uri' });
return;
}
// Return the API key as the authorization code
const code = API_KEY;
const redirectUrl = new URL(redirectUri);
redirectUrl.searchParams.set('code', code || 'no-api-key');
if (state) redirectUrl.searchParams.set('state', state);
res.redirect(redirectUrl.toString());
});
// Token endpoint - exchange code for access token
app.post('/token', express.urlencoded({ extended: true }), (req, res) => {
const code = req.body.code;
// Verify the code is our API key
if (code !== API_KEY) {
res.status(400).json({ error: 'invalid_grant', error_description: 'Invalid authorization code' });
return;
}
// Return the API key as access token
res.json({
access_token: API_KEY,
token_type: 'Bearer',
expires_in: 86400 * 365, // 1 year
});
});
// SSE endpoint - establishes the SSE stream (protected)