From 8d4dd8bed3f360c6b2f60790996f352f0509c9c2 Mon Sep 17 00:00:00 2001 From: qer Date: Tue, 30 Jun 2026 18:22:20 +0800 Subject: [PATCH 1/2] fix(todo-list): accept 'completed' status and normalize to 'done' Closes #1016 The LLM sometimes passes 'completed' as the status for TodoList items, but the schema only accepted 'pending' | 'in_progress' | 'done'. This produced two problems: 1. Validation failed when the model used 'completed'. 2. Even if validation passed, statusMarker() had no case for 'completed' and fell through to the unreachable default branch. Changes: - Extend TodoStatus union to include 'completed' so it is accepted at the type level. - Map 'completed' -> 'done' in setTodos() so persisted state stays clean. - Handle 'completed' in statusMarker() so it renders as '[done]'. - Update the markdown description to explicitly warn against using 'completed'. - Add a test confirming 'completed' is accepted and mapped to 'done'. --- .../src/tools/builtin/state/todo-list.md | 3 ++- .../src/tools/builtin/state/todo-list.ts | 12 +++++++++--- .../agent-core/test/tools/todo-list.test.ts | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/agent-core/src/tools/builtin/state/todo-list.md b/packages/agent-core/src/tools/builtin/state/todo-list.md index 3dc3c08dc4..aeec3986de 100644 --- a/packages/agent-core/src/tools/builtin/state/todo-list.md +++ b/packages/agent-core/src/tools/builtin/state/todo-list.md @@ -19,9 +19,10 @@ Use this tool to maintain a structured TODO list as you work through a multi-ste - If no available tool can move any task forward, tell the user where you are stuck instead of repeatedly re-ordering the same todos. **How to use:** -- Call with `todos: [...]` to replace the full list. Statuses: pending / in_progress / done. +- Call with `todos: [...]` to replace the full list. Statuses: `pending` / `in_progress` / `done`. - Call with no `todos` argument to retrieve the current list without changing it. - Call with `todos: []` to clear the list. +- **Important:** the status must be exactly `done`, not `completed` or `finished`. - Keep titles short and actionable (e.g. "Read session-control.ts", "Add planMode flag to TurnManager"). - Update statuses as you make progress. - When work is underway, keep exactly one task `in_progress`. diff --git a/packages/agent-core/src/tools/builtin/state/todo-list.ts b/packages/agent-core/src/tools/builtin/state/todo-list.ts index 852042e196..9a5ffdb208 100644 --- a/packages/agent-core/src/tools/builtin/state/todo-list.ts +++ b/packages/agent-core/src/tools/builtin/state/todo-list.ts @@ -28,7 +28,7 @@ export const TODO_STORE_KEY = 'todo'; const TODO_LIST_WRITE_REMINDER = 'Ensure that you continue to use the todo list to track progress. Mark tasks done immediately after finishing them, and keep exactly one task in_progress when work is underway.'; -export type TodoStatus = 'pending' | 'in_progress' | 'done'; +export type TodoStatus = 'pending' | 'in_progress' | 'done' | 'completed'; export interface TodoItem { readonly title: string; @@ -45,7 +45,9 @@ declare module '../../store' { const TodoItemSchema = z.object({ title: z.string().min(1).describe('Short, actionable title for the todo.'), - status: z.enum(['pending', 'in_progress', 'done']).describe('Current status of the todo.'), + status: z + .preprocess((val) => (val === 'completed' ? 'done' : val), z.enum(['pending', 'in_progress', 'done'])) + .describe('Current status of the todo. Must be exactly one of: pending, in_progress, done. Do NOT use completed or finished.'), }); export interface TodoListInput { @@ -81,6 +83,7 @@ function statusMarker(status: TodoStatus): string { case 'in_progress': return '[in_progress]'; case 'done': + case 'completed': return '[done]'; default: { const _exhaustive: never = status; @@ -133,7 +136,10 @@ export class TodoListTool implements BuiltinTool { private setTodos(todos: readonly TodoItem[]): void { this.store.set( TODO_STORE_KEY, - todos.map((todo) => ({ title: todo.title, status: todo.status })), + todos.map((todo) => ({ + title: todo.title, + status: todo.status === 'completed' ? 'done' : todo.status, + })), ); } } diff --git a/packages/agent-core/test/tools/todo-list.test.ts b/packages/agent-core/test/tools/todo-list.test.ts index 003e14a2ec..3d8f4aad30 100644 --- a/packages/agent-core/test/tools/todo-list.test.ts +++ b/packages/agent-core/test/tools/todo-list.test.ts @@ -142,6 +142,23 @@ describe('TodoListTool', () => { ]); }); + it('accepts "completed" as a status and maps it to "done"', async () => { + const { tool, getTodos } = makeTool(); + + const result = await executeTool(tool, { + turnId: 't1', + toolCallId: 'call_1', + args: { + todos: [{ title: 'done task', status: 'completed' }], + }, + signal, + }); + + expect(result).toMatchObject({ isError: false }); + expect(result.output).toContain('[done] done task'); + expect(getTodos()).toEqual([{ title: 'done task', status: 'done' }]); + }); + it('renders a done todo with a marker matching the status enum value', async () => { const { tool } = makeTool([{ title: 'shipped', status: 'done' }]); From 458d24d33e79728a0e05e0b6dc230c0e7ec8b645 Mon Sep 17 00:00:00 2001 From: qer Date: Mon, 6 Jul 2026 18:08:15 +0800 Subject: [PATCH 2/2] feat(plugins): add Vercel MCP plugin Introduces kimi-vercel, an official MCP plugin for managing Vercel projects and deployments. - MCP server (stdio transport) with 4 tools: - vercel_list_projects: list all projects - vercel_get_project: get project details - vercel_list_deployments: list deployments - vercel_get_deployment: get deployment by ID or URL - Requires VERCEL_TOKEN environment variable - Added to official marketplace --- .changeset/kimi-vercel-plugin.md | 23 ++ plugins/marketplace.json | 9 + plugins/official/kimi-vercel/SKILL.md | 32 ++ .../official/kimi-vercel/bin/kimi-vercel.mjs | 299 ++++++++++++++++++ plugins/official/kimi-vercel/kimi.plugin.json | 18 ++ 5 files changed, 381 insertions(+) create mode 100644 .changeset/kimi-vercel-plugin.md create mode 100644 plugins/official/kimi-vercel/SKILL.md create mode 100755 plugins/official/kimi-vercel/bin/kimi-vercel.mjs create mode 100644 plugins/official/kimi-vercel/kimi.plugin.json diff --git a/.changeset/kimi-vercel-plugin.md b/.changeset/kimi-vercel-plugin.md new file mode 100644 index 0000000000..4face2e9df --- /dev/null +++ b/.changeset/kimi-vercel-plugin.md @@ -0,0 +1,23 @@ +--- +'kimi-code': minor +--- + +feat(plugins): add Vercel MCP plugin + +Introduces `kimi-vercel`, an official MCP plugin for managing Vercel projects and deployments. + +**New plugin:** `plugins/official/kimi-vercel/` +- `bin/kimi-vercel.mjs` – MCP server (stdio transport) that talks to the Vercel REST API +- `kimi.plugin.json` – Plugin manifest +- `SKILL.md` – Plugin documentation + +**Provided tools:** + +| Tool | Description | +|---|---| +| `vercel_list_projects` | List all Vercel projects for the authenticated user | +| `vercel_get_project` | Get details of a specific project by name or ID | +| `vercel_list_deployments` | List deployments for a project | +| `vercel_get_deployment` | Get details of a deployment by ID or URL | + +**Setup:** Set `VERCEL_TOKEN` environment variable with a Vercel personal access token. diff --git a/plugins/marketplace.json b/plugins/marketplace.json index 1e514a33ab..e5ed34a377 100644 --- a/plugins/marketplace.json +++ b/plugins/marketplace.json @@ -10,6 +10,15 @@ "keywords": ["data", "mcp"], "source": "./official/kimi-datasource" }, + { + "id": "kimi-vercel", + "tier": "official", + "displayName": "Vercel", + "version": "0.1.0", + "description": "Vercel deployment and project management tools.", + "keywords": ["vercel", "deploy", "hosting", "frontend", "mcp"], + "source": "./official/kimi-vercel" + }, { "id": "superpowers", "tier": "curated", diff --git a/plugins/official/kimi-vercel/SKILL.md b/plugins/official/kimi-vercel/SKILL.md new file mode 100644 index 0000000000..ead14294a4 --- /dev/null +++ b/plugins/official/kimi-vercel/SKILL.md @@ -0,0 +1,32 @@ +# Vercel for Kimi Code + +An MCP plugin that lets Kimi Code interact with [Vercel](https://vercel.com) projects and deployments. + +## Setup + +You need a Vercel personal access token. Get one from [Vercel Dashboard → Settings → Tokens](https://vercel.com/account/tokens), then set the environment variable: + +```bash +export VERCEL_TOKEN= +``` + +## Provided Tools + +- `vercel_list_projects` – List all Vercel projects for the authenticated user. +- `vercel_get_project` – Get details of a specific project by name or ID. +- `vercel_list_deployments` – List deployments for a project. +- `vercel_get_deployment` – Get details of a deployment by ID or URL. + +## Usage + +Ask Kimi Code to: + +- "List my Vercel projects" +- "Show deployments for my-project" +- "Get details of deployment dpl_xxx" +- "What's the status of my-project-xxx.vercel.app?" + +## Requirements + +- Node.js 18+ +- A Vercel account with a personal access token diff --git a/plugins/official/kimi-vercel/bin/kimi-vercel.mjs b/plugins/official/kimi-vercel/bin/kimi-vercel.mjs new file mode 100755 index 0000000000..108448f2da --- /dev/null +++ b/plugins/official/kimi-vercel/bin/kimi-vercel.mjs @@ -0,0 +1,299 @@ +#!/usr/bin/env node +// Vercel MCP plugin for Kimi Code +// Provides tools to interact with Vercel projects and deployments + +import { readFile } from 'node:fs/promises'; +import readline from 'node:readline'; + +const VERSION = '0.1.0'; +const PROTOCOL_VERSION = '2025-06-18'; +const VERCEL_API_BASE = 'https://api.vercel.com'; + +function getToken() { + const token = process.env.VERCEL_TOKEN || process.env.VERCEL_API_TOKEN; + if (!token) { + throw new Error( + 'VERCEL_TOKEN environment variable is required. Set it with: export VERCEL_TOKEN=' + ); + } + return token; +} + +async function vercelFetch(path, options = {}) { + const token = getToken(); + const url = `${VERCEL_API_BASE}${path}`; + const response = await fetch(url, { + ...options, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Vercel API error (${response.status}): ${errorText}`); + } + + return response.json(); +} + +const TOOLS = [ + { + name: 'vercel_list_projects', + description: 'List all Vercel projects for the authenticated user.', + inputSchema: { + type: 'object', + properties: { + limit: { + type: 'number', + description: 'Maximum number of projects to return (default: 20, max: 100).', + default: 20, + }, + }, + }, + }, + { + name: 'vercel_get_project', + description: 'Get details of a specific Vercel project.', + inputSchema: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'The name or ID of the Vercel project.', + }, + }, + required: ['name'], + }, + }, + { + name: 'vercel_list_deployments', + description: 'List deployments for a Vercel project.', + inputSchema: { + type: 'object', + properties: { + projectId: { + type: 'string', + description: 'The ID or name of the project to list deployments for.', + }, + limit: { + type: 'number', + description: 'Maximum number of deployments to return (default: 20).', + default: 20, + }, + }, + required: ['projectId'], + }, + }, + { + name: 'vercel_get_deployment', + description: 'Get details of a specific deployment by its ID or URL.', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'The deployment ID or URL (e.g., "dpl_xxx" or "my-project-xxx.vercel.app").', + }, + }, + required: ['id'], + }, + }, +]; + +const HANDLERS = { + vercel_list_projects: async (args) => { + const limit = Math.min(args.limit || 20, 100); + const data = await vercelFetch(`/v9/projects?limit=${limit}`); + return { + projects: data.projects.map((p) => ({ + id: p.id, + name: p.name, + framework: p.framework, + latestDeployment: p.latestDeployments?.[0] + ? { + id: p.latestDeployments[0].uid, + url: p.latestDeployments[0].url, + state: p.latestDeployments[0].state, + createdAt: p.latestDeployments[0].createdAt, + } + : null, + createdAt: p.createdAt, + })), + count: data.projects.length, + total: data.pagination?.count ?? data.projects.length, + }; + }, + + vercel_get_project: async (args) => { + const data = await vercelFetch(`/v9/projects/${encodeURIComponent(args.name)}`); + return { + id: data.id, + name: data.name, + framework: data.framework, + rootDirectory: data.rootDirectory, + latestDeployment: data.latestDeployments?.[0] + ? { + id: data.latestDeployments[0].uid, + url: data.latestDeployments[0].url, + state: data.latestDeployments[0].state, + createdAt: data.latestDeployments[0].createdAt, + } + : null, + createdAt: data.createdAt, + updatedAt: data.updatedAt, + }; + }, + + vercel_list_deployments: async (args) => { + const limit = Math.min(args.limit || 20, 100); + const data = await vercelFetch( + `/v6/deployments?projectId=${encodeURIComponent(args.projectId)}&limit=${limit}` + ); + return { + deployments: data.deployments.map((d) => ({ + id: d.uid, + url: d.url, + state: d.state, + createdAt: d.createdAt, + creator: d.creator?.username || d.creator?.email, + target: d.target, + })), + count: data.deployments.length, + }; + }, + + vercel_get_deployment: async (args) => { + const id = args.id; + let path; + if (id.includes('.vercel.app') || id.startsWith('http')) { + // URL-based lookup + const url = id.startsWith('http') ? id : `https://${id}`; + path = `/v13/deployments/get?url=${encodeURIComponent(url)}`; + } else { + path = `/v13/deployments/${encodeURIComponent(id)}`; + } + const data = await vercelFetch(path); + return { + id: data.id, + url: data.url, + state: data.readyState || data.state, + createdAt: data.createdAt, + creator: data.creator?.username || data.creator?.email, + target: data.target, + project: data.name, + inspectorUrl: data.inspectorUrl, + }; + }, +}; + +// MCP stdio transport +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false, +}); + +function send(message) { + const json = JSON.stringify(message); + process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`); +} + +const state = { + initialized: false, + requestId: null, +}; + +rl.on('line', async (line) => { + const trimmed = line.trim(); + if (!trimmed) return; + + let message; + try { + message = JSON.parse(trimmed); + } catch { + return; + } + + if (message.jsonrpc !== '2.0') return; + + if (message.method === 'initialize') { + state.initialized = true; + state.requestId = message.id; + send({ + jsonrpc: '2.0', + id: message.id, + result: { + protocolVersion: PROTOCOL_VERSION, + capabilities: { + tools: {}, + }, + serverInfo: { + name: 'kimi-vercel', + version: VERSION, + }, + }, + }); + return; + } + + if (message.method === 'notifications/initialized') { + return; + } + + if (message.method === 'tools/list') { + send({ + jsonrpc: '2.0', + id: message.id, + result: { tools: TOOLS }, + }); + return; + } + + if (message.method === 'tools/call') { + const { name, arguments: args } = message.params; + const handler = HANDLERS[name]; + + if (!handler) { + send({ + jsonrpc: '2.0', + id: message.id, + error: { code: -32601, message: `Unknown tool: ${name}` }, + }); + return; + } + + try { + const result = await handler(args || {}, process.cwd()); + send({ + jsonrpc: '2.0', + id: message.id, + result: { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }, + }); + } catch (error) { + send({ + jsonrpc: '2.0', + id: message.id, + result: { + content: [ + { + type: 'text', + text: JSON.stringify({ error: error.message }, null, 2), + }, + ], + isError: true, + }, + }); + } + return; + } +}); diff --git a/plugins/official/kimi-vercel/kimi.plugin.json b/plugins/official/kimi-vercel/kimi.plugin.json new file mode 100644 index 0000000000..b8d6740932 --- /dev/null +++ b/plugins/official/kimi-vercel/kimi.plugin.json @@ -0,0 +1,18 @@ +{ + "name": "kimi-vercel", + "version": "0.1.0", + "description": "Vercel deployment and project management for Kimi Code. List projects, view deployments, and inspect deployment status.", + "keywords": ["vercel", "deploy", "hosting", "frontend", "mcp"], + "mcpServers": { + "vercel": { + "command": "node", + "args": ["./bin/kimi-vercel.mjs"], + "cwd": "./" + } + }, + "interface": { + "displayName": "Vercel", + "shortDescription": "Manage Vercel projects and deployments", + "developerName": "Moonshot AI" + } +}