LSP & MCP Providers
Show the whole prompt
Read https://intentius.io/chant/lexicon-authoring/lsp-mcp/.
Add the LSP completion and hover providers and the MCP tools and resources I describe to the lexicon I name, with tool and resource names in the lexicon's namespace as the page shows.
Write `completions.test.ts` and `hover.test.ts` and tests for each tool, run `npx vitest run lexicons/<name>` and `chant dev check-lexicon lexicons/<name>`, and open a pull request.
Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.Optional: hand this page to your coding agent
Read https://intentius.io/chant/lexicon-authoring/lsp-mcp/.Add the LSP completion and hover providers and the MCP tools and resources I describe to the lexicon I name, with tool and resource names in the lexicon's namespace as the page shows.Write `completions.test.ts` and `hover.test.ts` and tests for each tool, run `npx vitest run lexicons/<name>` and `chant dev check-lexicon lexicons/<name>`, and open a pull request.Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.A lexicon can add editor completions and hover through LSP (Language Server Protocol), and agent tools through MCP (Model Context Protocol).
LSP Providers
Section titled “LSP Providers”Core’s LexiconIndex, lexiconCompletions and lexiconHover, in
packages/core/src/lsp/lexicon-providers.ts, work with any lexicon’s registry
data:
import { LexiconIndex, lexiconCompletions, lexiconHover } from "@intentius/chant/lsp/lexicon-providers";
// The generated registry JSON: Record<className, LexiconEntry>.import lexiconData from "../generated/lexicon-my-lexicon.json";
const index = new LexiconIndex(lexiconData);
// In your completionProvider():return lexiconCompletions(ctx, index, "My resource");
// In your hoverProvider():return lexiconHover(ctx, index, myCustomHoverFormatter);The two context types come from packages/core/src/lsp/types.ts:
| Type | Fields |
|---|---|
CompletionContext | uri, content, position, wordAtCursor, linePrefix |
HoverContext | uri, content, position, word, lineText |
lexiconCompletions reads linePrefix and wordAtCursor and returns
CompletionItem[]. lexiconHover reads word, looks it up in the index, and
returns HoverInfo | undefined; its third argument is an optional
(className, entry) => HoverInfo | undefined formatter.
Completions
Section titled “Completions”The completionProvider() method on your plugin returns completion items for resource and property type names. Use lexiconCompletions to generate standard completions from your registry:
completionProvider(ctx) { return lexiconCompletions(ctx, index, "K8s resource");}See
lexicons/aws/src/lsp/completions.tsfor the AWS completion provider.
The hoverProvider() method returns hover information when users mouse over resource type constructors. Use lexiconHover with an optional custom formatter:
hoverProvider(ctx) { return lexiconHover(ctx, index, (className, entry) => { return { contents: `**${className}** — \`${entry.resourceType}\`` }; });}See
lexicons/aws/src/lsp/hover.tsfor the AWS hover provider.
MCP Contributions
Section titled “MCP Contributions”A lexicon can contribute MCP tools and resources for AI agents:
McpToolContribution and McpResourceContribution are defined in
packages/core/src/mcp/types.ts.
import type { McpToolContribution } from "@intentius/chant/mcp/types";
mcpTools(): McpToolContribution[] { return [ { name: "k8s:lookup-resource", description: "Look up a resource type definition", inputSchema: { type: "object" as const, properties: { name: { type: "string" } }, required: ["name"], }, async handler(params: Record<string, unknown>): Promise<unknown> { return { resourceType: String(params.name) }; }, }, ];}A tool’s handler takes Record<string, unknown> and returns Promise<unknown>.
Resources
Section titled “Resources”import type { McpResourceContribution } from "@intentius/chant/mcp/types";
mcpResources(): McpResourceContribution[] { return [ { uri: "catalog", name: "K8s Resource Catalog", description: "All available Kubernetes resource types", mimeType: "application/json", async handler(): Promise<string> { return JSON.stringify([]); }, }, ];}A resource’s handler takes no arguments and must return Promise<string>.
Serialize before returning.
Namespacing
Section titled “Namespacing”Core registers every contribution under the lexicon’s own namespace: a tool as
<lexicon>:<verb>, a resource as chant://<lexicon>/<path>. That is what an
agent sees and what the docs name, so k8s:lookup-resource above registers as
exactly that, and catalog registers as chant://k8s/catalog.
Applying the namespace is idempotent (chant #1341). namespacedToolName and
namespacedResourceUri in packages/core/src/cli/mcp/server.ts accept three
authored forms and normalize them all to one registered name:
| You write | Registers as |
|---|---|
diff | <lexicon>:diff |
<lexicon>:diff | <lexicon>:diff |
catalog | chant://<lexicon>/catalog |
<lexicon>:resource-catalog | chant://<lexicon>/resource-catalog |
chant://<lexicon>/catalog | chant://<lexicon>/catalog |
That is what lets the shared createDiffTool and createCatalogResource
helpers write their own prefix without doubling it. What does not normalize is a
URI naming a different lexicon: chant://other/x contributed by aws
registers as the unusable chant://aws/chant://other/x. chant dev check-lexicon fails any contribution whose registered name is not a single
well-formed identifier, which catches that case.
Conventional names
Section titled “Conventional names”Most lexicons contribute a common pair, both from the shared helpers in
lexicon-plugin-helpers.ts:
| Contribution | What it does | Lexicons |
|---|---|---|
<lexicon>:diff | Diff built output against a previous build | 12 |
chant://<lexicon>/resource-catalog | The lexicon’s resource types as JSON | 12 |
Both come from packages/core/src/lexicon-plugin-helpers.ts, so contributing
them is two lines:
import { createDiffTool, createCatalogResource } from "@intentius/chant/lexicon-plugin-helpers";
mcpTools() { return [createDiffTool(mySerializer, "Diff built output for My Lexicon", "my-lexicon")];},
mcpResources() { return [ createCatalogResource( import.meta.url, // locates src/generated/ "My Lexicon Resource Catalog", "JSON list of all supported resource types", "lexicon-my-lexicon.json", // the generated registry filename "my-lexicon", ), ];},The same module exports createSkillsLoader, which the skills() member uses.
See Skills.
The CI lexicons also ship a read-only context set, each tool answering from a
local build without touching a live forge. gitlab and forgejo contribute all
eight (checks, workflow or pipeline, references, affected,
workflow-yaml or pipeline-yaml, source, owns, compare); github
contributes the first five. A new CI lexicon should match those names rather
than invent its own. See lexicons/gitlab/src/mcp/context-tools.ts.
Symbol resolvers
Section titled “Symbol resolvers”chant workspace graph --intent <path>#<symbol> follows a declaration through history instead of a line range (#3034). Core resolves symbols in TypeScript and JavaScript. A lexicon whose members hold files in another language can resolve them too, by returning resolvers from symbolResolvers() (#3313):
import type { LexiconPlugin, SymbolResolver } from "@intentius/chant";
const sqlResolver: SymbolResolver = { language: "SQL", extensions: [".sql"], declarations(path, text) { // Every declaration in the file: its dotted name, its kind and its 1-based lines. return [{ qualified: "app.users", kind: "table", lines: { start: 2, end: 4 } }]; },};
export const plugin: LexiconPlugin = { // ... symbolResolvers: () => [sqlResolver],};The intent walk asks for them only for a file core’s resolver does not read, and only from the lexicons the file’s member configures in its chant.config. The config is read without running it when it can be, so graph --intent loads your plugin but no project code. A bare name finds the one declaration whose dotted name ends with it, and a name that matches several is refused as ambiguous, as for TypeScript. declarations does no I/O and calls no network. chant drops a resolver whose extensions don’t start with a dot, and any declaration whose lines are not a 1-based range. When a member’s lexicon fails to load, the intent-symbol-unsupported message names it.
Next Steps
Section titled “Next Steps”Add skills for AI agent guidance, then package and publish your lexicon.