@datar-platform/cli 0.1.0-alpha.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.
- package/README.md +131 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +50 -0
- package/dist/config.js.map +1 -0
- package/dist/http.d.ts +9 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +22 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/registry.d.ts +26 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +18 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/server.d.ts +20 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +75 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/ask.d.ts +14 -0
- package/dist/mcp/tools/ask.d.ts.map +1 -0
- package/dist/mcp/tools/ask.js +66 -0
- package/dist/mcp/tools/ask.js.map +1 -0
- package/dist/mcp/tools/discovery.d.ts +10 -0
- package/dist/mcp/tools/discovery.d.ts.map +1 -0
- package/dist/mcp/tools/discovery.js +76 -0
- package/dist/mcp/tools/discovery.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +23 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +29 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/query.d.ts +16 -0
- package/dist/mcp/tools/query.d.ts.map +1 -0
- package/dist/mcp/tools/query.js +87 -0
- package/dist/mcp/tools/query.js.map +1 -0
- package/dist/mcp/tools/reads.d.ts +9 -0
- package/dist/mcp/tools/reads.d.ts.map +1 -0
- package/dist/mcp/tools/reads.js +177 -0
- package/dist/mcp/tools/reads.js.map +1 -0
- package/package.json +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @datar-platform/cli
|
|
2
|
+
|
|
3
|
+
The **`datar` CLI** and **MCP server** — connect any AI harness (Claude Code, Cursor,
|
|
4
|
+
etc.) to a Datar organization with a single API token, the way you connect a CLI to a
|
|
5
|
+
cloud account. The token is bound server-side to one org and a set of scopes, so once you
|
|
6
|
+
authenticate, the harness already knows _which org_ it's in and _what it may do_ — no org
|
|
7
|
+
selection, no endpoint guessing.
|
|
8
|
+
|
|
9
|
+
Built entirely on [`@datar-platform/sdk`](../sdk). **v1 is read + discovery only,**
|
|
10
|
+
available over two transports that share one tool registry: a **local stdio** server
|
|
11
|
+
(`datar mcp`, this package) and a **remote Streamable HTTP** endpoint
|
|
12
|
+
(`/api/mcp` in the web app — no local install needed, just a token).
|
|
13
|
+
|
|
14
|
+
## Install & authenticate
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add -g @datar-platform/cli # or run via npx
|
|
18
|
+
|
|
19
|
+
export DATAR_API_TOKEN=dt_... # mint at <baseUrl>/settings/api-tokens
|
|
20
|
+
export DATAR_BASE_URL=https://acme.datar.co.za # or http://localhost:3000 for dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### How read-only is actually enforced
|
|
24
|
+
|
|
25
|
+
The read-only guarantee is **client-side**, not scope-based:
|
|
26
|
+
|
|
27
|
+
- Every discovery/read tool and `datar_query` issue **HTTP GET** only, and tRPC refuses
|
|
28
|
+
mutations over GET — so they physically cannot write, whatever the token's scopes are.
|
|
29
|
+
- `datar_ask` is the **only** tool that can act (it drives an A2A agent, which may call
|
|
30
|
+
mutating endpoints). It is **opt-in** — registered only when `DATAR_MCP_ALLOW_AGENT=1`.
|
|
31
|
+
Default off ⇒ the whole surface is provably read-only.
|
|
32
|
+
|
|
33
|
+
> ⚠️ **Do not rely on token scopes alone for read-only.** Historically most endpoints were
|
|
34
|
+
> built on `serviceProcedure`, which did **not** run the API-token scope-enforcement
|
|
35
|
+
> middleware (only `orgProtectedProcedure` did) — a "read-scoped" `dt_` token could still
|
|
36
|
+
> write through those endpoints. This has since been fixed: `serviceProcedure`,
|
|
37
|
+
> `serviceAdminProcedure`, `memberRoleProcedure`, and `marketplaceRoleProcedure` all now run
|
|
38
|
+
> `.use(apiScopeMiddleware)` (`packages/api/src/trpc.ts`), with a regression test at
|
|
39
|
+
> `packages/api/tests/services/trpc.service-procedure-scope.test.ts`. Scope-based read-only
|
|
40
|
+
> is now real, but this client-side guarantee (GET-only tools, `datar_ask` off) remains the
|
|
41
|
+
> primary one — treat token scoping as defense-in-depth on top of it, not a replacement.
|
|
42
|
+
|
|
43
|
+
## Use as a CLI
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
datar whoami # the org/user this token resolves to
|
|
47
|
+
datar ls # list Datar service agents
|
|
48
|
+
datar ask "how many open tenders do we have?" # orchestrator routes it
|
|
49
|
+
datar ask procurement "list my draft submissions" # target one agent
|
|
50
|
+
datar query v2.people.org.getActiveMembers # any read procedure by path
|
|
51
|
+
datar projects ls # quick record listings
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Use as an MCP server (Claude Code)
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
claude mcp add datar \
|
|
58
|
+
--env DATAR_API_TOKEN=dt_... \
|
|
59
|
+
--env DATAR_BASE_URL=https://acme.datar.co.za \
|
|
60
|
+
-- npx -y @datar-platform/cli mcp
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then ask Claude things like _"list Datar services"_, _"show my projects"_, or _"what
|
|
64
|
+
PayFast-backed transactions came in this month?"_ — it will call the tools below.
|
|
65
|
+
|
|
66
|
+
## Use remotely — no local install (Streamable HTTP)
|
|
67
|
+
|
|
68
|
+
Any MCP-capable harness can connect straight to a Datar tenant with nothing but a token —
|
|
69
|
+
`apps/web/app/api/mcp/route.ts` exposes the **same tool registry** over the MCP Streamable
|
|
70
|
+
HTTP transport (stateless — a fresh `McpServer` per request, as the SDK recommends for
|
|
71
|
+
serverless):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
claude mcp add datar-remote --transport http \
|
|
75
|
+
https://acme.datar.co.za/api/mcp \
|
|
76
|
+
--header "Authorization: Bearer dt_..."
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Auth is the same bearer-token model as everywhere else in the platform (`dt_` API token /
|
|
80
|
+
`sk_` access key), validated with the same check the A2A gateway uses
|
|
81
|
+
(`resolveA2AAuth`) — a missing/invalid token gets a `401` before any MCP handling happens.
|
|
82
|
+
Every tool call then re-enters the app's own tRPC endpoints under the caller's real
|
|
83
|
+
identity, exactly like the local CLI does (the SDK client is just pointed at the request's
|
|
84
|
+
own origin instead of an env var). Same `DATAR_MCP_ALLOW_AGENT=1` gate controls whether
|
|
85
|
+
`datar_ask` is registered on the remote endpoint too.
|
|
86
|
+
|
|
87
|
+
### Tools exposed (26 read-only by default; +`datar_ask` = 27 with `DATAR_MCP_ALLOW_AGENT=1`)
|
|
88
|
+
|
|
89
|
+
- **Discovery** — `datar_whoami`, `datar_list_services`, `datar_describe_service`
|
|
90
|
+
(skills + input/output JSON schemas from each agent card — the catalog that makes
|
|
91
|
+
downstream calls precise).
|
|
92
|
+
- **Typed reads** (deterministic, no nested-LLM cost) — `drive_list_files`,
|
|
93
|
+
`drive_get_file`, `drive_list_folders`, `projects_list`, `projects_get`,
|
|
94
|
+
`projects_metrics`, `tasks_list`, `people_list`, `requests_list`,
|
|
95
|
+
`requests_pending_approvals`, `transact_list_accounts`, `transact_list_transactions`,
|
|
96
|
+
`procurement_list_tenders`, `procurement_list_public_tenders`,
|
|
97
|
+
`procurement_list_suppliers`, `inventory_list_items`, `inventory_low_stock`,
|
|
98
|
+
`isms_list_risks`, `isms_list_controls`, `invoicing_list_invoices`,
|
|
99
|
+
`invoicing_list_clients`, `messages_list`.
|
|
100
|
+
- **Natural-language fallback** — `datar_ask` (forwards to any agent / the orchestrator;
|
|
101
|
+
reuses tool-calling, KB grounding, cross-service routing). **Opt-in / can act:** only
|
|
102
|
+
registered when `DATAR_MCP_ALLOW_AGENT=1`; omitted by default so the surface stays read-only.
|
|
103
|
+
- **Read escape hatch** — `datar_query` (any `v2.*` tRPC query by path; mutation-looking
|
|
104
|
+
paths are refused, and `client.query()` only issues GETs, which tRPC never serves for
|
|
105
|
+
mutations).
|
|
106
|
+
|
|
107
|
+
## Verify locally
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pnpm -F @datar-platform/cli build && pnpm -F @datar-platform/cli test
|
|
111
|
+
# Drive it interactively with the MCP inspector:
|
|
112
|
+
npx @modelcontextprotocol/inspector node packages/cli/dist/index.js mcp
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Architecture notes
|
|
116
|
+
|
|
117
|
+
- The tool registry (`src/mcp/tools/`, exported as `@datar-platform/cli/mcp/tools`) is
|
|
118
|
+
**transport-agnostic** — a `ToolDef[]` with no MCP/stdio/HTTP types. `src/mcp/server.ts`'s
|
|
119
|
+
`createDatarMcpServer(client, tools)` (exported as `@datar-platform/cli/mcp`) builds an
|
|
120
|
+
`McpServer` from it and is reused verbatim by both transports:
|
|
121
|
+
- `startStdioServer()` (this package's `datar mcp` command) — `StdioServerTransport`.
|
|
122
|
+
- `apps/web/app/api/mcp/route.ts` — `WebStandardStreamableHTTPServerTransport`, stateless
|
|
123
|
+
(fresh server + transport per request; the SDK's own guidance for serverless/Fluid
|
|
124
|
+
Compute — reusing one across requests causes message-ID collisions between clients).
|
|
125
|
+
- Everything routes through one `Datar` SDK client. No direct DynamoDB / tRPC-server
|
|
126
|
+
access; the CLI (and the remote endpoint) are pure API consumers — the remote endpoint's
|
|
127
|
+
client just points at the incoming request's own origin instead of an env var.
|
|
128
|
+
|
|
129
|
+
**Roadmap (not in v1):** curated writes behind `api.*.write` scopes (once scope enforcement
|
|
130
|
+
lands platform-wide, see the warning above) · per-tenant MCP-token onboarding UI + usage
|
|
131
|
+
metering · external connectors (multi-store Shopify, cloud-drive sources, CRM).
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the Datar API token + tenant base URL from the environment (and,
|
|
3
|
+
* optionally, a `~/.datar/config.json` written by `datar login`).
|
|
4
|
+
*
|
|
5
|
+
* Auth model: the `dt_` API token is bound server-side to an organization and
|
|
6
|
+
* a set of scopes, so once resolved here the caller's tenant and permissions
|
|
7
|
+
* are already fixed — no org selection needed on the client. Mint tokens in the
|
|
8
|
+
* Datar web UI at /settings/api-tokens. For the read-only MCP server, scope the
|
|
9
|
+
* token to `api.*.read` + `a2a.*` only; that is the real read-only guarantee.
|
|
10
|
+
*/
|
|
11
|
+
import type { DatarOptions } from "@datar-platform/sdk";
|
|
12
|
+
export interface ResolvedConfig extends DatarOptions {
|
|
13
|
+
/** Where the token/baseUrl were resolved from — for `datar whoami` output. */
|
|
14
|
+
source: "env" | "config-file";
|
|
15
|
+
}
|
|
16
|
+
declare const CONFIG_PATH: string;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve credentials with precedence: env vars first (so CI / MCP-host env
|
|
19
|
+
* always wins), then the on-disk config file. Throws a clear, actionable error
|
|
20
|
+
* when neither yields a usable token + baseUrl.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveConfig(): ResolvedConfig;
|
|
23
|
+
export { CONFIG_PATH };
|
|
24
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,cAAe,SAAQ,YAAY;IAClD,8EAA8E;IAC9E,MAAM,EAAE,KAAK,GAAG,aAAa,CAAC;CAC/B;AAED,QAAA,MAAM,WAAW,QAA2C,CAAC;AAe7D;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,cAAc,CA6B9C;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the Datar API token + tenant base URL from the environment (and,
|
|
3
|
+
* optionally, a `~/.datar/config.json` written by `datar login`).
|
|
4
|
+
*
|
|
5
|
+
* Auth model: the `dt_` API token is bound server-side to an organization and
|
|
6
|
+
* a set of scopes, so once resolved here the caller's tenant and permissions
|
|
7
|
+
* are already fixed — no org selection needed on the client. Mint tokens in the
|
|
8
|
+
* Datar web UI at /settings/api-tokens. For the read-only MCP server, scope the
|
|
9
|
+
* token to `api.*.read` + `a2a.*` only; that is the real read-only guarantee.
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
const CONFIG_PATH = join(homedir(), ".datar", "config.json");
|
|
15
|
+
function readFileConfig() {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve credentials with precedence: env vars first (so CI / MCP-host env
|
|
25
|
+
* always wins), then the on-disk config file. Throws a clear, actionable error
|
|
26
|
+
* when neither yields a usable token + baseUrl.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveConfig() {
|
|
29
|
+
const file = readFileConfig();
|
|
30
|
+
const apiToken = process.env.DATAR_API_TOKEN ?? file.apiToken;
|
|
31
|
+
const baseUrl = process.env.DATAR_BASE_URL ?? file.baseUrl;
|
|
32
|
+
const source = process.env.DATAR_API_TOKEN
|
|
33
|
+
? "env"
|
|
34
|
+
: "config-file";
|
|
35
|
+
if (!apiToken) {
|
|
36
|
+
throw new Error("No Datar API token found. Set DATAR_API_TOKEN (recommended for MCP) " +
|
|
37
|
+
`or run \`datar login\`. Mint a token at <baseUrl>/settings/api-tokens.`);
|
|
38
|
+
}
|
|
39
|
+
if (!baseUrl) {
|
|
40
|
+
throw new Error("No Datar base URL found. Set DATAR_BASE_URL (e.g. https://acme.datar.co.za " +
|
|
41
|
+
"or http://localhost:3000) or run `datar login`.");
|
|
42
|
+
}
|
|
43
|
+
if (!apiToken.startsWith("dt_")) {
|
|
44
|
+
// Non-fatal shape check — access keys (sk_) also work, but dt_ is the norm.
|
|
45
|
+
process.stderr.write(`[datar] warning: API token does not start with "dt_"; continuing anyway.\n`);
|
|
46
|
+
}
|
|
47
|
+
return { apiToken, baseUrl: baseUrl.replace(/\/$/, ""), source };
|
|
48
|
+
}
|
|
49
|
+
export { CONFIG_PATH };
|
|
50
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AASjC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;AAO7D,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAe,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAE9B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,QAAQ,CAAC;IAC9D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,OAAO,CAAC;IAC3D,MAAM,MAAM,GAA6B,OAAO,CAAC,GAAG,CAAC,eAAe;QAClE,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,aAAa,CAAC;IAElB,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,sEAAsE;YACpE,wEAAwE,CAC3E,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,6EAA6E;YAC3E,iDAAiD,CACpD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,4EAA4E;QAC5E,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,4EAA4E,CAC7E,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;AACnE,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal authenticated GET helper for Datar endpoints that are NOT tRPC —
|
|
3
|
+
* specifically the A2A agent-card surface (`/api/a2a/agents` and each agent's
|
|
4
|
+
* `/.well-known/agent.json`). tRPC calls go through the SDK's transport; this
|
|
5
|
+
* covers the discovery routes the SDK doesn't wrap.
|
|
6
|
+
*/
|
|
7
|
+
import type { ResolvedConfig } from "./config.js";
|
|
8
|
+
export declare function datarGet<T = unknown>(config: ResolvedConfig, path: string): Promise<T>;
|
|
9
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,wBAAsB,QAAQ,CAAC,CAAC,GAAG,OAAO,EACxC,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,CAAC,CAAC,CAgBZ"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal authenticated GET helper for Datar endpoints that are NOT tRPC —
|
|
3
|
+
* specifically the A2A agent-card surface (`/api/a2a/agents` and each agent's
|
|
4
|
+
* `/.well-known/agent.json`). tRPC calls go through the SDK's transport; this
|
|
5
|
+
* covers the discovery routes the SDK doesn't wrap.
|
|
6
|
+
*/
|
|
7
|
+
export async function datarGet(config, path) {
|
|
8
|
+
const url = `${config.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
9
|
+
const res = await fetch(url, {
|
|
10
|
+
method: "GET",
|
|
11
|
+
headers: {
|
|
12
|
+
Authorization: `Bearer ${config.apiToken}`,
|
|
13
|
+
Accept: "application/json",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok) {
|
|
17
|
+
const body = await res.text().catch(() => "");
|
|
18
|
+
throw new Error(`GET ${path} failed (${res.status}): ${body.slice(0, 300)}`);
|
|
19
|
+
}
|
|
20
|
+
return (await res.json());
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,MAAsB,EACtB,IAAY;IAEZ,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;IAC3E,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,MAAM,EAAE,KAAK;QACb,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,MAAM,CAAC,QAAQ,EAAE;YAC1C,MAAM,EAAE,kBAAkB;SAC3B;KACF,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,KAAK,CACb,OAAO,IAAI,YAAY,GAAG,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAC5D,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAC;AACjC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `datar` CLI entry point.
|
|
4
|
+
*
|
|
5
|
+
* Subcommands (v1, read + discovery only):
|
|
6
|
+
* datar mcp Start the stdio MCP server (for Claude Code etc.)
|
|
7
|
+
* datar whoami Show the org/user this token resolves to
|
|
8
|
+
* datar ls List available service agents
|
|
9
|
+
* datar ask [service] <text> Ask an agent (defaults to the orchestrator)
|
|
10
|
+
* datar query <path> [json] Call a tRPC query by path (read-only)
|
|
11
|
+
* datar <service> ls List a service's primary records (drive, projects, …)
|
|
12
|
+
*
|
|
13
|
+
* All commands authenticate with DATAR_API_TOKEN + DATAR_BASE_URL (or `datar login`).
|
|
14
|
+
*/
|
|
15
|
+
import { Datar } from "@datar-platform/sdk";
|
|
16
|
+
import { resolveConfig } from "./config.js";
|
|
17
|
+
import { startStdioServer } from "./mcp/server.js";
|
|
18
|
+
import { rejectIfMutation } from "./mcp/tools/query.js";
|
|
19
|
+
async function main(argv) {
|
|
20
|
+
const [cmd, ...rest] = argv;
|
|
21
|
+
switch (cmd) {
|
|
22
|
+
case undefined:
|
|
23
|
+
case "help":
|
|
24
|
+
case "-h":
|
|
25
|
+
case "--help":
|
|
26
|
+
return printHelp();
|
|
27
|
+
case "mcp":
|
|
28
|
+
return startStdioServer();
|
|
29
|
+
case "whoami": {
|
|
30
|
+
const { client, config } = connect();
|
|
31
|
+
const [profile, tenants] = await Promise.allSettled([
|
|
32
|
+
client.query("v2.auth.getProfile"),
|
|
33
|
+
client.query("v2.auth.resolveTenantsForSession"),
|
|
34
|
+
]);
|
|
35
|
+
print({
|
|
36
|
+
baseUrl: config.baseUrl,
|
|
37
|
+
credentialSource: config.source,
|
|
38
|
+
user: profile.status === "fulfilled" ? profile.value : null,
|
|
39
|
+
tenants: tenants.status === "fulfilled" ? tenants.value : null,
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
case "ls":
|
|
44
|
+
case "services": {
|
|
45
|
+
const { client } = connect();
|
|
46
|
+
print(await client.query("v2.ai.agents.listServices"));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
case "ask": {
|
|
50
|
+
const { client } = connect();
|
|
51
|
+
// `datar ask projects "how many open tasks"` OR `datar ask "…"` (orchestrator)
|
|
52
|
+
const known = new Set([
|
|
53
|
+
"orchestrator",
|
|
54
|
+
"drive",
|
|
55
|
+
"projects",
|
|
56
|
+
"people",
|
|
57
|
+
"requests",
|
|
58
|
+
"inventory",
|
|
59
|
+
"messages",
|
|
60
|
+
"social",
|
|
61
|
+
"procurement",
|
|
62
|
+
"transact",
|
|
63
|
+
"isms",
|
|
64
|
+
"advice",
|
|
65
|
+
]);
|
|
66
|
+
let service = "orchestrator";
|
|
67
|
+
let words = rest;
|
|
68
|
+
if (rest.length > 1 && known.has(rest[0])) {
|
|
69
|
+
service = rest[0];
|
|
70
|
+
words = rest.slice(1);
|
|
71
|
+
}
|
|
72
|
+
const message = words.join(" ").trim();
|
|
73
|
+
if (!message)
|
|
74
|
+
throw new Error("Usage: datar ask [service] <question>");
|
|
75
|
+
const res = await client.ai.ask({ service: service, message });
|
|
76
|
+
process.stdout.write(`${res.text}\n`);
|
|
77
|
+
if (res.data)
|
|
78
|
+
print(res.data);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
case "query": {
|
|
82
|
+
const { client } = connect();
|
|
83
|
+
const path = rest[0];
|
|
84
|
+
if (!path)
|
|
85
|
+
throw new Error("Usage: datar query <v2.path> [json-input]");
|
|
86
|
+
const reason = rejectIfMutation(path);
|
|
87
|
+
if (reason)
|
|
88
|
+
throw new Error(reason);
|
|
89
|
+
const input = rest[1] ? JSON.parse(rest[1]) : undefined;
|
|
90
|
+
print(await client.query(path, input));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// `datar <service> ls` — thin listing shortcuts over the SDK.
|
|
94
|
+
case "drive":
|
|
95
|
+
case "projects":
|
|
96
|
+
case "requests":
|
|
97
|
+
case "transact":
|
|
98
|
+
case "procurement":
|
|
99
|
+
case "inventory":
|
|
100
|
+
case "isms":
|
|
101
|
+
case "invoicing":
|
|
102
|
+
case "messages":
|
|
103
|
+
return serviceList(cmd, rest);
|
|
104
|
+
default:
|
|
105
|
+
process.stderr.write(`Unknown command: ${cmd}\n\n`);
|
|
106
|
+
printHelp();
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** `datar <service> ls` dispatch. */
|
|
111
|
+
async function serviceList(service, rest) {
|
|
112
|
+
const sub = rest[0] ?? "ls";
|
|
113
|
+
if (sub !== "ls") {
|
|
114
|
+
throw new Error(`Unknown subcommand: datar ${service} ${sub} (only 'ls' in v1)`);
|
|
115
|
+
}
|
|
116
|
+
const { client } = connect();
|
|
117
|
+
const listers = {
|
|
118
|
+
drive: () => client.drive.listFiles(),
|
|
119
|
+
projects: () => client.projects.list(),
|
|
120
|
+
requests: () => client.requests.getAll(),
|
|
121
|
+
transact: () => client.transact.listAccounts(),
|
|
122
|
+
procurement: () => client.procurement.listTenders(),
|
|
123
|
+
inventory: () => client.inventory.listItems(),
|
|
124
|
+
isms: () => client.isms.listRisks(),
|
|
125
|
+
invoicing: () => client.invoicing.listInvoices(),
|
|
126
|
+
messages: () => client.messages.list(),
|
|
127
|
+
};
|
|
128
|
+
const fn = listers[service];
|
|
129
|
+
if (!fn)
|
|
130
|
+
throw new Error(`No 'ls' for service '${service}'`);
|
|
131
|
+
print(await fn());
|
|
132
|
+
}
|
|
133
|
+
function connect() {
|
|
134
|
+
const config = resolveConfig();
|
|
135
|
+
const client = new Datar({
|
|
136
|
+
apiToken: config.apiToken,
|
|
137
|
+
baseUrl: config.baseUrl,
|
|
138
|
+
});
|
|
139
|
+
return { client, config };
|
|
140
|
+
}
|
|
141
|
+
function print(value) {
|
|
142
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
143
|
+
}
|
|
144
|
+
function printHelp() {
|
|
145
|
+
process.stdout.write([
|
|
146
|
+
"datar — CLI + MCP server for the Datar platform (v1: read + discovery)",
|
|
147
|
+
"",
|
|
148
|
+
"Usage:",
|
|
149
|
+
" datar mcp Start the stdio MCP server (for Claude Code)",
|
|
150
|
+
" datar whoami Show the org/user this token resolves to",
|
|
151
|
+
" datar ls List available service agents",
|
|
152
|
+
" datar ask [service] <text> Ask an agent (default: orchestrator)",
|
|
153
|
+
" datar query <v2.path> [json] Call a tRPC query by path (read-only)",
|
|
154
|
+
" datar <service> ls List records (drive|projects|requests|transact|",
|
|
155
|
+
" procurement|inventory|isms|invoicing|messages)",
|
|
156
|
+
"",
|
|
157
|
+
"Auth: set DATAR_API_TOKEN and DATAR_BASE_URL. Mint a token at",
|
|
158
|
+
" <baseUrl>/settings/api-tokens (scope it to reads for v1).",
|
|
159
|
+
"",
|
|
160
|
+
].join("\n"));
|
|
161
|
+
}
|
|
162
|
+
main(process.argv.slice(2)).catch((err) => {
|
|
163
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
164
|
+
process.exitCode = 1;
|
|
165
|
+
});
|
|
166
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-agnostic tool registry.
|
|
3
|
+
*
|
|
4
|
+
* A `ToolDef` is a plain description of one MCP tool: its name, human/agent-
|
|
5
|
+
* facing description, a Zod raw-shape input schema, and a handler that receives
|
|
6
|
+
* the shared `Datar` SDK client plus the parsed args. The stdio server
|
|
7
|
+
* (`server.ts`) adapts these into `registerTool` calls; a future remote HTTP
|
|
8
|
+
* endpoint can consume the SAME array — hence no MCP or stdio types leak in here.
|
|
9
|
+
*
|
|
10
|
+
* v1 invariant: READ ONLY. Every handler here must only ever call SDK read
|
|
11
|
+
* methods or `client.query(...)` (tRPC GET). None may call a mutation. This is
|
|
12
|
+
* defense-in-depth on top of the real guard, which is the token's scopes.
|
|
13
|
+
*/
|
|
14
|
+
import type { ZodRawShape } from "zod";
|
|
15
|
+
import type { Datar } from "@datar-platform/sdk";
|
|
16
|
+
export interface ToolDef<Shape extends ZodRawShape = ZodRawShape> {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
/** Zod raw shape (object of zod validators). Empty object = no args. */
|
|
20
|
+
inputSchema: Shape;
|
|
21
|
+
/** Read-only handler. Returns any JSON-serialisable value. */
|
|
22
|
+
handler: (client: Datar, args: Record<string, unknown>) => Promise<unknown>;
|
|
23
|
+
}
|
|
24
|
+
/** Helper to define a tool with inferred shape while keeping the union homogeneous. */
|
|
25
|
+
export declare function tool<Shape extends ZodRawShape>(def: ToolDef<Shape>): ToolDef;
|
|
26
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/mcp/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD,MAAM,WAAW,OAAO,CAAC,KAAK,SAAS,WAAW,GAAG,WAAW;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,WAAW,EAAE,KAAK,CAAC;IACnB,8DAA8D;IAC9D,OAAO,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7E;AAED,uFAAuF;AACvF,wBAAgB,IAAI,CAAC,KAAK,SAAS,WAAW,EAAE,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAE5E"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-agnostic tool registry.
|
|
3
|
+
*
|
|
4
|
+
* A `ToolDef` is a plain description of one MCP tool: its name, human/agent-
|
|
5
|
+
* facing description, a Zod raw-shape input schema, and a handler that receives
|
|
6
|
+
* the shared `Datar` SDK client plus the parsed args. The stdio server
|
|
7
|
+
* (`server.ts`) adapts these into `registerTool` calls; a future remote HTTP
|
|
8
|
+
* endpoint can consume the SAME array — hence no MCP or stdio types leak in here.
|
|
9
|
+
*
|
|
10
|
+
* v1 invariant: READ ONLY. Every handler here must only ever call SDK read
|
|
11
|
+
* methods or `client.query(...)` (tRPC GET). None may call a mutation. This is
|
|
12
|
+
* defense-in-depth on top of the real guard, which is the token's scopes.
|
|
13
|
+
*/
|
|
14
|
+
/** Helper to define a tool with inferred shape while keeping the union homogeneous. */
|
|
15
|
+
export function tool(def) {
|
|
16
|
+
return def;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/mcp/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH,uFAAuF;AACvF,MAAM,UAAU,IAAI,CAA4B,GAAmB;IACjE,OAAO,GAAyB,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datar MCP server (stdio transport).
|
|
3
|
+
*
|
|
4
|
+
* Holds exactly one `Datar` SDK client (authenticated by the resolved `dt_`
|
|
5
|
+
* token) and registers the v1 read + discovery tool registry against an
|
|
6
|
+
* `McpServer`. Errors are returned as `isError` tool results so the harness can
|
|
7
|
+
* see and recover from them rather than the whole server crashing.
|
|
8
|
+
*
|
|
9
|
+
* The registry (./tools) is transport-agnostic — a future remote HTTP MCP
|
|
10
|
+
* endpoint can register the same `ToolDef[]` against a StreamableHTTP transport.
|
|
11
|
+
*/
|
|
12
|
+
import { Datar } from "@datar-platform/sdk";
|
|
13
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
|
+
import type { ResolvedConfig } from "../config.js";
|
|
15
|
+
import type { ToolDef } from "./registry.js";
|
|
16
|
+
/** Build an `McpServer` with the given client + tools registered. */
|
|
17
|
+
export declare function createDatarMcpServer(client: Datar, tools: ToolDef[]): McpServer;
|
|
18
|
+
/** Entry point for `datar mcp`: resolve config, wire the client, serve on stdio. */
|
|
19
|
+
export declare function startStdioServer(config?: ResolvedConfig): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAO7C,qEAAqE;AACrE,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,KAAK,EACb,KAAK,EAAE,OAAO,EAAE,GACf,SAAS,CAgCX;AAED,oFAAoF;AACpF,wBAAsB,gBAAgB,CACpC,MAAM,GAAE,cAAgC,GACvC,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datar MCP server (stdio transport).
|
|
3
|
+
*
|
|
4
|
+
* Holds exactly one `Datar` SDK client (authenticated by the resolved `dt_`
|
|
5
|
+
* token) and registers the v1 read + discovery tool registry against an
|
|
6
|
+
* `McpServer`. Errors are returned as `isError` tool results so the harness can
|
|
7
|
+
* see and recover from them rather than the whole server crashing.
|
|
8
|
+
*
|
|
9
|
+
* The registry (./tools) is transport-agnostic — a future remote HTTP MCP
|
|
10
|
+
* endpoint can register the same `ToolDef[]` against a StreamableHTTP transport.
|
|
11
|
+
*/
|
|
12
|
+
import { Datar } from "@datar-platform/sdk";
|
|
13
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
import { resolveConfig } from "../config.js";
|
|
16
|
+
import { buildRegistry } from "./tools/index.js";
|
|
17
|
+
const SERVER_NAME = "datar";
|
|
18
|
+
const SERVER_VERSION = "0.1.0";
|
|
19
|
+
/** Build an `McpServer` with the given client + tools registered. */
|
|
20
|
+
export function createDatarMcpServer(client, tools) {
|
|
21
|
+
const server = new McpServer({
|
|
22
|
+
name: SERVER_NAME,
|
|
23
|
+
version: SERVER_VERSION,
|
|
24
|
+
});
|
|
25
|
+
for (const def of tools) {
|
|
26
|
+
server.registerTool(def.name, { description: def.description, inputSchema: def.inputSchema }, async (args) => {
|
|
27
|
+
try {
|
|
28
|
+
const result = await def.handler(client, args ?? {});
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: safeStringify(result) }],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
return {
|
|
35
|
+
isError: true,
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: `Error in ${def.name}: ${errMessage(err)}`,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return server;
|
|
47
|
+
}
|
|
48
|
+
/** Entry point for `datar mcp`: resolve config, wire the client, serve on stdio. */
|
|
49
|
+
export async function startStdioServer(config = resolveConfig()) {
|
|
50
|
+
const client = new Datar({
|
|
51
|
+
apiToken: config.apiToken,
|
|
52
|
+
baseUrl: config.baseUrl,
|
|
53
|
+
});
|
|
54
|
+
const server = createDatarMcpServer(client, buildRegistry(config));
|
|
55
|
+
const transport = new StdioServerTransport();
|
|
56
|
+
await server.connect(transport);
|
|
57
|
+
// stderr is safe (stdout is the MCP channel); stdout must stay JSON-RPC only.
|
|
58
|
+
process.stderr.write(`[datar-mcp] connected to ${config.baseUrl} — read + discovery tools ready.\n`);
|
|
59
|
+
}
|
|
60
|
+
function safeStringify(value) {
|
|
61
|
+
if (typeof value === "string")
|
|
62
|
+
return value;
|
|
63
|
+
try {
|
|
64
|
+
return JSON.stringify(value, null, 2);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return String(value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function errMessage(err) {
|
|
71
|
+
if (err instanceof Error)
|
|
72
|
+
return err.message;
|
|
73
|
+
return String(err);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAIjF,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,WAAW,GAAG,OAAO,CAAC;AAC5B,MAAM,cAAc,GAAG,OAAO,CAAC;AAE/B,qEAAqE;AACrE,MAAM,UAAU,oBAAoB,CAClC,MAAa,EACb,KAAgB;IAEhB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,cAAc;KACxB,CAAC,CAAC;IAEH,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,MAAM,CAAC,YAAY,CACjB,GAAG,CAAC,IAAI,EACR,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,EAC9D,KAAK,EAAE,IAA6B,EAAE,EAAE;YACtC,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;gBACrD,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;iBAClE,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,YAAY,GAAG,CAAC,IAAI,KAAK,UAAU,CAAC,GAAG,CAAC,EAAE;yBACjD;qBACF;iBACF,CAAC;YACJ,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,SAAyB,aAAa,EAAE;IAExC,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;IACnE,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,8EAA8E;IAC9E,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,4BAA4B,MAAM,CAAC,OAAO,oCAAoC,CAC/E,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,GAAY;IAC9B,IAAI,GAAG,YAAY,KAAK;QAAE,OAAO,GAAG,CAAC,OAAO,CAAC;IAC7C,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Natural-language fallback — forward a free-form question to a Datar A2A agent
|
|
3
|
+
* (or the orchestrator, which routes for you). This reuses the whole agent
|
|
4
|
+
* stack (tool-calling, KB grounding, cross-service delegation) for anything the
|
|
5
|
+
* typed read tools don't cover.
|
|
6
|
+
*
|
|
7
|
+
* THIS TOOL CAN ACT. The agent may call mutating endpoints, and token scopes do
|
|
8
|
+
* NOT reliably stop it (serviceProcedure endpoints skip the scope middleware).
|
|
9
|
+
* So it is registered only when DATAR_MCP_ALLOW_AGENT=1 — the default read-only
|
|
10
|
+
* surface omits it entirely. See ./index.ts and the CLI README.
|
|
11
|
+
*/
|
|
12
|
+
import type { ToolDef } from "../registry.js";
|
|
13
|
+
export declare const askTool: ToolDef;
|
|
14
|
+
//# sourceMappingURL=ask.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/ask.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAoB9C,eAAO,MAAM,OAAO,EAAE,OAyCpB,CAAC"}
|