Skip to content
Tutorial / Step 16

Build Your First Cordis Tool Plugin

Walk from apply(ctx) through configuration, tool registration, result observation, and unload verification.

Answer in brief

Walk from apply(ctx) through configuration, tool registration, result observation, and unload verification.

This exercise follows the official Cordis interfaces while using a small project-status Tool. Developer Preview commands may move, so compare with the current upstream tutorial before running.

1. Start with apply(ctx)

import type { Context } from '@deepseek-ai/cordis'
export const name = 'project-status'
export function apply(ctx: Context) {
  console.log('project-status mounted')
}

The Plugin describes contributions; cordis.yml composes them. Rows may start concurrently, so service ordering belongs in inject, not YAML position.

2. Validate Configuration

import Schema from '@deepseek-ai/schemastery'
export interface Config { label: string; maxLength: number }
export const Config: Schema<Config> = Schema.object({
  label: Schema.string().default('workspace'),
  maxLength: Schema.number().default(200),
})

Values that deployments may change belong in Config. Invalid configuration should fail clearly during load.

3. Register the Tool

import { defineTool } from '@deepseek-ai/dsh-tools'
export const inject = ['tools']

export function apply(ctx: Context, config: Config) {
  ctx.tools.register(defineTool({
    name: 'project_status',
    description: 'Return the configured tutorial workspace label.',
    parameters: {},
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute() {
      return config.label.slice(0, config.maxLength)
    },
  }))
}

The registration disposer belongs to the Plugin lifecycle, so unload should remove the Tool.

4. Compose and Observe

- name: '@deepseek-ai/dsh-system-prompt'
- name: '@deepseek-ai/dsh-tools'
- name: './project-status.ts'
  config:
    label: 'tutorial-workspace'
    maxLength: 80

Add a separate observer for tools/result and log only tool name, status, and a bounded summary. The producer and observer remain decoupled through registry and events.

Verify one mount, Tool discovery, valid output, load-time rejection of invalid config, no duplicate registration after HMR, removal on unload, and no credentials in logs.

Test first through the real execution pipeline without a model key; then mount the patch into Web UI and test model selection. This separates implementation failures from model routing behavior.

Return to the Tutorial Hub for architecture and security tracks.

Primary sources