JAVIS Board JAVIS Board Developer docs
← Back to home
Internal · v1

MCP integration guide

JAVIS Board exposes the kanban as a Model Context Protocol server. Self-hosted JAVIS agents — and any other MCP-capable client — connect with a workspace-scoped bearer token and act on behalf of human users.

Endpoint
POST /mcp/board
Transport
HTTP + JSON-RPC 2.0
Auth
Sanctum bearer + acting-user header
16
Tools
3
Resources
1
Server
180d
Audit retention

Overview

The MCP server lives at POST /mcp/board and speaks JSON-RPC 2.0 over HTTP (with SSE for streamed responses). Every call has two identities: the agent (a per-workspace service user authenticated by bearer token) and the actor (the human the agent is acting on behalf of, identified by phone).

Identity model in one sentence

The bearer token says “I am Acme’s JAVIS”. The X-Acting-User-Phone header says “and I’m doing this for Rezha.” The server checks (a) the bearer is a service user attached to a workspace as agent, (b) the actor phone resolves to a user who is a member of that workspace, and (c) any board/card referenced lives in that same workspace. Then it executes the tool under the actor’s authorization, marking the resulting record with source = agent.

Why MCP and not REST

Tools self-describe. The agent fetches the tool catalog at runtime — names, descriptions, JSON schemas, semantics — instead of having a contract hard-coded into JAVIS code. The same server is also reachable from Claude Desktop, Cursor, or any other MCP-capable client without further work.

Quickstart

Provision a token for one workspace and call the smoke-test tool.

1. Provision JAVIS for a workspace

From the admin panel: Admin → Manage Workspaces. On the workspace row, click Provision JAVIS. The page creates the workspace’s service user (if missing), assigns the ws.agent role, rotates the Sanctum token, and shows the plaintext token once in a persistent notification. Copy it immediately.

Tokens are shown once

Lost a token? Click Provision JAVIS again — the existing token will be revoked and a fresh one issued. Existing card history and audit rows are preserved.

2. Make your first call

cURL · list-boards-toolcreates an audit row
# Replace TOKEN, PHONE, BASE_URL with your values.
curl -s -X POST "$BASE_URL/mcp/board" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "X-Acting-User-Phone: $PHONE" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "list-boards-tool",
      "arguments": {}
    },
    "id": 1
  }'

3. Verify the audit row

Every successful or denied call writes a row to agent_call_audit. Inspect it from the admin agent activity feed or directly via SQL:

SQL
SELECT id, agent_user_id, actor_user_id, tool, result, error_code, duration_ms, created_at
  FROM agent_call_audit
 ORDER BY id DESC LIMIT 5;

Authentication

Headers required on every call

HeaderPurpose
Authorization: Bearer …Sanctum token issued for the workspace JAVIS service user. Has the ability agent.
Accept: application/jsonRequired to opt into JSON 401 responses (otherwise Laravel redirects).
Content-Type: application/jsonJSON-RPC body.
X-Acting-User-PhoneE.164 phone of the human on whose behalf the agent acts. Required for any mutating call. Auto-provisions a new users row if the phone is unknown (since the agent has is_service=true).
X-Acting-User-NameOptional. Display name to use when auto-provisioning a new user from an unknown phone.

What the server checks, in order

  • Sanctum: bearer token resolves to an existing user.
  • The token-holding user has is_service = true.
  • That user is attached to exactly one workspace via the workspace_members pivot with role = 'agent'. That workspace is the “agent workspace”.
  • The acting-user phone resolves to a real user (or, if missing, is auto-created with is_service=false).
  • The actor is a member of the agent’s workspace.
  • Any board_id / card_id / list_id in the request belongs to that workspace.
  • The actor’s role grants the permission the tool requires (card.edit, card.move, list.create, etc.).

If any check fails, the tool returns a JSON-RPC isError: true with a human-readable message and an audit row is written with result = 'denied'.

Tools

All tools are listed here grouped by category. Names use the kebab-case convention Laravel/MCP derives from the class name (CreateCardToolcreate-card-tool).

Read no permissions required beyond membership

ToolArgumentsPermission
list-boards-tool (none) membership
get-board-tool board_id membership
list-cards-tool board_id, list_id?, assigned_to_user_id?, include_archived?, limit? membership
get-card-tool card_id membership
search-cards-tool board_id, query, limit? membership

Card mutations requires card.* permission

ToolArgumentsPermission
create-card-tool board_id, list_id, title, description?, due_at?, due_reminder_minutes?, assignee_user_ids?, assignee_phones?, label_ids? card.edit
update-card-tool card_id + any of title, description, due_at, due_reminder_minutes, cover_color, cover_label card.edit
move-card-tool card_id, target_list_id, target_board_id?, position? card.move
assign-member-tool card_id, (user_id | user_phone), action = add | remove card.edit
archive-card-tool card_id card.edit
restore-card-tool card_id card.edit
comment-on-card-tool card_id, body card.comment

List structural requires list.* permission

ToolArgumentsPermission
create-list-tool board_id, name, position? list.create
rename-list-tool list_id, name list.edit
archive-list-tool list_id, restore? list.archive
reorder-lists-tool board_id, ordered_list_ids[] list.reorder

Worked example: create_card

JSON-RPC request
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "create-card-tool",
    "arguments": {
      "board_id": 5,
      "list_id": 10,
      "title": "Reschedule pricing review",
      "due_at": "2026-05-15T10:00:00Z",
      "assignee_phones": ["+62812..."]
    }
  },
  "id": 42
}
JSON-RPC response
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [{
      "type": "text",
      "text": "{\"card_id\":11,\"title\":\"Reschedule pricing review\",\"list_id\":10,\"position\":\"459769.0000000000\",\"source\":\"agent\",\"created_by_user_id\":42,\"created_via_agent_user_id\":4}"
    }],
    "isError": false
  }
}

Resources

Resources are read-only data the agent can fetch by URI. Useful for seeding conversation context cheaply, without invoking a tool.

URI templateReturns
board://{board_id}/snapshot Full board state: lists with their non-archived cards (id, title, due_at, source).
workspace://{workspace_id}/my-assignments Cards assigned to the acting user across all boards in the workspace, ordered by due date.
workspace://{workspace_id}/members Workspace member directory (id, name, phone, role) — useful for resolving @mentions and assignment targets.

Listing and reading

JSON-RPC · resources/templates/list
{ "jsonrpc": "2.0", "method": "resources/templates/list", "params": {}, "id": 1 }
JSON-RPC · resources/read
{
  "jsonrpc": "2.0",
  "method": "resources/read",
  "params": { "uri": "board://5/snapshot" },
  "id": 2
}

Errors & audit

Error shapes

  • HTTP 401 — bearer missing/invalid. JSON body: {"message":"Unauthenticated."}
  • HTTP 422 — mutating request without X-Acting-User-Phone. JSON body: {"message":"X-Acting-User-Phone header required for mutating requests.","error":{"code":"acting_user_required"}}
  • JSON-RPC isError: true — every other failure (bad arguments, no permission, missing record, cross-workspace boundary). The text content carries a human-readable reason.

Audit codes

error_codeMeaning
no_actorBearer authenticated but no actor could be resolved.
forbiddenActor is not a workspace member, board is in another workspace, or actor lacks the required permission.
not_foundBoard / list / card / user referenced does not exist.
validationArgument schema validation failed.
<ExceptionClass>Uncaught exception class basename (truncated at 64 chars). Look in Laravel logs.

Audit retention

Rows older than 180 days are pruned daily at 03:30 by php artisan agent:prune-audit. Override via --days=N or test with --dry-run.

Client integration

Any MCP-compatible client works. Below: the official TypeScript SDK pattern, since most agent stacks (including the JAVIS production agent) run in Node.

TypeScript · @modelcontextprotocol/sdk
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL(`${BASE_URL}/mcp/board`),
  {
    requestInit: {
      headers: {
        "Authorization": `Bearer ${TOKEN}`,
        "X-Acting-User-Phone": phoneOfTheUserSpeakingNow,
      }
    }
  }
);

const client = new Client({ name: "javis-agent", version: "1.0.0" });
await client.connect(transport);

// One round-trip — server returns full tool list with JSON schemas.
const { tools } = await client.listTools();

const result = await client.callTool({
  name: "create-card-tool",
  arguments: { board_id: 5, list_id: 10, title: "From Javis" }
});
Pass the actor on every call

The acting-user header is per request, not per session. If your agent is in a multi-user channel (a WhatsApp group), update the header before each call to identify the human currently speaking. The bearer token never changes.

Data model side-effects

What changes in the database when JAVIS acts.

Cards created via the agent

  • cards.source = 'agent'
  • cards.created_by_user_id = the human (the actor)
  • cards.created_via_agent_user_id = the workspace JAVIS service user
  • UI surfaces this as the “via JAVIS” badge on the card.

Events fired

Tools dispatch the same events the web UI does — CardCreated, CardUpdated, CardMoved, CardArchived, CommentAdded, ListCreated, ListMoved, ListArchived, ListUpdated. Broadcast subscribers (Reverb) and notification dispatchers see no difference between web-originated and agent-originated changes, beyond the source field.

Notifications

Assignments and due-date changes schedule the same NotificationDispatch rows that the REST API path writes. The actor is recorded on the card.assignees pivot and as the actor_id in the assignment notification payload.

Ops & troubleshooting

Rotating a token

Admin → Manage Workspaces → Provision JAVIS on the affected workspace. The previous token is deleted; the new plaintext is shown once. Live agent processes need to be restarted with the new token.

Disabling JAVIS for a workspace

Delete the JAVIS user’s tokens ($javis->tokens()->delete()) — every subsequent MCP call returns 401, but no kanban data is affected. The user row and audit history remain.

Pagination on tools/list

Default page size is 10 tools. The response includes nextCursor when there are more. JAVIS’s production client should walk the cursor; ad-hoc curl callers can pass ?per_page=50 to get them all in one shot.

Local debugging

Run php artisan mcp:inspector to open the Laravel/MCP web inspector against your local server. Useful for poking at tool schemas without writing a client.

Appendix · file map

Where to look in the codebase, in execution order.

FileRole
routes/ai.phpRegisters Mcp::web('/mcp/board', BoardServer::class) with auth:sanctum + acting-user middleware.
app/Http/Middleware/ResolveActingUser.phpReads X-Acting-User-Phone, swaps the auth user to the actor, stashes the original service caller as service_caller on the request.
app/Mcp/Servers/BoardServer.phpRegisters all 16 tools and 3 resources.
app/Mcp/Tools/BoardTool.phpBase class. Wraps tool execution in workspace-boundary checks, validation, audit, and structured error mapping.
app/Mcp/Support/McpContext.phpPure helpers: actor(), agent(), agentWorkspaceId(), assertActorInAgentWorkspace(), assertBoardInWorkspace(), audit().
app/Mcp/Tools/*.phpOne file per tool. Each is a thin adapter that validates input and delegates to an App\Actions\* class.
app/Actions/Cards/*, app/Actions/Lists/*Business logic. The same actions are intended to back Livewire and (eventually) the REST controllers, so MCP and the UI never drift.
app/Mcp/Resources/*.phpURI-templated resources for read-only context.
app/Console/Commands/Mcp/PruneAgentAudit.phpDaily prune of agent_call_audit rows older than 180 days.
database/migrations/…_add_created_via_agent_user_id_to_cards_table.phpAdds the via-agent FK column on cards.
database/migrations/…_create_agent_call_audit_table.phpCreates the audit table.