Skip to main content

Basic Usage

import { Job, JobContext } from '@frontmcp/sdk';
import { z } from 'zod';

@Job({
  name: 'send-email',
  description: 'Send an email to a recipient',
  inputSchema: {
    to: z.string().email().describe('Recipient email'),
    subject: z.string().describe('Email subject'),
    body: z.string().describe('Email body'),
  },
  outputSchema: {
    messageId: z.string(),
    sent: z.boolean(),
  },
})
class SendEmailJob extends JobContext {
  async execute(input: { to: string; subject: string; body: string }) {
    const mailer = this.get(MailerToken);
    const result = await mailer.send(input);
    return { messageId: result.id, sent: true };
  }
}

Signature

function Job<I extends ToolInputType, O extends ToolOutputType>(
  opts: JobMetadata<I, O>
): ClassDecorator

Configuration Options

Required Properties

PropertyTypeDescription
namestringUnique job identifier
inputSchemaZodShapeZod object shape for input validation
outputSchemaZodShapeZod schema for output validation

Optional Properties

PropertyTypeDefaultDescription
descriptionstringJob description
idstringnameStable identifier for tracking
timeoutnumber300000Max execution time in ms (5 min)
retryJobRetryConfigRetry configuration
tagsstring[]Categorization tags
labelsRecord<string, string>Key-value labels
hideFromDiscoverybooleanfalseHide from list-jobs
permissionsJobPermission[]RBAC permission rules

RetryConfig

PropertyTypeDefaultDescription
maxAttemptsnumber3Maximum retry attempts
backoffMsnumber1000Initial backoff delay in ms
backoffMultipliernumber2Backoff multiplier per attempt
maxBackoffMsnumber60000Maximum backoff delay in ms

Permission

PropertyTypeDescription
action'create' | 'read' | 'update' | 'delete' | 'execute' | 'list'Permission action
rolesstring[]Required roles (at least one must match)
scopesstring[]Required OAuth scopes
custom(authInfo) => boolean | Promise<boolean>Custom guard function

Examples

Simple Job

@Job({
  name: 'greet',
  inputSchema: { name: z.string() },
  outputSchema: { message: z.string() },
})
class GreetJob extends JobContext {
  async execute(input: { name: string }) {
    return { message: `Hello, ${input.name}!` };
  }
}

Job with Retry

@Job({
  name: 'fetch-api',
  description: 'Fetch data from external API with retries',
  inputSchema: { endpoint: z.string().url() },
  outputSchema: { data: z.unknown(), statusCode: z.number() },
  retry: {
    maxAttempts: 5,
    backoffMs: 2000,
    backoffMultiplier: 2,
    maxBackoffMs: 30000,
  },
  timeout: 60000,
})
class FetchApiJob extends JobContext {
  async execute(input: { endpoint: string }) {
    this.log(`Attempt ${this.attempt}: fetching ${input.endpoint}`);
    const response = await this.fetch(input.endpoint);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return { data: await response.json(), statusCode: response.status };
  }
}

Job with Permissions

@Job({
  name: 'delete-records',
  description: 'Delete records from database',
  inputSchema: {
    table: z.string(),
    ids: z.array(z.string()),
  },
  outputSchema: {
    deletedCount: z.number(),
  },
  permissions: [
    { action: 'execute', roles: ['admin'] },
    { action: 'execute', scopes: ['data:delete'] },
  ],
  tags: ['destructive', 'admin'],
})
class DeleteRecordsJob extends JobContext {
  async execute(input: { table: string; ids: string[] }) {
    const db = this.get(DatabaseToken);
    const count = await db.deleteMany(input.table, input.ids);
    return { deletedCount: count };
  }
}

Function-Based Alternative

For simpler jobs, use the job() function:
import { job } from '@frontmcp/sdk';
import { z } from 'zod';

const GreetJob = job({
  name: 'greet',
  description: 'Generate a greeting',
  inputSchema: { name: z.string() },
  outputSchema: { message: z.string() },
})((input, ctx) => {
  ctx.log(`Greeting ${input.name}`);
  return { message: `Hello, ${input.name}!` };
});

Context Methods

The JobContext base class provides:

Dependency Injection

async execute(input) {
  const service = this.get(ServiceToken);      // Required dependency
  const optional = this.tryGet(OptionalToken); // Optional dependency
}

Logging and Progress

async execute(input) {
  this.log('Starting processing');
  await this.progress(50, 100, 'Halfway done');
}

Error Handling

async execute(input) {
  // Fail with an error when validation fails
  if (!input.data) {
    this.fail(new Error('Missing required data'));
  }

  // Early return with a previously computed output
  const cachedResult = this.tryGet(CacheToken)?.get(input.data);
  if (cachedResult) {
    this.respond(cachedResult); // Ends execution
  }
}

JobContext

Context class details

JobRegistry

Job registry API

Jobs Guide

Jobs documentation

@Workflow

Define workflows