@ts-pf/mcp 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +191 -2
  3. package/dist/all.d.ts +63 -0
  4. package/dist/all.d.ts.map +1 -0
  5. package/dist/all.js +55 -0
  6. package/dist/all.js.map +1 -0
  7. package/dist/audit.d.ts +21 -0
  8. package/dist/audit.d.ts.map +1 -0
  9. package/dist/audit.js +85 -0
  10. package/dist/audit.js.map +1 -0
  11. package/dist/bind.d.ts +42 -0
  12. package/dist/bind.d.ts.map +1 -0
  13. package/dist/bind.js +46 -0
  14. package/dist/bind.js.map +1 -0
  15. package/dist/bound-tool.d.ts +18 -0
  16. package/dist/bound-tool.d.ts.map +1 -0
  17. package/dist/bound-tool.js +31 -0
  18. package/dist/bound-tool.js.map +1 -0
  19. package/dist/call.d.ts +12 -0
  20. package/dist/call.d.ts.map +1 -0
  21. package/dist/call.js +104 -0
  22. package/dist/call.js.map +1 -0
  23. package/dist/composite.d.ts +17 -0
  24. package/dist/composite.d.ts.map +1 -0
  25. package/dist/composite.js +14 -0
  26. package/dist/composite.js.map +1 -0
  27. package/dist/coverage.d.ts +11 -0
  28. package/dist/coverage.d.ts.map +1 -0
  29. package/dist/coverage.js +35 -0
  30. package/dist/coverage.js.map +1 -0
  31. package/dist/error-text.d.ts +25 -0
  32. package/dist/error-text.d.ts.map +1 -0
  33. package/dist/error-text.js +56 -0
  34. package/dist/error-text.js.map +1 -0
  35. package/dist/executor.d.ts +19 -0
  36. package/dist/executor.d.ts.map +1 -0
  37. package/dist/executor.js +49 -0
  38. package/dist/executor.js.map +1 -0
  39. package/dist/host.d.ts +13 -0
  40. package/dist/host.d.ts.map +1 -0
  41. package/dist/host.js +226 -0
  42. package/dist/host.js.map +1 -0
  43. package/dist/index.d.ts +11 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +5 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/infer.d.ts +17 -0
  48. package/dist/infer.d.ts.map +1 -0
  49. package/dist/infer.js +2 -0
  50. package/dist/infer.js.map +1 -0
  51. package/dist/meta.d.ts +20 -0
  52. package/dist/meta.d.ts.map +1 -0
  53. package/dist/meta.js +10 -0
  54. package/dist/meta.js.map +1 -0
  55. package/dist/names.d.ts +22 -0
  56. package/dist/names.d.ts.map +1 -0
  57. package/dist/names.js +14 -0
  58. package/dist/names.js.map +1 -0
  59. package/dist/node.d.ts +41 -0
  60. package/dist/node.d.ts.map +1 -0
  61. package/dist/node.js +47 -0
  62. package/dist/node.js.map +1 -0
  63. package/dist/result.d.ts +7 -0
  64. package/dist/result.d.ts.map +1 -0
  65. package/dist/result.js +31 -0
  66. package/dist/result.js.map +1 -0
  67. package/dist/schema.d.ts +23 -0
  68. package/dist/schema.d.ts.map +1 -0
  69. package/dist/schema.js +172 -0
  70. package/dist/schema.js.map +1 -0
  71. package/dist/sdk.d.ts +31 -0
  72. package/dist/sdk.d.ts.map +1 -0
  73. package/dist/sdk.js +87 -0
  74. package/dist/sdk.js.map +1 -0
  75. package/dist/search.d.ts +13 -0
  76. package/dist/search.d.ts.map +1 -0
  77. package/dist/search.js +52 -0
  78. package/dist/search.js.map +1 -0
  79. package/dist/tool-meta.d.ts +14 -0
  80. package/dist/tool-meta.d.ts.map +1 -0
  81. package/dist/tool-meta.js +44 -0
  82. package/dist/tool-meta.js.map +1 -0
  83. package/dist/tool.d.ts +102 -0
  84. package/dist/tool.d.ts.map +1 -0
  85. package/dist/tool.js +30 -0
  86. package/dist/tool.js.map +1 -0
  87. package/dist/toolkit.d.ts +36 -0
  88. package/dist/toolkit.d.ts.map +1 -0
  89. package/dist/toolkit.js +22 -0
  90. package/dist/toolkit.js.map +1 -0
  91. package/dist/toolset.d.ts +89 -0
  92. package/dist/toolset.d.ts.map +1 -0
  93. package/dist/toolset.js +67 -0
  94. package/dist/toolset.js.map +1 -0
  95. package/dist/types.d.ts +57 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +2 -0
  98. package/dist/types.js.map +1 -0
  99. package/package.json +80 -4
  100. package/skills/ts-pf-mcp/SKILL.md +134 -0
@@ -0,0 +1,36 @@
1
+ import type { AnySchema, InferSchemaInput } from '@ts-pf/contract';
2
+ import { type CompositeTool, type CompositeToolDef } from './composite.js';
3
+ import { type CoverageCheck } from './coverage.js';
4
+ import type { DollarGuard, Split } from './names.js';
5
+ import { type AnyNode, type ToolkitNode } from './node.js';
6
+ import type { AnyTool } from './tool.js';
7
+ import { type Host, type HostsCheck, type HostsNames, type HostsPaths, type NameCheck, type PathsOf, type Toolset, type ToolsInput } from './toolset.js';
8
+ export type Toolkit<T, Ctx> = ToolkitNode<T, T, Ctx, ''> & {
9
+ /** Type the context every mapper, inject, and binding sees. Same toolkit at runtime. */
10
+ $context<C>(): Toolkit<T, C>;
11
+ /** Group tools behind `get_<key>[_<bucket>]_capabilities` / `call_<key>[_<bucket>]_capability`. */
12
+ $host<const Tools extends ToolsInput<Ctx>, const Sp extends Split = 'effect'>(def: {
13
+ /** What the model reads before searching. */
14
+ description: string;
15
+ tools: Tools & NameCheck<Tools, never>;
16
+ /** `'effect'` (default): `query` (read) and `operations` (write + destructive). */
17
+ split?: Sp;
18
+ }): Host<Ctx, PathsOf<Tools>, NoInfer<Sp>>;
19
+ /** One MCP server's tools: flat tools (the object key is the name) plus hosts. */
20
+ $toolset<const Tools extends ToolsInput<Ctx> = {}, const H extends {
21
+ [key: string]: Host<Ctx, string, Split>;
22
+ } = {}>(def: {
23
+ tools?: Tools & NameCheck<Tools, HostsNames<H>>;
24
+ hosts?: H & HostsCheck<H>;
25
+ }): Toolset<T, Ctx, PathsOf<Tools> | HostsPaths<H>>;
26
+ /** A composite N→1 tool over `call`. */
27
+ $tool<Sch extends AnySchema>(def: CompositeToolDef<T, Ctx, Sch>): CompositeTool<Ctx, InferSchemaInput<Sch>>;
28
+ /** Compile error (and runtime throw) naming every procedure no toolset exposes and no `exclude` names. */
29
+ $coverage<const S extends readonly Toolset<T, never, string>[], const X extends readonly (AnyTool | AnyNode)[] = []>(options: {
30
+ toolsets: S;
31
+ exclude?: X;
32
+ } & CoverageCheck<T, S, X[number]>): void;
33
+ };
34
+ /** Typed tool builders mirroring `contract`. Root members start with `$`, so no contract key can collide. */
35
+ export declare function createToolkit<T>(contract: T & DollarGuard<T>): Toolkit<T, object>;
36
+ //# sourceMappingURL=toolkit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolkit.d.ts","sourceRoot":"","sources":["../src/toolkit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { createCompositeTool, } from './composite.js';
2
+ import { uncoveredPaths } from './coverage.js';
3
+ import { createNode } from './node.js';
4
+ import { createHost, createToolset, } from './toolset.js';
5
+ /** Typed tool builders mirroring `contract`. Root members start with `$`, so no contract key can collide. */
6
+ export function createToolkit(contract) {
7
+ const members = {
8
+ $context: () => toolkit,
9
+ $host: createHost,
10
+ $toolset: (def) => createToolset(contract, def),
11
+ $tool: createCompositeTool,
12
+ $coverage: (options) => {
13
+ const missing = uncoveredPaths(contract, options.toolsets, options.exclude ?? []);
14
+ if (missing.length > 0) {
15
+ throw new Error(`$coverage(): not exposed by any toolset nor excluded: ${missing.join(', ')}`);
16
+ }
17
+ },
18
+ };
19
+ const toolkit = createNode(contract, [], members);
20
+ return toolkit;
21
+ }
22
+ //# sourceMappingURL=toolkit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolkit.js","sourceRoot":"","sources":["../src/toolkit.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,mBAAmB,GACpB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAsB,cAAc,EAAE,MAAM,eAAe,CAAA;AAElE,OAAO,EAAgB,UAAU,EAAoB,MAAM,WAAW,CAAA;AAEtE,OAAO,EAEL,UAAU,EACV,aAAa,GASd,MAAM,cAAc,CAAA;AAqCrB,6GAA6G;AAC7G,MAAM,UAAU,aAAa,CAC3B,QAA4B;IAE5B,MAAM,OAAO,GAAG;QACd,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO;QACvB,KAAK,EAAE,UAAU;QACjB,QAAQ,EAAE,CAAC,GAAwC,EAAE,EAAE,CACrD,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC;QAC9B,KAAK,EAAE,mBAAmB;QAC1B,SAAS,EAAE,CAAC,OAGX,EAAE,EAAE;YACH,MAAM,OAAO,GAAG,cAAc,CAC5B,QAAQ,EACR,OAAO,CAAC,QAAQ,EAChB,OAAO,CAAC,OAAO,IAAI,EAAE,CACtB,CAAA;YACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CACb,yDAAyD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9E,CAAA;YACH,CAAC;QACH,CAAC;KACF,CAAA;IACD,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,CAAuB,CAAA;IACvE,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -0,0 +1,89 @@
1
+ import type { KeysOfUnion } from './infer.js';
2
+ import { type HostToolNames, type Split } from './names.js';
3
+ import { type AnyTool, type ToolSpec } from './tool.js';
4
+ export type ToolRecord<Ctx = never> = {
5
+ readonly [name: string]: AnyTool<Ctx>;
6
+ };
7
+ export type ToolsInput<Ctx = never> = ToolRecord<Ctx> | readonly ToolRecord<Ctx>[];
8
+ type Dups<L> = L extends readonly [infer H, ...infer R] ? (keyof H & KeysOfUnion<R[number]>) | Dups<R> : never;
9
+ type RecordsOf<Tools> = Tools extends readonly unknown[] ? Tools[number] : Tools;
10
+ export type PathsOf<Tools> = RecordsOf<Tools> extends infer R ? R extends unknown ? R[keyof R] extends infer V ? V extends {
11
+ readonly '~tool': {
12
+ path: infer P;
13
+ };
14
+ } ? P : never : never : never : never;
15
+ type NameMsg<K, Taken> = K extends Taken ? `Tool name "${K & string}" collides with a generated host tool name` : `Duplicate tool name "${K & string}"`;
16
+ type NameProblems<Tools, Taken extends string> = Tools extends readonly unknown[] ? Dups<Tools> | (KeysOfUnion<Tools[number]> & Taken) : keyof Tools & Taken;
17
+ /**
18
+ * Marks each offending key in each record with a message literal (the
19
+ * `RouterImplCheck` trick). `unknown` when nothing collides, so `Tools &
20
+ * NameCheck` stays `Tools`: intersecting a tuple with a mapped tuple makes the
21
+ * checker compare array methods structurally, which costs ~1.6M instantiations
22
+ * on a 400-procedure `all()`.
23
+ */
24
+ export type NameCheck<Tools, Taken extends string> = [
25
+ NameProblems<Tools, Taken>
26
+ ] extends [never] ? unknown : NameMarks<Tools, Taken>;
27
+ type NameMarks<Tools, Taken extends string> = Tools extends readonly unknown[] ? {
28
+ [I in keyof Tools]: {
29
+ [K in keyof Tools[I] & (Dups<Tools> | Taken)]: NameMsg<K, Taken>;
30
+ };
31
+ } : {
32
+ [K in keyof Tools & Taken]: NameMsg<K, Taken>;
33
+ };
34
+ /** Runtime description of a tool host; what `'~host'` holds. */
35
+ export type HostSpec = {
36
+ readonly description: string;
37
+ readonly tools: Readonly<Record<string, ToolSpec>>;
38
+ readonly split: Split;
39
+ };
40
+ export interface Host<Ctx, Paths extends string, Sp extends Split> {
41
+ readonly '~host': {
42
+ ctx: (c: Ctx) => void;
43
+ paths: Paths;
44
+ split: Sp;
45
+ };
46
+ }
47
+ /** `never` context: any host is assignable. */
48
+ export type AnyHost<Sp extends Split = Split> = Host<never, string, Sp>;
49
+ export type HostsNames<H> = {
50
+ [K in keyof H & string]: H[K] extends AnyHost<infer Sp> ? HostToolNames<K, Sp> : never;
51
+ }[keyof H & string];
52
+ export type HostsPaths<H> = {
53
+ [K in keyof H]: H[K] extends Host<never, infer P, Split> ? P : never;
54
+ }[keyof H];
55
+ export type HostsCheck<H> = {
56
+ [K in keyof H & string]: H[K] extends AnyHost<infer Sp> ? [HostToolNames<K, Sp> & HostsNames<Omit<H, K>>] extends [never] ? unknown : {
57
+ '~error': `Host "${K}" generates "${HostToolNames<K, Sp> & HostsNames<Omit<H, K>>}", which another host also generates`;
58
+ } : unknown;
59
+ };
60
+ /** Runtime description of a toolset; what `'~toolset'` holds. */
61
+ export type ToolsetSpec = {
62
+ readonly contract: unknown;
63
+ readonly tools: Readonly<Record<string, ToolSpec>>;
64
+ readonly hosts: Readonly<Record<string, HostSpec>>;
65
+ };
66
+ export interface Toolset<T, Ctx, Paths extends string> {
67
+ readonly '~toolset': {
68
+ contract: T;
69
+ ctx: (c: Ctx) => void;
70
+ paths: Paths;
71
+ };
72
+ }
73
+ /** `unknown` contract and `never` context: any toolset is assignable. */
74
+ export type AnyToolset = Toolset<unknown, never, string>;
75
+ export declare function hostSpec(host: AnyHost): HostSpec;
76
+ export declare function toolsetSpec(toolset: AnyToolset): ToolsetSpec;
77
+ /** The MCP tool names a host lists (empty buckets are not listed, but their names stay reserved). */
78
+ export declare function hostToolNames(key: string, split: Split): string[];
79
+ export declare function createHost(def: {
80
+ description: string;
81
+ tools: unknown;
82
+ split?: Split;
83
+ }): unknown;
84
+ export declare function createToolset(contract: unknown, def: {
85
+ tools?: unknown;
86
+ hosts?: Record<string, unknown>;
87
+ }): unknown;
88
+ export {};
89
+ //# sourceMappingURL=toolset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolset.d.ts","sourceRoot":"","sources":["../src/toolset.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import { discoverToolName, dispatchToolName, hostScope, } from './names.js';
2
+ import { isTool, toolSpec } from './tool.js';
3
+ export function hostSpec(host) {
4
+ return host['~host'];
5
+ }
6
+ export function toolsetSpec(toolset) {
7
+ return toolset['~toolset'];
8
+ }
9
+ /** Flatten one record or a list of records; the object key is the tool name. */
10
+ function collectTools(input, where) {
11
+ const records = (Array.isArray(input) ? input : [input ?? {}]);
12
+ const tools = {};
13
+ for (const record of records) {
14
+ for (const [name, tool] of Object.entries(record)) {
15
+ if (!isTool(tool)) {
16
+ throw new TypeError(`${where}: "${name}" is not a tool`);
17
+ }
18
+ if (Object.hasOwn(tools, name)) {
19
+ throw new Error(`${where}: duplicate tool name "${name}"`);
20
+ }
21
+ tools[name] = toolSpec(tool);
22
+ }
23
+ }
24
+ return tools;
25
+ }
26
+ /** The MCP tool names a host lists (empty buckets are not listed, but their names stay reserved). */
27
+ export function hostToolNames(key, split) {
28
+ const scopes = split === 'effect'
29
+ ? [hostScope(key, 'query'), hostScope(key, 'operations')]
30
+ : [hostScope(key, undefined)];
31
+ return scopes.flatMap((scope) => [
32
+ discoverToolName(scope),
33
+ dispatchToolName(scope),
34
+ ]);
35
+ }
36
+ export function createHost(def) {
37
+ if (!def.description) {
38
+ throw new TypeError('$host(): description is required');
39
+ }
40
+ const spec = {
41
+ description: def.description,
42
+ tools: collectTools(def.tools, '$host()'),
43
+ split: def.split ?? 'effect',
44
+ };
45
+ return { '~host': spec };
46
+ }
47
+ export function createToolset(contract, def) {
48
+ const tools = collectTools(def.tools, '$toolset()');
49
+ const hosts = {};
50
+ const taken = new Map();
51
+ for (const name of Object.keys(tools))
52
+ taken.set(name, `tool "${name}"`);
53
+ for (const [key, host] of Object.entries(def.hosts ?? {})) {
54
+ const spec = hostSpec(host);
55
+ for (const name of hostToolNames(key, spec.split)) {
56
+ const owner = taken.get(name);
57
+ if (owner) {
58
+ throw new Error(`$toolset(): host "${key}" generates "${name}", which ${owner} also uses`);
59
+ }
60
+ taken.set(name, `host "${key}"`);
61
+ }
62
+ hosts[key] = spec;
63
+ }
64
+ const spec = { contract, tools, hosts };
65
+ return { '~toolset': spec };
66
+ }
67
+ //# sourceMappingURL=toolset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolset.js","sourceRoot":"","sources":["../src/toolset.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { JsonSchema } from '@ts-pf/docs';
2
+ import type { Effect } from './meta.js';
3
+ /** What a caller brings to `McpTools.list` / `McpTools.call`. */
4
+ export type McpCallInfo = {
5
+ /** Validated token info (HTTP: from `verifyBearer` or the SDK OAuth helpers). */
6
+ authInfo?: {
7
+ token: string;
8
+ clientId: string;
9
+ scopes: string[];
10
+ expiresAt?: number;
11
+ resource?: URL;
12
+ resourceMetadataUrl?: string;
13
+ extra?: Record<string, unknown>;
14
+ };
15
+ /** The HTTP request, when there is one. Headers are never forwarded to procedures. */
16
+ requestInfo?: Request;
17
+ signal?: AbortSignal;
18
+ inputResponses?: Record<string, unknown>;
19
+ /** `modern` = MCP 2026-07-28, `legacy` = the 2025 protocol. */
20
+ era?: 'legacy' | 'modern';
21
+ /**
22
+ * Set by `McpTools.call` before `context` runs: the tool being called (for a
23
+ * host call, the inner tool). Copy it into your own context type to trace it
24
+ * in interceptors.
25
+ */
26
+ tool?: {
27
+ name: string;
28
+ path: string;
29
+ };
30
+ };
31
+ export type McpToolAnnotations = {
32
+ readOnlyHint?: boolean;
33
+ destructiveHint?: boolean;
34
+ };
35
+ export type McpToolDefinition = {
36
+ name: string;
37
+ description: string;
38
+ inputSchema: JsonSchema;
39
+ /** Only when the tool declares `.output(schema, map)`; results are then checked. */
40
+ outputSchema?: JsonSchema;
41
+ annotations: McpToolAnnotations;
42
+ effect: Effect;
43
+ };
44
+ export type McpToolResult = {
45
+ content: Array<{
46
+ type: 'text';
47
+ text: string;
48
+ }>;
49
+ structuredContent?: Record<string, unknown>;
50
+ isError?: true;
51
+ };
52
+ /** A bound toolset: list and call tools without a transport (tests, agent frameworks, `./sdk`). */
53
+ export interface McpTools {
54
+ list(info?: McpCallInfo): Promise<McpToolDefinition[]>;
55
+ call(name: string, args: unknown, info?: McpCallInfo): Promise<McpToolResult>;
56
+ }
57
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAEvC,iEAAiE;AACjE,MAAM,MAAM,WAAW,GAAG;IACxB,iFAAiF;IACjF,QAAQ,CAAC,EAAE;QACT,KAAK,EAAE,MAAM,CAAA;QACb,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,MAAM,EAAE,CAAA;QAChB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,QAAQ,CAAC,EAAE,GAAG,CAAA;QACd,mBAAmB,CAAC,EAAE,MAAM,CAAA;QAC5B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAChC,CAAA;IACD,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACxC,+DAA+D;IAC/D,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACzB;;;;OAIG;IACH,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACtC,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,UAAU,CAAA;IACvB,oFAAoF;IACpF,YAAY,CAAC,EAAE,UAAU,CAAA;IACzB,WAAW,EAAE,kBAAkB,CAAA;IAC/B,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC3C,OAAO,CAAC,EAAE,IAAI,CAAA;CACf,CAAA;AAED,mGAAmG;AACnG,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAA;IACtD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;CAC9E"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,82 @@
1
1
  {
2
2
  "name": "@ts-pf/mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.0",
4
+ "description": "Opt-in typed projection of a ts-pf contract into MCP tools",
5
+ "keywords": [
6
+ "ts-pf",
7
+ "typescript",
8
+ "rpc",
9
+ "typesafe",
10
+ "typed-rpc",
11
+ "api",
12
+ "contract-first",
13
+ "mcp",
14
+ "model-context-protocol",
15
+ "llm",
16
+ "ai",
17
+ "tools"
18
+ ],
19
+ "license": "MIT",
20
+ "engines": {
21
+ "node": ">=22"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/crobinson42/ts-pf.git",
26
+ "directory": "packages/mcp"
27
+ },
28
+ "homepage": "https://github.com/crobinson42/ts-pf/tree/main/packages/mcp#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/crobinson42/ts-pf/issues"
31
+ },
32
+ "type": "module",
33
+ "sideEffects": false,
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "import": "./dist/index.js"
38
+ },
39
+ "./meta": {
40
+ "types": "./dist/meta.d.ts",
41
+ "import": "./dist/meta.js"
42
+ },
43
+ "./sdk": {
44
+ "types": "./dist/sdk.d.ts",
45
+ "import": "./dist/sdk.js"
46
+ }
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "files": [
52
+ "dist",
53
+ "skills"
54
+ ],
55
+ "scripts": {
56
+ "build": "tsc -p tsconfig.build.json",
57
+ "test": "vitest run",
58
+ "type-check": "tsc --noEmit",
59
+ "lint": "biome check src tests bench/gen.mjs",
60
+ "bench:types": "node bench/gen.mjs"
61
+ },
62
+ "dependencies": {
63
+ "@ts-pf/contract": "0.2.0",
64
+ "@ts-pf/docs": "0.1.7",
65
+ "@ts-pf/protocol": "0.2.0",
66
+ "@ts-pf/server": "0.4.0"
67
+ },
68
+ "peerDependencies": {
69
+ "@modelcontextprotocol/server": ">=2.4.0 <3"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "@modelcontextprotocol/server": {
73
+ "optional": true
74
+ }
75
+ },
76
+ "devDependencies": {
77
+ "@modelcontextprotocol/client": "^2.4.0",
78
+ "@modelcontextprotocol/server": "^2.4.0",
79
+ "@ts-pf/client": "0.2.0",
80
+ "zod": "^4.0.0"
81
+ }
82
+ }
@@ -0,0 +1,134 @@
1
+ ---
2
+ name: ts-pf-mcp
3
+ description: Use when exposing a ts-pf contract as MCP tools for LLM clients — one-call serving, shaping tools for the model, toolsets, tool hosts, coverage, or serving over HTTP / stdio with the MCP SDK v2. Triggers: @ts-pf/mcp, MCP, Model Context Protocol, createToolkit, createMcpFactory, bindTools, all(), $toolset, $host, $coverage, mcp({ effect }), audit().
4
+ ---
5
+
6
+ # @ts-pf/mcp
7
+
8
+ A typed projection of a ts-pf contract into MCP tools. The contract defines the tools; an executor (`{ app, context }` in-process through `runProcedure`, or `{ client }`) runs them; `@modelcontextprotocol/server` v2 serves them.
9
+
10
+ Install: `npm i @ts-pf/mcp @modelcontextprotocol/server` (peer **>=2.4.0 <3**, needed for `./sdk` only). The app already has `@ts-pf/contract` and `@ts-pf/server`; add `@ts-pf/docs` to import `docs()` in contract files.
11
+
12
+ Link for agents: `npx skills experimental_sync -y`
13
+
14
+ ## Do
15
+
16
+ Small contract: one call. Every procedure is a tool named by path with `_` (`planet.find` → `planet_find`); `docs({ hidden: true })` is skipped. `context` is typed from the app's `$context<C>()` and required for a branded app.
17
+
18
+ ```ts
19
+ import { createMcpFactory } from '@ts-pf/mcp/sdk'
20
+
21
+ export const factory = createMcpFactory(app, { name: 'planets', version: '1.0.0' })
22
+ ```
23
+
24
+ Classify and describe in the contract. Contract files import only `@ts-pf/mcp/meta` (no dependencies). Unclassified = `write`.
25
+
26
+ ```ts
27
+ import { procedure } from '@ts-pf/contract'
28
+ import { docs } from '@ts-pf/docs'
29
+ import { mcp } from '@ts-pf/mcp/meta'
30
+ import { z } from 'zod'
31
+
32
+ export const find = procedure
33
+ .input(z.object({ id: z.number() }))
34
+ .meta({ ...docs({ description: 'Find a planet by id' }), ...mcp({ effect: 'read' }) })
35
+ ```
36
+
37
+ Shape for the model with the toolkit; split into toolsets; enforce coverage; bind; serve.
38
+
39
+ ```ts
40
+ import { all, bindTools, createToolkit } from '@ts-pf/mcp'
41
+ import { createMcpFactory } from '@ts-pf/mcp/sdk'
42
+ import { contract } from './contract.js'
43
+
44
+ const tk = createToolkit(contract) // .$context<AppCtx>() to type context in inject / mappers
45
+
46
+ const findPlanet = tk.planet.find
47
+ .describe('Get one planet by id. Call planet_list first if you only have a name.')
48
+ .errors({ NOT_FOUND: ({ data }) => `No planet ${data.id}. Call planet_list for ids.` })
49
+
50
+ export const planetsMcp = tk.$toolset({
51
+ tools: [all(tk.planet, { exclude: [tk.planet.find] }), { planet_find: findPlanet }],
52
+ })
53
+ tk.$coverage({ toolsets: [planetsMcp] })
54
+
55
+ export const tools = bindTools(planetsMcp, { app, context: () => ({ db }) })
56
+ export const factory = createMcpFactory(tools, { name: 'planets', version: '1.0.0' })
57
+ ```
58
+
59
+ - In a toolset the object key is the tool name. Duplicates and collisions with generated host names are type errors naming the key.
60
+ - `.inject({ tenantId: ({ context }) => context.tenantId })` removes the key from the model's schema and fills it per call (also forced safe defaults: `.inject({ includePii: () => false })`). Injected values win over anything the model sends. `all(node, { inject })` applies a key only where the input has it; a typo or a conflicting field type is a compile error naming the path.
61
+ - `.input(schema, map)` replaces the model input; it is exclusive with `.inject` (inject inside the map).
62
+ - `.output(map)` reshapes; `.output(schema, map)` also advertises `outputSchema`, returns `structuredContent`, and checks the result (a mismatch is `INTERNAL`). Mapper `call` is a typed client on the same executor, for joins. Literal unions in a mapper return need `as const`.
63
+ - `.errors({ CODE: ({ data }) => 'next step' })` for declared codes only. `VALIDATION` returns the issues plus the model-facing schema. Unknown throws are `INTERNAL: Internal server error` (via `toWireError`) and logged with `console.error` unless `onError` is set.
64
+ - More than about 15 tools in a group: `tk.$host({ description, tools: all(tk.admin) })`. `split: 'effect'` (default) gives `get_admin_query_capabilities` / `call_admin_query_capability` (read) and `get_admin_operations_capabilities` / `call_admin_operations_capability` (write + destructive); `split: false` gives `get_admin_capabilities` / `call_admin_capability`. Pin hot tools flat beside the host. Whole-server discovery is one host over `all(tk)`.
65
+ - `tk.$coverage({ toolsets, exclude })` is a compile error (and a runtime throw) naming every procedure no toolset exposes and no `exclude` names. Hosts count; `$tool` composites do not; `docs({ hidden: true })` counts as decided at runtime.
66
+ - `audit(toolsets)` in CI: missing descriptions (error), unclassified tools, schema roots (unions, recursion, non-object), more than 15 tools, names over 60 characters.
67
+ - Identity comes from `info.authInfo` (HTTP: your verified token passed to `createMcpHandler(factory).fetch(req, { authInfo })`). stdio never sets it: `serveStdio((ctx) => factory({ ...ctx, authInfo }))`, with `serveStdio` from `@modelcontextprotocol/server/stdio`. Enforce roles in procedure middleware; annotations and `docs({ hidden: true })` are hints, not security.
68
+ - OAuth over HTTP: check the token in the SDK at the HTTP edge, so a missing or bad token is a `401` challenge and the client can start sign-in. Map claims to the app's context in `context`. A gateway calling upstream as the user uses `client: (info) => createClient(…)`. Full verifier and metadata setup: the package README, "OAuth".
69
+
70
+ ```ts
71
+ import { createMcpHandler, getOAuthProtectedResourceMetadataUrl, type OAuthMetadata, type OAuthTokenVerifier, oauthMetadataResponse, requireBearerAuth } from '@modelcontextprotocol/server'
72
+ import { createMcpFactory } from '@ts-pf/mcp/sdk'
73
+ import { PFError } from '@ts-pf/protocol'
74
+
75
+ declare const verifier: OAuthTokenVerifier // JWKS or introspection → AuthInfo (set expiresAt; bad token → OAuthError invalid_token)
76
+ declare const oauthMetadata: OAuthMetadata // your IdP's RFC 8414 metadata
77
+ const resourceServerUrl = new URL('https://api.example.com/mcp')
78
+ const gate = requireBearerAuth({
79
+ verifier,
80
+ requiredScopes: ['mcp'], // optional
81
+ expectedResource: resourceServerUrl,
82
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(resourceServerUrl),
83
+ })
84
+ const mcpHandler = createMcpHandler(
85
+ createMcpFactory(app, {
86
+ name: 'planets',
87
+ version: '1.0.0',
88
+ // IdP claims → contract context; `extra` is verifier-defined, so check it.
89
+ context: (info): AppCtx => {
90
+ const sub = info.authInfo?.extra?.sub
91
+ if (typeof sub !== 'string') throw new PFError({ code: 'UNAUTHORIZED', message: 'Token has no subject' })
92
+ return { db, user: { id: sub } }
93
+ },
94
+ }),
95
+ )
96
+
97
+ export async function fetchHandler(request: Request): Promise<Response> {
98
+ const metadata = oauthMetadataResponse(request, { oauthMetadata, resourceServerUrl })
99
+ if (metadata) return metadata
100
+ const authInfo = await gate(request)
101
+ return authInfo instanceof Response ? authInfo : mcpHandler.fetch(request, { authInfo })
102
+ }
103
+ ```
104
+ - Tests and agent frameworks: `tools.list(info)` / `tools.call(name, args, info)`, no transport. End to end: `createMcpHandler(factory).fetch` with the v2 `Client` and `versionNegotiation: { mode: 'auto' }` (the client defaults to the 2025 protocol).
105
+
106
+ ## API
107
+
108
+ - `mcp` (`@ts-pf/mcp/meta`)
109
+ - `createToolkit` — root members `$context`, `$tool`, `$host`, `$toolset`, `$coverage`
110
+ - `all`
111
+ - `bindTools` — `{ app, context, interceptors?, onError?, maxResultChars? }` or `{ client, context?, onError?, maxResultChars? }` (`context` required when the toolkit's context type has required keys)
112
+ - `audit` — `(toolsets, { maxTools? = 15, maxNameLength? = 60, missingDescription? = 'error' })` → `{ level, tool, message }[]`
113
+ - `createMcpFactory` (`@ts-pf/mcp/sdk`) — `(app, { name, version, instructions?, listTtlMs?, context, interceptors?, onError?, maxResultChars? })` (`context` optional for an unbranded app) or `(tools, { name, version, instructions?, listTtlMs? })`
114
+ - `McpTools`
115
+ - `McpCallInfo` — `{ authInfo?, requestInfo?, signal?, inputResponses?, era?, tool? }`; `tool` is `{ name, path }` of the tool being called (the inner tool for a host call)
116
+ - `McpToolDefinition`
117
+ - `McpToolResult`
118
+
119
+ ## Pair with
120
+
121
+ - `ts-pf-contract`
122
+ - `ts-pf-server`
123
+ - `ts-pf-docs`
124
+
125
+ ## Don't
126
+
127
+ - Import `@ts-pf/mcp` or `@ts-pf/mcp/sdk` from a contract file (use `@ts-pf/mcp/meta`).
128
+ - Reuse `FetchHandler` or a message handler to serve MCP; MCP is JSON-RPC 2.0, served by the SDK.
129
+ - Build on `@modelcontextprotocol/sdk` (v1) or the deprecated low-level `Server`.
130
+ - Cache or share one `McpServer` across requests; the factory builds one per request.
131
+ - Name contract keys starting with `$` (reserved for toolkit root members).
132
+ - Forward `Authorization` / `Cookie` headers into context; use `info.authInfo`.
133
+ - Expect interceptors to see the tool name; they see the procedure path. Copy `info.tool` into your context type in `context: (info) => …` if you trace tools.
134
+ - Switch to hosts automatically by tool count, or add resources / prompts, `.visible()`, `confirm`, `.title()`, `.text()`, or custom host name templates (later versions).