toolcraft 0.0.144 → 0.0.146

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.
package/composition.json CHANGED
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.144",
116
+ "version": "0.0.146",
117
117
  "license": "MIT"
118
118
  },
119
119
  {
@@ -123,7 +123,7 @@
123
123
  },
124
124
  {
125
125
  "name": "toolcraft-schema",
126
- "version": "0.0.144",
126
+ "version": "0.0.146",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.144",
116
+ "version": "0.0.146",
117
117
  "license": "MIT"
118
118
  },
119
119
  {
@@ -123,7 +123,7 @@
123
123
  },
124
124
  {
125
125
  "name": "toolcraft-schema",
126
- "version": "0.0.144",
126
+ "version": "0.0.146",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -111,18 +111,30 @@ function loginField(field) {
111
111
  type: field === "password" || field === "apiKey" ? "password" : field === "email" ? "email" : "text"
112
112
  };
113
113
  }
114
- function renderLogin(providerName, fields, transaction, csrfToken, error) {
114
+ function renderLogin(providerName, fields, transaction, csrfToken, error, submittedValues = {}) {
115
+ const scriptNonce = randomBytes(18).toString("base64");
115
116
  const controls = fields
116
117
  .map((field) => {
117
118
  const name = escapeHtml(field.name);
118
- return `<label>${escapeHtml(field.label ?? field.name)}<input name="${name}" type="${field.type ?? "text"}" required autocomplete="${field.type === "password" ? "current-password" : "off"}"></label>`;
119
+ const type = field.type ?? "text";
120
+ const autocomplete = type === "email" ? "username" : field.name === "password" ? "current-password" : "off";
121
+ const value = type === "email" ? submittedValues[field.name] ?? "" : "";
122
+ return `<label>${escapeHtml(field.label ?? field.name)}<input name="${name}" type="${type}" required autocomplete="${autocomplete}"${value.length === 0 ? "" : ` value="${escapeHtml(value)}"`}></label>`;
119
123
  })
120
124
  .join("");
121
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Connect ${escapeHtml(providerName)}</title><style>body{font:16px system-ui;max-width:28rem;margin:10vh auto;padding:1rem;color:#171717}form{display:grid;gap:1rem}label{display:grid;gap:.35rem}input,button{font:inherit;padding:.7rem}button{cursor:pointer}${error === undefined ? "" : ".error{color:#b42318}"}</style></head><body><h1>Connect ${escapeHtml(providerName)}</h1>${error === undefined ? "" : `<p class="error">${escapeHtml(error)}</p>`}<form method="post" action="/oauth/connect"><input type="hidden" name="transaction" value="${escapeHtml(transaction.id)}"><input type="hidden" name="csrf" value="${escapeHtml(csrfToken)}">${controls}<button type="submit">Connect</button></form></body></html>`;
125
+ const idleLabel = `Connect ${providerName}`;
126
+ const html = `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Connect ${escapeHtml(providerName)}</title><style>body{font:16px system-ui;max-width:28rem;margin:10vh auto;padding:1rem;color:#171717}form{display:grid;gap:1rem}label{display:grid;gap:.35rem}input,button{font:inherit;padding:.7rem}button{cursor:pointer}button:disabled{cursor:wait;opacity:.7}.status{margin:0;color:#525252}.error{color:#b42318}</style></head><body><h1>Connect ${escapeHtml(providerName)}</h1>${error === undefined ? "" : `<p class="error" role="alert">${escapeHtml(error)}</p>`}<form id="oauth-connect-form" method="post" action="/oauth/connect"><input type="hidden" name="transaction" value="${escapeHtml(transaction.id)}"><input type="hidden" name="csrf" value="${escapeHtml(csrfToken)}">${controls}<button id="oauth-connect-button" type="submit" data-idle-label="${escapeHtml(idleLabel)}">${escapeHtml(idleLabel)}</button><p id="oauth-connect-status" class="status" role="status" aria-live="polite" hidden>Signing in… This may take a moment.</p></form><script nonce="${scriptNonce}">(()=>{const form=document.getElementById("oauth-connect-form");const button=document.getElementById("oauth-connect-button");const status=document.getElementById("oauth-connect-status");if(!(form instanceof HTMLFormElement)||!(button instanceof HTMLButtonElement)||!(status instanceof HTMLElement))return;const reset=()=>{form.removeAttribute("aria-busy");button.disabled=false;button.textContent=button.dataset.idleLabel||"Connect";status.hidden=true};form.addEventListener("submit",()=>{form.setAttribute("aria-busy","true");button.disabled=true;button.textContent="Connecting…";status.hidden=false});addEventListener("pageshow",reset)})();</script></body></html>`;
127
+ return {
128
+ contentSecurityPolicy: loginContentSecurityPolicy(transaction, scriptNonce),
129
+ html
130
+ };
122
131
  }
123
- function loginContentSecurityPolicy(transaction) {
132
+ function loginContentSecurityPolicy(transaction, scriptNonce) {
124
133
  const callbackOrigin = new URL(transaction.redirectUri).origin;
125
- return `default-src 'none'; style-src 'unsafe-inline'; form-action 'self' ${callbackOrigin}; base-uri 'none'; frame-ancestors 'none'`;
134
+ return `default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${scriptNonce}'; form-action 'self' ${callbackOrigin}; base-uri 'none'; frame-ancestors 'none'`;
135
+ }
136
+ function renderExpiredConnection() {
137
+ return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Connection expired</title><style>body{font:16px system-ui;max-width:28rem;margin:10vh auto;padding:1rem;color:#171717}p{line-height:1.5}</style></head><body><h1>Connection expired</h1><p>This sign-in link has expired or was already used.</p><p>Close this tab, return to the app that started the connection, and click Connect again.</p></body></html>`;
126
138
  }
127
139
  function interactionCookieName(transactionId) {
128
140
  const suffix = createHash("sha256").update(transactionId).digest("base64url").slice(0, 22);
@@ -186,11 +198,12 @@ export async function prepareHostedOAuthRuntime(config) {
186
198
  const security = createAuthorizationInteractionSecurity({
187
199
  cookieName: interactionCookieName(transaction.id)
188
200
  });
189
- return new Response(renderLogin(displayName, fields, transaction, security.csrfToken), {
201
+ const login = renderLogin(displayName, fields, transaction, security.csrfToken);
202
+ return new Response(login.html, {
190
203
  status: 200,
191
204
  headers: {
192
205
  "content-type": "text/html; charset=utf-8",
193
- "content-security-policy": loginContentSecurityPolicy(transaction),
206
+ "content-security-policy": login.contentSecurityPolicy,
194
207
  "cache-control": "no-store",
195
208
  "referrer-policy": "no-referrer",
196
209
  "x-content-type-options": "nosniff",
@@ -273,10 +286,13 @@ export async function prepareHostedOAuthRuntime(config) {
273
286
  }) ||
274
287
  transaction.expiresAt <= Date.now()) {
275
288
  response.writeHead(400, {
276
- "content-type": "text/plain; charset=utf-8",
277
- "cache-control": "no-store"
289
+ "content-type": "text/html; charset=utf-8",
290
+ "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; frame-ancestors 'none'",
291
+ "cache-control": "no-store",
292
+ "referrer-policy": "no-referrer",
293
+ "x-content-type-options": "nosniff"
278
294
  });
279
- response.end("This connection has expired or was already used. Restart the connection.");
295
+ response.end(renderExpiredConnection());
280
296
  return true;
281
297
  }
282
298
  const controller = new AbortController();
@@ -312,13 +328,15 @@ export async function prepareHostedOAuthRuntime(config) {
312
328
  const safeError = error instanceof HostedOAuthLoginError
313
329
  ? error.message
314
330
  : "Sign-in failed. Please try again.";
331
+ const login = renderLogin(displayName, fields, transaction, csrf, safeError, values);
315
332
  response.writeHead(400, {
316
333
  "content-type": "text/html; charset=utf-8",
317
- "content-security-policy": loginContentSecurityPolicy(transaction),
334
+ "content-security-policy": login.contentSecurityPolicy,
318
335
  "cache-control": "no-store",
319
- "referrer-policy": "no-referrer"
336
+ "referrer-policy": "no-referrer",
337
+ "x-content-type-options": "nosniff"
320
338
  });
321
- response.end(renderLogin(displayName, fields, transaction, csrf, safeError));
339
+ response.end(login.html);
322
340
  }
323
341
  return true;
324
342
  }
package/dist/index.d.ts CHANGED
@@ -95,6 +95,12 @@ export interface Renderers<TResult> {
95
95
  markdown?: (result: TResult, primitives: RenderPrimitives) => string;
96
96
  json?: (result: TResult, primitives: RenderPrimitives) => unknown;
97
97
  }
98
+ export interface ToolAnnotations {
99
+ readOnlyHint?: boolean;
100
+ destructiveHint?: boolean;
101
+ idempotentHint?: boolean;
102
+ openWorldHint?: boolean;
103
+ }
98
104
  export type HandlerContext<TParamsSchema extends ObjectSchema<any> = AnyObjectSchema, TSecrets extends SecretDeclarations | undefined = undefined, TServices extends object = EmptyServices> = TServices & {
99
105
  params: Static<TParamsSchema>;
100
106
  secrets: InferSecrets<TSecrets>;
@@ -106,7 +112,9 @@ export type HandlerContext<TParamsSchema extends ObjectSchema<any> = AnyObjectSc
106
112
  };
107
113
  export interface CommandConfig<TServices extends object, TParamsSchema extends ObjectSchema<any>, TSecrets extends SecretDeclarations | undefined, TResult> {
108
114
  name: string;
115
+ title?: string;
109
116
  description?: string;
117
+ annotations?: ToolAnnotations;
110
118
  hidden?: boolean;
111
119
  examples?: CommandExample[];
112
120
  aliases?: string[];
@@ -138,7 +146,9 @@ export interface StreamCommandConfig<TServices extends object, TParamsSchema ext
138
146
  export interface Command<TServices extends object = EmptyServices, TParamsSchema extends ObjectSchema<any> = AnyObjectSchema, TSecrets extends SecretDeclarations | undefined = undefined, TResult = unknown> {
139
147
  kind: "command";
140
148
  name: string;
149
+ title?: string;
141
150
  description?: string;
151
+ annotations?: ToolAnnotations;
142
152
  hidden: boolean;
143
153
  examples: CommandExample[];
144
154
  aliases: string[];
package/dist/index.js CHANGED
@@ -33,6 +33,9 @@ function cloneRequires(requires) {
33
33
  check: requires.check
34
34
  };
35
35
  }
36
+ function cloneToolAnnotations(annotations) {
37
+ return annotations === undefined ? undefined : { ...annotations };
38
+ }
36
39
  function cloneStringArray(values) {
37
40
  return values === undefined ? undefined : [...values];
38
41
  }
@@ -291,7 +294,9 @@ function createBaseCommand(config) {
291
294
  const command = {
292
295
  kind: "command",
293
296
  name: config.name,
297
+ title: config.title,
294
298
  description: config.description,
299
+ annotations: cloneToolAnnotations(config.annotations),
295
300
  hidden: config.hidden ?? false,
296
301
  examples: cloneCommandExamples(config.examples),
297
302
  aliases: [...(config.aliases ?? [])],
@@ -309,6 +314,8 @@ function createBaseCommand(config) {
309
314
  };
310
315
  Object.defineProperty(command, commandConfigSymbol, {
311
316
  value: {
317
+ title: config.title,
318
+ annotations: cloneToolAnnotations(config.annotations),
312
319
  scope: cloneScope(config.scope),
313
320
  hidden: config.hidden ?? false,
314
321
  examples: cloneCommandExamples(config.examples),
@@ -360,7 +367,9 @@ function materializeCommand(command, inherited) {
360
367
  const materialized = {
361
368
  kind: "command",
362
369
  name: command.name,
370
+ title: internal.title,
363
371
  description: command.description,
372
+ annotations: cloneToolAnnotations(internal.annotations),
364
373
  hidden: internal.hidden,
365
374
  examples: cloneCommandExamples(internal.examples),
366
375
  aliases: [...command.aliases],
@@ -378,6 +387,8 @@ function materializeCommand(command, inherited) {
378
387
  };
379
388
  Object.defineProperty(materialized, commandConfigSymbol, {
380
389
  value: {
390
+ title: internal.title,
391
+ annotations: cloneToolAnnotations(internal.annotations),
381
392
  scope: cloneScope(internal.scope),
382
393
  hidden: internal.hidden,
383
394
  examples: cloneCommandExamples(internal.examples),
package/dist/mcp.js CHANGED
@@ -222,6 +222,8 @@ function enumerateTools(root, casing, allowlist, omitRootToolNamePrefix) {
222
222
  command: node,
223
223
  commandPath: resolvedCommandPath,
224
224
  name,
225
+ ...(node.title === undefined ? {} : { title: node.title }),
226
+ ...(node.annotations === undefined ? {} : { annotations: { ...node.annotations } }),
225
227
  description: buildToolDescription(node.description, params, node.examples, node.name, casing),
226
228
  inputSchema: applySchemaCasing(toJsonSchema(params), casing),
227
229
  ...(node.result === undefined
@@ -915,12 +917,18 @@ function createResolvedMCPServer(root, options, runtime = {}) {
915
917
  throw toToolError(error, report?.displayPath);
916
918
  }
917
919
  };
918
- if (tool.outputSchema === undefined) {
919
- server.tool(tool.name, tool.description, tool.inputSchema, handler);
920
- }
921
- else {
922
- server.tool(tool.name, tool.description, tool.inputSchema, handler, tool.outputSchema);
923
- }
920
+ server.registerTool({
921
+ name: tool.name,
922
+ ...(tool.title === undefined ? {} : { title: tool.title }),
923
+ description: tool.description,
924
+ inputSchema: tool.inputSchema,
925
+ ...(tool.outputSchema === undefined
926
+ ? {}
927
+ : {
928
+ outputSchema: tool.outputSchema
929
+ }),
930
+ ...(tool.annotations === undefined ? {} : { annotations: tool.annotations })
931
+ }, handler);
924
932
  }
925
933
  return {
926
934
  ...server,
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.144",
11
+ "version": "0.0.146",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.144",
3
+ "version": "0.0.146",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.144",
3
+ "version": "0.0.146",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -158,7 +158,7 @@
158
158
  "yaml"
159
159
  ],
160
160
  "optionalDependencies": {
161
- "toolcraft-schema": "0.0.144",
161
+ "toolcraft-schema": "0.0.146",
162
162
  "toolcraft-design": "*",
163
163
  "@poe-code/frontmatter": "*",
164
164
  "@poe-code/agent-mcp-config": "*",