Your First Project

Get a working project running in under 5 minutes. You'll have an AI that can create and edit notes.

Step 1: Create the Project

Run the create command and follow the prompts:

malv create

This scaffolds a complete project with a Notes app already set up. You get:

my-project/
├── malv.json
├── packages/
│   ├── apps/
│   │   └── notes/           # A working notes app
│   │       ├── tools.json   # create_note, edit_note, list_notes
│   │       ├── tokens.json  # note_access token
│   │       ├── storage.json # Storage permissions
│   │       └── src/         # Tool implementations
│   └── web/                 # Web interface to test it
└── package.json

Step 2: Install Dependencies

cd my-project
yarn install

Step 3: Configure Environment

Set up the required environment variables:

malv env

This prompts you for API keys and configuration. The command reads what each app needs from its environment.json and helps you fill in the values.

Step 4: Start the Development Server

malv dev

You'll see your apps start up:

Starting apps...
  @my-org/notes  → http://localhost:4550

Infrastructure server running at http://localhost:59459
Web server running at http://localhost:3000

Watching for changes...

Step 5: Try It Out

Open http://localhost:3000 in your browser. You'll see a chat interface.

Try these messages:

Create a note called "Shopping List" with the content "Milk, eggs, bread"

The AI calls the create_note tool. You'll see a Note tab appear with your content.

Now try:

Add butter to the list

The AI calls edit_note. It knows which note you're working with because create_note issued a token that tracks the selected note.

What notes do I have?

The AI calls list_notes and shows you all your notes.

What Just Happened?

The Notes app demonstrates the core concepts:

Tools - create_note, edit_note, and list_notes are defined in tools.json. The AI discovered them automatically.

Tokens - When you create a note, the app issues a note_access token. This is why "add butter to the list" works without specifying which note. The AI sees you have a note selected.

Storage - Notes are saved to storage. The paths in storage.json control where data can be written.

Objects - The Note tab that appears is an object. It's defined in objects.json and rendered by custom code.

Explore the Code

Take a look at how the Notes app is built:

tools.json - Defines what tools exist and their input/output schemas:

{
  "name": "create_note",
  "description": "Creates a new note with the given title and content",
  "capabilities": ["storage", "token", "object"],
  ...
}

tokens.json - Defines the note_access token that tracks which note is selected:

{
  "note_access": {
    "state": "Note selected",
    "schema": {
      "properties": {
        "noteId": { "type": "string" }
      }
    }
  }
}

src/tools/create_note.ts - The actual implementation:

export default async function create_note(
  input: Input,
  capabilities: Capabilities
): Promise<Output> {
  const noteId = crypto.randomUUID();
  
  // Save to storage
  await capabilities.storage.put(`/notes/${noteId}.json`, {
    title: input.title,
    content: input.content
  });
  
  // Issue token so AI knows which note is selected
  await capabilities.token.sign('note_access', { noteId });
  
  // Create visual object
  await capabilities.object.set('@my-org/notes', {
    type: 'note',
    name: input.title,
    metadata: { noteId }
  });
  
  return { noteId, success: true };
}

Next Steps

Now that you have a working project:

Or try modifying the Notes app - add a delete_note tool and see how quickly you can get it working.