MCP TypeScript Template ð
ããŒã«ã«MCP TypeScript Template ð ã«ã€ããŠ
MCP TypeScript Templateã¯ãAPIãããŒã¿ã»ãããã¯ãŒã¯ãããŒãMCPã«æ¥ç¶ãããµãŒããŒéçºè åãã®TypeScriptãã¬ãŒã ã¯ãŒã¯ã§ããåä»ãå®çŸ©ãå®è¡åºç€ã«å ããæ§ç¯ã»ãã¹ãã»ä¿å®ã«äœ¿ããšãŒãžã§ã³ãã¹ãã«ãåããŠããŸãã
äž»ãªæ©èœ
- 01ããŒã«ããªãœãŒã¹ãããã³ãããMCP Appsã®åä»ããã«ããŒ
- 02JWTïŒOAuthã¹ã³ãŒããšããã³ãåäœã®ã¹ãã¬ãŒãž
- 03stdioãHTTPãCloudflare Workersã«å¯Ÿå¿
- 04å®çŸ©ãã§ãã¯ããã¹ãããã°ãä»»æã®OpenTelemetry
掻çšã·ãŒã³
- 01APIãããŒã¿ã»ãããMCPã¯ã©ã€ã¢ã³ãã«å ¬é
- 02ã¢ã¯ã»ã¹å¶åŸ¡ãšç¶æ ä¿åãåãããµãŒããŒã®æ§ç¯
- 03éçºæã®å®çŸ©ãšããŒã«åäœã®æ€èšŒ
MCP 察å¿
MCPãµãŒããŒãäœæããããã®ãã¬ãŒã ã¯ãŒã¯ãšå®è¡åºç€ãæäŸããŸããäœæãããµãŒããŒã¯stdioãŸãã¯HTTPçµç±ã§ãããŒã«ããªãœãŒã¹ãããã³ãããMCP AppsãMCPã¯ã©ã€ã¢ã³ãã«å ¬éããŸãã
èšå®
ããŒã«ã«ãµãŒããŒã®èšå®ã¯ã©ã®ã¯ã©ã€ã¢ã³ãã§ãå ±éã§ããã³ããŒããŠã¯ã©ã€ã¢ã³ãã® MCP èšå®ãã¡ã€ã«ã«è²Œãä»ããŠãã ããã
ãã®ãµãŒããŒã®ããã±ãŒãžããªã¢ãŒããšã³ããã€ã³ãã¯ãŸã ååŸã§ããŠããŸããããããžã§ã¯ãã® README ã®æé ã«åŸã£ãŠãã ããã
ããŒã«
ããŒã«ã«ãµãŒããŒã®ããŒã«ã¯ãã·ã³äžã§èµ·åããŠåããŠååŸã§ããŸããã¡ã³ãããŒã¯ãŸã ããŒã«äžèЧãæäŸããŠããŸããã
詳现
@cyanheads/mcp-ts-core
MCP ãµãŒããŒãæ§ç¯ããããã®ããšãŒãžã§ã³ããã€ãã£ã㪠TypeScript ãã¬ãŒã ã¯ãŒã¯ã
ãµãŒããŒã®ã©ã³ã¿ã€ã ã€ã³ãã©ã¹ãã©ã¯ãã£ãšãæ§ç¯ããã¹ãããªãªãŒã¹ã«åœ¹ç«ã€ãšãŒãžã§ã³ãã¹ãã«ãæäŸããŸãã
説æã§ãããã®ãªãäœã§ã AI ããŒã«ã«
APIãããŒã¿ã»ãããã¯ãŒã¯ãããŒã Model Context ProtocolïŒMCPïŒçµç±ã§ AI ãšãŒãžã§ã³ãã«æ¥ç¶ã§ããŸãããããžã§ã¯ãã«ã¯ãã¡ã€ã³ã³ãŒããé
眮ãã@cyanheads/mcp-ts-core ãèªèšŒãã¹ãã¬ãŒãžããã®ã³ã°ããã©ã³ã¹ããŒããæ¯ããŸãã
ãšãŒãžã§ã³ããã€ãã£ãã ãã¹ãŠã®ã¹ãã£ãã©ãŒã«ãã«ã¯ããã¬ãŒã ã¯ãŒã¯ã®ãªãã¡ã¬ã³ã¹ãšäžé£ã®ãšãŒãžã§ã³ãã¹ãã«ãå«ãŸããŸãããããã¯ãããŒã«ã®èšèšããã¹ãã®äœæãã»ãã¥ãªãã£ã®ã¬ãã¥ãŒããªãªãŒã¹ã®å®æœã«åœ¹ç«ã€ã¯ãŒã¯ãããŒã§ãããµãŒããŒã®åäœã¯ããªããæ±ºãããšãŒãžã§ã³ãã¯ã¹ãã«ã«åŸã£ãŠæ§ç¯ããŸãã
ãã¬ãŒã ã¯ãŒã¯ã¯äŸåé¢ä¿ã®ãŸãŸã§ãã ã€ã³ãã©ã¹ãã©ã¯ãã£ã®ä¿®æ£ã¯ãããã±ãŒãžã®ã¢ããã°ã¬ãŒãã§åã蟌ããŸããmaintenance ã¹ãã«ãå®è¡ãããšããšãŒãžã§ã³ããã³ã¢ãæŽæ°ããææ°ã®ã¹ãã«ãåæããŠã倿Žå
容ãåæ ããŸãã
ã¯ã€ãã¯ã¹ã¿ãŒã
ãµãŒããŒã¯ BunãNode.js 24+ããŸã㯠Cloudflare Workers ã§å®è¡ã§ããŸãã
bunx @cyanheads/mcp-ts-core init my-mcp-server
cd my-mcp-server
bun install
ã¹ãã£ãã©ãŒã«ãã«ã¯ããœãŒã¹ããªãŒããã«ããšãã¹ãã®èšå®ãCLAUDE.md/AGENTS.mdããšãŒãžã§ã³ãã¹ãã«ãClaude Code ãš Codex çšã®ãã©ã°ã€ã³ã¡ã¿ããŒã¿ãå«ãŸããŠããŸããClaude CodeãCodexããŸãã¯å¥ã®ãšãŒãžã§ã³ãã§éããäœããããã®ã説æããŠãã ããã
ããŒã ã®åšåº« API çšã® MCP ãµãŒããŒãæ§ç¯ããŠãã ãããååã®æ€çŽ¢ãå庫ããšã®åšåº«ç¢ºèªãåšåº«ç§»åã®èª¿æ»ã調æŽãšç§»åã®èšé²ãå¿ èŠã§ããå§ããŸãããã
ãã§ã« TypeScript ãããžã§ã¯ããããå Žåã¯ãbun add @cyanheads/mcp-ts-core ãå®è¡ããcreateApp() ã§å®çŸ©ãç»é²ããŠãã ããã
ããŒã«ã¯ã¹ããŒããšé¢æ°ã§æ§æãããŸã
ããã¯ã3 é
ç®ã®ã«ã¿ãã°ãæ€çŽ¢ããå®å
šãªãµãŒããŒã§ãã詊ãã«ã¯ãã¹ãã£ãã©ãŒã«ãã® src/index.ts ãæ¬¡ã®å
容ã«çœ®ãæããŠãã ããã
import { createApp, tool, z } from '@cyanheads/mcp-ts-core';
const catalog = ['Notebook', 'Mechanical pencil', 'Desk lamp'];
const search = tool('catalog_search', {
description: 'Search catalog item names. An empty query lists all items.',
annotations: { readOnlyHint: true },
input: z.object({
query: z.string().describe('Text to find in an item name'),
}),
output: z.object({
items: z.array(z.string()).describe('Matching item names'),
}),
async handler({ query }) {
return {
items: catalog.filter((name) =>
name.toLowerCase().includes(query.toLowerCase()),
),
};
},
});
await createApp({ name: 'catalog-mcp-server', title: 'catalog-mcp-server', tools: [search] });
ãã«ãã㊠HTTP çµç±ã§å®è¡ããŸãã
bun run rebuild
bun run start:http
MCP ã¯ã©ã€ã¢ã³ãã http://127.0.0.1:3010/mcpïŒStreamable HTTPïŒã«æ¥ç¶ããããã¯ã©ã€ã¢ã³ããã bun /absolute/path/to/dist/index.js ã䜿ã£ãŠ stdio çµç±ã§èµ·åããŠãã ããã
å«ãŸããæ©èœ
| å¿ èŠãªããš | ãã¬ãŒã ã¯ãŒã¯ãæäŸãããã® |
|---|---|
| ã¢ã·ã¹ã¿ã³ãã«åœ¹ç«ã€æ©èœãæäŸãã | ããŒã«ããªãœãŒã¹ãããã³ãããã€ã³ã¿ã©ã¯ãã£ã㪠MCP Apps çšã®åä»ããã«ã㌠|
| ãšãŒãžã§ã³ããæ©èœãæ£ãã䜿ããããã«ãã | ãµãŒããŒã®æç€ºãçµæã®æ¡å ã埩æ§ã¬ã€ãã³ã¹ä»ãã®å®£èšåãšã©ãŒ |
| ã¢ã¯ã»ã¹ãå¶åŸ¡ããç¶æ ãä¿æãã | JWT/OAuthãå®çŸ©ããšã®ã¹ã³ãŒããããã¯ãšã³ããåãæ¿ãå¯èœãªããã³ãã¹ã³ãŒãã®ã¹ãã¬ãŒãž |
| ããŒã«ã«ã§å®è¡ãããããµãŒãã¹ããã¹ããã | Bun/Node.js äžã® stdio ãš HTTPãCloudflare Workers çšã®ç¬ç«ãããšã³ããªãŒãã€ã³ã |
| 倱æãææ¡ãããã¹ãæ€åºãã | æ§é åãã°ããªãã·ã§ã³ã® OpenTelemetryãå®çŸ©ã®ãªã³ããã³ã³ãã©ã¯ããã¹ãããã¡ãºãã¹ã |
ãªãã·ã§ã³ã®ã€ã³ãã°ã¬ãŒã·ã§ã³ïŒDuckDBãSupabaseãOpenTelemetry SDKïŒã¯ãã¢äŸåé¢ä¿ã§ããå¿ èŠã«å¿ããŠã€ã³ã¹ããŒã«ããŠãã ããã
ãšãŒãžã§ã³ãã«åœ¹ç«ã€çµæãè¿ã
ãšãŒãžã§ã³ããåãåãå
容ã¯ã2 ã€ã®å®£èšæžã¿ã³ã³ãã©ã¯ãã«ãã£ãŠæ±ºãŸããŸããenrichment ã«ã¯æåæã®ã³ã³ããã¹ãïŒåèšæ°ãè§£ææžã¿ã®ã¯ãšãªãçµæã空ã®å Žåã®éç¥ïŒãå«ãŸããctx.enrich() ã§èšå®ããŸããerrors ã«ã¯æ³å®ãããåãšã©ãŒãšãã®åŸ©æ§ã¬ã€ãã³ã¹ãèšèŒããããã³ãã©ãŒã¯åä»ãã® ctx.fail() ã§ãšã©ãŒãã¹ããŒããŸãã
runSearch(query, limit) ã¯æ€çŽ¢ããã¯ãšã³ãã®ä»£ãããšãªããã®ã§ãã{ items, total, parsed } ãè¿ãããã€ã³ããã¯ã¹ã忢ããŠããå Žå㯠null ãè¿ããŸãã
import { createApp, tool, z } from '@cyanheads/mcp-ts-core';
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
const search = tool('search', {
description: 'Search the catalog and return ranked matches.',
annotations: { readOnlyHint: true },
input: z.object({
query: z.string().describe('Search terms'),
limit: z.number().int().min(1).default(10).describe('Max results'),
}),
output: z.object({
items: z.array(z.string()).describe('Matching item names, best first'),
}),
enrichment: {
effectiveQuery: z.string().describe('Query as the server parsed it'),
totalCount: z.number().describe('Total matches before the limit'),
notice: z.string().optional().describe('Guidance when nothing matched'),
},
errors: [
{
reason: 'index_unavailable',
code: JsonRpcErrorCode.ServiceUnavailable,
when: 'The upstream search index is unreachable.',
retryable: true,
recovery: 'Retry in a few seconds â the index may be briefly unavailable.',
},
],
handler: async (input, ctx) => {
const res = await runSearch(input.query, input.limit);
if (!res) throw ctx.fail('index_unavailable');
ctx.enrich({ effectiveQuery: res.parsed, totalCount: res.total });
if (res.items.length === 0) {
ctx.enrich({ notice: `No matches for "${input.query}". Try broader terms.` });
}
return { items: res.items }; // enrichment never rides in the domain return
},
});
await createApp({ tools: [search] });
ã©ã¡ãã®ã³ã³ãã©ã¯ãã tools/list ã§å
¬éããããããã¯ã©ã€ã¢ã³ãã¯åŒã³åºãåã«å
容ã確èªã§ããŸãããŸããå®çŸ©ãªã³ã¿ãŒã¯ãã³ãã©ãŒãã³ã³ãã©ã¯ãã«æºæ ããŠãããããã§ãã¯ããŸããå®£èšæžã¿ã®çç±ã䌎ããšã©ãŒã¯ããã®é
ç®ã®åŸ©æ§ãã³ããšãšãã«ã¯ã©ã€ã¢ã³ãã«å±ããŸãããŸãããã¹ãŠã®ããŒã«ãšã©ãŒã«ã¯ããµãŒããŒãã°ã®èšé²ãšå
±éãããªã¯ãšã¹ã ID ãä»äžãããŸãã
ã¯ã©ã€ã¢ã³ãã®åãµãŒãã§ã¹ã§åãããŒã¿ã衚瀺ãã
MCP ãã¹ãã«ãã£ãŠãããŒã«ã®çµæã®ãã¡ãšãŒãžã§ã³ãã«æž¡ãéšåã¯ç°ãªããŸããstructuredContentïŒJSONïŒãcontent[]ïŒããã¹ãïŒããŸãã¯ãã®äž¡æ¹ã§ãããã¬ãŒã ã¯ãŒã¯ã¯äž¡æ¹ã«åãããŒã¿ãèšå®ãããããã©ã®ãã¹ãã§ããšãŒãžã§ã³ãã«ã¯åãçµæã衚瀺ãããŸãã
format() ã¯ããã¹ãåŽãã¬ã³ããªã³ã°ããŸããæå®ããªãå Žåãcontent[] ã«ã¯ JSON ãèšå®ãããŸãããã©ãŒãããã®æŽåæ§ããã§ãã¯ãããªã³ã¿ãŒã«ãŒã«ã¯ãã¬ã³ããªã³ã°ãããããã¹ãã«åºåãã£ãŒã«ãã 1 ã€ã§ãæ¬ ããŠãããš lint:mcp ã倱æãããŸãããã¬ãŒã ã¯ãŒã¯ããšã³ãªããã¡ã³ããäž¡æ¹ã®ãµãŒãã§ã¹ã«è¿œå ããããããšã³ãªããã¡ã³ãã« format() ã®ãšã³ããªãŒã¯äžèŠã§ãããã®ãã©ãŒããã¿ãŒã¯ãé
ç®ã Markdown ãªã¹ããšããŠã¬ã³ããªã³ã°ããŸãã
format: (result) => [{
type: 'text',
text: result.items.length > 0
? result.items.map((name) => `- ${name}`).join('\n')
: 'No matching items.',
}],
ãªãœãŒã¹
ãªãœãŒã¹ã¯ URI ã§ããŒã¿ãå
¬éããŸãããã®ãªãœãŒã¹ã¯ãç¬èªã® getItem() ãµãŒãã¹ããããŒã¿ãèªã¿åããŸãã
import { resource, z } from '@cyanheads/mcp-ts-core';
export const itemData = resource('items://{itemId}', {
description: 'Retrieve item data by ID.',
params: z.object({
itemId: z.string().describe('Item ID'),
}),
async handler(params) {
return await getItem(params.itemId);
},
});
ãšã³ããªãŒãã€ã³ãã§ã¯ããã¹ãŠã createApp() ã§ç»é²ããŸãã
await createApp({
name: 'my-mcp-server',
title: 'my-mcp-server', // display name in client UIs
tools: allToolDefinitions,
resources: allResourceDefinitions,
prompts: allPromptDefinitions,
instructions: 'Brief composition hints for the model.', // optional, sent on every `initialize`
skills: 'skills', // optional, serves root skills/ over the SEP-2640 Skills extension
});
Cloudflare Workers ã§ã¯ãå¥ã®ãšã³ããªãŒãã€ã³ãããåãå®çŸ©ã createWorkerHandler() ã«æž¡ããŸãã
ã©ã³ã¿ã€ã ãšçµ±åã®è©³çް
- èªèšŒãšã¹ãã¬ãŒãžïŒ å®çŸ©ã«
auth: ['scope']ãæå®ãããšããã³ãã©ãŒã®å®è¡åã« JWT ãŸã㯠OAuth ã®ããšã§ã¹ã³ãŒãã確èªãããŸããctx.stateã¯ããã³ãããšã«åé¢ãããã¹ãã¬ãŒãžã§ãèšå®ã«å¿ããŠã¡ã¢ãªå ããã¡ã€ã«ã·ã¹ãã ãSupabaseãCloudflare D1/KV/R2 ã䜿çšããŸãã - ã¯ã©ã€ã¢ã³ããšã®ããåãïŒ
ctx.requestInput(...)ãè¿ããšããŠãŒã¶ãŒããã®å ¥åããµã³ãã«çšã®ã¯ã©ã€ã¢ã³ãã®ã¢ãã«ããŸãã¯ã¯ã©ã€ã¢ã³ãã®ã«ãŒããèŠæ±ã§ããŸããåçã¯ctx.inputsã«èšå®ããããã³ãã©ãŒãå床å®è¡ãããŸãã - ãããã³ã«ã®äºææ§ïŒ HTTP ã¯ã2026-07-28 ã®ã¯ã©ã€ã¢ã³ãïŒãªã¯ãšã¹ãããšã®
_metaãšã³ãããŒãïŒãšãã»ãã·ã§ã³ããŒã¹ã®2025幎é ã®ã¯ã©ã€ã¢ã³ãã«å¯Ÿå¿ããŸããSDK ã®äºææ§ã¬ã€ã€ãŒããæ§ããŒãžã§ã³ã®ã¯ã©ã€ã¢ã³ãåãã®å ¥åèŠæ±ãåŠçããŸãã - ãµãŒããŒã®è¡šç€ºæ
å ±ïŒ
instructionsãæå®ãããšããã¹ãŠã®ããŒã«ã®èª¬æã«èšèŒãã代ããã«ãinitializeæã«ã¢ãã«ãžãµãŒããŒå šäœã®ã¬ã€ãã³ã¹ãäžåºŠã ãæäŸã§ããŸããè奿 å ±ãã£ãŒã«ãïŒtitleãwebsiteUrlãdescriptionãiconsïŒã¯ãã¯ã©ã€ã¢ã³ãã®ãµãŒããŒæ å ±ã/.well-known/mcp.jsonã®ãµãŒããŒã«ãŒããHTTP ã©ã³ãã£ã³ã°ããŒãžã«åæ ãããŸããskills: 'skills'ãæå®ãããšãSEP-2640 Skills æ¡åŒµïŒskills/listãskills/getïŒãéããŠããµãŒããŒãã«ãŒãã®skills/ã§å ¬éãã Agent Skills ãæäŸããŸãããããã¯ãã«ãæã«lint:packagingã§æ€èšŒãããŸãã - å®çŸ©ã®ãã§ãã¯ïŒ
lint:mcpã¯ãååãã¹ããŒããã¹ã³ãŒããã¢ãããŒã·ã§ã³ããã©ãŒãããã®æŽåæ§ãJSON Schema ã®ç§»æ€æ§ããã§ãã¯ããŸããå®è¡ãããã®ã¯ãã«ãæã§ãããèµ·åæã§ã¯ãªããããæ°ããã«ãŒã«ã«ãã£ãŠãããã€æžã¿ã®ãµãŒããŒãå£ããããšã¯ãããŸããã - DataCanvasïŒ ãšãŒãžã§ã³ããã¹ããŒãžã³ã°æžã¿ã® API çµæã«å¯Ÿã㊠SQL ãå®è¡ããCSVãParquetãJSON ããšã¯ã¹ããŒãã§ããããªãã·ã§ã³ã® DuckDB ã¯ãŒã¯ã¹ããŒã¹ã§ãããšãŒãžã§ã³ãéã§ãã£ã³ãã¹ããŒã¯ã³ãæž¡ãããšã§ãã¯ãŒã¯ã¹ããŒã¹ãå
±æã§ããŸãã
CANVAS_PROVIDER_TYPE=duckdbãš@duckdb/node-apiã§æå¹ã«ããŸãïŒBun ãŸã㯠Node.js ã®ã¿ïŒãbrapi-mcp-server ã§ã¯ãAPI çµæãããŒã¿ãã¬ãŒã ã«èªã¿èŸŒãã§ã¯ãšãªãå®è¡ããæé ã玹ä»ããŠããŸãã - ãã©ãŒïŒ
/mirrorã¢ãžã¥ãŒã«ã¯ãå€§èŠæš¡ãªäžæµããŒã¿ã»ããã®æ°žç¶çãªããŒã«ã«ã³ããŒãããªãã·ã§ã³ã® FTS5 ã€ã³ããã¯ã¹ä»ãã§çµã¿èŸŒã¿ SQLite ã«ä¿æããŸããããã«ãããããŒã«ã¯åŒã³åºãã®ãã³ã«ã©ã€ã API ãããŒãžã³ã°ãã代ããã«ãããŒã«ã«ã§ã¯ãšãªãå®è¡ã§ããŸããsyncã€ã³ãžã§ã¹ã¿ãŒãšã¹ããŒããèšè¿°ãããšãã¹ãã¬ãŒãžãåéå¯èœãªååããŒããå¢åæŽæ°ã¯ãã¬ãŒã ã¯ãŒã¯ãåŠçããŸããBun ãŸã㯠Node.js ã®ã¿ïŒNode ã§ã¯better-sqlite3ã¯ãªãã·ã§ã³ã®ãã¢äŸåé¢ä¿ã§ãïŒãfaa-aircraft-registry-mcp-server ã¯ããã®æ¹åŒã§ FAA ã®ç»é²æ å ±å šäœãæäŸããŸãã
èšå®ãšãã³ãã©ãŒãã¿ãŒã³ã«ã€ããŠã¯ãã¬ãŒã ã¯ãŒã¯ãªãã¡ã¬ã³ã¹ããPino ãã®ã³ã°ãš OpenTelemetry ã®ãã¬ãŒã¹ããã³ã¡ããªã¯ã¹ã«ã€ããŠã¯å¯èŠ³æž¬æ§ã¬ã€ããåç §ããŠãã ããã
ãµãŒããŒã®æ§æ
my-mcp-server/
src/
index.ts # createApp() entry point
worker.ts # createWorkerHandler() (optional)
config/
server-config.ts # Server-specific env vars
services/
[domain]/ # Domain services (init/accessor pattern)
mcp-server/
tools/definitions/ # Tool definitions (.tool.ts)
resources/definitions/ # Resource definitions (.resource.ts)
prompts/definitions/ # Prompt definitions (.prompt.ts)
package.json
tsconfig.json # extends @cyanheads/mcp-ts-core/tsconfig.base.json
CLAUDE.md / AGENTS.md # Server conventions; points to core's framework reference
ãã¬ãŒã ã¯ãŒã¯ã®ã€ã³ãã©ã¹ãã©ã¯ãã£ã¯ node_modules ã«çœ®ããããœãŒã¹ããªãŒã«ã¯ãµãŒããŒã®å®çŸ©ãèšå®ããã¡ã€ã³ãµãŒãã¹ãå«ãŸããŸãã
èšå®
åºæ¬èšå®ã¯ç°å¢å€æ°ããååŸããZod ã§æ€èšŒããŸãããµãŒããŒåºæã®å€æ°ã«ã¯å°çšã®ã¹ããŒããçšæãããªã¯ãšã¹ãæã« Workers ãç°å¢å€æ°ãæ³šå ¥ã§ãããããé å»¶ããŠè§£æããŸãã
| 倿° | 説æ | ããã©ã«ã |
|---|---|---|
MCP_TRANSPORT_TYPE | stdio ãŸã㯠http | stdio |
MCP_HTTP_PORT | HTTP ãµãŒããŒã®ããŒã | 3010 |
MCP_HTTP_HOST | HTTP ãµãŒããŒã®ãã¹ãå | 127.0.0.1 |
MCP_AUTH_MODE | noneãjwtããŸã㯠oauth | none |
MCP_AUTH_SECRET_KEY | JWT 眲åã·ãŒã¯ã¬ããïŒjwt ã¢ãŒãã§ã¯å¿
é ïŒ | â |
MCP_REQUEST_STATE_KEY | ãªããã€ã³çšã®ããŒïŒ32 ãã€ã以äžã§ããã¹ãŠã®ã€ã³ã¹ã¿ã³ã¹ã§åäžïŒããã®ããŒã䜿ã£ãŠãã³ãã©ãŒãè¿ã requestState ãå°å°ãããã®ãµãŒããŒããååŸããŠããªããã®ãã¯ã©ã€ã¢ã³ããéä¿¡ããå Žåã¯æåŠããŸã | â |
STORAGE_PROVIDER_TYPE | in-memoryãfilesystemãsupabaseãcloudflare-d1/kv/r2 | in-memory |
CANVAS_PROVIDER_TYPE | none ãŸã㯠duckdbïŒãªãã·ã§ã³ã®ãã¢äŸåé¢ä¿ @duckdb/node-apiïŒ | none |
OTEL_ENABLED | OpenTelemetry ãæå¹ã«ããŸã | false |
LOG_TOOL_FAILURE_PAYLOADS | 倱æããããŒã«åŒã³åºãã®åŒæ°ãšçµæããããŒåã«åºã¥ããŠæ©å¯æ å ±ãäŒããŠãã°ã«èšé²ããŸãïŒèªç±åœ¢åŒã®å€ã«å«ãŸããã·ãŒã¯ã¬ããã¯æ€åºã§ããŸããïŒ | false |
LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES | ãã°ã«èšé²ãããã€ããŒãããšã®äžéïŒUTF-8 ãã€ãæ°ïŒ | 16384 |
OPENROUTER_API_KEY | ãªãã·ã§ã³ã® OpenRouter LLM ãããã€ããŒïŒ/servicesïŒã® API ã㌠| â |
èšå®ã®å šãªãã¡ã¬ã³ã¹ã«ã€ããŠã¯ãCLAUDE.md/AGENTS.md ãåç §ããŠãã ããã
API ã®æŠèŠ
ãšã³ããªãŒãã€ã³ã
| 颿° | ç®ç |
|---|---|
createApp(options) | Bun ãŸã㯠Node.js ã®ãµãŒããŒãèµ·åãšã·ã£ããããŠã³ã管çããŸã |
createWorkerHandler(options) | Cloudflare Workers çšãExportedHandler ãè¿ããŸã |
ãã«ããŒ
| ãã«ã㌠| çšé |
|---|---|
tool(name, options) | handler(input, ctx) ãæã€ããŒã«ãå®çŸ©ããŸã |
resource(uriTemplate, options) | handler(params, ctx) ãæã€ãªãœãŒã¹ãå®çŸ©ããŸã |
prompt(name, options) | generate(args) ãæã€ããã³ãããå®çŸ©ããŸã |
appTool(name, options) | _meta.ui ãèªåèšå®ãã MCP Apps ããŒã«ãå®çŸ©ããŸã |
appResource(uriTemplate, options) | èªã¿åãã³ã³ãã³ãã«é©å㪠MIME ã¿ã€ããš _meta.ui ã®ãã©ãŒãªã³ã°ãèšå®ãã MCP Apps HTML ãªãœãŒã¹ãå®çŸ©ããŸã |
ã³ã³ããã¹ã
ããŒã«ãšãªãœãŒã¹ã®ãã³ãã©ãŒã¯ Context ãåãåããŸããctx.enrich ãš ctx.fail ã¯ãå®çŸ©ã§å®£èšãããã³ã³ãã©ã¯ãã«å¯ŸããŠåä»ããããŠããŸãã
| ãããã㣠| å | 説æ |
|---|---|---|
ctx.log | ContextLogger | ãªã¯ãšã¹ãåäœã®ãã¬ãŒïŒrequestIdãtraceIdãtenantId ãèªåçã«é¢é£ä»ããŸãïŒãã¯ã©ã€ã¢ã³ãã«ã notifications/message ãšããŠãã©ãŒãããŸã |
ctx.state | ContextState | ããã³ãåäœã®ããŒãšå€ã®ã¹ãã¬ãŒãž |
ctx.requestInput | (spec) => never | äžæåæ¢ããŠåŒã³åºãå ã«è¿œå ã®å ¥åãæ±ããŸããåçãåãåããšãã³ãã©ãŒãåå®è¡ãããŸã |
ctx.inputs | ContextInputs | ãªã¯ãšã¹ããžã®å¿çãã¯ã©ã€ã¢ã³ãã宣èšããçš®é¡ã«éå®ãããŸã â .accepted()ã.view()ã.state()ã.dropped |
ctx.clientCapabilities | ClientCapabilities | undefined | ãã®ãªã¯ãšã¹ãã«ã€ããŠã¯ã©ã€ã¢ã³ãã宣èšããå 容ã§ããçç¥å¯èœãªã³ã³ããã¹ããæ±ãããã©ãããæ±ºããŸãããåæãæ±ããããã³ãããçç¥ãããã©ããã¯æ±ºããŸãã |
ctx.enrich | Enrich / TypedEnrich<E> | 宣èšãããçµæã³ã³ããã¹ããæ§é ååºåãšããã¹ãã³ã³ãã³ãã«è¿œå ããŸã |
ctx.content | ContentCollect | ç»åïŒé³å£°ãããã¯ã content[] ã«è¿œå ããŸã â content.image(data, mimeType)ãcontent.audio(...)ããŸãã¯çã®ããã㯠|
ctx.fail | (reason, msg?, data?) => McpError | throw ctx.fail(...) çšã®ãšã©ãŒãäœæããŸããerrors ã³ã³ãã©ã¯ãã宣èšãããŠããå Žåã«äœ¿çšã§ããŸã |
ctx.recoveryFor | (reason) => object | 宣èšããã埩æ§ãã³ãã { recovery: { hint } } ã«è§£æ±ºããŸãããã¬ãŒã ã¯ãŒã¯ã¯ããã®çç±ãæã¡ãç¬èªã®ãã³ããå«ãŸãªããšã©ãŒãçºçããå Žåããã§ã«ãã®ãã³ããéä¿¡ããŸã |
ctx.signal | AbortSignal | ãã£ã³ã»ã«ã·ã°ãã« |
ctx.notifyResourceUpdated | Function? | ãªãœãŒã¹ã倿Žãããããšãã賌èªäžã®ã¯ã©ã€ã¢ã³ãã«éç¥ããŸã |
ctx.notifyResourceListChanged | Function? | ãªãœãŒã¹äžèЧã倿Žãããããšãã¯ã©ã€ã¢ã³ãã«éç¥ããŸã |
ctx.notifyPromptListChanged | Function? | ããã³ããäžèЧã倿Žãããããšãã¯ã©ã€ã¢ã³ãã«éç¥ããŸã |
ctx.notifyToolListChanged | Function? | ããŒã«äžèЧã倿Žãããããšãã¯ã©ã€ã¢ã³ãã«éç¥ããŸã |
ctx.requestId | string | ãªã¯ãšã¹ã ID â åŒã³åºãã®ãã°ã¬ã³ãŒããšå
±æããããšã©ãŒã§ã¯ data.requestId ãšããŠè¿ãããŸã |
ctx.tenantId | string? | ããã³ã IDïŒJWT ã® tid ã¯ã¬ãŒã ããŸã㯠stdio ããã³ HTTP+MCP_AUTH_MODE=none ã®å Žå㯠'default'ïŒ |
ctx.auth | AuthContext? | ãªã¯ãšã¹ããèªèšŒãããŠããå Žåã®ããŒã¯ã³ã¯ã¬ãŒã ãšã¹ã³ãŒã |
ctx.sessionId | string? | ã¹ããŒããã«ïŒauto ã»ãã·ã§ã³ã¢ãŒãã§ã® HTTP ã»ãã·ã§ã³ ID â èªå¯äž»äœã§ã¯ãªããã¹ã³ãŒãèšå®çšã®ããŒã§ã |
ctx.uri | URL? | ããŒã¹æžã¿ã®ãªãœãŒã¹ URIããªãœãŒã¹ãã³ãã©ãŒã§ã®ã¿èšå®ãããŸã |
ãµããã¹ã®ãšã¯ã¹ããŒã
import { createApp, tool, resource, prompt } from '@cyanheads/mcp-ts-core';
import { createWorkerHandler } from '@cyanheads/mcp-ts-core/worker';
import { McpError, JsonRpcErrorCode, notFound, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
import { checkScopes } from '@cyanheads/mcp-ts-core/auth';
import { markdown, fetchWithTimeout } from '@cyanheads/mcp-ts-core/utils';
import { OpenRouterProvider, GraphService } from '@cyanheads/mcp-ts-core/services';
import type { DataCanvas, CanvasInstance } from '@cyanheads/mcp-ts-core/canvas';
import { defineMirror, sqliteMirrorStore } from '@cyanheads/mcp-ts-core/mirror';
import { validateDefinitions } from '@cyanheads/mcp-ts-core/linter';
import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
import { mcpTest, toolContractSuite } from '@cyanheads/mcp-ts-core/testing/vitest';
import { fuzzTool, fuzzResource, fuzzPrompt } from '@cyanheads/mcp-ts-core/testing/fuzz';
import { renderAppTool } from '@cyanheads/mcp-ts-core/testing/apps';
ãšã¯ã¹ããŒãã®å šäžèЧã«ã€ããŠã¯ãCLAUDE.md/AGENTS.md ãåç §ããŠãã ããã
äŸ
examples/ ã«ã¯ããããªãã¯ãšã¯ã¹ããŒãã®ã¿ã䜿çšããŠæ§ç¯ããããªãã¡ã¬ã³ã¹ãµãŒããŒãå«ãŸããŠããŸããexamples/index.tsïŒNode/BunïŒãš examples/worker.tsïŒCloudflare WorkersïŒã¯ãåã definitions/index.ts ãã¬ã«ãç»é²ããŸãã
| çš®é¡ | åå | ãã¿ãŒã³ |
|---|---|---|
| ããŒã« | template_echo_message | ctx.fail ã䜿ã£ã errors[] ã³ã³ãã©ã¯ããinputAliasesãå¿ å®åºŠã®é«ã format() |
| ããŒã« | template_cat_fact | fetchWithTimeoutãåä»ãããã not-found ã³ã³ãã©ã¯ãããšã³ãªããã¡ã³ãã®ãšã³ãŒ |
| ããŒã« | template_image_test | ctx.content.image |
| ããŒã« | template_madlibs_elicitation | return ctx.requestInputãseverity ãå«ãå
¥åæåŠã³ã³ãã©ã¯ã |
| ããŒã« | template_data_explorer | appToolïŒappResourceããã¹ãã®ããŒãèšå®ãcacheHint |
| ãªãœãŒã¹ | echo://{message} | ãã³ãã¬ãŒãåããããªãœãŒã¹ |
| ãªãœãŒã¹ | ui://template-data-explorer/app.html | template_data_explorer ãšçµã¿åããã UI ãªãœãŒã¹ |
| ããã³ãã | code_review | completable() åŒæ°ãcode åŒæ° |
ãã¹ã
import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
import { myTool } from '@/mcp-server/tools/definitions/my-tool.tool.js';
const ctx = createMockContext();
const input = myTool.input.parse({ query: 'test' });
const result = await myTool.handler(input, ctx);
createMockContext() ã¯ãèšé²çšã® logãsignalãããã³ã¡ã¢ãªå
ãããã€ããŒäžã®å®éã® StorageService ã«ãã£ãŠããã¯ã¢ããããã state ãæäŸããŸãããã®ãããããŒã®æ€èšŒãTTL ã®æéåããä¿åå€ã® JSON å€æã¯æ¬çªç°å¢ãšåãããã«åäœããŸããããšãã°ãDate 㯠ISO æååãšããŠèªã¿æ»ãããJSON ã§ãšã³ã³ãŒãã§ããªãå€ã¯æåŠãããŸãã{ tenantId } ãæž¡ããªãå Žåãããã³ãã«ã¯ 'default' ã䜿ãããŸããåä»ãã® ctx.fail ã䜿ãã«ã¯ { errors: myTool.errors } ãæž¡ããŸããè€æ°åã®ã©ãŠã³ãããªãããè¡ããã³ãã©ãŒã 2 åç®ããéå§ããã«ã¯ { inputResponses, requestState } ãæž¡ããã¯ã©ã€ã¢ã³ãã宣èšããæ©èœãèšå®ããã«ã¯ { clientCapabilities } ãæž¡ããŸãïŒã·ãŒããããã¬ã¹ãã³ã¹ã¯æ¬çªç°å¢ãšåæ§ã宣èšãããçš®é¡ã«çµã蟌ãŸããŸãïŒã
/testing ã§ã¯ãã»ãã·ã§ã³ã«çŽã¥ãã³ã³ããã¹ãçšã® createMockSession()ãäžæµ HTTP çšã®å³æ Œãªãã§ã€ã¯ã§ãã createFetchMock()ãããã³å®çŸ©ãã¹ããŒãããã³ãã©ãŒããã©ãŒãããããšã©ãŒãšã³ãããŒãã®åãã§ãã¯ã«ããã runToolContract() ããšã¯ã¹ããŒããããŠããŸãã/testing/vitest ã§ã¯ãmcpTest ãã£ã¯ã¹ãã£ïŒctxãsessionãfetchMockãstorageïŒãš toolContractSuite() ã远å ãããŸãã
/testing/fuzz 㯠fast-check ã䜿ã£ãŠãZod ã¹ããŒãããæå¹ãªå
¥åãšæµå¯Ÿçãªãã€ããŒããçæããã¯ã©ãã·ã¥ãã¹ã¿ãã¯ãã¬ãŒã¹ã®æŒããããããã¿ã€ãæ±æããªããããã§ãã¯ããŸãã
import { fuzzTool } from '@cyanheads/mcp-ts-core/testing/fuzz';
const report = await fuzzTool(myTool, { numRuns: 100 });
expect(report.crashes).toHaveLength(0);
expect(report.leaks).toHaveLength(0);
expect(report.prototypePollution).toBe(false);
ã«ã¹ã¿ã ã®ããããã£ããŒã¹ãã¹ãçšã«ãfuzzResourceãfuzzPromptãzodToArbitraryãADVERSARIAL_STRINGS ããšã¯ã¹ããŒããããŠããŸãã
/testing/apps ã¯ãããã¬ã¹ã® MCP Apps ãã¹ãã§ããrenderAppTool 㯠MCP Apps ã¯ã©ã€ã¢ã³ããšããŠãµãŒããŒã«æ¥ç¶ããã¢ããªããŒã«ãåŒã³åºããMCP Apps 仿§ã§èŠå®ããããµã³ãããã¯ã¹ãš CSP ã®ããšã§ ui:// ãã¥ãŒã chrome-headless-shell ã«èªã¿èŸŒã¿ããã¥ãŒã«å¯ŸããŠã¹ã¯ãªããåãããæé ãå®è¡ããã¬ããŒããè¿ããŸãã
import { renderAppTool } from '@cyanheads/mcp-ts-core/testing/apps';
const run = await renderAppTool({
server: { command: 'bun', args: ['run', 'dist/index.js'] }, // or { url }
tool: 'my_app_tool',
arguments: { query: 'probe' },
steps: [{ click: '#action-btn' }, { screenshot: 'after-click' }],
});
expect(run.initialized).toBe(true);
expect(run.errors).toHaveLength(0);
expect(run.cspViolations).toHaveLength(0);
ã¬ããŒãã«ã¯ããã¥ãŒãšãã¹ãéã®ãã¹ãŠã®ã¡ãã»ãŒãžããã¥ãŒã«æç»ãããããã¹ããã¹ã¯ãªãŒã³ã·ã§ããã®ãã¹ãå«ãŸããŸããã³ãã³ãã©ã€ã³ããåãå®è¡ãè¡ããreport.json ãšã¹ã¯ãªãŒã³ã·ã§ããã --out ã®äžã«æžãåºãæ¹æ³ã¯æ¬¡ã®ãšããã§ãã
bunx @cyanheads/mcp-ts-core app-render --tool my_app_tool --args '{"query":"probe"}' \
--click '#action-btn' --out ./app-run -- bun run dist/index.js
ãªãã·ã§ã³ã®ãã¢äŸåé¢ä¿ã§ãã @modelcontextprotocol/client ãš @modelcontextprotocol/ext-appsãããã³ chrome-headless-shell ã®ãã«ããå¿
èŠã§ããPuppeteer ã®ãã£ãã·ã¥ã«ããææ°ã®ãã«ãïŒnpx @puppeteer/browsers install chrome-headless-shell@stable --path ~/.cache/puppeteerïŒã䜿ãããbrowserPathã--browserããŸã㯠MCP_APPS_BROWSER_PATH ã§å®è¡å¯èœãã¡ã€ã«ã®ãã¹ãæç€ºããŠãã ãããã€ã³ã¹ããŒã«æžã¿ã® ChromeãEdgeãBraveãChromium ã¯æ€çŽ¢ãããŸããã
ããã¥ã¡ã³ã
- CLAUDE.md/AGENTS.mdïŒãšã¯ã¹ããŒãããã¿ãŒã³ã
Contextããšã©ãŒã³ãŒããèªèšŒãèšå®ããã¹ããç¶²çŸ ãããã¬ãŒã ã¯ãŒã¯ãªãã¡ã¬ã³ã¹ã§ããnpm ããã±ãŒãžã«å梱ãããŠããããããšãŒãžã§ã³ãã¯initã®åŸã«node_modulesãããããèªã¿èŸŒã¿ãŸãã - docs/telemetry/ïŒãã¬ãŒã ã¯ãŒã¯ãåºåãããã¹ãŠã®ã¹ãã³ãã¡ããªã¯ã¹ã屿§ïŒobservability.mdïŒã«å ããGrafana ããã·ã¥ããŒãã®äŸãšãDatadogãNew RelicãHoneycomb åãã®ã¯ãšãªã¬ã·ããæ²èŒããŠããŸãïŒdashboards.mdïŒã
- CHANGELOG.mdïŒããŒãžã§ã³å±¥æŽã§ãã
changelog/å ã«ãªãªãŒã¹ããšã« 1 ã€ã®ãã¡ã€ã«ã眮ããŠããŸããåãã¡ã€ã«ã«ã¯æŠèŠãç§»è¡ã«é¢ããæ³šæäºé ãã³ããããš Issue ãžã®ãªã³ã¯ãå«ãŸããŸããåŸç¶ã®å€æŽãå¿ èŠãªãªãªãŒã¹ã«ã¯ãmaintenanceã¹ãã«ã察å¿ããããã®agent-notesãä»ããŠããŸãã
éçº
bun run rebuild # clean + build (scripts/clean.ts + scripts/build.ts)
bun run devcheck # full gate: lint/format, typecheck, MCP defs, packaging, framework antipatterns, docs/skills/changelog sync, audit, outdated, tracked-secrets and to-do marker scans
bun run lint:mcp # validate MCP definitions against spec
bun run test:all # rebuild + coverage + Node.js + Workers + integration
bun run test:package # pack the tarball and consume it as an external project would
bun run test:soak # sustained HTTP load; fails when the server heap grows per request
ã©ã€ã»ã³ã¹
Apache 2.0 â LICENSE ãåç §ããŠãã ããã
ã³ã¡ã³ã
MCP ã¯åããŠã§ããïŒ
ãããã³ã«ã®æŠèŠããæåã®ãµãŒããŒã®å°å ¥ãŸã§ãããããçåã«ãŸãšããŠçããŸãã
AI ã¢ããªã±ãŒã·ã§ã³ãå€éšã®ããŒã«ãããŒã¿ãœãŒã¹ã«çµ±äžçãªæ¹æ³ã§æ¥ç¶ããããã®ãªãŒãã³ãªãããã³ã«ã§ããã¯ã©ã€ã¢ã³ãã¯ã¢ãã«ãšã®å¯Ÿè©±ãæ ããMCP ãµãŒããŒã¯ããŒã«ã»ãªãœãŒã¹ã»ããã³ãããã³ãã¬ãŒããæäŸããŸããæšæºã¡ãã»ãŒãžã§ããåãããããã1 ã€ã®ãµãŒããŒãè€æ°ã®ã¯ã©ã€ã¢ã³ãã§åå©çšã§ããŸãã