← Back to Skills
General

clawdirect-dev

napoleond By napoleond 👁 18 views ▲ 0 votes

Build agent-facing web experiences with ATXP-based

GitHub
---
name: clawdirect-dev
description: Build agent-facing web experiences with ATXP-based authentication, following the ClawDirect pattern. Use this skill when building websites that AI agents interact with via MCP tools, implementing cookie-based agent auth, or creating agent skills for web apps. Provides templates using @longrun/turtle, Express, SQLite, and ATXP.
---

# ClawDirect-Dev

Build agent-facing web experiences with ATXP-based authentication.

**Reference implementation**: https://github.com/napoleond/clawdirect

## What is ATXP?

ATXP (Agent Transaction Protocol) enables AI agents to authenticate and pay for services. When building agent-facing websites, ATXP provides:

- **Agent identity**: Know which agent is making requests
- **Payments**: Charge for premium actions (optional)
- **MCP integration**: Expose tools that agents can call programmatically

For full ATXP details: https://skills.sh/atxp-dev/cli/atxp

## How Agents Interact

Agents interact with your site in two ways:

1. **Browser**: Agents use browser automation tools to visit your website, click buttons, fill forms, and navigateβ€”just like humans do
2. **MCP tools**: Agents call your MCP endpoints directly for programmatic actions (authentication, payments, etc.)

The cookie-based auth pattern bridges these: agents get an auth cookie via MCP, then use it while browsing.

**Important**: Agent browsers often cannot set HTTP-only cookies directly. The recommended pattern is for agents to pass the cookie value in the query string (e.g., `?myapp_cookie=XYZ`), and have the server set the cookie and redirect to a clean URL.

## Architecture Overview

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         AI Agent                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚   Browser Tool      β”‚         β”‚   MCP Client            β”‚    β”‚
β”‚  β”‚   (visits website)  β”‚         β”‚   (calls tools)         β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚                                 β”‚
             β–Ό                                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Your Application                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚
β”‚  β”‚   Web Server        β”‚    β”‚   MCP Server            β”‚        β”‚
β”‚  β”‚   (Express)         β”‚    β”‚   (@longrun/turtle)     β”‚        β”‚
β”‚  β”‚                     β”‚    β”‚                         β”‚        β”‚
β”‚  β”‚   - Serves UI       β”‚    β”‚   - yourapp_cookie      β”‚        β”‚
β”‚  β”‚   - Cookie auth     β”‚    β”‚   - yourapp_action      β”‚        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚
β”‚            β”‚                            β”‚                       β”‚
β”‚            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                       β”‚
β”‚                       β–Ό                                         β”‚
β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                β”‚
β”‚              β”‚     SQLite      β”‚                                β”‚
β”‚              β”‚   auth_cookies  β”‚                                β”‚
β”‚              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

## Build Steps

1. **Create MCP server** alongside your website
2. **Implement cookie tool** in the MCP server
3. **Use cookie for auth** in your web API
4. **Publish an agent skill** for your site

## Step 1: Project Setup

Initialize a Node.js project with the required stack:

```bash
mkdir my-agent-app && cd my-agent-app
npm init -y
npm install @longrun/turtle @atxp/server @atxp/express better-sqlite3 express cors dotenv zod
npm install -D typescript @types/node @types/express @types/cors @types/better-sqlite3 tsx
```

Create `tsconfig.json`:
```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}
```

Create `.env`:
```
FUNDING_DESTINATION_ATXP=<your_atxp_account>
PORT=3001
```

## Step 2: Database with Cookie Auth

Create `src/db.ts`:

```typescript
import Database from 'better-sqlite3';
import crypto from 'crypto';

const DB_PATH = process.env.DB_PATH || './data.db';
let db: Database.Database;

export function getDb(): Database.Database {
  if (!db) {
    db = new Database(DB_PATH);
    db.pragma('journal_mode = WAL');

    // Auth cookies table - maps cookies to ATXP accounts
    db.exec(`
      CREATE TABLE IF NOT EXISTS auth_cookies (
        cookie_value TEXT PRIMARY KEY,
        atxp_account TEXT NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `);

    // Add your app's tables here
  }
  return db;
}

export function createAuthCookie(atxpAccount: string): string {
  const cookieValue = crypto.randomBytes(32).toString('hex');
  getDb().prepare(`
    INSERT INTO auth_cookies (cookie_value, atxp_account)
    VALUES (?, ?)
  `).run(cookieValue, atxpAccount);
  return cookieValue;
}

export function getAtxpAccountFromCookie(cookieValue: string): string | null {
  const result = getDb().prepare(`
    SELECT atxp_account FROM auth_cookies WHERE cookie_value = ?
  `).get(cookieValue) as { atxp_account: string } | undefined;
  return result?.atxp_account || null;
}
```

## Step 3: MCP Tools with Cookie Tool

Create `src/tools.ts`:

```typescript
import { defineTool } from '@longrun/turtle';
import { z } from 'zod';
import { requirePayment, atxpAccountId } from '@atxp/server';
import BigNumber from 'bignumber.js';
import { createAuthCookie } from './db.js';

// Cookie tool - agents call this to get browser auth
export const cookieTool = defineTool(
  'myapp_cookie',  // Replace 'myapp' with your app name
  'Get an authentication cookie for browser use. Set this cookie to authenticate when using the web interface.',
  z.object({}),
  async () => {
    // Free but requires ATXP auth
    const accountId = atxpAccountId();
    if (!accountId) {
      throw new Error('Authentication required');
    }

    const cookie = createAuthCookie(accountId);

    return JSON.stringify({
      cookie,
      instructions: 'To authenticate in a browser, navigate to https://your-domain.com?myapp_cookie=<cookie_value> - the server will set the HTTP-only cookie and redirect. Alternatively, set the cookie directly if your browser tool supports it.'
    });
  }
);

// Example paid tool
export const paidActionTool = defineTool(
  'myapp_action',
  'Perform some action. Cost: $0.10',
  z.object({
    input: z.string().describe('Input for the action')
  }),
  async ({ input }) => {
    await requirePayment({ price: new BigNumber(0.10) });

    const accountId = atxpAccountId();
    if (!accountId) {
      throw new Error('Authentication required');
    }

    // Your action logic here
    return JSON.stringify({ success: true, input });
  }
);

export const allTools = [cookieTool, paidActionTool];
```

## Step 4: Express API with Cookie Validation

Create `src/api.ts`:

```typescript
import { Router, Request, Response } from 'express';
import { getAtxpAccountFromCookie } from './db.js';

export const apiRouter = Router();

// Helper to extract cookie
function getCookieValue(req: Request, cookieName: string): string | null {
  const cookieHeader = req.headers.cookie;
  if (!cookieHeader) return null;

  const cookies = cookieHeader.split(';').map(c => c.trim());
  for (const cookie of cookies) {
    if (cookie.startsWith(`${cookieName}=`)) {
      return cookie.substring(cookieName.length + 1);
    }
  }
  return null;
}

// Middleware to require cookie auth
function requireCookieAuth(req: Request, res: Response, next: Function) {
  const cookieValue = getCookieValue(req, 'myapp_cookie');

  if (!cookieValue) {
    res.status(401).json({
      error: 'Authentication required',
      message: 'Use the myapp_cookie MCP tool to get an authentication cookie'
    });
    return;
  }

  const atxpAccount = getAtxpAccountFromCookie(cookieValue);
  if (!atxpAccount) {
    res.status(401).json({
      error: 'Invalid cookie',
      message: 'Your cookie is invalid or expired. Get a new one via the MCP tool.'
    });
    return;
  }

  // Attach account to request for use in handlers
  (req as any).atxpAccount = atxpAccount;
  next();
}

// Public endpoint (no auth)
apiRouter.get('/api/public', (_req: Request, res: Response) => {
  res.json({ message: 'Public data' });
});

// Protected endpoint (requires cookie auth)
apiRouter.post('/api/protected', requireCookieAuth, (req: Request, res: Response) => {
  const account = (req as any).atxpAccount;
  res.json({ message: 'Authenticated action', account });
});
```

## Step 5: Server Entry Point

Create `src/index.ts`:

```typescript
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { createServer } from '@longrun/turtle';
import { atxpExpress } from '@atxp/express';
import { getDb } from './db.js';
import { allTools } from './tools.js';
import { apiRouter } from './api.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const FUNDING_DESTINATION = process.env.FUNDING_DESTINATION_ATXP;
if (!FUNDING_DESTINATION) {
  throw new Error('FUNDING_DESTINATION_ATXP is required');
}

const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001;

async function main() {
  // Initialize database
  getDb();

  // Create MCP server
  const mcpServer = createServer({
    name: 'myapp',
    version: '1.0.0',
    tools: allTools
  });

  // Create Express app
  const app = express();
  app.use(cors());
  app.use(express.json());

  // Cookie bootstrap middleware - handles ?myapp_cookie=XYZ for agent browser

... (truncated)
general

Comments

Sign in to leave a comment

Loading comments...