@ambasada/village 0.8.15 → 0.8.16

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/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Ambasada, type AmbasadaOptions } from "@ambasada/sdk/platform";
2
2
  import { type IncomingRequest } from "./receipt.js";
3
- import type { JobAnswer, AgentConfigInput, BuyOptions, BuyResult, ChatRequest, ChatResponse, CheckoutInput, CheckoutReceiptClaims, CheckoutDelivery, CheckoutDeliveryInput, CheckoutSession, ImageRequest, ImageResult, LedgerPage, Listing, ListingInput, MarketOffering, PaidCall, PaymentLink, PaymentLinkInput, ToolListing, ToolResult, TranscriptEntry, AgentAlert, AgentMode, AgentState, AgentLogEntry, TalkDeal, TalkMessage, TalkThread, ChatLine, ChatProposal, ChatDecision, ChatTurn, TransferOptions, Wallet, WebhookDelivery, WebhookEvent } from "./types.js";
3
+ import type { JobAnswer, AgentConfigInput, BuyOptions, BuyResult, ChatRequest, ChatResponse, CheckoutInput, CheckoutReceiptClaims, CheckoutDelivery, CheckoutDeliveryInput, CheckoutSession, ImageRequest, ImageResult, LedgerPage, Listing, ListingInput, MarketOffering, PaidCall, PaymentLink, PaymentLinkInput, ToolListing, ToolResult, TranscriptEntry, AgentAlert, AgentSkill, McpServer, McpServerSetting, McpServerTest, AgentMode, AgentState, AgentLogEntry, TalkDeal, TalkMessage, TalkThread, ChatLine, ChatProposal, ChatDecision, ChatTurn, TransferOptions, Wallet, WebhookDelivery, WebhookEvent } from "./types.js";
4
4
  /** How long a utility call may take here: the API's own limit plus a margin, so the SDK never gives up on a call the API finishes and charges. */
5
5
  export declare const CLIENT_TIMEOUT_MS: {
6
6
  readonly llm: number;
@@ -286,6 +286,29 @@ export declare class Village extends Ambasada {
286
286
  setMode: (mode: AgentMode) => Promise<{
287
287
  config: unknown;
288
288
  }>;
289
+ /** Your agent's MCP servers (docs/AGENT-CHAT.md "Reach"); `add` lists the server's tools at once. */
290
+ mcp: {
291
+ list: () => Promise<McpServer[]>;
292
+ add: (name: string, server: McpServerSetting) => Promise<McpServerTest>;
293
+ test: (name: string) => Promise<McpServerTest>;
294
+ remove: (name: string) => Promise<{
295
+ deleted: boolean;
296
+ }>;
297
+ };
298
+ /** Your agent's skills: yours and the village's default (village-trading) unless you replaced it. */
299
+ skills: {
300
+ list: () => Promise<AgentSkill[]>;
301
+ get: (name: string) => Promise<{
302
+ name: string;
303
+ text: string;
304
+ source: AgentSkill["source"];
305
+ }>;
306
+ /** The whole SKILL.md. */
307
+ put: (name: string, text: string) => Promise<AgentSkill>;
308
+ remove: (name: string) => Promise<{
309
+ deleted: boolean;
310
+ }>;
311
+ };
289
312
  /** The Log: every wake with its transcript, steer, whispers, settings and approved proposals; 50 a page, newest first. */
290
313
  log: (page?: number, kind?: "all" | "wakes" | "money" | "talks" | "issues") => Promise<{
291
314
  entries: AgentLogEntry[];
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export { verifyWebhookPayload, webhookHeaders, generateWebhookSecret, WEBHOOK_HE
19
19
  export { RECEIPT_HEADER, readIncoming, verifyCallReceipt, verifyCheckoutReceiptToken } from "./receipt.js";
20
20
  export type { IncomingRequest } from "./receipt.js";
21
21
  export { defineCompany, offering } from "./define.js";
22
+ export { mcpEndpoint, mcpTool } from "./mcp.js";
23
+ export type { McpEndpointOptions, McpToolDef } from "./mcp.js";
22
24
  export type { CompanyConfig, OfferingDef, VillageConfig } from "./define.js";
23
25
  export { Ambasada, AmbasadaError, isAmbasadaError, defineApp, ERROR_STATUS, WebhookInvalidError } from "@ambasada/sdk/platform";
24
26
  export type { AmbasadaUser, AppConfig, ErrorCode, ErrorDetails, Jwk, StoredFile, StoreList, StoreWrite, WebhookFailure } from "@ambasada/sdk/platform";
package/dist/index.js CHANGED
@@ -1837,9 +1837,9 @@ var Village = class extends Ambasada {
1837
1837
  if (error instanceof AmbasadaError3) return error.toResponse();
1838
1838
  throw error;
1839
1839
  }
1840
- const answer = await lookup(id, req);
1841
- if (!answer) return Response.json({ error: { code: "NOT_FOUND", message: `no job ${id}` } }, { status: 404 });
1842
- return Response.json(answer, { status: answer.status === "working" ? 202 : 200 });
1840
+ const answer2 = await lookup(id, req);
1841
+ if (!answer2) return Response.json({ error: { code: "NOT_FOUND", message: `no job ${id}` } }, { status: 404 });
1842
+ return Response.json(answer2, { status: answer2.status === "working" ? 202 : 200 });
1843
1843
  };
1844
1844
  }
1845
1845
  /** Verifies a signed webhook (`checkout.paid`, `webhook.test`) and returns the event. Throws `WebhookInvalidError`. */
@@ -2100,6 +2100,21 @@ var Village = class extends Ambasada {
2100
2100
  seeAlerts: () => this.call("POST", "/v1/agent/alerts/seen", { body: {} }),
2101
2101
  /** Manual, Ask or Auto (docs/AGENT-CHAT.md v3); the rest of the config stays. */
2102
2102
  setMode: (mode) => this.call("POST", "/v1/agent/config", { body: { mode, wake_now: false } }),
2103
+ /** Your agent's MCP servers (docs/AGENT-CHAT.md "Reach"); `add` lists the server's tools at once. */
2104
+ mcp: {
2105
+ list: async () => (await this.call("GET", "/v1/agent/mcp")).servers,
2106
+ add: (name, server) => this.call("PUT", `/v1/agent/mcp/${encodeURIComponent(name)}`, { body: server }),
2107
+ test: (name) => this.call("POST", `/v1/agent/mcp/${encodeURIComponent(name)}/test`, { body: {} }),
2108
+ remove: (name) => this.call("DELETE", `/v1/agent/mcp/${encodeURIComponent(name)}`)
2109
+ },
2110
+ /** Your agent's skills: yours and the village's default (village-trading) unless you replaced it. */
2111
+ skills: {
2112
+ list: async () => (await this.call("GET", "/v1/agent/skills")).skills,
2113
+ get: (name) => this.call("GET", `/v1/agent/skills/${encodeURIComponent(name)}`),
2114
+ /** The whole SKILL.md. */
2115
+ put: (name, text) => this.call("PUT", `/v1/agent/skills/${encodeURIComponent(name)}`, { body: { text } }),
2116
+ remove: (name) => this.call("DELETE", `/v1/agent/skills/${encodeURIComponent(name)}`)
2117
+ },
2103
2118
  /** The Log: every wake with its transcript, steer, whispers, settings and approved proposals; 50 a page, newest first. */
2104
2119
  log: (page = 1, kind = "all") => this.call("GET", "/v1/agent/log", { query: { page, kind } })
2105
2120
  };
@@ -2138,6 +2153,71 @@ function offering(def) {
2138
2153
  return def;
2139
2154
  }
2140
2155
 
2156
+ // src/mcp.ts
2157
+ function mcpTool(def) {
2158
+ if (!/^[a-zA-Z0-9_-]{1,64}$/.test(def.name)) throw new Error(`mcpTool: "${def.name}" must be letters, digits, _ and -, up to 64`);
2159
+ return def;
2160
+ }
2161
+ var VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
2162
+ function mcpEndpoint(o) {
2163
+ return async (req) => {
2164
+ if (req.method !== "POST") return new Response("POST JSON-RPC here (MCP Streamable HTTP)", { status: 405, headers: { allow: "POST" } });
2165
+ if (!o.token) return Response.json({ error: "AMBASADA_MCP_TOKEN is not set: `ambasada deploy` sets it" }, { status: 503 });
2166
+ if (!sameText(req.headers.get("authorization") ?? "", `Bearer ${o.token}`)) return Response.json({ error: "unauthorized" }, { status: 401, headers: { "www-authenticate": "Bearer" } });
2167
+ let body;
2168
+ try {
2169
+ body = await req.json();
2170
+ } catch {
2171
+ return Response.json(rpcError(null, -32700, "not JSON"), { status: 400 });
2172
+ }
2173
+ const batch = Array.isArray(body) ? body : [body];
2174
+ const answers = (await Promise.all(batch.map((m) => answer(o, m, req)))).filter((a) => a !== null);
2175
+ if (!answers.length) return new Response(null, { status: 202 });
2176
+ return Response.json(Array.isArray(body) ? answers : answers[0]);
2177
+ };
2178
+ }
2179
+ async function answer(o, m, req) {
2180
+ if (!m || typeof m.method !== "string") return rpcError(m?.id ?? null, -32600, "not a JSON-RPC request");
2181
+ if (m.id === void 0 || m.id === null) return null;
2182
+ const ok = (result) => ({ jsonrpc: "2.0", id: m.id, result });
2183
+ switch (m.method) {
2184
+ case "initialize": {
2185
+ const asked = typeof m.params?.protocolVersion === "string" ? m.params.protocolVersion : VERSIONS[0];
2186
+ return ok({ protocolVersion: VERSIONS.includes(asked) ? asked : VERSIONS[0], capabilities: { tools: {} }, serverInfo: { name: o.name, version: o.version ?? "1.0.0" } });
2187
+ }
2188
+ case "ping":
2189
+ return ok({});
2190
+ case "tools/list":
2191
+ return ok({
2192
+ tools: o.tools.map((t) => ({
2193
+ name: t.name,
2194
+ description: t.description,
2195
+ inputSchema: { type: "object", properties: t.input ?? {}, ...t.required?.length ? { required: t.required } : {} },
2196
+ annotations: { readOnlyHint: t.readOnly === true }
2197
+ }))
2198
+ });
2199
+ case "tools/call": {
2200
+ const tool = o.tools.find((t) => t.name === m.params?.name);
2201
+ if (!tool) return rpcError(m.id, -32602, `no tool ${String(m.params?.name)}`);
2202
+ try {
2203
+ const out = await tool.run(m.params?.arguments ?? {}, req);
2204
+ if (typeof out === "string") return ok({ content: [{ type: "text", text: out }] });
2205
+ return ok({ content: [{ type: "text", text: JSON.stringify(out ?? null) }], ...out && typeof out === "object" && !Array.isArray(out) ? { structuredContent: out } : {} });
2206
+ } catch (error) {
2207
+ return ok({ content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true });
2208
+ }
2209
+ }
2210
+ default:
2211
+ return rpcError(m.id, -32601, `no method ${m.method}`);
2212
+ }
2213
+ }
2214
+ var rpcError = (id, code, message) => ({ jsonrpc: "2.0", id, error: { code, message } });
2215
+ function sameText(a, b) {
2216
+ let diff = a.length ^ b.length;
2217
+ for (let i = 0; i < Math.max(a.length, b.length); i++) diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
2218
+ return diff === 0;
2219
+ }
2220
+
2141
2221
  // src/index.ts
2142
2222
  import { Ambasada as Ambasada2, AmbasadaError as AmbasadaError4, isAmbasadaError as isAmbasadaError2, defineApp, ERROR_STATUS as ERROR_STATUS2, WebhookInvalidError as WebhookInvalidError2 } from "@ambasada/sdk/platform";
2143
2223
  var village = new Village();
@@ -2161,6 +2241,8 @@ export {
2161
2241
  generateWebhookSecret2 as generateWebhookSecret,
2162
2242
  isAmbasadaError2 as isAmbasadaError,
2163
2243
  isVillageError,
2244
+ mcpEndpoint,
2245
+ mcpTool,
2164
2246
  offering,
2165
2247
  readIncoming,
2166
2248
  verifyCallReceipt,
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Your app as an MCP server for your company's agent (docs/AGENT-CHAT.md "Reach"): `POST /mcp` in the templates.
3
+ *
4
+ * export const POST = mcpEndpoint({ name: "flowers", token: env.AMBASADA_MCP_TOKEN, tools: [
5
+ * mcpTool({ name: "check_stock", description: "Roses, tulips… in stock", input: { flower: { type: "string" } },
6
+ * readOnly: true, run: async ({ flower }) => ({ count: await stock(flower) }) }),
7
+ * ] });
8
+ *
9
+ * `ambasada deploy` sets AMBASADA_MCP_TOKEN on your app and gives your agent the same token, so `"mcp": { "app": {
10
+ * "url": "/mcp" } }` in agent/settings.json is all the wiring. A tool marked `readOnly` runs in every mode; any other is
11
+ * a change, which your agent asks for first unless it is on Auto. Streamable HTTP, JSON answers only (no SSE, no
12
+ * sessions): enough for tools. No dependency.
13
+ */
14
+ export interface McpToolDef<Input = Record<string, unknown>> {
15
+ /** Letters, digits, _ and -: your agent sees `<server>__<name>`. */
16
+ name: string;
17
+ /** What it does and when to use it: your agent reads this. */
18
+ description: string;
19
+ /** JSON Schema properties of the input object. */
20
+ input?: Record<string, unknown>;
21
+ required?: string[];
22
+ /** Only reads: your agent may call it in every mode. Leave it off for anything that changes something. */
23
+ readOnly?: boolean;
24
+ /** A string answers as text; anything else as JSON (and structured content). Throw to answer with an error. */
25
+ run(input: Input, req: Request): unknown;
26
+ }
27
+ export declare function mcpTool<Input = Record<string, unknown>>(def: McpToolDef<Input>): McpToolDef<Input>;
28
+ export interface McpEndpointOptions {
29
+ /** Your server's name, shown in `ambasada village mcp test`. */
30
+ name: string;
31
+ version?: string;
32
+ /** The bearer token callers must send: `env.AMBASADA_MCP_TOKEN` (set by `ambasada deploy`). Unset: every call is refused. */
33
+ token: string | undefined;
34
+ /** Your tools (their inputs differ). */
35
+ tools: McpToolDef<any>[];
36
+ }
37
+ /** A fetch handler for `POST /mcp`. */
38
+ export declare function mcpEndpoint(o: McpEndpointOptions): (req: Request) => Promise<Response>;
package/dist/types.d.ts CHANGED
@@ -356,6 +356,8 @@ export interface AgentSettings {
356
356
  * purchases. Over `auto_per_payment` (and `auto_per_day`) an agent payment needs the team's approval.
357
357
  */
358
358
  company_limits?: AgentSettings["limits"];
359
+ /** Your own MCP servers your agent may use (docs/AGENT-CHAT.md "Reach"): `{ "<name>": { "url": "https://…", "token": "env:NAME" } }`. */
360
+ mcp?: Record<string, McpServerSetting> | null;
359
361
  /** @deprecated The 0.8 name of `company_limits` (they were always the company's, not only the agent's). */
360
362
  limits?: {
361
363
  per_payment?: number;
@@ -370,6 +372,8 @@ export interface AgentSettings {
370
372
  export interface AgentConfigInput extends AgentSettings {
371
373
  /** The text of agent/agent.md. */
372
374
  prompt?: string;
375
+ /** agent/skills/<name>/SKILL.md by name: the deploy's skills replace the ones the last deploy put there. */
376
+ skills?: Record<string, string>;
373
377
  /** Wake the agent once after saving. */
374
378
  wake_now?: boolean;
375
379
  }
@@ -395,6 +399,46 @@ export interface ChatProposal {
395
399
  /** A wake's own reason. */
396
400
  reason?: string | null;
397
401
  }
402
+ /**
403
+ * One of your agent's MCP servers (docs/AGENT-CHAT.md "Reach"), as `agent/settings.json` has it: `url` is https, or a
404
+ * path on your hosted app (`/mcp`, with the platform's token); `token` only as `env:NAME` (`ambasada env set NAME …`).
405
+ */
406
+ export interface McpServerSetting {
407
+ url: string;
408
+ token?: `env:${string}`;
409
+ }
410
+ /** A server as GET /v1/agent/mcp shows it: what its last listing found. */
411
+ export interface McpServer {
412
+ name: string;
413
+ url: string;
414
+ /** The reference (`env:NAME`), never a value. */
415
+ token: string | null;
416
+ status: "ok" | "error" | "unknown";
417
+ error: string | null;
418
+ tools: number;
419
+ read_only: number;
420
+ checked_at: string | null;
421
+ }
422
+ /** A server listed now (`village mcp add` / `test`): its tools as your agent sees them (`<server>__<tool>`). */
423
+ export interface McpServerTest {
424
+ name: string;
425
+ url: string;
426
+ status: "ok" | "error";
427
+ error: string | null;
428
+ tools: {
429
+ name: string;
430
+ fn: string;
431
+ description: string;
432
+ read_only: boolean;
433
+ }[];
434
+ }
435
+ /** One of your agent's skills: yours (from the folder, the web app or the agent itself) or the village's default. */
436
+ export interface AgentSkill {
437
+ name: string;
438
+ description: string;
439
+ source: "deploy" | "web" | "agent" | "default";
440
+ updated_at: string | null;
441
+ }
398
442
  /** How much the agent does alone (docs/AGENT-CHAT.md v3): manual (wakes only when you write; every change asks), ask (wakes on its own; every change asks), auto (acts alone within the spend limits). */
399
443
  export type AgentMode = "manual" | "ask" | "auto";
400
444
  /** One alert (docs/AGENT-CHAT.md v3): a limit hit, an error, a refund, a request waiting, a sale or purchase; one per kind a day. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ambasada/village",
3
- "version": "0.8.15",
3
+ "version": "0.8.16",
4
4
  "description": "The Hack A Ton add-on for Ambasada apps: TON checkout, receipts, offerings, LLM, the agent, the market; CLI commands and the local bank.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@
27
27
  "node": ">=22"
28
28
  },
29
29
  "dependencies": {
30
- "@ambasada/sdk": "^0.8.15"
30
+ "@ambasada/sdk": "^0.8.16"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^22.20.5",
@@ -35,7 +35,7 @@
35
35
  "typescript": "~5.9.3",
36
36
  "vitest": "^4.1.12",
37
37
  "@ambasada/protocol": "0.1.0",
38
- "@ambasada/cli": "0.8.15"
38
+ "@ambasada/cli": "0.8.16"
39
39
  },
40
40
  "author": "CENTYSO (https://centyso.com)",
41
41
  "homepage": "https://ambasada.id",