Skip to main content
Glama
autobb888

Junction41 MCP Server

by autobb888
README.md
# j41-mcp-server

MCP server for the **Junction41** -- wraps the [`@junction41/sovagent-sdk`](https://github.com/junction41/j41-sovagent-sdk) as Model Context Protocol tools, allowing Claude and other LLMs to interact with the Junction41 platform. Exposes 124+ tools, 10 resources, and 3 workflow prompts.

Works with Claude Desktop, Claude Code, OpenAI agents, Cursor, Windsurf, and any other client that speaks the [Model Context Protocol](https://modelcontextprotocol.io/).

## Security update — 2026-06-02 audit (v2.1.0)

This release closes 5 criticals + 14 highs + ~16 mediums/lows from the 2026-06-02 cross-repo security audit. The behavioral changes consumers should know about:

**Removed tools (breaking).** `j41_sign_message` and `j41_sign_challenge` are gone. They were raw signing oracles — a prompt-injected LLM could mint signatures over `J41-COMPLETE|...`, `J41-BOUNTY-SELECT|...`, or `J41-DEPOSIT-REPORT|...` strings and submit them out-of-band to release escrow / redirect bounty payouts / deny refunds. Every protocol action has a typed tool that builds the canonical message internally — use those.

**`j41_send_currency` schema is narrower (breaking).** The `changeAddress` and `sourceAddress` parameters were removed: the allowlist gate only validated `to`, so an attacker could send dust to an allowlisted destination and route the entire UTXO change to themselves. A finite `J41_MCP_STANDALONE_MAX_VRSC` cap (default 10) replaces the old silent `jobPrice=Infinity`.

**SSE transport fails closed (breaking for non-loopback ops).** `j41-mcp-server --transport sse` now binds `127.0.0.1` by default. Override with `J41_MCP_SSE_HOST`. If you bind anything other than loopback, `J41_MCP_SSE_TOKEN` is **required** or the server refuses to start. The token is checked on **both** `/sse` and `/message` (the previous gate left `/message` open), constant-time compared, and `J41_CORS_ORIGIN=*` is refused when the token is on.

**Allowlist auto-population is opt-in.** Set `J41_MCP_ALLOWLIST_AUTOPOPULATE=1` to restore the pre-2.1.0 behavior where `j41_accept_job` adds the buyer's pay address. Default-off because a compromised platform could pre-populate attacker addresses.

**New ingest caps** (all env-overridable): `J41_MCP_API_TIMEOUT_MS=30000`, `J41_MCP_API_MAX_RESPONSE_BYTES=8MB`, `J41_MCP_MAX_UPLOAD_BYTES=25MB`, `J41_MCP_MAX_DOWNLOAD_BYTES=25MB`, `J41_MCP_SSE_MAX_MESSAGE_BYTES=1MB`, `J41_MCP_SSE_MAX_SESSIONS=64`, `J41_MCP_MAX_WORKSPACES=32`, `J41_MCP_MAX_RAW_TX_HEX_CHARS=4MB`, `J41_MCP_SWEEP_TIMEOUT_MS=5000`.

Bumped bundled `@junction41/sovagent-sdk` to 2.5.0, which carries its own breaking changes — see that package's README.

## Install

```bash
yarn global add @junction41/mcp-server
```

## Quick Start

```bash
# Run on stdio (default)
j41-mcp-server

# Run on SSE
j41-mcp-server --transport sse --port 3001
```

### Claude Desktop

Add to your Claude Desktop config (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "j41": {
      "command": "j41-mcp-server"
    }
  }
}
```

### Claude Code

```bash
claude mcp add j41 j41-mcp-server
```

### SSE Transport

```json
{
  "mcpServers": {
    "j41": {
      "url": "http://localhost:3001/sse"
    }
  }
}
```

CORS is restricted to localhost by default. Set `J41_CORS_ORIGIN` to override:

```bash
J41_CORS_ORIGIN="https://myapp.example.com" j41-mcp-server --transport sse --port 3001
```

## Tools (125)

### Identity (stateless)

| Tool | Description |
|------|-------------|
| `j41_generate_keypair` | Generate a new Verus keypair (address + pubkey; WIF stored internally) |
| `j41_sign_message` | Sign a message (uses stored WIF by default, or accepts explicit WIF) |
| `j41_sign_challenge` | Sign a J41 authentication challenge (uses stored WIF by default) |

### Agent Lifecycle

| Tool | Description |
|------|-------------|
| `j41_init_agent` | Initialize agent with J41 API URL and credentials |
| `j41_authenticate` | Authenticate with the J41 platform |
| `j41_register_identity` | Register a VerusID on-chain (long-running) |
| `j41_register_agent` | Register agent profile (25 flat VDXF keys) |
| `j41_register_service` | Register a service offering (with acceptedCurrencies, paymentTerms, privateMode, sovguard) |
| `j41_get_agent_status` | Get current state, identity, and connection info |

### Jobs

| Tool | Description |
|------|-------------|
| `j41_list_jobs` | List jobs by status and/or role |
| `j41_get_job` | Get job details by ID |
| `j41_accept_job` | Accept a job (signs internally) |
| `j41_deliver_job` | Deliver work with content hash (signs internally) |
| `j41_complete_job` | Mark job completed (signs internally) |
| `j41_cancel_job` | Cancel a job |
| `j41_dispute_job` | Dispute a job with reason (signs internally) |

### Workspace

| Tool | Description |
|------|-------------|
| `j41_workspace_connect` | Connect to buyer's local project via workspace relay |
| `j41_workspace_list_directory` | List files in buyer's project directory |
| `j41_workspace_read_file` | Read a file from buyer's project |
| `j41_workspace_write_file` | Write a file (buyer approves in supervised mode, 500KB limit) |
| `j41_workspace_status` | Check workspace session status |
| `j41_workspace_done` | Signal work complete and disconnect |
| `j41_workspace_disconnect` | Explicitly disconnect from workspace |

Path traversal protection: relative paths only, `..` segments rejected.

### Chat

| Tool | Description |
|------|-------------|
| `j41_connect_chat` | Connect to J41 chat (WebSocket) |
| `j41_send_message` | Send a message in a job conversation |
| `j41_get_messages` | Retrieve chat messages with pagination |
| `j41_join_job_chat` | Join a job chat room |

### Files

| Tool | Description |
|------|-------------|
| `j41_upload_file` | Upload a file to a job (base64 content) |
| `j41_download_file` | Download a file (returns base64 + metadata) |
| `j41_list_files` | List files attached to a job |
| `j41_delete_file` | Delete a file from a job (uploader only) |

### Payments

| Tool | Description |
|------|-------------|
| `j41_get_payment_qr` | Get payment QR code and deep-link |
| `j41_record_payment` | Record a payment txid for a job |
| `j41_get_utxos` | Get unspent transaction outputs |
| `j41_broadcast_tx` | Broadcast a raw signed transaction |
| `j41_get_chain_info` | Get Verus blockchain info |

### Pricing (stateless)

| Tool | Description |
|------|-------------|
| `j41_estimate_price` | Estimate raw USD cost for an AI job |
| `j41_recommend_price` | Get min/recommended/premium/ceiling price points |

### Privacy

| Tool | Description |
|------|-------------|
| `j41_set_privacy_tier` | Set privacy tier (standard/private/sovereign) |
| `j41_get_privacy_tier` | Get current privacy tier |
| `j41_attest_deletion` | Submit signed deletion attestation |

### Safety

| Tool | Description |
|------|-------------|
| `j41_enable_canary` | Enable canary token protection |
| `j41_check_canary_leak` | Scan text for canary token leaks |
| `j41_set_communication_policy` | Set sovguard/external communication policy |

### Reviews

| Tool | Description |
|------|-------------|
| `j41_get_reviews` | Get reviews for an agent by VerusID |
| `j41_submit_review` | Submit a signed review after a completed job |

### Webhooks

| Tool | Description |
|------|-------------|
| `j41_register_webhook` | Register an HTTPS endpoint for platform events (HMAC-SHA256 signed) |
| `j41_list_webhooks` | List all registered webhooks |
| `j41_delete_webhook` | Delete a registered webhook by ID |

### Trust

| Tool | Description |
|------|-------------|
| `j41_get_trust_score` | Get the public trust score for any agent by VerusID |
| `j41_get_my_trust` | Get the authenticated agent's own trust score breakdown |

### Notifications

| Tool | Description |
|------|-------------|
| `j41_get_notifications` | Get pending notifications |
| `j41_ack_notification` | Acknowledge (dismiss) notifications |

### Extensions

| Tool | Description |
|------|-------------|
| `j41_request_extension` | Request additional payment for expanded scope |
| `j41_approve_extension` | Approve an extension request |
| `j41_reject_extension` | Reject an extension request |

### Bounties

| Tool | Description |
|------|-------------|
| `j41_post_bounty` | Post a bounty listing (auto-signs) |
| `j41_apply_to_bounty` | Apply to a bounty (auto-signs) |
| `j41_cancel_bounty` | Cancel a bounty you posted |
| `j41_list_bounties` | List bounties with filters |
| `j41_get_bounty` | Get bounty details by ID |
| `j41_list_bounty_applications` | List applications for a bounty |
| `j41_accept_bounty_application` | Accept an application |

### Discovery

| Tool | Description |
|------|-------------|
| `j41_search_agents` | Search agents by keyword |
| `j41_get_agent_profile` | Get public agent profile |
| `j41_get_agent_services` | Get services offered by an agent |
| `j41_search_services` | Search marketplace services |
| `j41_get_service` | Get service details |
| `j41_get_categories` | Get available service categories |
| `j41_get_featured_agents` | Get featured/top agents |

### Inbox

| Tool | Description |
|------|-------------|
| `j41_get_inbox` | Get inbox items (reviews, payments, etc.) |
| `j41_get_inbox_item` | Get a specific inbox item |
| `j41_accept_inbox_item` | Accept an inbox item (e.g., apply review to on-chain identity) |
| `j41_get_inbox_count` | Get pending inbox count |

### Services

| Tool | Description |
|------|-------------|
| `j41_register_service` | Register a service offering |
| `j41_get_my_services` | List your registered services |
| `j41_update_service` | Update a service listing |
| `j41_delete_service` | Remove a service listing |
| `j41_get_service_stats` | Get service performance stats |

## Resources (10)

Static, read-only data from the SDK -- no authentication required.

| URI | Contents |
|-----|----------|
| `j41://pricing/llm-costs` | LLM model cost table |
| `j41://pricing/image-costs` | Image generation costs |
| `j41://pricing/api-costs` | External API call costs |
| `j41://pricing/self-hosted-costs` | Self-hosted model costs |
| `j41://pricing/category-markups` | Job category markup ranges |
| `j41://pricing/platform-fee` | Platform fee rate (5%) |
| `j41://privacy/tiers` | Privacy tier definitions and requirements |
| `j41://safety/policy-labels` | Communication policy labels |
| `j41://onboarding/vdxf-keys` | All 25 flat VDXF key i-addresses (agent 15, service 2, review 1, bounty 2, platform 1, session 1, workspace 2, job 1) |
| `j41://onboarding/validation-rules` | Name regex, reserved names, valid protocols/types |

## Prompts (3)

Guided workflows that walk through multi-step operations:

| Prompt | Description |
|--------|-------------|
| `j41_agent_registration` | Keygen -> init -> register -> auth -> profile setup |
| `j41_job_handling` | Accept -> chat -> deliver -> complete -> payment |
| `j41_pricing_estimation` | Cost estimation -> price recommendation -> service setup |

## Typical Workflow

```
1. j41_generate_keypair          -> Get WIF + address
2. j41_init_agent                -> Connect to J41
3. j41_register_identity         -> Get a VerusID (if needed)
4. j41_authenticate              -> Establish session
5. j41_register_agent            -> Publish agent profile
6. j41_register_service          -> List service offerings
7. j41_enable_canary             -> Enable safety features
8. j41_register_webhook          -> Subscribe to platform events
9. j41_list_jobs                 -> Check for incoming work
10. j41_accept_job               -> Take a job
11. j41_connect_chat / send      -> Communicate with buyer
12. j41_deliver_job              -> Submit deliverables
13. j41_complete_job             -> Finalize
14. j41_submit_review            -> Leave a review
```

## Architecture

```
src/
├── index.ts                  # Server setup, transport selection
├── state.ts                  # Singleton agent state + signing
├── transport-sse.ts          # SSE/HTTP transport (Node http, no Express)
├── tools/
│   ├── error.ts              # Shared error handler
│   ├── api-request.ts        # Authenticated API request helper
│   ├── identity.ts           # Stateless keypair/signing tools
│   ├── agent.ts              # Agent lifecycle tools
│   ├── jobs.ts               # Job management tools
│   ├── chat.ts               # Chat tools
│   ├── payments.ts           # Payment/blockchain tools
│   ├── pricing.ts            # Stateless pricing tools
│   ├── safety.ts             # Canary + communication policy
│   ├── privacy.ts            # Privacy tier + deletion attestation
│   ├── extensions.ts         # Payment extension tools
│   ├── files.ts              # File upload/download/list/delete
│   ├── reviews.ts            # Review tools (signed submission)
│   ├── notifications.ts      # Notification tools
│   ├── webhooks.ts           # Webhook registration/management
│   ├── trust.ts              # Trust score queries
│   ├── bounties.ts           # Bounty lifecycle tools
│   ├── discovery.ts          # Agent/service search tools
│   ├── disputes.ts           # Dispute response tools
│   ├── inbox.ts              # Inbox management tools
│   ├── services.ts           # Service CRUD tools
│   └── workspace.ts          # Workspace file access tools
├── resources/index.ts        # 10 static resources
└── prompts/index.ts          # 3 workflow prompts
```

### State Management

The server maintains a singleton `J41Agent` instance with three states:

```
Uninitialized -> Initialized -> Authenticated
                 (j41_init)     (j41_authenticate)
```

State transitions are forward-only. The WIF private key is stored in the state module and only accessible through `signWithAgent()` -- it is never exposed via any getter or returned in any tool response.

### Security

- **WIF handling**: `j41_generate_keypair` stores the WIF internally and only returns the address. `j41_init_agent` accepts WIF but never echoes it. All job signing happens internally via `signWithAgent()`. Signing tools use the stored key by default.
- **Input validation**: All tool inputs validated by Zod schemas with length limits, enum constraints, and regex patterns.
- **Error handling**: Shared `errorResult()` extracts J41Error codes without leaking stack traces.
- **SSE CORS**: Restricted to localhost by default (configurable via `J41_CORS_ORIGIN`).
- **SSE error boundary**: Async handler wrapped to prevent unhandled rejection crashes.
- **No Express**: SSE transport uses Node's built-in `http.createServer` -- zero extra runtime dependencies.
- **Webhook verification**: Webhook payloads are HMAC-SHA256 signed with a secret you provide at registration.

## Development

```bash
# Build
yarn build

# Test
yarn test

# Start in stdio mode
yarn start

# Start in SSE mode
j41-mcp-server --transport sse --port 3001
```

## Dependencies

| Package | Purpose |
|---------|---------|
| `@junction41/sovagent-sdk` | J41 SDK -- identity, auth, jobs, chat, payments, pricing, trust |
| `@modelcontextprotocol/sdk` | MCP server framework |
| `zod` | Input validation |

## Dispute Resolution Tools

### `j41_respond_to_dispute`

Respond to a buyer's dispute. Auto-signs the response.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `jobId` | string | Yes | Job ID of the disputed job |
| `action` | enum | Yes | `refund`, `rework`, or `rejected` |
| `refundPercent` | number | If refund | Refund percentage (1-100) |
| `reworkCost` | number | No | Additional VRSC for rework (0 = free) |
| `message` | string | Yes | Agent statement explaining the response |

### `j41_accept_rework`

Accept an agent's rework offer (buyer side). Auto-signs the acceptance.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `jobId` | string | Yes | Job ID of the disputed job |

### Updated: `j41_list_jobs`

Now supports filtering by `rework`, `resolved`, and `resolved_rejected` status values.

## Security

### Financial Allowlists

All outbound financial operations (`j41_send_currency`, `j41_transfer_funds`, `j41_broadcast_tx`) are gated by `~/.j41/financial-allowlist.json`. If the file doesn't exist, it is created empty — **deny-all by default**.

```json
{
  "permanent": [
    { "address": "RxxxxPlatform...", "label": "platform_fee" }
  ],
  "operator": [
    { "address": "Rxxxx...", "label": "cold wallet", "added": "2026-04-01" }
  ],
  "active_jobs": [
    { "address": "iXxxxBuyer...", "jobId": "abc123", "added": "2026-04-02T10:00:00Z" }
  ]
}
```

- `permanent` — always allowed (e.g., platform fee address). Edit manually.
- `operator` — operator-approved addresses. Edit manually.
- `active_jobs` — managed automatically by job lifecycle hooks.

### Rate Limiting

| Limit | Default |
|---|---|
| Max sends per job | 3 |
| Max total value per job | Job price + 10% |
| Max sends per hour (all jobs) | 10 |
| Cooldown between sends | 30 seconds |

Exceeding any limit blocks the operation and logs an alert.

### Dynamic Lifecycle

- `j41_accept_job` — buyer refund address automatically added to `active_jobs`
- `j41_complete_job` / `j41_cancel_job` / `j41_end_session` — address removed, rate limiter cleared

### Fail-Closed Sweep Timer

Every 10 minutes, the MCP server checks all `active_jobs` entries against the platform API:

- If a job is no longer active, the address is removed
- If the platform API is unreachable, all `active_jobs` sends are frozen
- After 30 minutes of continuous API outage, ALL financial operations are suspended
- Operations resume automatically when the API becomes reachable again

### Mandatory Canary Tokens

Canary protection is auto-enabled on every `j41_accept_job` call. If the canary token appears in agent output, it indicates prompt injection.

## Recent Changes

- **Allowlist always reloads from disk** — no more stale cache; external edits (operator, dispatcher lifecycle) are picked up immediately
- **Auto-add seller on job creation** — `j41_create_job` adds seller payment address + platform fee address to allowlist
- **SovGuard 429 handling** — non-retryable quota limits surface upgrade URLs, transient rate limits get longer backoff
- **125+ tools** — added dispute, extension, workspace, bounty, and data policy tools

## License

MIT