toolcraft 0.0.146 → 0.0.148

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/README.md CHANGED
@@ -334,6 +334,28 @@ defineCommand({
334
334
  });
335
335
  ```
336
336
 
337
+ MCP assumes an unannotated tool may mutate data destructively, is not safe to retry, and can
338
+ interact with an open world. Declare the standard hints on each MCP command so clients can present
339
+ the operation accurately:
340
+
341
+ ```ts
342
+ defineCommand({
343
+ name: "inspect",
344
+ title: "Inspect deployment",
345
+ description: "Inspect a deployment without changing it.",
346
+ annotations: {
347
+ readOnlyHint: true,
348
+ destructiveHint: false,
349
+ idempotentHint: true,
350
+ openWorldHint: false
351
+ },
352
+ scope: ["mcp"],
353
+ params: S.Object({ deploymentId: S.String() }),
354
+ result: S.Object({ status: S.String() }),
355
+ handler: async ({ params }) => inspectDeployment(params.deploymentId)
356
+ });
357
+ ```
358
+
337
359
  ## HTTP errors
338
360
 
339
361
  Toolcraft exports a fixed HTTP error hierarchy for transports and generated API clients:
@@ -554,7 +576,9 @@ Toolcraft configuration is code-first. Use `defineCommand(config)` and `defineGr
554
576
  ### `defineCommand(config)`
555
577
 
556
578
  - `name: string`
579
+ - `title?: string` — human-readable MCP display name.
557
580
  - `description?: string`
581
+ - `annotations?: { title?; readOnlyHint?; destructiveHint?; idempotentHint?; openWorldHint? }` — standard MCP behavior hints passed through unchanged.
558
582
  - `aliases?: string[]`
559
583
  - `positional?: string[]` — parameter names mapped from CLI argv order.
560
584
  - `params: S.Object(...)` — input schema from `toolcraft-schema`.
package/composition.json CHANGED
@@ -98,7 +98,7 @@
98
98
  },
99
99
  {
100
100
  "name": "tiny-http-mcp-server",
101
- "version": "0.1.6",
101
+ "version": "0.1.7",
102
102
  "license": "MIT"
103
103
  },
104
104
  {
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.146",
116
+ "version": "0.0.148",
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.146",
126
+ "version": "0.0.148",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -98,7 +98,7 @@
98
98
  },
99
99
  {
100
100
  "name": "tiny-http-mcp-server",
101
- "version": "0.1.6",
101
+ "version": "0.1.7",
102
102
  "license": "MIT"
103
103
  },
104
104
  {
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.146",
116
+ "version": "0.0.148",
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.146",
126
+ "version": "0.0.148",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -1,4 +1,9 @@
1
1
  import { hostedOAuth } from "./http-hosted-oauth.js";
2
+ const ignoredRevocationObserver = async (grant) => {
3
+ const ignoredSubject = grant.subject;
4
+ void ignoredSubject;
5
+ };
6
+ void ignoredRevocationObserver;
2
7
  const ignoredHostedOAuth = hostedOAuth({
3
8
  publicUrl: "https://calendar.example/mcp",
4
9
  storage: ignoredStorage,
@@ -30,6 +35,7 @@ const ignoredHostedOAuth = hostedOAuth({
30
35
  }
31
36
  });
32
37
  void ignoredHostedOAuth;
38
+ void ignoredHostedOAuth.assertProductionReady();
33
39
  const ignoredRedirectHostedOAuth = hostedOAuth({
34
40
  publicUrl: "https://calendar.example/mcp",
35
41
  storage: ignoredStorage,
@@ -1,5 +1,5 @@
1
1
  import { type JWK } from "jose";
2
- import { type AuthorizationServerStore, type AuthorizationTransactionRecord, type OAuthAuthorizationServerSigningKey } from "mcp-oauth-server";
2
+ import { type AuthorizationServerStore, type AuthorizationGrantRecord, type AuthorizationTransactionRecord, type OAuthAuthorizationServerSigningKey } from "mcp-oauth-server";
3
3
  import type { HttpAdditionalRequestHandler, TinyHttpMcpServerOAuthOptions } from "tiny-http-mcp-server/server";
4
4
  export type HostedOAuthLoginFieldName = "email" | "password" | "apiKey" | (string & {});
5
5
  export interface HostedOAuthLoginField {
@@ -33,6 +33,7 @@ export interface HostedOAuthStorage<TCredential = unknown> {
33
33
  resolveSubject(providerName: string, accountId: string): Promise<string>;
34
34
  healthCheck?(): Promise<void>;
35
35
  cleanup?(now?: number): Promise<void>;
36
+ onGrantRevoked?(grant: AuthorizationGrantRecord): Promise<void> | void;
36
37
  }
37
38
  export interface HostedOAuthCredentialAccess<TCredential = unknown> {
38
39
  read(): Promise<TCredential>;
@@ -105,6 +106,7 @@ export interface HostedOAuthConfiguration<TCredential = unknown, TServices exten
105
106
  prepare(options?: {
106
107
  production?: boolean;
107
108
  }): Promise<PreparedHostedOAuth>;
109
+ assertProductionReady(): Promise<PreparedHostedOAuth>;
108
110
  }
109
111
  export declare function isHostedOAuthConfiguration(value: unknown): value is HostedOAuthConfiguration<unknown, object>;
110
112
  export declare function hostedOAuth<TCredential = unknown, TServices extends object = object>(options: HostedOAuthOptions<TCredential, TServices>): HostedOAuthConfiguration<TCredential, TServices>;
@@ -85,6 +85,9 @@ export function hostedOAuth(options) {
85
85
  issuer: new URL(publicUrl.origin),
86
86
  scopes: this.advanced?.scopes ?? ["mcp", "offline_access"]
87
87
  };
88
+ },
89
+ async assertProductionReady() {
90
+ return this.prepare({ production: true });
88
91
  }
89
92
  };
90
93
  }
@@ -225,7 +228,8 @@ export async function prepareHostedOAuthRuntime(config) {
225
228
  accessTokenTtlSeconds: config.advanced?.accessTokenTtlSeconds,
226
229
  authorizationCodeTtlSeconds: config.advanced?.authorizationCodeTtlSeconds,
227
230
  authorizationTransactionTtlSeconds: config.advanced?.authorizationTransactionTtlSeconds,
228
- refreshTokenTtlSeconds: config.advanced?.refreshTokenTtlSeconds
231
+ refreshTokenTtlSeconds: config.advanced?.refreshTokenTtlSeconds,
232
+ onGrantRevoked: config.storage.onGrantRevoked
229
233
  });
230
234
  const requestHandler = async (request, response) => {
231
235
  const url = new URL(request.url ?? "/", prepared.issuer);
package/dist/index.d.ts CHANGED
@@ -95,7 +95,9 @@ export interface Renderers<TResult> {
95
95
  markdown?: (result: TResult, primitives: RenderPrimitives) => string;
96
96
  json?: (result: TResult, primitives: RenderPrimitives) => unknown;
97
97
  }
98
+ /** Standard MCP hints that describe a tool's behavior to clients. */
98
99
  export interface ToolAnnotations {
100
+ title?: string;
99
101
  readOnlyHint?: boolean;
100
102
  destructiveHint?: boolean;
101
103
  idempotentHint?: boolean;
package/dist/mcp-proxy.js CHANGED
@@ -78,7 +78,9 @@ function createProxyCommand(parent, tool, commandName, connection) {
78
78
  return markProxyNode({
79
79
  kind: "command",
80
80
  name: commandName,
81
+ title: tool.title,
81
82
  description: tool.description,
83
+ annotations: tool.annotations === undefined ? undefined : { ...tool.annotations },
82
84
  hidden: false,
83
85
  examples: [],
84
86
  aliases: [],
package/dist/mcp.js CHANGED
@@ -219,11 +219,11 @@ function enumerateTools(root, casing, allowlist, omitRootToolNamePrefix) {
219
219
  }
220
220
  commandPathsByToolName.set(name, resolvedCommandPath);
221
221
  tools.push({
222
+ ...(node.annotations === undefined ? {} : { annotations: { ...node.annotations } }),
222
223
  command: node,
223
224
  commandPath: resolvedCommandPath,
224
225
  name,
225
226
  ...(node.title === undefined ? {} : { title: node.title }),
226
- ...(node.annotations === undefined ? {} : { annotations: { ...node.annotations } }),
227
227
  description: buildToolDescription(node.description, params, node.examples, node.name, casing),
228
228
  inputSchema: applySchemaCasing(toJsonSchema(params), casing),
229
229
  ...(node.result === undefined
@@ -0,0 +1,6 @@
1
+ import type { HostedOAuthStorage } from "../http-hosted-oauth.js";
2
+ export interface HostedOAuthStorageConformanceOptions<TCredential> {
3
+ createStorage(): HostedOAuthStorage<TCredential> | Promise<HostedOAuthStorage<TCredential>>;
4
+ credentials: readonly [TCredential, TCredential];
5
+ }
6
+ export declare function verifyHostedOAuthStorage<TCredential>(options: HostedOAuthStorageConformanceOptions<TCredential>): Promise<void>;
@@ -0,0 +1,140 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ function assert(condition, message) {
3
+ if (!condition)
4
+ throw new Error(`Hosted OAuth storage conformance failed: ${message}`);
5
+ }
6
+ export async function verifyHostedOAuthStorage(options) {
7
+ const storage = await options.createStorage();
8
+ const [firstCredential, secondCredential] = options.credentials;
9
+ const firstSubject = await storage.resolveSubject("provider", "account-a");
10
+ const repeatedSubject = await storage.resolveSubject("provider", "account-a");
11
+ const secondSubject = await storage.resolveSubject("provider", "account-b");
12
+ const otherProviderSubject = await storage.resolveSubject("other-provider", "account-a");
13
+ assert(firstSubject === repeatedSubject, "subject resolution must be stable");
14
+ assert(firstSubject !== secondSubject, "different provider accounts must resolve independently");
15
+ assert(firstSubject !== otherProviderSubject, "provider namespaces must resolve independently");
16
+ await storage.credentials.set(firstSubject, firstCredential);
17
+ assert(isDeepStrictEqual(await storage.credentials.get(firstSubject), firstCredential), "credentials must round-trip");
18
+ await storage.credentials.update(firstSubject, () => secondCredential);
19
+ assert(isDeepStrictEqual(await storage.credentials.get(firstSubject), secondCredential), "credential updates must persist");
20
+ let releaseFirstUpdate;
21
+ let markFirstUpdateStarted;
22
+ const firstUpdateStarted = new Promise((resolve) => {
23
+ markFirstUpdateStarted = resolve;
24
+ });
25
+ const releaseFirst = new Promise((resolve) => {
26
+ releaseFirstUpdate = resolve;
27
+ });
28
+ const firstUpdate = storage.credentials.update(firstSubject, async (current) => {
29
+ assert(isDeepStrictEqual(current, secondCredential), "credential updates must read current data");
30
+ markFirstUpdateStarted?.();
31
+ await releaseFirst;
32
+ return firstCredential;
33
+ });
34
+ await firstUpdateStarted;
35
+ let secondUpdateStarted = false;
36
+ const secondUpdate = storage.credentials.update(firstSubject, (current) => {
37
+ secondUpdateStarted = true;
38
+ assert(isDeepStrictEqual(current, firstCredential), "credential updates must execute atomically");
39
+ return secondCredential;
40
+ });
41
+ const updates = Promise.all([firstUpdate, secondUpdate]);
42
+ await Promise.resolve();
43
+ assert(!secondUpdateStarted, "credential updates must be serialized");
44
+ releaseFirstUpdate?.();
45
+ await updates;
46
+ assert(isDeepStrictEqual(await storage.credentials.get(firstSubject), secondCredential), "serialized credential updates must persist");
47
+ await storage.credentials.delete(firstSubject);
48
+ assert((await storage.credentials.get(firstSubject)) === undefined, "credential deletion must persist");
49
+ const transaction = {
50
+ id: "interaction-1",
51
+ clientId: "client-1",
52
+ redirectUri: "https://client.example/callback",
53
+ codeChallenge: "challenge",
54
+ resource: "https://resource.example/mcp",
55
+ scopes: ["mcp"],
56
+ createdAt: 1,
57
+ expiresAt: 2
58
+ };
59
+ await storage.interactions.set(transaction);
60
+ assert(isDeepStrictEqual(await storage.interactions.get(transaction.id), transaction), "interactions must round-trip");
61
+ await storage.interactions.delete(transaction.id);
62
+ assert((await storage.interactions.get(transaction.id)) === undefined, "interaction deletion must persist");
63
+ const firstKey = await storage.signingKey();
64
+ const secondKey = await storage.signingKey();
65
+ assert(firstKey.keyId === secondKey.keyId && isDeepStrictEqual(firstKey.publicJwk, secondKey.publicJwk), "signing keys must remain stable");
66
+ const restartedStorage = await options.createStorage();
67
+ if (storage.capabilities.stableKeys) {
68
+ const restartedKey = await restartedStorage.signingKey();
69
+ assert(firstKey.keyId === restartedKey.keyId &&
70
+ isDeepStrictEqual(firstKey.publicJwk, restartedKey.publicJwk), "stable signing keys must survive adapter restart");
71
+ }
72
+ if (storage.capabilities.durable) {
73
+ assert((await restartedStorage.resolveSubject("provider", "account-a")) === firstSubject, "durable subject resolution must survive adapter restart");
74
+ }
75
+ const store = storage.authorizationServer;
76
+ const client = { id: "client-1", redirectUris: ["https://client.example/callback"], createdAt: 1 };
77
+ await store.putClient(client);
78
+ assert(isDeepStrictEqual(await store.getClient(client.id), client), "clients must round-trip");
79
+ await store.putAuthorizationTransaction(transaction);
80
+ assert(isDeepStrictEqual(await store.takeAuthorizationTransaction(transaction.id), transaction) &&
81
+ (await store.takeAuthorizationTransaction(transaction.id)) === undefined, "authorization transactions must be consumed atomically");
82
+ const grant = {
83
+ id: "grant-1",
84
+ clientId: client.id,
85
+ subject: firstSubject,
86
+ resource: transaction.resource,
87
+ scopes: ["mcp", "offline_access"],
88
+ createdAt: 1
89
+ };
90
+ const code = {
91
+ tokenHash: "code-1",
92
+ grantId: grant.id,
93
+ clientId: client.id,
94
+ subject: firstSubject,
95
+ redirectUri: transaction.redirectUri,
96
+ codeChallenge: transaction.codeChallenge,
97
+ resource: transaction.resource,
98
+ scopes: grant.scopes,
99
+ expiresAt: 10
100
+ };
101
+ await store.putAuthorizationCode(code);
102
+ assert(isDeepStrictEqual(await store.takeAuthorizationCode(code.tokenHash), code) &&
103
+ (await store.takeAuthorizationCode(code.tokenHash)) === undefined, "authorization codes must be consumed atomically");
104
+ await store.putGrant(grant);
105
+ assert(isDeepStrictEqual(await store.getGrant(grant.id), grant), "grants must round-trip");
106
+ const accessToken = {
107
+ tokenHash: "access-1",
108
+ tokenId: "token-1",
109
+ grantId: grant.id,
110
+ subject: firstSubject,
111
+ clientId: client.id,
112
+ resource: transaction.resource,
113
+ expiresAt: 10
114
+ };
115
+ await store.putAccessToken(accessToken);
116
+ assert(isDeepStrictEqual(await store.getAccessToken(accessToken.tokenHash), accessToken), "access tokens must round-trip");
117
+ await store.revokeToken(accessToken.tokenHash, 5);
118
+ assert((await store.getAccessToken(accessToken.tokenHash))?.revokedAt === 5, "access-token revocation must persist");
119
+ const refreshToken = {
120
+ tokenHash: "refresh-1",
121
+ familyId: "family-1",
122
+ grantId: grant.id,
123
+ clientId: client.id,
124
+ subject: firstSubject,
125
+ resource: transaction.resource,
126
+ scopes: grant.scopes,
127
+ createdAt: 1,
128
+ expiresAt: 10,
129
+ status: "active"
130
+ };
131
+ await store.putRefreshToken(refreshToken);
132
+ const rotation = await store.rotateRefreshToken(refreshToken.tokenHash, "refresh-2", 2, 20);
133
+ assert(rotation.status === "rotated" && isDeepStrictEqual(rotation.previous, refreshToken), "refresh tokens must rotate atomically");
134
+ assert((await store.rotateRefreshToken(refreshToken.tokenHash, "refresh-3", 3, 20)).status ===
135
+ "replay", "refresh-token replay must be detected");
136
+ await store.revokeGrant(grant.id, 6);
137
+ assert((await store.getGrant(grant.id))?.revokedAt === 6, "grant revocation must persist");
138
+ await storage.healthCheck?.();
139
+ await storage.cleanup?.(Date.now());
140
+ }
@@ -2,3 +2,4 @@ export { createCommandTestHarness, type CommandTestHarness, type ConfirmationReq
2
2
  export { fakeFetch, fakeService, type FetchRoute, type ServiceCall } from "./fakes.js";
3
3
  export { createMemoryFs, type FsChange, type MemoryFs } from "./memory-fs.js";
4
4
  export type { ParityResult, SurfaceOutcome } from "./parity.js";
5
+ export { verifyHostedOAuthStorage, type HostedOAuthStorageConformanceOptions } from "./hosted-oauth-storage.js";
@@ -1,3 +1,4 @@
1
1
  export { createCommandTestHarness } from "./harness.js";
2
2
  export { fakeFetch, fakeService } from "./fakes.js";
3
3
  export { createMemoryFs } from "./memory-fs.js";
4
+ export { verifyHostedOAuthStorage } from "./hosted-oauth-storage.js";
@@ -63,6 +63,7 @@ export type RefreshTokenRotationResult = {
63
63
  previous: RefreshTokenRecord;
64
64
  } | {
65
65
  status: "replay";
66
+ grant?: AuthorizationGrantRecord;
66
67
  } | {
67
68
  status: "invalid";
68
69
  };
@@ -79,7 +80,7 @@ export interface AuthorizationServerStore {
79
80
  getAccessToken(tokenHash: string): Promise<AccessTokenRecord | undefined>;
80
81
  putRefreshToken(token: RefreshTokenRecord): Promise<void>;
81
82
  rotateRefreshToken(tokenHash: string, replacementTokenHash: string, now: number, expiresAt: number): Promise<RefreshTokenRotationResult>;
82
- revokeToken(tokenHash: string, now: number): Promise<void>;
83
+ revokeToken(tokenHash: string, now: number): Promise<void | AuthorizationGrantRecord>;
83
84
  revokeGrant(grantId: string, now: number): Promise<void>;
84
85
  }
85
86
  export interface AuthorizationInteractionStartContext {
@@ -111,6 +112,7 @@ export interface OAuthAuthorizationServerOptions {
111
112
  maxRequestBodyBytes?: number;
112
113
  now?: () => number;
113
114
  randomToken?: () => string;
115
+ onGrantRevoked?(grant: AuthorizationGrantRecord): Promise<void> | void;
114
116
  }
115
117
  export interface CompleteAuthorizationInput {
116
118
  transactionId: string;
@@ -206,7 +206,11 @@ export function createInMemoryAuthorizationServerStore() {
206
206
  }
207
207
  if (token.status === "rotated") {
208
208
  revokeFamily(token.familyId, now);
209
- return { status: "replay" };
209
+ const grant = grants.get(token.grantId);
210
+ return {
211
+ status: "replay",
212
+ ...(grant === undefined ? {} : { grant: structuredClone(grant) })
213
+ };
210
214
  }
211
215
  refreshTokens.set(tokenHash, { ...token, status: "rotated" });
212
216
  refreshTokens.set(replacementTokenHash, {
@@ -220,13 +224,19 @@ export function createInMemoryAuthorizationServerStore() {
220
224
  },
221
225
  async revokeToken(tokenHash, now) {
222
226
  const refreshToken = refreshTokens.get(tokenHash);
227
+ const accessToken = accessTokens.get(tokenHash);
228
+ const grantId = refreshToken?.grantId ?? accessToken?.grantId;
229
+ const grant = grantId === undefined ? undefined : grants.get(grantId);
230
+ const alreadyRevoked = grant?.revokedAt !== undefined ||
231
+ refreshToken?.status === "revoked" ||
232
+ accessToken?.revokedAt !== undefined;
223
233
  if (refreshToken !== undefined) {
224
234
  revokeFamily(refreshToken.familyId, now);
225
235
  }
226
- const accessToken = accessTokens.get(tokenHash);
227
236
  if (accessToken !== undefined) {
228
237
  accessTokens.set(tokenHash, { ...accessToken, revokedAt: now });
229
238
  }
239
+ return grant === undefined || alreadyRevoked ? undefined : structuredClone(grant);
230
240
  },
231
241
  async revokeGrant(grantId, now) {
232
242
  const grant = grants.get(grantId);
@@ -258,6 +268,10 @@ export function createOAuthAuthorizationServer(options) {
258
268
  }
259
269
  const now = options.now ?? Date.now;
260
270
  const randomToken = options.randomToken ?? opaqueToken;
271
+ async function notifyGrantRevoked(grant) {
272
+ if (grant !== undefined)
273
+ await options.onGrantRevoked?.(grant);
274
+ }
261
275
  const accessTokenTtlMs = (options.accessTokenTtlSeconds ?? 300) * 1000;
262
276
  const authorizationCodeTtlMs = (options.authorizationCodeTtlSeconds ?? 60) * 1000;
263
277
  const authorizationTransactionTtlMs = (options.authorizationTransactionTtlSeconds ?? 600) * 1000;
@@ -534,11 +548,15 @@ export function createOAuthAuthorizationServer(options) {
534
548
  const tokenHash = hashToken(refreshToken);
535
549
  const existing = await options.store.rotateRefreshToken(tokenHash, replacementTokenHash, currentTime, currentTime + refreshTokenTtlMs);
536
550
  if (existing.status !== "rotated") {
551
+ if (existing.status === "replay")
552
+ await notifyGrantRevoked(existing.grant);
537
553
  throw new OAuthProtocolError("invalid_grant", "Refresh token is invalid or replayed.");
538
554
  }
539
555
  if (existing.previous.clientId !== body.get("client_id") ||
540
556
  existing.previous.resource !== requestedResource) {
557
+ const revokedGrant = await options.store.getGrant(existing.previous.grantId);
541
558
  await options.store.revokeGrant(existing.previous.grantId, currentTime);
559
+ await notifyGrantRevoked(revokedGrant);
542
560
  throw new OAuthProtocolError("invalid_grant", "Refresh token binding is invalid.");
543
561
  }
544
562
  const grant = await options.store.getGrant(existing.previous.grantId);
@@ -563,8 +581,9 @@ export function createOAuthAuthorizationServer(options) {
563
581
  requireFormContentType(request);
564
582
  const body = new URLSearchParams(await readRequestBody(request));
565
583
  const token = body.get("token");
566
- if (token !== null)
567
- await options.store.revokeToken(hashToken(token), now());
584
+ if (token !== null) {
585
+ await notifyGrantRevoked(await options.store.revokeToken(hashToken(token), now()));
586
+ }
568
587
  return new Response(null, { status: 200, headers: { "cache-control": "no-store" } });
569
588
  }
570
589
  async function handle(request) {
@@ -655,7 +674,9 @@ export function createOAuthAuthorizationServer(options) {
655
674
  denyAuthorization,
656
675
  verifyAccessToken,
657
676
  async revokeGrant(grantId) {
677
+ const grant = await options.store.getGrant(grantId);
658
678
  await options.store.revokeGrant(grantId, now());
679
+ await notifyGrantRevoked(grant?.revokedAt === undefined ? grant : undefined);
659
680
  }
660
681
  };
661
682
  }
@@ -18,7 +18,7 @@
18
18
  },
19
19
  {
20
20
  "name": "tiny-http-mcp-server",
21
- "version": "0.1.6",
21
+ "version": "0.1.7",
22
22
  "license": "MIT"
23
23
  },
24
24
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiny-http-mcp-server",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Minimal MCP server over HTTP built on tiny-stdio-mcp-server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -304,6 +304,7 @@ declare class McpClient {
304
304
  }
305
305
  interface Tool {
306
306
  name: string;
307
+ title?: string;
307
308
  description?: string;
308
309
  inputSchema: Record<string, unknown>;
309
310
  outputSchema?: Record<string, unknown>;
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.146",
11
+ "version": "0.0.148",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.146",
3
+ "version": "0.0.148",
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.146",
3
+ "version": "0.0.148",
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.146",
161
+ "toolcraft-schema": "0.0.148",
162
162
  "toolcraft-design": "*",
163
163
  "@poe-code/frontmatter": "*",
164
164
  "@poe-code/agent-mcp-config": "*",