apps 1.0.0-beta.3 → 1.0.0-beta.4

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.
@@ -102,6 +102,7 @@ export interface RequirementSpec {
102
102
  readonly description?: string;
103
103
  }
104
104
  declare const RequirementKey: unique symbol;
105
+ declare const ServiceDeclKey: unique symbol;
105
106
  declare const RequirementPhantom: unique symbol;
106
107
  /** Requirement arity, tracked at the type level so handler contexts
107
108
  * know whether a role arrives as one entry, maybe-one, or a list. */
@@ -111,8 +112,8 @@ export type RequirementArity = "one" | "optional" | "many";
111
112
  * a requirement (bare value = arity "one"); .optional()/.array()
112
113
  * return terminal Requirements so nonsense chains do not typecheck.
113
114
  * The phantom property is never assigned — it exists so auth-compose
114
- * callbacks and handler contexts can recover the service's grants, a
115
- * .grant() pin, and the arity at the type level.
115
+ * callbacks and handler contexts can recover the service's grants, an
116
+ * auth-door pin, and the arity at the type level.
116
117
  */
117
118
  export interface Requirement<D extends ServiceDecl = ServiceDecl, G extends string | undefined = string | undefined, A extends RequirementArity = RequirementArity> {
118
119
  readonly [RequirementKey]: RequirementSpec;
@@ -123,13 +124,33 @@ export interface Requirement<D extends ServiceDecl = ServiceDecl, G extends stri
123
124
  };
124
125
  describe(text: string): Requirement<D, G, A>;
125
126
  }
126
- export interface Service<D extends ServiceDecl = ServiceDecl, G extends (keyof NonNullable<D["auth"]> & string) | undefined = undefined> extends Requirement<D, G, "one"> {
127
- readonly decl: D;
128
- optional(): Requirement<D, G, "optional">;
129
- array(): Requirement<D, G, "many">;
130
- /** Pin one auth method: this tool supports only the named grant. */
131
- grant<N extends keyof NonNullable<D["auth"]> & string>(name: N): Service<D, N>;
132
- describe(text: string): Service<D, G>;
127
+ /**
128
+ * A grant-pinned requirement: what `service.auth.<name>` returns. A
129
+ * deliberately narrower type than Service — it can shape arity and
130
+ * describe itself, but carries no `auth` (pinning twice is nonsense)
131
+ * and no declaration access. Pinning is property access because the
132
+ * grants are static facts of the declaration: `github.auth.pat`
133
+ * autocompletes and a wrong name fails compilation.
134
+ */
135
+ export interface PinnedRequirement<D extends ServiceDecl = ServiceDecl, N extends string = string> extends Requirement<D, N, "one"> {
136
+ optional(): Requirement<D, N, "optional">;
137
+ array(): Requirement<D, N, "many">;
138
+ describe(text: string): PinnedRequirement<D, N>;
139
+ }
140
+ /** The grant-pinned doors of a service, keyed by declared auth method. */
141
+ export type ServiceAuth<D extends ServiceDecl> = {
142
+ readonly [N in keyof NonNullable<D["auth"]> & string]: PinnedRequirement<D, N>;
143
+ };
144
+ export interface Service<D extends ServiceDecl = ServiceDecl> extends Requirement<D, undefined, "one"> {
145
+ /** Symbol-keyed so author IntelliSense shows only authoring verbs;
146
+ * the declaration is plumbing the manifest reads at collection. */
147
+ readonly [ServiceDeclKey]: D;
148
+ /** Pin one auth method: `github.auth.pat` is this tool supporting
149
+ * only that grant. */
150
+ readonly auth: ServiceAuth<D>;
151
+ optional(): Requirement<D, undefined, "optional">;
152
+ array(): Requirement<D, undefined, "many">;
153
+ describe(text: string): Service<D>;
133
154
  }
134
155
  /** A publish-time authoring problem accumulated without interrupting defines. */
135
156
  export interface Problem {
@@ -223,11 +244,17 @@ export interface DiscoveredToolSpec {
223
244
  export interface DynamicToolsDecl<Reqs extends Readonly<Record<string, Requirement>> | undefined = Readonly<Record<string, Requirement>> | undefined> {
224
245
  readonly description: string;
225
246
  readonly requirements?: Reqs;
226
- discover(ctx: HandlerCtx<Reqs>): Promise<readonly DiscoveredToolSpec[]>;
227
- call(tool: {
228
- readonly name: string;
229
- readonly detail: unknown;
230
- }, input: unknown, ctx: HandlerCtx<Reqs>): Promise<unknown>;
247
+ discover(args: {
248
+ readonly ctx: HandlerCtx<Reqs>;
249
+ }): Promise<readonly DiscoveredToolSpec[]>;
250
+ call(args: {
251
+ readonly tool: {
252
+ readonly name: string;
253
+ readonly detail: unknown;
254
+ };
255
+ readonly input: unknown;
256
+ readonly ctx: HandlerCtx<Reqs>;
257
+ }): Promise<unknown>;
231
258
  }
232
259
  /**
233
260
  * The erased registry row of a dynamic pair — same storage boundary
@@ -240,11 +267,8 @@ export interface DynamicToolsDecl<Reqs extends Readonly<Record<string, Requireme
240
267
  export interface DynamicToolsSpec {
241
268
  readonly description: string;
242
269
  readonly requirements?: Readonly<Record<string, Requirement>>;
243
- discover(ctx: never): Promise<readonly DiscoveredToolSpec[]>;
244
- call(tool: {
245
- readonly name: string;
246
- readonly detail: unknown;
247
- }, input: never, ctx: never): Promise<unknown>;
270
+ discover(args: never): Promise<readonly DiscoveredToolSpec[]>;
271
+ call(args: never): Promise<unknown>;
248
272
  }
249
273
  declare const DynamicKey: unique symbol;
250
274
  /** The value defineDynamicTools returns: an opaque handle a tool file
@@ -330,7 +354,7 @@ export type InferSchema<S> = S extends {
330
354
  /**
331
355
  * The capability a handler receives for one connected requirement
332
356
  * role: the grant's kind-discriminated material (narrowed by any
333
- * .grant() pin) plus the spending fetch. Arity shapes the value —
357
+ * auth-door pin) plus the spending fetch. Arity shapes the value —
334
358
  * `.optional()` may be absent, `.array()` is every connected account.
335
359
  */
336
360
  export type HandlerRole<R> = R extends Requirement<infer D, infer G, infer A> ? [A] extends ["many"] ? ReadonlyArray<HandlerRoleEntry<D, G>> : [A] extends ["optional"] ? HandlerRoleEntry<D, G> | undefined : HandlerRoleEntry<D, G> : never;
@@ -354,7 +378,9 @@ export interface ToolAnnotations {
354
378
  * The generic authored shape: input/ctx/output are all inferred — the
355
379
  * handler's input from the Standard Schema, ctx from the requirements
356
380
  * record (grants, pins, arity), and the return type checked against
357
- * the output schema when one is stated.
381
+ * the output schema when one is stated. The handler takes ONE
382
+ * destructurable argument so unused halves never demand an underscore:
383
+ * `handler: async ({ input }) => ...` / `async ({ ctx }) => ...`.
358
384
  */
359
385
  export interface ToolDecl<Input extends SchemaLike = SchemaLike, Reqs extends Readonly<Record<string, Requirement>> | undefined = Readonly<Record<string, Requirement>> | undefined, Output extends SchemaLike | undefined = SchemaLike | undefined> {
360
386
  /** Address segment. Single-tool files may omit it (filename). */
@@ -364,7 +390,10 @@ export interface ToolDecl<Input extends SchemaLike = SchemaLike, Reqs extends Re
364
390
  readonly input: Input;
365
391
  readonly output?: Output;
366
392
  readonly annotations?: ToolAnnotations;
367
- handler(input: InferSchema<Input>, ctx: HandlerCtx<Reqs>): Promise<Output extends SchemaLike ? InferSchema<Output> : unknown>;
393
+ handler(args: {
394
+ readonly input: InferSchema<Input>;
395
+ readonly ctx: HandlerCtx<Reqs>;
396
+ }): Promise<Output extends SchemaLike ? InferSchema<Output> : unknown>;
368
397
  }
369
398
  /**
370
399
  * The erased registry row. Registration drops the generics — storage
@@ -380,7 +409,7 @@ export interface ToolSpec {
380
409
  readonly input: SchemaLike;
381
410
  readonly output?: SchemaLike;
382
411
  readonly annotations?: ToolAnnotations;
383
- handler(input: never, ctx: never): Promise<unknown>;
412
+ handler(args: never): Promise<unknown>;
384
413
  }
385
414
  declare const ToolKey: unique symbol;
386
415
  export interface Tool {
@@ -15,7 +15,7 @@ import {
15
15
  service,
16
16
  setActiveRegistry,
17
17
  toolSpecOf
18
- } from "../chunk-A3H32P2J.js";
18
+ } from "../chunk-535V5K64.js";
19
19
  export {
20
20
  __enterFile,
21
21
  __leaveFile,
@@ -232,7 +232,7 @@ var callTool = async (registry, address, input, ctx, detail) => {
232
232
  if (!parsed2.ok) return failure("input", parsed2.reason);
233
233
  try {
234
234
  const handler = registered.spec.handler;
235
- return { ok: true, value: await handler(parsed2.value, ctx) };
235
+ return { ok: true, value: await handler({ input: parsed2.value, ctx }) };
236
236
  } catch (error) {
237
237
  return failure("handler", `handler failed (${errorMessage(error)})`);
238
238
  }
@@ -254,7 +254,11 @@ var callTool = async (registry, address, input, ctx, detail) => {
254
254
  const call = pair.call;
255
255
  return {
256
256
  ok: true,
257
- value: await call({ name: dynamic.name, detail }, parsed.value, ctx)
257
+ value: await call({
258
+ tool: { name: dynamic.name, detail },
259
+ input: parsed.value,
260
+ ctx
261
+ })
258
262
  };
259
263
  } catch (error) {
260
264
  return failure("handler", `dynamic call failed (${errorMessage(error)})`);
@@ -268,7 +272,7 @@ var discoverTool = async (registry, segment, ctx) => {
268
272
  let emitted;
269
273
  try {
270
274
  const discover = pair.discover;
271
- emitted = await discover(ctx);
275
+ emitted = await discover({ ctx });
272
276
  } catch (error) {
273
277
  return { ok: false, reason: `discover failed (${errorMessage(error)})` };
274
278
  }
@@ -4,6 +4,7 @@ var basic = (opts) => ({ kind: "basic", ...opts });
4
4
  var secrets = (opts) => ({ kind: "secrets", ...opts });
5
5
  var oauth = (opts) => ({ kind: "oauth", ...opts });
6
6
  var RequirementKey = Symbol.for("executor/apps/requirement");
7
+ var ServiceDeclKey = Symbol.for("executor/apps/service-decl");
7
8
  var recordAuthoringProblem = (message) => {
8
9
  const file = sourceFileFor("wrapper");
9
10
  activeRegistry.problems.push({ message: `${file}: ${message}` });
@@ -73,12 +74,23 @@ var makeTerminal = (spec) => ({
73
74
  });
74
75
  var service = (decl) => {
75
76
  const file = sourceFileFor("service(...)");
77
+ const makePinned = (spec) => ({
78
+ [RequirementKey]: spec,
79
+ optional: () => makeTerminal({ ...spec, arity: "optional" }),
80
+ array: () => makeTerminal({ ...spec, arity: "many" }),
81
+ describe: (text) => makePinned({ ...spec, description: text })
82
+ });
76
83
  const make = (spec) => ({
77
84
  [RequirementKey]: spec,
78
- decl,
85
+ [ServiceDeclKey]: decl,
86
+ auth: Object.fromEntries(
87
+ Object.keys(decl.auth ?? {}).map((name) => [
88
+ name,
89
+ makePinned({ ...spec, grant: name })
90
+ ])
91
+ ),
79
92
  optional: () => makeTerminal({ ...spec, arity: "optional" }),
80
93
  array: () => makeTerminal({ ...spec, arity: "many" }),
81
- grant: (name) => make({ ...spec, grant: name }),
82
94
  describe: (text) => make({ ...spec, description: text })
83
95
  });
84
96
  const value = make({ service: decl, arity: "one" });
@@ -87,7 +99,7 @@ var service = (decl) => {
87
99
  return value;
88
100
  };
89
101
  var isRequirement = (value) => typeof value === "object" && value !== null && RequirementKey in value;
90
- var isService = (value) => isRequirement(value) && "decl" in value && "optional" in value && "array" in value && "grant" in value;
102
+ var isService = (value) => isRequirement(value) && ServiceDeclKey in value;
91
103
  var requirementSpecOf = (value) => isService(value) || isRequirement(value) ? value[RequirementKey] : void 0;
92
104
  var defineApp = (manifest) => {
93
105
  const file = sourceFileFor("defineApp(...)");
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  defineDynamicTools
3
- } from "./chunk-A3H32P2J.js";
3
+ } from "./chunk-535V5K64.js";
4
4
 
5
5
  // src/mcp/client.ts
6
6
  var mcpDiscover = async (_endpoint, _ctx, _auth, _operations) => {
@@ -17,8 +17,8 @@ function fromMCP(endpoint, options) {
17
17
  return defineDynamicTools({
18
18
  description: `Tools served by the MCP server at ${endpoint}.`,
19
19
  ...options?.requirements === void 0 ? {} : { requirements: options.requirements },
20
- discover: (ctx) => mcpDiscover(endpoint, ctx, auth, operations),
21
- call: (tool, input, ctx) => mcpCall(endpoint, tool.name, input, ctx, auth)
20
+ discover: ({ ctx }) => mcpDiscover(endpoint, ctx, auth, operations),
21
+ call: ({ tool, input, ctx }) => mcpCall(endpoint, tool.name, input, ctx, auth)
22
22
  });
23
23
  }
24
24
 
@@ -2,7 +2,7 @@ import {
2
2
  defineDynamicTools,
3
3
  recordAuthoringProblem,
4
4
  requirementSpecOf
5
- } from "./chunk-A3H32P2J.js";
5
+ } from "./chunk-535V5K64.js";
6
6
 
7
7
  // src/openapi/mapper.ts
8
8
  var openapiDiscover = async (_spec, _ctx, _auth, _operations) => {
@@ -31,8 +31,8 @@ function fromOpenAPI(spec, options) {
31
31
  return defineDynamicTools({
32
32
  description: typeof spec === "string" && spec.startsWith("https://") ? `Operations from the OpenAPI document at ${spec}.` : "Operations from an OpenAPI document shipped with the app.",
33
33
  ...options?.requirements === void 0 ? {} : { requirements: options.requirements },
34
- discover: (ctx) => openapiDiscover(spec, ctx, auth, operations),
35
- call: (tool, input, ctx) => openapiCall(tool, input, ctx, auth)
34
+ discover: ({ ctx }) => openapiDiscover(spec, ctx, auth, operations),
35
+ call: ({ tool, input, ctx }) => openapiCall(tool, input, ctx, auth)
36
36
  });
37
37
  }
38
38
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  manifestOf
3
- } from "./chunk-MUFMJRXR.js";
3
+ } from "./chunk-2DVKSPQH.js";
4
4
  import {
5
5
  PublishError
6
6
  } from "./chunk-B7NEUIJL.js";
@@ -9,7 +9,7 @@ import {
9
9
  __leaveFile,
10
10
  newRegistry,
11
11
  setActiveRegistry
12
- } from "./chunk-A3H32P2J.js";
12
+ } from "./chunk-535V5K64.js";
13
13
 
14
14
  // src/manifest/index.ts
15
15
  import { fileURLToPath, pathToFileURL } from "node:url";
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  fromOpenAPI
3
- } from "./chunk-F7GZYEAC.js";
3
+ } from "./chunk-QA72XJZU.js";
4
4
  import {
5
5
  authHeaders
6
6
  } from "./chunk-S5KFP4JQ.js";
7
7
  import {
8
8
  fromMCP
9
- } from "./chunk-YUZ4TXOY.js";
9
+ } from "./chunk-DP6UJCMX.js";
10
10
  import {
11
11
  __enterFile,
12
12
  __leaveFile,
@@ -24,7 +24,7 @@ import {
24
24
  service,
25
25
  setActiveRegistry,
26
26
  toolSpecOf
27
- } from "./chunk-A3H32P2J.js";
27
+ } from "./chunk-535V5K64.js";
28
28
  export {
29
29
  __enterFile,
30
30
  __leaveFile,
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  loadApp,
3
3
  manifest
4
- } from "../chunk-Y6LLTN7W.js";
5
- import "../chunk-MUFMJRXR.js";
4
+ } from "../chunk-XYT6GZ7M.js";
5
+ import "../chunk-2DVKSPQH.js";
6
6
  import {
7
7
  JsonSchemaObject,
8
8
  PublishError
9
9
  } from "../chunk-B7NEUIJL.js";
10
- import "../chunk-A3H32P2J.js";
10
+ import "../chunk-535V5K64.js";
11
11
  export {
12
12
  JsonSchemaObject,
13
13
  PublishError,
package/dist/mcp/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  fromMCP
3
- } from "../chunk-YUZ4TXOY.js";
4
- import "../chunk-A3H32P2J.js";
3
+ } from "../chunk-DP6UJCMX.js";
4
+ import "../chunk-535V5K64.js";
5
5
  export {
6
6
  fromMCP
7
7
  };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  fromOpenAPI
3
- } from "../chunk-F7GZYEAC.js";
4
- import "../chunk-A3H32P2J.js";
3
+ } from "../chunk-QA72XJZU.js";
4
+ import "../chunk-535V5K64.js";
5
5
  export {
6
6
  fromOpenAPI
7
7
  };
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  loadApp
3
- } from "../chunk-Y6LLTN7W.js";
3
+ } from "../chunk-XYT6GZ7M.js";
4
4
  import {
5
5
  callTool,
6
6
  discoverTool
7
- } from "../chunk-MUFMJRXR.js";
7
+ } from "../chunk-2DVKSPQH.js";
8
8
  import "../chunk-B7NEUIJL.js";
9
9
  import {
10
10
  CallError,
11
11
  DiscoverError
12
12
  } from "../chunk-4OZFMCTT.js";
13
- import "../chunk-A3H32P2J.js";
13
+ import "../chunk-535V5K64.js";
14
14
 
15
15
  // src/runner/index.ts
16
16
  import { Effect } from "effect";
@@ -3,7 +3,7 @@ import {
3
3
  compileInputSchema,
4
4
  discoverTool,
5
5
  manifestOf
6
- } from "../chunk-MUFMJRXR.js";
6
+ } from "../chunk-2DVKSPQH.js";
7
7
  export {
8
8
  callTool,
9
9
  compileInputSchema,
@@ -2,7 +2,7 @@ import {
2
2
  callTool,
3
3
  discoverTool,
4
4
  manifestOf
5
- } from "../chunk-MUFMJRXR.js";
5
+ } from "../chunk-2DVKSPQH.js";
6
6
  export {
7
7
  callTool,
8
8
  discoverTool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apps",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "Author executor apps: declare services, auth, and tools in plain TypeScript; the executor platform runs, validates, and serves them.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -126,6 +126,7 @@ export interface RequirementSpec {
126
126
  }
127
127
 
128
128
  const RequirementKey: unique symbol = Symbol.for("executor/apps/requirement");
129
+ const ServiceDeclKey: unique symbol = Symbol.for("executor/apps/service-decl");
129
130
 
130
131
  declare const RequirementPhantom: unique symbol;
131
132
 
@@ -138,8 +139,8 @@ export type RequirementArity = "one" | "optional" | "many";
138
139
  * a requirement (bare value = arity "one"); .optional()/.array()
139
140
  * return terminal Requirements so nonsense chains do not typecheck.
140
141
  * The phantom property is never assigned — it exists so auth-compose
141
- * callbacks and handler contexts can recover the service's grants, a
142
- * .grant() pin, and the arity at the type level.
142
+ * callbacks and handler contexts can recover the service's grants, an
143
+ * auth-door pin, and the arity at the type level.
143
144
  */
144
145
  export interface Requirement<
145
146
  D extends ServiceDecl = ServiceDecl,
@@ -155,16 +156,39 @@ export interface Requirement<
155
156
  describe(text: string): Requirement<D, G, A>;
156
157
  }
157
158
 
158
- export interface Service<
159
+ /**
160
+ * A grant-pinned requirement: what `service.auth.<name>` returns. A
161
+ * deliberately narrower type than Service — it can shape arity and
162
+ * describe itself, but carries no `auth` (pinning twice is nonsense)
163
+ * and no declaration access. Pinning is property access because the
164
+ * grants are static facts of the declaration: `github.auth.pat`
165
+ * autocompletes and a wrong name fails compilation.
166
+ */
167
+ export interface PinnedRequirement<
159
168
  D extends ServiceDecl = ServiceDecl,
160
- G extends (keyof NonNullable<D["auth"]> & string) | undefined = undefined,
161
- > extends Requirement<D, G, "one"> {
162
- readonly decl: D;
163
- optional(): Requirement<D, G, "optional">;
164
- array(): Requirement<D, G, "many">;
165
- /** Pin one auth method: this tool supports only the named grant. */
166
- grant<N extends keyof NonNullable<D["auth"]> & string>(name: N): Service<D, N>;
167
- describe(text: string): Service<D, G>;
169
+ N extends string = string,
170
+ > extends Requirement<D, N, "one"> {
171
+ optional(): Requirement<D, N, "optional">;
172
+ array(): Requirement<D, N, "many">;
173
+ describe(text: string): PinnedRequirement<D, N>;
174
+ }
175
+
176
+ /** The grant-pinned doors of a service, keyed by declared auth method. */
177
+ export type ServiceAuth<D extends ServiceDecl> = {
178
+ readonly [N in keyof NonNullable<D["auth"]> & string]: PinnedRequirement<D, N>;
179
+ };
180
+
181
+ export interface Service<D extends ServiceDecl = ServiceDecl>
182
+ extends Requirement<D, undefined, "one"> {
183
+ /** Symbol-keyed so author IntelliSense shows only authoring verbs;
184
+ * the declaration is plumbing the manifest reads at collection. */
185
+ readonly [ServiceDeclKey]: D;
186
+ /** Pin one auth method: `github.auth.pat` is this tool supporting
187
+ * only that grant. */
188
+ readonly auth: ServiceAuth<D>;
189
+ optional(): Requirement<D, undefined, "optional">;
190
+ array(): Requirement<D, undefined, "many">;
191
+ describe(text: string): Service<D>;
168
192
  }
169
193
 
170
194
  /** A publish-time authoring problem accumulated without interrupting defines. */
@@ -294,12 +318,12 @@ export interface DynamicToolsDecl<
294
318
  > {
295
319
  readonly description: string;
296
320
  readonly requirements?: Reqs;
297
- discover(ctx: HandlerCtx<Reqs>): Promise<readonly DiscoveredToolSpec[]>;
298
- call(
299
- tool: { readonly name: string; readonly detail: unknown },
300
- input: unknown,
301
- ctx: HandlerCtx<Reqs>,
302
- ): Promise<unknown>;
321
+ discover(args: { readonly ctx: HandlerCtx<Reqs> }): Promise<readonly DiscoveredToolSpec[]>;
322
+ call(args: {
323
+ readonly tool: { readonly name: string; readonly detail: unknown };
324
+ readonly input: unknown;
325
+ readonly ctx: HandlerCtx<Reqs>;
326
+ }): Promise<unknown>;
303
327
  }
304
328
 
305
329
  /**
@@ -313,12 +337,8 @@ export interface DynamicToolsDecl<
313
337
  export interface DynamicToolsSpec {
314
338
  readonly description: string;
315
339
  readonly requirements?: Readonly<Record<string, Requirement>>;
316
- discover(ctx: never): Promise<readonly DiscoveredToolSpec[]>;
317
- call(
318
- tool: { readonly name: string; readonly detail: unknown },
319
- input: never,
320
- ctx: never,
321
- ): Promise<unknown>;
340
+ discover(args: never): Promise<readonly DiscoveredToolSpec[]>;
341
+ call(args: never): Promise<unknown>;
322
342
  }
323
343
 
324
344
  const DynamicKey: unique symbol = Symbol.for("executor/apps/dynamic");
@@ -462,18 +482,28 @@ const makeTerminal = <
462
482
 
463
483
  export const service = <const D extends ServiceDecl>(decl: D): Service<D> => {
464
484
  const file = sourceFileFor("service(...)");
465
- const make = <G extends (keyof NonNullable<D["auth"]> & string) | undefined>(
485
+ const makePinned = <N extends keyof NonNullable<D["auth"]> & string>(
466
486
  spec: RequirementSpec,
467
- ): Service<D, G> => ({
487
+ ): PinnedRequirement<D, N> => ({
488
+ [RequirementKey]: spec,
489
+ optional: () => makeTerminal<D, N, "optional">({ ...spec, arity: "optional" }),
490
+ array: () => makeTerminal<D, N, "many">({ ...spec, arity: "many" }),
491
+ describe: (text) => makePinned<N>({ ...spec, description: text }),
492
+ });
493
+ const make = (spec: RequirementSpec): Service<D> => ({
468
494
  [RequirementKey]: spec,
469
- decl,
470
- optional: () => makeTerminal<D, G, "optional">({ ...spec, arity: "optional" }),
471
- array: () => makeTerminal<D, G, "many">({ ...spec, arity: "many" }),
472
- grant: <N extends keyof NonNullable<D["auth"]> & string>(name: N) =>
473
- make<N>({ ...spec, grant: name }),
474
- describe: (text) => make<G>({ ...spec, description: text }),
495
+ [ServiceDeclKey]: decl,
496
+ auth: Object.fromEntries(
497
+ Object.keys(decl.auth ?? {}).map((name) => [
498
+ name,
499
+ makePinned({ ...spec, grant: name }),
500
+ ]),
501
+ ) as ServiceAuth<D>,
502
+ optional: () => makeTerminal<D, undefined, "optional">({ ...spec, arity: "optional" }),
503
+ array: () => makeTerminal<D, undefined, "many">({ ...spec, arity: "many" }),
504
+ describe: (text) => make({ ...spec, description: text }),
475
505
  });
476
- const value = make<undefined>({ service: decl, arity: "one" });
506
+ const value = make({ service: decl, arity: "one" });
477
507
  if (!activeRegistry.services.has(decl.origin))
478
508
  activeRegistry.services.set(decl.origin, { decl, file });
479
509
  return value;
@@ -483,11 +513,7 @@ const isRequirement = (value: unknown): value is Requirement =>
483
513
  typeof value === "object" && value !== null && RequirementKey in value;
484
514
 
485
515
  const isService = (value: unknown): value is Service =>
486
- isRequirement(value) &&
487
- "decl" in value &&
488
- "optional" in value &&
489
- "array" in value &&
490
- "grant" in value;
516
+ isRequirement(value) && ServiceDeclKey in value;
491
517
 
492
518
  export const requirementSpecOf = (value: unknown): RequirementSpec | undefined =>
493
519
  isService(value) || isRequirement(value) ? value[RequirementKey] : undefined;
@@ -535,7 +561,7 @@ export type InferSchema<S> = S extends {
535
561
  /**
536
562
  * The capability a handler receives for one connected requirement
537
563
  * role: the grant's kind-discriminated material (narrowed by any
538
- * .grant() pin) plus the spending fetch. Arity shapes the value —
564
+ * auth-door pin) plus the spending fetch. Arity shapes the value —
539
565
  * `.optional()` may be absent, `.array()` is every connected account.
540
566
  */
541
567
  export type HandlerRole<R> = R extends Requirement<infer D, infer G, infer A>
@@ -570,7 +596,9 @@ export interface ToolAnnotations {
570
596
  * The generic authored shape: input/ctx/output are all inferred — the
571
597
  * handler's input from the Standard Schema, ctx from the requirements
572
598
  * record (grants, pins, arity), and the return type checked against
573
- * the output schema when one is stated.
599
+ * the output schema when one is stated. The handler takes ONE
600
+ * destructurable argument so unused halves never demand an underscore:
601
+ * `handler: async ({ input }) => ...` / `async ({ ctx }) => ...`.
574
602
  */
575
603
  export interface ToolDecl<
576
604
  Input extends SchemaLike = SchemaLike,
@@ -586,10 +614,10 @@ export interface ToolDecl<
586
614
  readonly input: Input;
587
615
  readonly output?: Output;
588
616
  readonly annotations?: ToolAnnotations;
589
- handler(
590
- input: InferSchema<Input>,
591
- ctx: HandlerCtx<Reqs>,
592
- ): Promise<Output extends SchemaLike ? InferSchema<Output> : unknown>;
617
+ handler(args: {
618
+ readonly input: InferSchema<Input>;
619
+ readonly ctx: HandlerCtx<Reqs>;
620
+ }): Promise<Output extends SchemaLike ? InferSchema<Output> : unknown>;
593
621
  }
594
622
 
595
623
  /**
@@ -606,7 +634,7 @@ export interface ToolSpec {
606
634
  readonly input: SchemaLike;
607
635
  readonly output?: SchemaLike;
608
636
  readonly annotations?: ToolAnnotations;
609
- handler(input: never, ctx: never): Promise<unknown>;
637
+ handler(args: never): Promise<unknown>;
610
638
  }
611
639
 
612
640
  const ToolKey: unique symbol = Symbol.for("executor/apps/tool");
package/src/mcp/index.ts CHANGED
@@ -36,7 +36,7 @@ export function fromMCP(
36
36
  return defineDynamicTools({
37
37
  description: `Tools served by the MCP server at ${endpoint}.`,
38
38
  ...(options?.requirements === undefined ? {} : { requirements: options.requirements }),
39
- discover: (ctx) => mcpDiscover(endpoint, ctx, auth, operations),
40
- call: (tool, input, ctx) => mcpCall(endpoint, tool.name, input, ctx, auth),
39
+ discover: ({ ctx }) => mcpDiscover(endpoint, ctx, auth, operations),
40
+ call: ({ tool, input, ctx }) => mcpCall(endpoint, tool.name, input, ctx, auth),
41
41
  });
42
42
  }
@@ -64,7 +64,7 @@ export function fromOpenAPI(
64
64
  ? `Operations from the OpenAPI document at ${spec}.`
65
65
  : "Operations from an OpenAPI document shipped with the app.",
66
66
  ...(options?.requirements === undefined ? {} : { requirements: options.requirements }),
67
- discover: (ctx) => openapiDiscover(spec, ctx, auth, operations),
68
- call: (tool, input, ctx) => openapiCall(tool, input, ctx, auth),
67
+ discover: ({ ctx }) => openapiDiscover(spec, ctx, auth, operations),
68
+ call: ({ tool, input, ctx }) => openapiCall(tool, input, ctx, auth),
69
69
  });
70
70
  }
@@ -457,11 +457,11 @@ export const callTool = async (
457
457
  try {
458
458
  // SAFETY: ToolSpec intentionally erases authored handler inputs to keep
459
459
  // authoring generic-free; validation and ToolCtx establish this call boundary.
460
- const handler = registered.spec.handler as (
461
- value: unknown,
462
- toolCtx: ToolCtx,
463
- ) => Promise<unknown>;
464
- return { ok: true, value: await handler(parsed.value, ctx) };
460
+ const handler = registered.spec.handler as (args: {
461
+ readonly input: unknown;
462
+ readonly ctx: ToolCtx;
463
+ }) => Promise<unknown>;
464
+ return { ok: true, value: await handler({ input: parsed.value, ctx }) };
465
465
  } catch (error) {
466
466
  return failure("handler", `handler failed (${errorMessage(error)})`);
467
467
  }
@@ -481,14 +481,18 @@ export const callTool = async (
481
481
  const parsed = await parseInput(discovered.input, input);
482
482
  if (!parsed.ok) return failure("input", parsed.reason);
483
483
  try {
484
- const call = pair.call as (
485
- tool: { readonly name: string; readonly detail: unknown },
486
- value: unknown,
487
- toolCtx: ToolCtx,
488
- ) => Promise<unknown>;
484
+ const call = pair.call as (args: {
485
+ readonly tool: { readonly name: string; readonly detail: unknown };
486
+ readonly input: unknown;
487
+ readonly ctx: ToolCtx;
488
+ }) => Promise<unknown>;
489
489
  return {
490
490
  ok: true,
491
- value: await call({ name: dynamic.name, detail }, parsed.value, ctx),
491
+ value: await call({
492
+ tool: { name: dynamic.name, detail },
493
+ input: parsed.value,
494
+ ctx,
495
+ }),
492
496
  };
493
497
  } catch (error) {
494
498
  return failure("handler", `dynamic call failed (${errorMessage(error)})`);
@@ -508,10 +512,10 @@ export const discoverTool = async (
508
512
 
509
513
  let emitted: readonly DiscoveredToolSpec[];
510
514
  try {
511
- const discover = pair.discover as (
512
- toolCtx: ToolCtx,
513
- ) => Promise<readonly DiscoveredToolSpec[]>;
514
- emitted = await discover(ctx);
515
+ const discover = pair.discover as (args: {
516
+ readonly ctx: ToolCtx;
517
+ }) => Promise<readonly DiscoveredToolSpec[]>;
518
+ emitted = await discover({ ctx });
515
519
  } catch (error) {
516
520
  return { ok: false, reason: `discover failed (${errorMessage(error)})` };
517
521
  }