Tools, resources & prompts
Author the core MCP surface with the @noodleseed/one SDK. Typed tools, readable resources, and reusable prompts.
A Noodle Seed server is a server(name, options, definitions) call. The definitions array holds
your tools, resources, and prompts. Everything imports from the bare @noodleseed/one package.
import { server, tool, resource, prompt, z } from '@noodleseed/one';
export default server('support', { title: 'Support', version: '1.0.0' }, [
// tools, resources, prompts go here
]);Tools
A tool is a typed action the model can call. It declares a Zod input schema, an optional output
schema, and a fulfil function that returns the result.
tool('greet', {
description: 'Greet a person by name.',
input: z.object({ name: z.string().default('world') }),
output: z.object({ message: z.string() }),
fulfil: ({ input }) => {
return { message: `Hello, ${input.name}!` };
},
});tool(name, options) accepts:
| Option | Type | Notes |
|---|---|---|
description | string | Shown to the model in tools/list. |
input | Zod schema | Compiled to JSON Schema 2020-12. |
output | Zod schema | Optional but recommended for structured results. |
fulfil | (ctx) => result | Returns the output. Receives { input, user, connectors }. |
annotations | object | Behavior hints (see below). |
visibility | ('model' | 'app')[] | Defaults to both. Use ['app'] for app-only tools. |
view | React entry or raw HTML | Links the tool result to an MCP App view. |
Annotations
Use the annotations helper to declare behavior hints so hosts can present the tool correctly.
import { annotations } from '@noodleseed/one';
tool('list_orders', {
description: 'List recent orders.',
input: z.object({ limit: z.number().int().min(1).max(50).default(10) }),
output: z.object({ orders: z.array(z.object({ id: z.string() })) }),
annotations: annotations.readOnly(),
fulfil: ({ input }) => ({ orders: [] }),
});annotations.readOnly(), annotations.action(), annotations.localAction(), and
annotations.openAction() map to the MCP readOnlyHint / destructiveHint / idempotentHint /
openWorldHint fields.
Calling connectors
To reach an external API, bind a connector on the server's use map and call it inside fulfil via
ctx.connectors. See the Connectors guide for the full pattern.
tool('current_weather', {
description: 'Get the current temperature for a city.',
input: z.object({ city: z.string() }),
output: z.object({ temp_c: z.number() }),
fulfil: ({ input, connectors }) => connectors.weather.current({ city: input.city }),
});Resources
A resource exposes readable context at a URI. The URI can be fixed or a {var} template.
resource('changelog', {
uri: 'docs://changelog',
title: 'Changelog',
mimeType: 'text/markdown',
fulfil: () => ({
contents: [{ uri: 'docs://changelog', mimeType: 'text/markdown', text: '# Changelog\n...' }],
}),
});For a templated URI, the variable is available in the resource context:
resource('case', {
uri: 'case://{case_id}',
title: 'Support case',
fulfil: ({ input }) => ({
contents: [{ uri: `case://${input.case_id}`, mimeType: 'application/json', text: '{}' }],
}),
});Prompts
A prompt is a reusable, argument-driven template surfaced through prompts/list. Declare
arguments with a Zod object, and return messages from fulfil.
prompt('incident_update', {
title: 'Incident update',
description: 'Draft a customer-safe update from an internal case.',
arguments: z.object({
case_id: z.string(),
audience: z.enum(['customer', 'executive']).default('customer'),
}),
fulfil: () => ({
messages: [{ role: 'user', content: { type: 'text', text: 'Draft a customer-safe update...' } }],
}),
});Check your work
noodle validate # compile + schema + connector-reference checks
noodle test # loopback MCP smoke (initialize, tools/list, a tool call)noodle validate and noodle test are local and need no account. See the
Quickstart for the full loop.