Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions docs/use/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ it). See [Packages](./packages.md#hidden-packages).
Pass a **`query`** string that describes what you want to do. Results are
ranked; order in the response matters. Query responses are intentionally
compact: the markdown response is a short list of matches with the result type,
title, one-line summary, and entity reference when applicable.
title, one-line summary, and entity reference when applicable. The top few
capability hits also include a compact inlined call shape (runtime accessor plus
a whitespace-collapsed input type, truncated when long) so you can often call
from **execute** without an immediate entity round trip.

An entire saved-package UUID or `kody.id` is treated as an exact package
identity when it resolves for the signed-in user. Kody also recognizes
Expand All @@ -42,31 +45,48 @@ Optional **`limit`** caps how many ranked hits return. Optional
**`maxResponseSize`** trims low-ranked matches against the compact list when the
response must stay small.

## Single-entity detail
## Entity detail

To get **full markdown and call shapes for one hit** (for example a capability’s
ready-to-run **execute** snippet plus `inputTypeDefinition` /
`outputTypeDefinition`), call **search** again with **`entity`** set to
`"{id}:{type}"` where **`type`** is `capability`, `value`, `integration`,
`package`, or `secret`.
To get **full markdown detail for one hit**, call **search** again with
**`entity`** set to `"{id}:{type}"` where **`type`** is `capability`, `value`,
`integration`, `package`, or `secret`. Capability entities additionally include
a ready-to-run **execute** snippet plus `inputTypeDefinition` /
`outputTypeDefinition`.

Pass an **array of 1–10 entity refs** when you need several related details at
once (for example a create/poll OpenAPI pair). Each ref resolves independently:
failures become per-entity error lines without aborting the whole batch. If
every ref fails, the tool returns an error result.

Examples:

- `coding_guide_get:capability`
- `["openapi:canva:createdesignexportjob:capability", "openapi:canva:getdesignexportjob:capability"]`
- `user:preferred_org:value`
- `github:integration`
- `my-package:package`
- `550e8400-e29b-41d4-a716-446655440000:package`
- `spotify:integration`
- `spotify-access-token:secret`

There is **no separate `detail` flag** on search. Deeper inspection of one
entity uses **`entity`**, not a different mode of the same ranked query.
There is **no separate `detail` flag** on search. Deeper inspection uses
**`entity`**, not a different mode of the same ranked query.

Top-level ranked result cards include an explicit entity ref for each hit when
applicable, using that same `"{id}:{type}"` format, so you can immediately copy
the ref into a follow-up `entity` lookup when needed.

For synthesized provider capabilities (OpenAPI bindings, connected MCP servers,
and remote connectors), capability detail also lists **related operations from
the same provider** so create/poll pairs and sibling tools are visible without a
second lookup. Built-in capabilities skip that section to keep common lookups
lean.

Package detail includes a short **Maintain** pointer: the git lane
(`package_get_git_remote` → clone/edit/push → `package_publish_external_push`)
and the tool-only alternative (`package_save` / repo sessions, with
`coding_guide_get({ guide: "package_authoring" })` for the full guide).

Capability detail shows the exact runtime pattern for **execute**:

```ts
Expand Down
258 changes: 258 additions & 0 deletions packages/worker/src/mcp/tools/search-format.node.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Script, createContext } from 'node:vm'
import { expect, test } from 'vitest'
import {
compactCapabilityInputTypeDefinition,
formatEntityDetailMarkdown,
formatSearchMarkdown,
inlineCapabilityInputTypeMaxLength,
parseEntityRef,
toSlimStructuredMatches,
} from './search-format.ts'
Expand Down Expand Up @@ -841,3 +843,259 @@ test('search markdown summarizes broad results safely and only suggests entity d
}),
])
})

test('search formatting inlines top capability call shapes, related ops, and package maintain pointers', () => {
const longInputType = `type LongInput = {\n\t${'field: string\n\t'.repeat(40)}}`
const compact = compactCapabilityInputTypeDefinition(longInputType)
expect(compact.truncated).toBe(true)
expect(compact.definition.length).toBeLessThanOrEqual(
inlineCapabilityInputTypeMaxLength,
)
expect(compact.definition.endsWith('...')).toBe(true)

const listMarkdown = formatSearchMarkdown({
matches: [
{
type: 'capability',
name: 'openapi:widgets:createwidget',
description: 'Create a widget.',
source: 'openapi',
openApi: {
bindingName: 'widgets',
kodyName: 'widgets',
operationSlug: 'createwidget',
method: 'post',
path: '/widgets',
},
inputTypeDefinition: 'type CreateWidgetInput = { name: string }',
},
{
type: 'capability',
name: 'openapi:widgets:listwidgets',
description: 'List widgets.',
source: 'openapi',
openApi: {
bindingName: 'widgets',
kodyName: 'widgets',
operationSlug: 'listwidgets',
method: 'get',
path: '/widgets',
},
inputTypeDefinition: compact.definition,
inputTypeDefinitionTruncated: true,
},
{
type: 'capability',
name: 'fourth_capability',
description: 'Beyond the top inline set.',
source: 'builtin',
},
],
includePreamble: false,
})
expect(listMarkdown).toContain('kody.openapi["widgets"].createwidget(args)')
expect(listMarkdown).toContain('type CreateWidgetInput = { name: string }')
expect(listMarkdown).toContain('use entity detail for the full definition')
expect(listMarkdown).not.toMatch(
/fourth_capability[\s\S]*kody\.fourth_capability\(args\)/,
)

const [slimWithShape, slimTruncated, slimWithoutShape] =
toSlimStructuredMatches({
baseUrl: 'http://localhost',
matches: [
{
type: 'capability',
name: 'openapi:widgets:createwidget',
description: 'Create a widget.',
source: 'openapi',
openApi: {
bindingName: 'widgets',
kodyName: 'widgets',
operationSlug: 'createwidget',
method: 'post',
path: '/widgets',
},
inputTypeDefinition: 'type CreateWidgetInput = { name: string }',
},
{
type: 'capability',
name: 'openapi:widgets:listwidgets',
description: 'List widgets.',
source: 'openapi',
inputTypeDefinition: compact.definition,
inputTypeDefinitionTruncated: true,
},
{
type: 'capability',
name: 'fourth_capability',
description: 'Beyond the top inline set.',
source: 'builtin',
},
],
})
expect(slimWithShape).toMatchObject({
type: 'capability',
inputTypeDefinition: 'type CreateWidgetInput = { name: string }',
})
expect(slimTruncated).toMatchObject({
type: 'capability',
inputTypeDefinition: compact.definition,
inputTypeDefinitionTruncated: true,
})
expect(slimWithoutShape).toMatchObject({ type: 'capability' })
expect(slimWithoutShape).not.toHaveProperty('inputTypeDefinition')
expect(slimWithoutShape).not.toHaveProperty('inputTypeDefinitionTruncated')

const openApiDetail = formatEntityDetailMarkdown({
type: 'capability',
id: 'openapi:widgets:createwidget',
title: 'openapi:widgets:createwidget',
description: 'Create a widget.',
spec: {
name: 'openapi:widgets:createwidget',
domain: 'openapi:widgets',
description: 'Create a widget.',
keywords: [],
readOnly: false,
idempotent: false,
destructive: false,
source: 'openapi',
openApi: {
bindingName: 'widgets',
kodyName: 'widgets',
operationSlug: 'createwidget',
method: 'post',
path: '/widgets',
},
inputFields: ['name'],
requiredInputFields: ['name'],
outputFields: [],
inputSchema: {
type: 'object',
properties: { name: { type: 'string' } },
required: ['name'],
},
inputTypeDefinition: 'type CreateWidgetInput = { name: string }',
},
relatedOperations: [
{
name: 'openapi:widgets:listwidgets',
entityRef: 'openapi:widgets:listwidgets:capability',
description: 'List widgets',
method: 'get',
path: '/widgets',
},
{
name: 'openapi:widgets:getwidget',
entityRef: 'openapi:widgets:getwidget:capability',
description: 'Get one widget.',
method: 'get',
path: '/widgets/{id}',
},
],
})
expect(openApiDetail.markdown).toContain(
'## Related operations (same provider)',
)
expect(openApiDetail.markdown).toContain('GET /widgets')
expect(openApiDetail.markdown).toContain(
'openapi:widgets:listwidgets:capability',
)
expect(openApiDetail.structured).toMatchObject({
type: 'capability',
relatedOperations: [
expect.objectContaining({
name: 'openapi:widgets:listwidgets',
method: 'get',
path: '/widgets',
}),
expect.objectContaining({
name: 'openapi:widgets:getwidget',
}),
],
})

const builtinDetail = formatEntityDetailMarkdown({
type: 'capability',
id: 'coding_guide_get',
title: 'coding_guide_get',
description: 'Load an official guide.',
spec: {
name: 'coding_guide_get',
domain: 'coding',
description: 'Load an official guide.',
keywords: [],
readOnly: true,
idempotent: true,
destructive: false,
source: 'builtin',
inputFields: ['guide'],
requiredInputFields: ['guide'],
outputFields: [],
inputSchema: {
type: 'object',
properties: { guide: { type: 'string' } },
required: ['guide'],
},
inputTypeDefinition: 'type CodingGuideGetInput = { guide: string }',
},
})
expect(builtinDetail.markdown).not.toContain(
'## Related operations (same provider)',
)
expect(builtinDetail.structured).not.toHaveProperty('relatedOperations')

const packageDetail = formatEntityDetailMarkdown({
type: 'package',
id: 'notes-helper',
title: '@user/notes-helper',
description: 'Notes helper package.',
baseUrl: 'http://localhost',
ownerUsername: 'user',
hostedUrl: null,
record: {
id: 'package-notes',
userId: 'user-1',
name: '@user/notes-helper',
kodyId: 'notes-helper',
description: 'Notes helper package.',
tags: [],
searchText: null,
sourceId: 'source-notes',
hasApp: false,
hidden: false,
createdAt: '2026-03-20T00:00:00.000Z',
updatedAt: '2026-03-20T00:00:00.000Z',
},
manifest: {
name: '@user/notes-helper',
exports: { '.': './index.ts' },
kody: {
id: 'notes-helper',
description: 'Notes helper package.',
},
},
files: {
'package.json': '{}',
'index.ts': 'export default function main() {}',
},
})
expect(packageDetail.markdown).toContain('## Maintain')
expect(packageDetail.markdown).toContain(
'package_get_git_remote({ kody_id: "notes-helper" })',
)
expect(packageDetail.markdown).toContain(
'package_publish_external_push({ kody_id: "notes-helper" })',
)
expect(packageDetail.markdown).toContain(
'coding_guide_get({ guide: "package_authoring" })',
)
expect(packageDetail.structured).toMatchObject({
type: 'package',
maintain: {
gitLane: 'package_get_git_remote({ kody_id: "notes-helper" })',
publish: 'package_publish_external_push({ kody_id: "notes-helper" })',
},
})
})
Loading
Loading