DeepSeek Harness Tutorial: Build Your First Agent Tool with defineTool

This tutorial demonstrates how to create a functional publish_article tool for AI agents using DeepSeek Harness's defineTool, covering the five core components, output schema versus render separation, execution pipeline hooks, and real-world WeChat API permission constraints.

AI Code to Success
AI Code to Success
AI Code to Success
DeepSeek Harness Tutorial: Build Your First Agent Tool with defineTool

Introduction

The previous article set up the wechat-publisher plugin skeleton, but it only logged heartbeats — agents couldn't actually use it. The real goal is to let an agent call a tool that publishes Markdown to a WeChat Official Account draft after writing an article.

1. defineTool: Writing Your First Tool

Replace the skeleton in scratch-plugin/src/wechat-publisher.ts with the following implementation:

import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'wechat-publisher'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'publish_article',
    description: '将本地 Markdown 文件发布为微信公众号草稿文章。',
    parameters: {
      markdownPath: { type: 'string', required: true, description: 'Markdown 文件的绝对路径' },
      title: { type: 'string', required: true, description: '文章标题' },
      coverImage: { type: 'string', required: true, default: '', description: '封面图绝对路径(可选,不传则为空)' },
      draft: { type: 'boolean', required: true, default: true, description: '是否仅存草稿,默认 true' },
    },
    output: {
      schema: { type: 'object', additionalProperties: false },
      render: (_args, value) => [{ type: 'text', text: value.message }],
    },
    async execute(args) {
      // 本篇先做结构演示:模拟发布流程
      console.log('[wechat-publisher] publishing:', args.title)
      console.log('[wechat-publisher] markdown:', args.markdownPath)
      if (args.coverImage) console.log('[wechat-publisher] cover:', args.coverImage)
      // TODO: 第 7 篇接入配置后,这里调用真实发布逻辑
      return {
        success: true,
        message: `已模拟发布《${args.title}》到公众号草稿箱`
      }
    }
  }))
}

2. Five Core Parts of a Tool

The tool definition consists of five key fields:

name ( publish_article) — the identifier the model uses to invoke the tool.

description — tells the model when to use the tool (e.g., "publish Markdown to WeChat draft").

parameters — JSON schema the model follows to fill arguments (path, title, cover image, draft flag).

output.schema — declares the canonical return type (here an object).

execute — the actual async function that runs the tool logic (simulated publishing in this example).

The inject: ['tools'] ensures the Cordis tool registry is ready, and defineTool automatically infers and validates parameter types from the schema.

3. output: Why Separate schema and render?

The output object has two fields:

output: {
  schema: {
    type: 'object',
    properties: {
      success: { type: 'boolean' },
      message: { type: 'string' },
    },
    required: ['success', 'message'],
  },
  render: (_args, value) => [
    { type: 'text', text: value.message },
  ],
}

execute returns a canonical value that must match output.schema — here { success, message }.

render transforms that canonical value into model-visible content (a content block) — here extracting the message as plain text.

This separation exists because the execution result needs to be stable, verifiable, and replayable, while the model-facing representation can be adapted per scenario.

4. Run It: Let the Agent Actually Call the Tool

Restart the development command if not already running:

pnpm dsh web --patch ./scratch-plugin/cordis.yml

Open http://127.0.0.1:3080, start a new session, and enter:

帮我把 /Users/me/article.md 发布到公众号,标题是《测试文章》。

Once registered, the model uses the tool's name, description, and parameter schema to decide whether to call it and generates the arguments. The tool returns 已模拟发布《测试文章》到公众号草稿箱, and the terminal logs the simulated publishing steps.

Key observation: you wrote zero model-interaction code. Defining the tool is enough; Harness and the model handle discovery, parameter generation, and invocation.

5. Advanced: What Happens Behind a Tool Call?

The tool execution pipeline determines where you can intervene:

tools/pre-execute , tools/execute , tools/post-execute are waterfall events — third-party plugins can rewrite a call (e.g., an audit plugin logging every publish in pre-execute).

Approval is a separate stage : ctx.approval handles prompts before guards; security mechanisms are covered in article 9.

tools/result is the final result notification point; observers get the settled outcome, while UI rendering uses presentation/render mechanisms.

Hooks are cross-tool : they don't couple to a specific tool, embodying "everything is a plugin."

6. Real Publishing Hurdle: Subscription Account API Permissions

Why does execute only simulate? Real testing revealed a platform limitation:

✅ Get token  : GET /cgi-bin/token           → 200, access_token obtained
❌ Upload image: POST /cgi-bin/media/uploadimg → 48001 api unauthorized

The personal subscription account returned 48001 api unauthorized for media/uploadimg, meaning the current account lacks that interface permission. This doesn't imply all subscription accounts always lack it, but the plugin must treat available API permissions as a runtime condition.

Therefore, the real execute will support two modes (implemented in articles 7 and 8): mode: browser (default) — uses browser automation, works for subscription accounts. mode: api (reserved) — uses WeChat Official Account APIs. Actual availability depends on account type, verification status, enabled interfaces, and IP whitelist.

Designing for this platform reality is more valuable than pretending APIs are universally available.

7. Next Steps: Turning It into a Real WeChat Tool

defineTool

is just the entry point. The tool authoring guide includes further capabilities:

Nested schemas — parameters support complex nested structures.

Background work — long tasks move to background without blocking the session (ideal for publishing).

Policy hooks — inject custom policies in pre/post-execute.

UI cards — render tool results as cards in the Web UI.

Capability layering — split replaceable capabilities into Service/Provider/Consumer (articles 8 and 10).

Whether the tool appears as native function calling or Code Mode is controlled by tools.mode.

Summary

Tool = defineTool + ctx.tools.register(); master the five core parts: name, description, parameters, output, execute. output splits into schema (canonical value) and render (model-visible content).

Once registered, the model decides invocation based on description and schema, and generates arguments.

Real publishing faces platform limits: API capability depends on account type, verification, and enabled permissions; the plugin must prepare both API and browser automation paths.

Execution pipeline: pre-execute → approval/guard → execute → post-execute → finalizeContent → result; hooks, guards, and approvals can intervene at each stage.

wechat-publisher progress: this article registered the publish_article tool; the model can call it (simulated for now). Next article adds configuration — making appId, appSecret, and publish mode configurable.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

TypeScriptbrowser automationAgent ToolsAPI PermissionsWeChat PublishingDeepSeek HarnessdefineToolTool Pipeline
AI Code to Success
Written by

AI Code to Success

Focused on hardcore practical AI technologies (OpenClaw, ClaudeCode, LLMs, etc.) and HarmonyOS development. No hype—just real-world tips, pitfall chronicles, and productivity tools. Follow to transform workflows with code.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.