Merge branch 'codex/tool-json-schema-dsl' into codex/canonical-tool-output

# Conflicts:
#	.agents/notes/implemented/feature/2026-06-30-interception-seams.md
#	docs/config-catalog.md
#	docs/cookbook/adding-a-tool.i18n.yaml
#	docs/cookbook/adding-a-tool.md
#	docs/cookbook/adding-a-tool.zh.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/tools.md
#	docs/event-producer-consumer.md
#	docs/persistence-catalog.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl
#	packages/bash/tool-bash/src/index.ts
#	packages/core/agent-loop/src/tool-calls.ts
#	packages/core/agent-loop/tests/cancel.spec.ts
#	packages/core/agent-loop/tests/contract-regressions.spec.ts
#	packages/core/agent-loop/tests/tool-calls.spec.ts
#	packages/core/tools/README.md
#	packages/core/tools/src/index.ts
#	packages/core/tools/tests/code-mode.spec.ts
#	packages/core/tools/tests/tools.spec.ts
#	packages/fs/tool-fs-search/tests/integration.spec.ts
#	packages/fs/tool-fs-search/tests/tools.spec.ts
#	packages/fs/tool-fs/tests/integration.spec.ts
#	packages/mcp/mcp-client/src/tools.ts
#	packages/timeout/timeout-policy/tests/timeout-policy.spec.ts
#	packages/web/tool-web/tests/integration.spec.ts
#	packages/web/tool-web/tests/tool-web.spec.ts
This commit is contained in:
Tianyi Cui
2026-07-21 23:39:03 +08:00
194 changed files with 3387 additions and 1222 deletions
@@ -26,6 +26,8 @@ import { apply } from '@deepseek-ai/dsh-mcp-client/src/index.ts'
import { publicToolName } from '@deepseek-ai/dsh-mcp-client/src/tools.ts'
import type { Config } from '@deepseek-ai/dsh-mcp-client'
const testToolSignal = new AbortController().signal
const fixtureServerPath = fileURLToPath(new URL('./fixture-server.ts', import.meta.url))
// Resolve package-local .bin for pnpm-hoisted MCP server binaries.
@@ -119,6 +121,7 @@ describe('fixture server — controlled scenarios', () => {
it('executes the dotted tool via its normalized public name', async () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: publicToolName('fixture', 'admin.reset'), arguments: {},
})
expect(result.isError).toBe(false)
@@ -127,6 +130,7 @@ describe('fixture server — controlled scenarios', () => {
it('executes add(2, 3) → "5"', async () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__fixture__add', arguments: { a: 2, b: 3 },
})
expect(result.isError).toBe(false)
@@ -135,6 +139,7 @@ describe('fixture server — controlled scenarios', () => {
it('executes greet("World") → "Hello, World!"', async () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__fixture__greet', arguments: { name: 'World' },
})
expect(result.isError).toBe(false)
@@ -143,6 +148,7 @@ describe('fixture server — controlled scenarios', () => {
it('executes fail() → isError result', async () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__fixture__fail', arguments: {},
})
expect(result.isError).toBe(true)
@@ -151,6 +157,7 @@ describe('fixture server — controlled scenarios', () => {
it('executes image() → image placeholder', async () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__fixture__image', arguments: {},
})
expect(result.isError).toBe(false)
@@ -241,6 +248,7 @@ describe('server-everything — official test server', () => {
it('executes echo({ message: "hello" }) → "Echo: hello"', async () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__everything__echo', arguments: { message: 'hello' },
})
expect(result.isError).toBe(false)
@@ -249,6 +257,7 @@ describe('server-everything — official test server', () => {
it('executes get-sum({ a: 3, b: 7 }) → contains "10"', async () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__everything__get-sum', arguments: { a: 3, b: 7 },
})
expect(result.isError).toBe(false)
@@ -257,6 +266,7 @@ describe('server-everything — official test server', () => {
it('executes get-tiny-image → image placeholder', async () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__everything__get-tiny-image', arguments: {},
})
expect(result.isError).toBe(false)
@@ -306,6 +316,7 @@ describe('server-filesystem — real filesystem operations', () => {
// Write via MCP tool
const writeResult = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__filesystem__write_file', arguments: { path: filePath, content },
})
expect(writeResult.isError).toBe(false)
@@ -316,6 +327,7 @@ describe('server-filesystem — real filesystem operations', () => {
// Read back via MCP tool
const readResult = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__filesystem__read_file', arguments: { path: filePath },
})
expect(readResult.isError).toBe(false)
@@ -327,6 +339,7 @@ describe('server-filesystem — real filesystem operations', () => {
await writeFile(join(tempDir, 'listed.txt'), 'listed')
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__filesystem__list_directory', arguments: { path: tempDir },
})
expect(result.isError).toBe(false)
@@ -418,6 +431,7 @@ describe('streamable-http — in-process MCP server', () => {
it('executes ping() → "pong" over HTTP', async () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__web__ping', arguments: {},
})
expect(result.isError).toBe(false)
@@ -426,6 +440,7 @@ describe('streamable-http — in-process MCP server', () => {
it('executes shout({ message }) with args over HTTP', async () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: nextCallId(), name: 'mcp__web__shout', arguments: { message: 'quiet' },
})
expect(result.isError).toBe(false)
@@ -9,6 +9,8 @@ import { publicToolName, syncTools, type ToolBridgeOptions } from '@deepseek-ai/
import { createTransport } from '@deepseek-ai/dsh-mcp-client/src/transport.ts'
import type { Config } from '@deepseek-ai/dsh-mcp-client'
const testToolSignal = new AbortController().signal
// ---- Mock MCP Client ----
interface MockTool {
@@ -145,7 +147,7 @@ describe('syncTools', () => {
expect(ctx.tools.get('search')).toBeDefined()
expect(ctx.tools.get('mcp__srv__search')).toBeDefined()
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'search', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'search', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: 'native' })
})
@@ -282,12 +284,14 @@ describe('syncTools', () => {
await syncTools(client, ctx, defaultOpts, new Map())
const missing = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('missing'), name: 'mcp__srv__supported', arguments: {},
})
expect(missing.error).toMatchObject({ info: { code: 'INVALID_TOOL_OUTPUT' } })
expect(missing.error?.message).toContain('structuredContent')
const fallback = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('fallback'), name: 'mcp__srv__future-schema', arguments: {},
})
if (fallback.isError) throw new Error('unsupported schema must use the bridge fallback')
@@ -315,7 +319,7 @@ describe('tool execution', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__echo', arguments: { msg: 'hi' } })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__echo', arguments: { msg: 'hi' } })
expect(result.isError).toBe(false)
expect(result.content).toEqual([{ type: 'text', text: 'hello world' }])
@@ -337,7 +341,7 @@ describe('tool execution', () => {
await syncTools(client as never, ctx, defaultOpts, new Map())
const publicName = publicToolName('srv', 'admin.reset')
const result = await ctx.tools.execute({ callId: CallId('c1'), name: publicName, arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: publicName, arguments: {} })
expect(result.isError).toBe(false)
expect(client.callTool).toHaveBeenCalledWith(
@@ -354,7 +358,7 @@ describe('tool execution', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__multi', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__multi', arguments: {} })
expect(result.content).toEqual([{ type: 'text', text: 'line1\nline2' }])
})
@@ -370,7 +374,7 @@ describe('tool execution', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__img', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__img', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: 'before\n[image: image/png, content discarded]' })
if (result.isError) throw new Error('expected MCP success')
@@ -386,6 +390,7 @@ describe('tool execution', () => {
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('primitive'), name: 'mcp__srv__primitive-blocks', arguments: {},
})
@@ -409,7 +414,7 @@ describe('tool execution', () => {
{ content: [{ type: 'text', text: '42' }], structuredContent: { answer: 42 } },
)
await syncTools(valid as never, ctx, defaultOpts, new Map())
const success = await ctx.tools.execute({ callId: CallId('valid'), name: 'mcp__srv__structured', arguments: {} })
const success = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('valid'), name: 'mcp__srv__structured', arguments: {} })
if (success.isError) throw new Error('expected supported structuredContent to validate')
expect(success.value).toEqual({ content: [{ type: 'text', text: '42' }], structuredContent: { answer: 42 } })
@@ -419,7 +424,7 @@ describe('tool execution', () => {
{ content: [{ type: 'text', text: 'wrong' }], structuredContent: { answer: 'forty-two' } },
)
await syncTools(invalid as never, invalidCtx, defaultOpts, new Map())
const failure = await invalidCtx.tools.execute({ callId: CallId('invalid'), name: 'mcp__srv__structured', arguments: {} })
const failure = await invalidCtx.tools.execute({ signal: testToolSignal, callId: CallId('invalid'), name: 'mcp__srv__structured', arguments: {} })
expect(failure.error).toMatchObject({ info: { code: 'INVALID_TOOL_OUTPUT' } })
expect(failure.content[0]?.type === 'text' ? failure.content[0].text : '')
.toContain('value.structuredContent.answer')
@@ -435,7 +440,7 @@ describe('tool execution', () => {
{ content: [], structuredContent: ['kept', { nested: true }] },
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('fallback'), name: 'mcp__srv__future-schema', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('fallback'), name: 'mcp__srv__future-schema', arguments: {} })
if (result.isError) throw new Error('unsupported MCP output schemas must fall back')
expect(result.value).toEqual({ content: [], structuredContent: ['kept', { nested: true }] })
})
@@ -447,7 +452,7 @@ describe('tool execution', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__fail', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__fail', arguments: {} })
expect(result.isError).toBe(true)
expect(result.content[0]).toEqual({ type: 'text', text: 'Error: something went wrong' })
@@ -461,6 +466,7 @@ describe('tool execution', () => {
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('task-only'), name: 'mcp__srv__task-only', arguments: {},
})
@@ -493,7 +499,7 @@ describe('tool execution', () => {
client.callTool.mockResolvedValue({ toolResult: { key: 'value' } })
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__legacy', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__legacy', arguments: {} })
expect(result.isError).toBe(false)
expect(result.content[0]).toEqual({ type: 'text', text: '{"key":"value"}' })
@@ -508,6 +514,7 @@ describe('tool execution', () => {
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('legacy-structured'), name: 'mcp__srv__legacy-structured', arguments: {},
})
@@ -524,6 +531,7 @@ describe('tool execution', () => {
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('legacy-error'), name: 'mcp__srv__legacy-error', arguments: {},
})
@@ -546,7 +554,7 @@ describe('tool execution edge cases', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__audio_tool', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__audio_tool', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: '[audio: audio/mp3, content discarded]' })
})
@@ -558,7 +566,7 @@ describe('tool execution edge cases', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__res_tool', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__res_tool', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: '[resource: content discarded]' })
})
@@ -570,7 +578,7 @@ describe('tool execution edge cases', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__link_tool', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__link_tool', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: '[resource: content discarded]' })
})
@@ -582,7 +590,7 @@ describe('tool execution edge cases', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__unknown_tool', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__unknown_tool', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: '[unsupported content type: video]' })
})
@@ -594,7 +602,7 @@ describe('tool execution edge cases', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__img2', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__img2', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: '[image: unknown, content discarded]' })
})
@@ -606,7 +614,7 @@ describe('tool execution edge cases', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__audio_no_mime', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__audio_no_mime', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: '[audio: unknown, content discarded]' })
})
@@ -618,7 +626,7 @@ describe('tool execution edge cases', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__notext', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__notext', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: '(notext returned no text content)' })
})
@@ -630,7 +638,7 @@ describe('tool execution edge cases', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__empty_tool', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__empty_tool', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: '(empty_tool returned no text content)' })
})
@@ -643,7 +651,7 @@ describe('tool execution edge cases', () => {
client.callTool.mockResolvedValue({ toolResult: undefined, structuredContent: undefined })
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__legacy2', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__legacy2', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: '(no output)' })
})
@@ -655,7 +663,7 @@ describe('tool execution edge cases', () => {
client.callTool.mockResolvedValue({})
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('legacy-empty'), name: 'mcp__srv__legacy-empty', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('legacy-empty'), name: 'mcp__srv__legacy-empty', arguments: {} })
expect(result.content[0]).toEqual({ type: 'text', text: '(no output)' })
})
@@ -667,7 +675,7 @@ describe('tool execution edge cases', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__err_notext', arguments: {} })
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__err_notext', arguments: {} })
expect(result.isError).toBe(true)
expect(result.content[0]).toEqual({ type: 'text', text: 'Error: [image: image/png, content discarded]' })
@@ -804,7 +812,7 @@ describe('tool execution — non-object args fallback', () => {
await syncTools(client as never, ctx, defaultOpts, new Map())
// Simulate model emitting `null` as tool arguments (malformed).
await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__coerce', arguments: null })
await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__coerce', arguments: null })
expect(client.callTool).toHaveBeenCalledWith(
{ name: 'coerce', arguments: {} },
@@ -820,7 +828,7 @@ describe('tool execution — non-object args fallback', () => {
)
await syncTools(client as never, ctx, defaultOpts, new Map())
await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__coerce2', arguments: 'bad' })
await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__coerce2', arguments: 'bad' })
expect(client.callTool).toHaveBeenCalledWith(
{ name: 'coerce2', arguments: {} },