MCP Resources
A resource is something a client can read rather than call: a config file, an invoice, a report. Clients show resources to the user, or attach them to the model's context. Tools act; resources are read.
A resource provider is a named set of resources, mounted on the adapter at any time:
import { MCP_ADAPTER, type McpResourceProvider } from '@forinda/kickjs-mcp'
const invoices: McpResourceProvider = {
name: 'invoices',
resources: [
// A fixed URI.
{ uri: 'config://billing', name: 'billing-config', read: () => 'currency=KES' },
],
templates: [
// A family of URIs (RFC 6570). `read` gets the template's variables.
{
uriTemplate: 'invoices://{id}',
name: 'invoice',
description: 'One invoice, as JSON',
read: async ({ id }, ctx) => {
const res = await ctx.fetch(
new Request(new URL(`/api/v1/invoices/${id}`, ctx.origin), { headers: ctx.headers }),
)
return res.json()
},
},
],
}
container.resolve(MCP_ADAPTER).registerResourceProvider(invoices)readreturns the contents. A string is sent as text (text/plainunless you setmimeType). AUint8Arrayis sent as binary. Anything else is sent as JSON text (application/json). An object shaped like MCP read contents ({ contents: [...] }) is sent as is.ctxworks as it does in a custom tool:principal,origin,headers,signal, andfetchinto your own routes. Reading through a route keeps its guards and validation. Passctx.headersto keep the caller's credentials.- A template's
list(ctx)(optional) names concrete resources, so they appear inresources/listtoo. Without it, clients see only the template and fill in the variables. - URIs must be unique. Mounting a URI or template that another provider already holds throws. Registering a provider under the same name replaces it.
- Connected clients are told when the list changes. 2025 sessions get
resources/list_changed; 2026-07-28 clients get it on theirsubscriptions/listenstream.
Who sees what
resourceFilter decides which resources a caller sees, like toolFilter does for tools. It applies to resources/list (including each URI a template's list returns), resources/templates/list and resources/read, and a hidden resource reads as not found:
McpAdapter({
name: 'billing',
resourceFilter: (resource, call) =>
resource.provider !== 'ledger' || call.principal?.scopes?.includes('ledger:read') === true,
})resource is an McpResourceSummary: kind ('resource' or 'template'), name, uri or uriTemplate, scopes, provider.
Scopes
A resource or template can require scopes, as a tool can. A caller whose token lacks them gets 403 with an insufficient_scope challenge naming them, so an OAuth client can ask for more access (Authentication):
{ uri: 'ledger://trial-balance', name: 'trial-balance', scopes: ['ledger:read'], read: () => trialBalance() }Not covered
Prompts and resource subscriptions (resources/subscribe, per-URI update notifications) aren't supported yet.