@adrive/mcp-server 0.4.0 → 1.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @adrive/mcp-server
2
2
 
3
- MCP server for **AgentDrive** — the persistent workspace your agents share across Claude Code, Codex, Cursor, and LangGraph. Files, semantic search, run logging, and cross-platform memory.
3
+ MCP server for **AgentDrive** — the cross-agent artifact workspace with provenance and portable storage across Claude Code, Codex, Cursor, and LangGraph. Files, semantic recall, run logging, and cross-platform attribution.
4
4
 
5
5
  Drop this server into any MCP-compatible client and your agents get a shared filesystem with provenance, semantic search across artifacts written by other agents, and per-run audit. Memory is a feature of the workspace, not the headline.
6
6
 
@@ -16,37 +16,28 @@ npx @adrive/mcp-server
16
16
 
17
17
  | Variable | Required | Default | Notes |
18
18
  |---|---|---|---|
19
- | `AGENTDRIVE_API_KEY` | yes | — | API key from `drive init` or the dashboard. |
19
+ | `AGENTDRIVE_API_KEY` | no | key saved by `adrive auth login` | Falls back to `~/.config/agentdrive/config.json`, so client configs never need the key inline. |
20
20
  | `AGENTDRIVE_API_URL` | no | `https://app.agentdrive.sh` | Override for self-host or staging. |
21
- | `AGENTDRIVE_AGENT_PLATFORM` | no | `claude-code` | Identifies which host is calling. Valid: `claude-code | codex | cursor | langgraph | crewai | autogen | hermes | openclaw | unknown`. Unknown values downgrade to `unknown` server-side. |
21
+ | `AGENTDRIVE_AGENT_PLATFORM` | no | `unknown` | Identifies which host is calling. Valid: `claude-code | codex | opencode | gemini-cli | cursor | langgraph | crewai | autogen | hermes | openclaw | unknown`. Unknown values downgrade to `unknown` server-side. |
22
22
  | `AGENTDRIVE_AGENT_ROLE` | no | (empty) | Free-form role label (`planner`, `reviewer`, etc.). Stamped on every request for provenance. |
23
23
 
24
+ The server derives the current project from its working directory (git remote, else repo path), which scopes `drive_sessions` and `drive_resume`.
25
+
24
26
  ## Client configuration
25
27
 
26
- ### Claude Code (`~/.claude/mcp-servers.json` or `claude_desktop_config.json`)
28
+ `curl -fsSL https://app.agentdrive.sh/install | sh` runs `adrive init`, which wires every detected harness. For Claude Code, install the plugin instead: `/plugin marketplace add Rakesh1002/agentdrive` then `/plugin install agentdrive@agentdrive`.
27
29
 
28
- ```json
29
- {
30
- "mcpServers": {
31
- "agentdrive": {
32
- "command": "npx",
33
- "args": ["-y", "@adrive/mcp-server"],
34
- "env": {
35
- "AGENTDRIVE_API_KEY": "agentdrive_live_..."
36
- }
37
- }
38
- }
39
- }
40
- ```
30
+ Manual entries, for reference:
41
31
 
42
- ### Codex CLI (`~/.codex/config.toml` or equivalent)
32
+ ### Codex CLI (`~/.codex/config.toml`)
43
33
 
44
34
  ```toml
45
- [[mcp_servers]]
46
- name = "agentdrive"
35
+ [mcp_servers.agentdrive]
47
36
  command = "npx"
48
37
  args = ["-y", "@adrive/mcp-server"]
49
- env = { AGENTDRIVE_API_KEY = "agentdrive_live_...", AGENTDRIVE_AGENT_PLATFORM = "codex" }
38
+
39
+ [mcp_servers.agentdrive.env]
40
+ AGENTDRIVE_AGENT_PLATFORM = "codex"
50
41
  ```
51
42
 
52
43
  ### Cursor (`~/.cursor/mcp.json`)
@@ -57,16 +48,13 @@ env = { AGENTDRIVE_API_KEY = "agentdrive_live_...", AGENTDRIVE_AGENT_PLATFORM =
57
48
  "agentdrive": {
58
49
  "command": "npx",
59
50
  "args": ["-y", "@adrive/mcp-server"],
60
- "env": {
61
- "AGENTDRIVE_API_KEY": "agentdrive_live_...",
62
- "AGENTDRIVE_AGENT_PLATFORM": "cursor"
63
- }
51
+ "env": { "AGENTDRIVE_AGENT_PLATFORM": "cursor" }
64
52
  }
65
53
  }
66
54
  }
67
55
  ```
68
56
 
69
- ## Tools (16)
57
+ ## Tools (22)
70
58
 
71
59
  **Files**
72
60
  - `drive_write` — create or update a file with versioning
@@ -83,6 +71,10 @@ env = { AGENTDRIVE_API_KEY = "agentdrive_live_...", AGENTDRIVE_AGENT_PLATFORM =
83
71
  - `drive_activity` — workspace event feed
84
72
  - `drive_versions` — file version history
85
73
 
74
+ **Sessions**
75
+ - `drive_sessions` — recent sessions in this project from every harness
76
+ - `drive_resume` — load a prior session's handoff (defaults to the latest from another harness)
77
+
86
78
  **Search and memory**
87
79
  - `drive_search` — semantic search across artifacts
88
80
  - `drive_recall` — recall facts and preferences (narrows to extracted facts in a future release)
@@ -3,6 +3,8 @@ export interface ApiClientConfig {
3
3
  apiKey: string;
4
4
  agentPlatform?: string;
5
5
  agentRole?: string;
6
+ project?: string;
7
+ fetchImpl?: (input: string, init?: RequestInit) => Promise<Response>;
6
8
  }
7
9
  export interface PagedResponse<T> {
8
10
  items: T[];
@@ -38,7 +40,6 @@ export interface ContextRequestBody {
38
40
  task: string;
39
41
  workspace_id?: string;
40
42
  limit?: number;
41
- include_facts?: boolean;
42
43
  }
43
44
  export interface ContextArtifact extends SearchHit {
44
45
  }
@@ -52,11 +53,35 @@ export interface RunRequestBody {
52
53
  duration_ms?: number;
53
54
  metadata?: Record<string, unknown>;
54
55
  }
56
+ export interface AgentRunRecord {
57
+ run_id: string;
58
+ org_id: string;
59
+ workspace_id?: string;
60
+ agent_id: string;
61
+ agent_platform: string;
62
+ agent_role?: string;
63
+ task: string;
64
+ artifacts_referenced: string[];
65
+ artifacts_produced: string[];
66
+ duration_ms?: number;
67
+ status: RunStatus;
68
+ metadata: Record<string, unknown>;
69
+ created_at: number;
70
+ updated_at?: number;
71
+ }
72
+ export interface ListRunsQuery {
73
+ project: string;
74
+ platform?: string;
75
+ limit?: number;
76
+ cursor?: string;
77
+ }
55
78
  export declare class ApiClient {
79
+ readonly project?: string;
80
+ readonly agentPlatform: string;
56
81
  private baseUrl;
57
82
  private apiKey;
58
- private agentPlatform;
59
83
  private agentRole;
84
+ private fetchImpl;
60
85
  constructor(config: ApiClientConfig);
61
86
  private headers;
62
87
  rawFetch(path: string, init: RequestInit): Promise<Response>;
@@ -82,6 +107,13 @@ export declare class ApiClient {
82
107
  version: number;
83
108
  updated_at: string;
84
109
  }>;
110
+ readFileByPath(workspaceId: string, path: string): Promise<{
111
+ id: string;
112
+ path: string;
113
+ content: string;
114
+ version: number;
115
+ updated_at: string;
116
+ }>;
85
117
  listFilesPage(workspaceId: string, prefix: string | undefined, cursor: string | null): Promise<PagedResponse<{
86
118
  id: string;
87
119
  path: string;
@@ -233,6 +265,8 @@ export declare class ApiClient {
233
265
  run_id: string;
234
266
  created_at: number;
235
267
  }>;
268
+ listRuns(query: ListRunsQuery): Promise<PagedResponse<AgentRunRecord>>;
269
+ getRun(runId: string): Promise<AgentRunRecord>;
236
270
  sandboxClone(workspaceId: string, body: {
237
271
  url: string;
238
272
  branch?: string;
@@ -12,15 +12,19 @@ export async function listAllPages(fetchPage, maxPages = 10) {
12
12
  return all;
13
13
  }
14
14
  export class ApiClient {
15
+ project;
16
+ agentPlatform;
15
17
  baseUrl;
16
18
  apiKey;
17
- agentPlatform;
18
19
  agentRole;
20
+ fetchImpl;
19
21
  constructor(config) {
20
22
  this.baseUrl = config.baseUrl.replace(/\/$/, '');
21
23
  this.apiKey = config.apiKey;
22
24
  this.agentPlatform = config.agentPlatform ?? 'claude-code';
23
25
  this.agentRole = config.agentRole ?? '';
26
+ this.project = config.project;
27
+ this.fetchImpl = config.fetchImpl ?? ((input, init) => fetch(input, init));
24
28
  }
25
29
  headers() {
26
30
  return {
@@ -32,7 +36,7 @@ export class ApiClient {
32
36
  }
33
37
  async rawFetch(path, init) {
34
38
  const url = `${this.baseUrl}/v1${path}`;
35
- return fetch(url, {
39
+ return this.fetchImpl(url, {
36
40
  ...init,
37
41
  headers: { ...this.headers(), ...init.headers },
38
42
  });
@@ -68,6 +72,13 @@ export class ApiClient {
68
72
  async readFile(workspaceId, fileId) {
69
73
  return this.request('GET', `/workspaces/${workspaceId}/files/${fileId}?content=true`);
70
74
  }
75
+ async readFileByPath(workspaceId, path) {
76
+ const page = await this.listFilesPage(workspaceId, path, null);
77
+ const match = page.items.find((f) => f.path === path);
78
+ if (!match)
79
+ throw new Error(`File not found: ${path}`);
80
+ return this.readFile(workspaceId, match.id);
81
+ }
71
82
  async listFilesPage(workspaceId, prefix, cursor) {
72
83
  const params = new URLSearchParams();
73
84
  if (prefix)
@@ -195,6 +206,19 @@ export class ApiClient {
195
206
  async logRun(body) {
196
207
  return this.request('POST', '/runs', body);
197
208
  }
209
+ async listRuns(query) {
210
+ const params = new URLSearchParams({ project: query.project });
211
+ if (query.platform)
212
+ params.set('platform', query.platform);
213
+ if (query.limit)
214
+ params.set('limit', String(query.limit));
215
+ if (query.cursor)
216
+ params.set('cursor', query.cursor);
217
+ return this.requestPage('GET', `/runs?${params.toString()}`);
218
+ }
219
+ async getRun(runId) {
220
+ return this.request('GET', `/runs/${encodeURIComponent(runId)}`);
221
+ }
198
222
  // --- Sandbox (Wave 5 Stream D) ---
199
223
  async sandboxClone(workspaceId, body) {
200
224
  return this.request('POST', `/workspaces/${workspaceId}/sandbox/clone`, body);
package/dist/index.js CHANGED
@@ -1,33 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { ApiClient } from './api-client.js';
5
- import { driveWriteSchema, driveWrite } from './tools/drive-write.js';
6
- import { driveReadSchema, driveRead } from './tools/drive-read.js';
7
- import { driveListSchema, driveList } from './tools/drive-list.js';
8
- import { driveShareSchema, driveShare } from './tools/drive-share.js';
9
- import { driveCommentSchema, driveComment } from './tools/drive-comment.js';
10
- import { driveFeedbackSchema, driveFeedback } from './tools/drive-feedback.js';
11
- import { driveActivitySchema, driveActivity } from './tools/drive-activity.js';
12
- import { driveInviteSchema, driveInvite } from './tools/drive-invite.js';
13
- import { driveRolesSchema, driveRoles } from './tools/drive-roles.js';
14
- import { driveOrgSchema, driveOrg } from './tools/drive-org.js';
15
- import { driveDeleteSchema, driveDelete } from './tools/drive-delete.js';
16
- import { driveVersionsSchema, driveVersions } from './tools/drive-versions.js';
17
- import { driveSearchSchema, driveSearch } from './tools/drive-search.js';
18
- import { driveRecallSchema, driveRecall } from './tools/drive-recall.js';
19
- import { driveLogRunSchema, driveLogRun } from './tools/drive-log-run.js';
20
- import { driveContextSchema, driveContext } from './tools/drive-context.js';
21
- import { driveSandboxCloneSchema, driveSandboxClone } from './tools/drive-sandbox-clone.js';
22
- import { driveSandboxExecSchema, driveSandboxExec } from './tools/drive-sandbox-exec.js';
23
- import { driveSandboxReadSchema, driveSandboxRead } from './tools/drive-sandbox-read.js';
24
- import { driveSandboxCommitSchema, driveSandboxCommit } from './tools/drive-sandbox-commit.js';
25
- const BASE_URL = process.env.AGENTDRIVE_API_URL || 'https://app.agentdrive.sh';
26
- const API_KEY = process.env.AGENTDRIVE_API_KEY;
27
- const AGENT_PLATFORM = process.env.AGENTDRIVE_AGENT_PLATFORM || 'claude-code';
4
+ import { ApiClient, TOOLS, SERVER_INSTRUCTIONS } from './registry.js';
5
+ import { createRequire } from 'node:module';
6
+ import { readFileSync } from 'node:fs';
7
+ import { homedir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ import { projectKeyFor, scopeProjectKey } from './project.js';
10
+ const require = createRequire(import.meta.url);
11
+ const pkg = require('../package.json');
12
+ function readCliConfig() {
13
+ const xdg = process.env.XDG_CONFIG_HOME;
14
+ const base = xdg && xdg.length > 0 ? xdg : join(homedir(), '.config');
15
+ try {
16
+ return JSON.parse(readFileSync(join(base, 'agentdrive', 'config.json'), 'utf8'));
17
+ }
18
+ catch {
19
+ return {};
20
+ }
21
+ }
22
+ const fileConfig = readCliConfig();
23
+ const cliConfig = process.env.AGENTDRIVE_API_KEY ? {} : fileConfig;
24
+ const BASE_URL = process.env.AGENTDRIVE_API_URL || cliConfig.api_url || 'https://app.agentdrive.sh';
25
+ const API_KEY = process.env.AGENTDRIVE_API_KEY || cliConfig.api_key;
26
+ const AGENT_PLATFORM = process.env.AGENTDRIVE_AGENT_PLATFORM || 'unknown';
28
27
  const AGENT_ROLE = process.env.AGENTDRIVE_AGENT_ROLE || '';
29
28
  if (!API_KEY) {
30
- console.error('AGENTDRIVE_API_KEY environment variable is required');
29
+ console.error('No AgentDrive API key found. Set AGENTDRIVE_API_KEY or run `adrive auth login`.');
31
30
  process.exit(1);
32
31
  }
33
32
  const client = new ApiClient({
@@ -35,32 +34,18 @@ const client = new ApiClient({
35
34
  apiKey: API_KEY,
36
35
  agentPlatform: AGENT_PLATFORM,
37
36
  agentRole: AGENT_ROLE,
37
+ project: scopeProjectKey(projectKeyFor(process.cwd()), !!fileConfig.preferences?.private_workspace),
38
38
  });
39
39
  const server = new McpServer({
40
40
  name: 'agentdrive',
41
- version: '0.4.0',
41
+ version: pkg.version,
42
42
  description: 'AgentDrive — the persistent workspace your agents share across Claude Code, Codex, Cursor, and LangGraph. Files, semantic search, run logging, cross-platform memory, and per-workspace Linux sandbox with git worktree.',
43
+ }, {
44
+ instructions: SERVER_INSTRUCTIONS,
43
45
  });
44
- server.tool('drive_write', 'Write a markdown file to a workspace. Creates or updates the file with automatic versioning.', driveWriteSchema, (args) => driveWrite(client, args));
45
- server.tool('drive_read', 'Read a file\'s content from a workspace.', driveReadSchema, (args) => driveRead(client, args));
46
- server.tool('drive_list', 'List files in a workspace, optionally filtered by path prefix.', driveListSchema, (args) => driveList(client, args));
47
- server.tool('drive_share', 'Generate a share link for a workspace or specific file. Optionally email-gate the link so only a specific person can access it. Humans open this link to view, comment, or edit files.', driveShareSchema, (args) => driveShare(client, args));
48
- server.tool('drive_comment', 'Add a comment to a file.', driveCommentSchema, (args) => driveComment(client, args));
49
- server.tool('drive_feedback', 'Read human comments on a file. Use this to check if humans have left feedback.', driveFeedbackSchema, (args) => driveFeedback(client, args));
50
- server.tool('drive_activity', 'Get recent activity in a workspace (file writes, comments, shares).', driveActivitySchema, (args) => driveActivity(client, args));
51
- server.tool('drive_invite', 'Invite a human to your organization by email. They will receive a verification code to join.', driveInviteSchema, (args) => driveInvite(client, args));
52
- server.tool('drive_roles', 'Manage per-workspace role delegation. Assign, list, or remove access for specific emails (view/comment/edit).', driveRolesSchema, (args) => driveRoles(client, args));
53
- server.tool('drive_org', 'View your organization details: agents, members, plan, and usage.', driveOrgSchema, (args) => driveOrg(client, args));
54
- server.tool('drive_delete', 'Delete a file from a workspace.', driveDeleteSchema, (args) => driveDelete(client, args));
55
- server.tool('drive_versions', 'View version history for a file in a workspace.', driveVersionsSchema, (args) => driveVersions(client, args));
56
- server.tool('drive_search', "Semantic search across your org's artifacts in AgentDrive. Returns the top-N most relevant files. Use this BEFORE writing similar artifacts to avoid duplication and surface prior context.", driveSearchSchema, (args) => driveSearch(client, args));
57
- server.tool('drive_recall', 'Recall persistent facts and preferences your agents have written. In v1 this returns semantic search results across artifacts; v1.5 will narrow to auto-extracted facts (preferences, decisions, key entities).', driveRecallSchema, (args) => driveRecall(client, args));
58
- server.tool('drive_log_run', "Log an agent run to AgentDrive's provenance store. Captures task, status, referenced/produced artifacts, and cross-platform identity (which agent on which tool). Used for retrospective context retrieval and team-wide audit.", driveLogRunSchema, (args) => driveLogRun(client, args));
59
- server.tool('drive_context', 'Auto-pull the most relevant artifacts from prior agent sessions for the task you are about to do. Combines semantic relevance with recency. Call this at the START of any non-trivial task to ground the agent in prior context — replaces manual copy-paste of artifacts between sessions.', driveContextSchema, (args) => driveContext(client, args));
60
- server.tool('drive_sandbox_clone', 'Clone a git repo into a per-workspace Linux sandbox. The sandbox persists across MCP sessions for the workspace. Use this to set up a real dev environment your other agents can also drive.', driveSandboxCloneSchema, (args) => driveSandboxClone(client, args));
61
- server.tool('drive_sandbox_exec', 'Run a shell command inside the per-workspace Linux sandbox. Captures stdout/stderr (64KB cap), exit code, and duration. Use for `npm install`, running tests, file ops — any real dev workflow.', driveSandboxExecSchema, (args) => driveSandboxExec(client, args));
62
- server.tool('drive_sandbox_read', 'Read a file from the per-workspace sandbox filesystem (e.g., generated build artifacts, test output, log files).', driveSandboxReadSchema, (args) => driveSandboxRead(client, args));
63
- server.tool('drive_sandbox_commit', 'Stage + commit the current sandbox worktree. The commit surfaces as an AgentDrive artifact at commits/<sha>.json with cross-agent provenance — other agents on other platforms can see who shipped what.', driveSandboxCommitSchema, (args) => driveSandboxCommit(client, args));
46
+ for (const tool of TOOLS) {
47
+ server.tool(tool.name, tool.description, tool.schema, (args) => tool.handler(client, args));
48
+ }
64
49
  async function main() {
65
50
  const transport = new StdioServerTransport();
66
51
  await server.connect(transport);
@@ -0,0 +1,3 @@
1
+ export declare function normalizeRemote(url: string): string | null;
2
+ export declare function projectKeyFor(cwd: string): string;
3
+ export declare function scopeProjectKey(key: string, privateWorkspace: boolean): string;
@@ -0,0 +1,69 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { basename } from 'node:path';
4
+ function git(cwd, args) {
5
+ try {
6
+ const out = execFileSync('git', ['-C', cwd, ...args], {
7
+ encoding: 'utf8',
8
+ timeout: 1000,
9
+ stdio: ['ignore', 'pipe', 'ignore'],
10
+ }).trim();
11
+ return out || null;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ function sha8(value) {
18
+ return createHash('sha256').update(value).digest('hex').slice(0, 8);
19
+ }
20
+ function slug(value) {
21
+ return value.toLowerCase().replace(/[^a-z0-9._-]/g, '-');
22
+ }
23
+ export function normalizeRemote(url) {
24
+ const trimmed = url.trim();
25
+ let host;
26
+ let path;
27
+ const scp = /^(?:[^@/]+@)?([^:/]+):(?!\/\/)(.+)$/.exec(trimmed);
28
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
29
+ let parsed;
30
+ try {
31
+ parsed = new URL(trimmed);
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ host = parsed.hostname;
37
+ path = parsed.pathname;
38
+ }
39
+ else if (scp) {
40
+ host = scp[1];
41
+ path = scp[2];
42
+ }
43
+ else {
44
+ return null;
45
+ }
46
+ const segments = path
47
+ .replace(/\.git\/?$/, '')
48
+ .split('/')
49
+ .filter(Boolean)
50
+ .map(slug);
51
+ if (!host || segments.length === 0)
52
+ return null;
53
+ return [slug(host), ...segments].join('__');
54
+ }
55
+ export function projectKeyFor(cwd) {
56
+ const remote = git(cwd, ['config', '--get', 'remote.origin.url']);
57
+ const normalized = remote ? normalizeRemote(remote) : null;
58
+ if (normalized)
59
+ return normalized.slice(0, 200);
60
+ const toplevel = git(cwd, ['rev-parse', '--show-toplevel']);
61
+ if (toplevel)
62
+ return `${slug(basename(toplevel)).slice(0, 150)}-${sha8(toplevel)}`;
63
+ return `local__${slug(basename(cwd)).slice(0, 150)}-${sha8(cwd)}`;
64
+ }
65
+ // A private workspace must not leak repo names through storage paths or the
66
+ // project index, so the key is replaced by a stable hash of itself.
67
+ export function scopeProjectKey(key, privateWorkspace) {
68
+ return privateWorkspace ? `p-${createHash('sha256').update(key).digest('hex').slice(0, 16)}` : key;
69
+ }
@@ -0,0 +1,20 @@
1
+ import type { z } from 'zod';
2
+ import type { ApiClient } from './api-client.js';
3
+ export interface ToolResult {
4
+ [x: string]: unknown;
5
+ content: Array<{
6
+ type: 'text';
7
+ text: string;
8
+ }>;
9
+ isError?: boolean;
10
+ }
11
+ export interface ToolDef {
12
+ name: string;
13
+ description: string;
14
+ schema: z.ZodRawShape;
15
+ handler: (client: ApiClient, args: any) => Promise<ToolResult>;
16
+ }
17
+ export declare const SERVER_INSTRUCTIONS = "AgentDrive \u2014 cross-agent artifact workspace with provenance for multi-agent workflows.\n\nReach for these tools without waiting to be asked:\n\n- **Session start:** call `drive_context` with a short summary of the\n current task. Returns the most relevant artifacts and last_run\n summaries from prior agents' work in the same workspace, each with\n an `attribution` block explaining *why* it was surfaced.\n- **Continuing earlier work:** if the user is picking up where a prior\n session left off (or a SessionStart note mentions prior sessions),\n call `drive_sessions` to list this project's recent sessions across\n harnesses, then `drive_resume` to load one's handoff brief.\n- **Cross-agent recall:** call `drive_search` (semantic) when the\n user asks \"what did Codex / Cursor / the last session do?\"\n- **Saving work:** call `drive_write` for any artifact worth keeping\n so the next agent in the chain can read it back.\n- **Provenance:** call `drive_log_run` at the end of meaningful\n work so the cross-agent timeline stays accurate.\n\nPrivacy: if the user has Private Workspace on, written content is\nencrypted client-side; the AgentDrive server only stores ciphertext.";
18
+ export declare const TOOLS: ToolDef[];
19
+ export { ApiClient } from './api-client.js';
20
+ export type { ApiClientConfig } from './api-client.js';
@@ -0,0 +1,73 @@
1
+ import { driveWriteSchema, driveWrite } from './tools/drive-write.js';
2
+ import { driveReadSchema, driveRead } from './tools/drive-read.js';
3
+ import { driveListSchema, driveList } from './tools/drive-list.js';
4
+ import { driveShareSchema, driveShare } from './tools/drive-share.js';
5
+ import { driveCommentSchema, driveComment } from './tools/drive-comment.js';
6
+ import { driveFeedbackSchema, driveFeedback } from './tools/drive-feedback.js';
7
+ import { driveActivitySchema, driveActivity } from './tools/drive-activity.js';
8
+ import { driveInviteSchema, driveInvite } from './tools/drive-invite.js';
9
+ import { driveRolesSchema, driveRoles } from './tools/drive-roles.js';
10
+ import { driveOrgSchema, driveOrg } from './tools/drive-org.js';
11
+ import { driveDeleteSchema, driveDelete } from './tools/drive-delete.js';
12
+ import { driveVersionsSchema, driveVersions } from './tools/drive-versions.js';
13
+ import { driveSearchSchema, driveSearch } from './tools/drive-search.js';
14
+ import { driveRecallSchema, driveRecall } from './tools/drive-recall.js';
15
+ import { driveLogRunSchema, driveLogRun } from './tools/drive-log-run.js';
16
+ import { driveContextSchema, driveContext } from './tools/drive-context.js';
17
+ import { driveSandboxCloneSchema, driveSandboxClone } from './tools/drive-sandbox-clone.js';
18
+ import { driveSandboxExecSchema, driveSandboxExec } from './tools/drive-sandbox-exec.js';
19
+ import { driveSandboxReadSchema, driveSandboxRead } from './tools/drive-sandbox-read.js';
20
+ import { driveSandboxCommitSchema, driveSandboxCommit } from './tools/drive-sandbox-commit.js';
21
+ import { driveSessionsSchema, driveSessions } from './tools/drive-sessions.js';
22
+ import { driveResumeSchema, driveResume } from './tools/drive-resume.js';
23
+ // Shared by every MCP-aware host. Stdio and the hosted Streamable-HTTP server
24
+ // surface the same guidance via the MCP `instructions` field at session start.
25
+ export const SERVER_INSTRUCTIONS = `AgentDrive — cross-agent artifact workspace with provenance for multi-agent workflows.
26
+
27
+ Reach for these tools without waiting to be asked:
28
+
29
+ - **Session start:** call \`drive_context\` with a short summary of the
30
+ current task. Returns the most relevant artifacts and last_run
31
+ summaries from prior agents' work in the same workspace, each with
32
+ an \`attribution\` block explaining *why* it was surfaced.
33
+ - **Continuing earlier work:** if the user is picking up where a prior
34
+ session left off (or a SessionStart note mentions prior sessions),
35
+ call \`drive_sessions\` to list this project's recent sessions across
36
+ harnesses, then \`drive_resume\` to load one's handoff brief.
37
+ - **Cross-agent recall:** call \`drive_search\` (semantic) when the
38
+ user asks "what did Codex / Cursor / the last session do?"
39
+ - **Saving work:** call \`drive_write\` for any artifact worth keeping
40
+ so the next agent in the chain can read it back.
41
+ - **Provenance:** call \`drive_log_run\` at the end of meaningful
42
+ work so the cross-agent timeline stays accurate.
43
+
44
+ Privacy: if the user has Private Workspace on, written content is
45
+ encrypted client-side; the AgentDrive server only stores ciphertext.`;
46
+ // Single source of truth for the 22 tools. Consumed by the stdio server
47
+ // (packages/mcp-server/src/index.ts) and the hosted Streamable-HTTP endpoint
48
+ // (apps/worker/src/routes/mcp.ts).
49
+ export const TOOLS = [
50
+ { name: 'drive_write', description: 'Write a markdown file to a workspace. Creates or updates the file with automatic versioning.', schema: driveWriteSchema, handler: driveWrite },
51
+ { name: 'drive_read', description: "Read a file's content from a workspace.", schema: driveReadSchema, handler: driveRead },
52
+ { name: 'drive_list', description: 'List files in a workspace, optionally filtered by path prefix.', schema: driveListSchema, handler: driveList },
53
+ { name: 'drive_share', description: 'Generate a share link for a workspace or specific file. Optionally email-gate the link so only a specific person can access it. Humans open this link to view, comment, or edit files.', schema: driveShareSchema, handler: driveShare },
54
+ { name: 'drive_comment', description: 'Add a comment to a file.', schema: driveCommentSchema, handler: driveComment },
55
+ { name: 'drive_feedback', description: 'Read human comments on a file. Use this to check if humans have left feedback.', schema: driveFeedbackSchema, handler: driveFeedback },
56
+ { name: 'drive_activity', description: 'Get recent activity in a workspace (file writes, comments, shares).', schema: driveActivitySchema, handler: driveActivity },
57
+ { name: 'drive_invite', description: 'Invite a human to your organization by email. They will receive a verification code to join.', schema: driveInviteSchema, handler: driveInvite },
58
+ { name: 'drive_roles', description: 'Manage per-workspace role delegation. Assign, list, or remove access for specific emails (view/comment/edit).', schema: driveRolesSchema, handler: driveRoles },
59
+ { name: 'drive_org', description: 'View your organization details: agents, members, plan, and usage.', schema: driveOrgSchema, handler: driveOrg },
60
+ { name: 'drive_delete', description: 'Delete a file from a workspace.', schema: driveDeleteSchema, handler: driveDelete },
61
+ { name: 'drive_versions', description: 'View version history for a file in a workspace.', schema: driveVersionsSchema, handler: driveVersions },
62
+ { name: 'drive_search', description: "Semantic search across your org's artifacts in AgentDrive. Returns the top-N most relevant files. Use this BEFORE writing similar artifacts to avoid duplication and surface prior context.", schema: driveSearchSchema, handler: driveSearch },
63
+ { name: 'drive_recall', description: 'Recall persistent facts and preferences your agents have written. In v1 this returns semantic search results across artifacts; v1.5 will narrow to auto-extracted facts (preferences, decisions, key entities).', schema: driveRecallSchema, handler: driveRecall },
64
+ { name: 'drive_log_run', description: "Log an agent run to AgentDrive's provenance store. Captures task, status, referenced/produced artifacts, and cross-platform identity (which agent on which tool). Used for retrospective context retrieval and team-wide audit.", schema: driveLogRunSchema, handler: driveLogRun },
65
+ { name: 'drive_context', description: 'Auto-pull the most relevant artifacts from prior agent sessions for the task you are about to do. Combines semantic relevance with recency. Call this at the START of any non-trivial task to ground the agent in prior context — replaces manual copy-paste of artifacts between sessions.', schema: driveContextSchema, handler: driveContext },
66
+ { name: 'drive_sessions', description: 'List recent agent sessions for this project across harnesses (Claude Code, Codex, OpenCode, Gemini CLI, Cursor), newest first. Use to find prior work to continue.', schema: driveSessionsSchema, handler: driveSessions },
67
+ { name: 'drive_resume', description: "Load a compact handoff brief (goal, files touched, recent turns) from a prior session, by default the latest one from a different harness, so you can continue that work.", schema: driveResumeSchema, handler: driveResume },
68
+ { name: 'drive_sandbox_clone', description: '[EXPERIMENTAL] Clone a git repo into a per-workspace Linux sandbox. Requires Cloudflare Containers on the AgentDrive worker; gracefully degrades to a 503 with "sandbox not configured" otherwise.', schema: driveSandboxCloneSchema, handler: driveSandboxClone },
69
+ { name: 'drive_sandbox_exec', description: '[EXPERIMENTAL] Run a shell command inside the per-workspace Linux sandbox. Captures stdout/stderr (64 KB cap), exit code, duration. Requires Cloudflare Containers; degrades to 503 otherwise.', schema: driveSandboxExecSchema, handler: driveSandboxExec },
70
+ { name: 'drive_sandbox_read', description: '[EXPERIMENTAL] Read a file from the per-workspace sandbox filesystem (build artifacts, test output, logs). Requires Cloudflare Containers; degrades to 503 otherwise.', schema: driveSandboxReadSchema, handler: driveSandboxRead },
71
+ { name: 'drive_sandbox_commit', description: '[EXPERIMENTAL] Stage + commit the current sandbox worktree. Surfaces as an AgentDrive artifact at commits/<sha>.json. Requires Cloudflare Containers; degrades to 503 otherwise.', schema: driveSandboxCommitSchema, handler: driveSandboxCommit },
72
+ ];
73
+ export { ApiClient } from './api-client.js';
@@ -0,0 +1,32 @@
1
+ export type SessionHarness = 'claude-code' | 'codex' | 'opencode' | 'gemini-cli' | 'cursor';
2
+ export interface SessionTool {
3
+ name: string;
4
+ path?: string;
5
+ summary?: string;
6
+ }
7
+ export interface SessionTurn {
8
+ role: string;
9
+ text: string;
10
+ ts?: string;
11
+ tools?: SessionTool[];
12
+ }
13
+ export interface SessionDoc {
14
+ harness: SessionHarness;
15
+ sessionId: string;
16
+ project: string;
17
+ cwd?: string;
18
+ title?: string;
19
+ updatedAt: string;
20
+ turns: SessionTurn[];
21
+ filesTouched: string[];
22
+ }
23
+ export declare function isEncrypted(content: string): boolean;
24
+ export declare function sessionRunId(harness: SessionHarness, sessionId: string): string;
25
+ export declare function sessionPath(project: string, harness: SessionHarness, sessionId: string): string;
26
+ export declare function sessionActivityAt(run: {
27
+ created_at: number;
28
+ updated_at?: number;
29
+ metadata?: Record<string, unknown>;
30
+ }): number;
31
+ export declare function renderSession(doc: SessionDoc): string;
32
+ export declare function buildHandoff(markdown: string, maxChars?: number): string;
@@ -0,0 +1,158 @@
1
+ // Must stay in sync with ENCRYPTED_PREFIX in packages/cli/src/lib/crypto.ts.
2
+ const ENCRYPTED_PREFIX = 'agentdrive-encrypted:v0:';
3
+ const DEFAULT_HANDOFF_CHARS = 12000;
4
+ const GOAL_CHARS = 1500;
5
+ const MAX_HANDOFF_FILES = 50;
6
+ const TURN_HEADER = /^## ([a-z0-9_-]+)(?: {2}_\((.*)\)_)?$/;
7
+ const TOOL_LINE = '> tool: ';
8
+ // Turn text lines that could be read as a turn header or tool line get a
9
+ // leading backslash so the parser never splits inside a turn body.
10
+ const COLLIDING_LINE = /^\\*(## |> tool: )/;
11
+ function oneLine(value) {
12
+ return value.replace(/\s+/g, ' ').trim();
13
+ }
14
+ function sanitizeId(value) {
15
+ return value.replace(/[^A-Za-z0-9._-]/g, '-').slice(0, 128);
16
+ }
17
+ function roleSlug(role) {
18
+ return role.toLowerCase().replace(/[^a-z0-9_-]/g, '') || 'system';
19
+ }
20
+ export function isEncrypted(content) {
21
+ return content.startsWith(ENCRYPTED_PREFIX);
22
+ }
23
+ export function sessionRunId(harness, sessionId) {
24
+ return `${harness}:${sanitizeId(sessionId)}`;
25
+ }
26
+ export function sessionPath(project, harness, sessionId) {
27
+ return `sessions/${project}/${harness}/${sanitizeId(sessionId)}.md`;
28
+ }
29
+ // Project listings are ordered by last activity, so ages must be shown from it too.
30
+ export function sessionActivityAt(run) {
31
+ const at = run.metadata?.last_activity_at;
32
+ return typeof at === 'number' ? at : run.updated_at ?? run.created_at;
33
+ }
34
+ export function renderSession(doc) {
35
+ const out = [`# Session — ${doc.harness} / ${oneLine(doc.sessionId)}`, ''];
36
+ out.push(`Project: ${oneLine(doc.project)}`);
37
+ if (doc.cwd)
38
+ out.push(`Cwd: ${oneLine(doc.cwd)}`);
39
+ if (doc.title)
40
+ out.push(`Title: ${oneLine(doc.title)}`);
41
+ out.push(`Updated: ${oneLine(doc.updatedAt)}`);
42
+ out.push(`Turns: ${doc.turns.length}`);
43
+ out.push('', 'Files touched:');
44
+ for (const f of doc.filesTouched)
45
+ out.push(`- ${oneLine(f)}`);
46
+ for (const turn of doc.turns) {
47
+ out.push('', turn.ts ? `## ${roleSlug(turn.role)} _(${oneLine(turn.ts)})_` : `## ${roleSlug(turn.role)}`, '');
48
+ const text = turn.text.replace(/\r\n?/g, '\n').trim();
49
+ if (text) {
50
+ for (const line of text.split('\n'))
51
+ out.push(COLLIDING_LINE.test(line) ? `\\${line}` : line);
52
+ }
53
+ if (turn.tools && turn.tools.length > 0) {
54
+ if (text)
55
+ out.push('');
56
+ for (const tool of turn.tools) {
57
+ const detail = tool.path ?? tool.summary;
58
+ out.push(`${TOOL_LINE}${oneLine(tool.name)}${detail ? ` ${oneLine(detail)}` : ''}`);
59
+ }
60
+ }
61
+ }
62
+ return `${out.join('\n')}\n`;
63
+ }
64
+ function parseSession(markdown) {
65
+ const parsed = { meta: [], files: [], turns: [] };
66
+ let current = null;
67
+ let inFiles = false;
68
+ for (const line of markdown.replace(/\r\n?/g, '\n').split('\n')) {
69
+ const header = TURN_HEADER.exec(line);
70
+ if (header) {
71
+ current = { role: header[1], lines: [line] };
72
+ parsed.turns.push(current);
73
+ continue;
74
+ }
75
+ if (current) {
76
+ current.lines.push(line);
77
+ continue;
78
+ }
79
+ const title = /^# Session — (\S+) \/ (.+)$/.exec(line);
80
+ if (title) {
81
+ parsed.harness = title[1];
82
+ parsed.sessionId = title[2];
83
+ }
84
+ else if (line === 'Files touched:') {
85
+ inFiles = true;
86
+ }
87
+ else if (inFiles && line.startsWith('- ')) {
88
+ parsed.files.push(line.slice(2));
89
+ }
90
+ else if (/^(Project|Cwd|Title|Updated|Turns): /.test(line)) {
91
+ parsed.meta.push(line);
92
+ }
93
+ }
94
+ return parsed;
95
+ }
96
+ function turnBody(turn) {
97
+ return turn.lines
98
+ .slice(1)
99
+ .filter((l) => !l.startsWith(TOOL_LINE))
100
+ .join('\n')
101
+ .trim();
102
+ }
103
+ function turnBlock(turn) {
104
+ const [header, ...rest] = turn.lines;
105
+ return [`#${header}`, ...rest].join('\n').trim();
106
+ }
107
+ function truncate(s, n) {
108
+ return s.length <= n ? s : `${s.slice(0, n - 3)}...`;
109
+ }
110
+ export function buildHandoff(markdown, maxChars = DEFAULT_HANDOFF_CHARS) {
111
+ const session = parseSession(markdown);
112
+ const from = session.harness ?? 'prior';
113
+ const intro = `Handoff from a ${from} session${session.sessionId ? ` (${session.sessionId})` : ''}. Continue this work; verify file state on disk before assuming it.`;
114
+ const sections = [intro];
115
+ if (session.meta.length > 0)
116
+ sections.push(session.meta.join('\n'));
117
+ const firstUser = session.turns.find((t) => t.role === 'user');
118
+ const goal = firstUser ? turnBody(firstUser) : '';
119
+ if (goal)
120
+ sections.push(`## Goal\n\n${truncate(goal, GOAL_CHARS)}`);
121
+ if (session.files.length > 0) {
122
+ const shown = session.files.slice(0, MAX_HANDOFF_FILES).map((f) => `- ${f}`);
123
+ const more = session.files.length - shown.length;
124
+ if (more > 0)
125
+ shown.push(`- (+${more} more)`);
126
+ sections.push(`## Files touched\n\n${shown.join('\n')}`);
127
+ }
128
+ if (session.turns.length === 0) {
129
+ const fixed = `${sections.join('\n\n')}\n\n## Transcript (tail)\n\n`;
130
+ const body = markdown.trim();
131
+ return (fixed + body.slice(Math.max(0, body.length - (maxChars - fixed.length)))).slice(0, maxChars);
132
+ }
133
+ const fixed = `${sections.join('\n\n')}\n\n## Recent turns\n\n`;
134
+ const markerReserve = 64;
135
+ let remaining = maxChars - fixed.length - markerReserve;
136
+ const picked = [];
137
+ for (let i = session.turns.length - 1; i >= 0 && remaining > 0; i--) {
138
+ const block = turnBlock(session.turns[i]);
139
+ if (block.length + 2 <= remaining) {
140
+ picked.unshift(block);
141
+ remaining -= block.length + 2;
142
+ continue;
143
+ }
144
+ if (picked.length === 0) {
145
+ const [header, ...rest] = block.split('\n');
146
+ const prefix = `${header}\n\n[... turn truncated ...]\n`;
147
+ const keep = remaining - prefix.length;
148
+ if (keep > 0) {
149
+ const body = rest.join('\n');
150
+ picked.unshift(prefix + body.slice(body.length - keep));
151
+ }
152
+ }
153
+ break;
154
+ }
155
+ const omitted = session.turns.length - picked.length;
156
+ const marker = omitted > 0 ? `[... ${omitted} earlier turns omitted ...]\n\n` : '';
157
+ return `${fixed}${marker}${picked.join('\n\n')}`.slice(0, maxChars);
158
+ }
@@ -4,16 +4,15 @@ export declare const driveContextSchema: {
4
4
  task: z.ZodString;
5
5
  workspace_id: z.ZodOptional<z.ZodString>;
6
6
  limit: z.ZodOptional<z.ZodNumber>;
7
- include_facts: z.ZodOptional<z.ZodBoolean>;
8
7
  };
9
8
  export declare function driveContext(client: ApiClient, args: {
10
9
  task: string;
11
10
  workspace_id?: string;
12
11
  limit?: number;
13
- include_facts?: boolean;
14
12
  }): Promise<{
15
13
  content: {
16
14
  type: "text";
17
15
  text: string;
18
16
  }[];
19
17
  }>;
18
+ export declare function relativeTime(ms: number): string;
@@ -3,14 +3,12 @@ export const driveContextSchema = {
3
3
  task: z.string().min(1).max(2000).describe('What you are about to do — used to rank prior artifacts by relevance'),
4
4
  workspace_id: z.string().optional().describe('Scope to a single workspace; omit for org-wide context'),
5
5
  limit: z.number().int().positive().max(20).optional().describe('Max artifacts to return (default 5, max 20)'),
6
- include_facts: z.boolean().optional().describe('Include extracted facts alongside artifacts (v1.5+)'),
7
6
  };
8
7
  export async function driveContext(client, args) {
9
8
  const { artifacts } = await client.context({
10
9
  task: args.task,
11
10
  workspace_id: args.workspace_id,
12
11
  limit: args.limit ?? 5,
13
- include_facts: args.include_facts ?? false,
14
12
  });
15
13
  if (artifacts.length === 0) {
16
14
  return { content: [{ type: 'text', text: 'No prior context found.' }] };
@@ -33,7 +31,7 @@ function truncate(s, n) {
33
31
  return s;
34
32
  return s.slice(0, n - 1) + '...';
35
33
  }
36
- function relativeTime(ms) {
34
+ export function relativeTime(ms) {
37
35
  if (ms < 0)
38
36
  return 'just now';
39
37
  const sec = Math.floor(ms / 1000);
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ import type { ApiClient } from '../api-client.js';
3
+ export declare const driveResumeSchema: {
4
+ session_id: z.ZodOptional<z.ZodString>;
5
+ platform: z.ZodOptional<z.ZodString>;
6
+ max_chars: z.ZodOptional<z.ZodNumber>;
7
+ };
8
+ export declare function driveResume(client: ApiClient, args: {
9
+ session_id?: string;
10
+ platform?: string;
11
+ max_chars?: number;
12
+ }): Promise<{
13
+ content: {
14
+ type: "text";
15
+ text: string;
16
+ }[];
17
+ }>;
@@ -0,0 +1,37 @@
1
+ import { z } from 'zod';
2
+ import { buildHandoff, isEncrypted } from '../session.js';
3
+ import { noProjectResult } from './drive-sessions.js';
4
+ export const driveResumeSchema = {
5
+ session_id: z.string().optional().describe('Session run id from drive_sessions (e.g. codex:abc123); omit to pick the latest session from another harness'),
6
+ platform: z.string().optional().describe('When session_id is omitted, only consider sessions from this harness'),
7
+ max_chars: z.number().int().min(1000).max(50000).optional().describe('Handoff size budget in characters (default 12000)'),
8
+ };
9
+ function text(value) {
10
+ return { content: [{ type: 'text', text: value }] };
11
+ }
12
+ async function pickSession(client, project, platform) {
13
+ const { items } = await client.listRuns({ project, platform, limit: 20 });
14
+ return items.find((r) => r.agent_platform !== client.agentPlatform) ?? items[0];
15
+ }
16
+ export async function driveResume(client, args) {
17
+ let run;
18
+ if (args.session_id) {
19
+ run = await client.getRun(args.session_id);
20
+ }
21
+ else {
22
+ if (!client.project)
23
+ return noProjectResult();
24
+ run = await pickSession(client, client.project, args.platform);
25
+ if (!run)
26
+ return text(`No prior sessions for project ${client.project}.`);
27
+ }
28
+ const transcriptPath = run.metadata.transcript_path;
29
+ if (typeof transcriptPath !== 'string' || !run.workspace_id) {
30
+ return text(`Session ${run.run_id} has no uploaded transcript (missing metadata.transcript_path or workspace_id).`);
31
+ }
32
+ const file = await client.readFileByPath(run.workspace_id, transcriptPath);
33
+ if (isEncrypted(file.content)) {
34
+ return text(`This session is encrypted client-side; run \`adrive resume ${run.run_id}\` in a shell to view it.`);
35
+ }
36
+ return text(buildHandoff(file.content, args.max_chars));
37
+ }
@@ -1,20 +1,20 @@
1
1
  import { z } from 'zod';
2
2
  import type { ApiClient } from '../api-client.js';
3
- declare const AGENT_PLATFORMS: readonly ["claude-code", "codex", "cursor", "langgraph", "crewai", "autogen", "hermes", "openclaw", "unknown"];
3
+ declare const AGENT_PLATFORMS: readonly ["claude-code", "codex", "cursor", "langgraph", "crewai", "autogen", "hermes", "openclaw", "opencode", "gemini-cli", "unknown"];
4
4
  export declare const driveSearchSchema: {
5
5
  query: z.ZodString;
6
6
  workspace_id: z.ZodOptional<z.ZodString>;
7
7
  top_k: z.ZodOptional<z.ZodNumber>;
8
8
  filter: z.ZodOptional<z.ZodObject<{
9
- agent_platform: z.ZodOptional<z.ZodEnum<["claude-code", "codex", "cursor", "langgraph", "crewai", "autogen", "hermes", "openclaw", "unknown"]>>;
9
+ agent_platform: z.ZodOptional<z.ZodEnum<["claude-code", "codex", "cursor", "langgraph", "crewai", "autogen", "hermes", "openclaw", "opencode", "gemini-cli", "unknown"]>>;
10
10
  agent_id: z.ZodOptional<z.ZodString>;
11
11
  path_prefix: z.ZodOptional<z.ZodString>;
12
12
  }, "strip", z.ZodTypeAny, {
13
- agent_platform?: "claude-code" | "cursor" | "codex" | "langgraph" | "crewai" | "autogen" | "hermes" | "openclaw" | "unknown" | undefined;
13
+ agent_platform?: "claude-code" | "cursor" | "codex" | "langgraph" | "crewai" | "autogen" | "hermes" | "openclaw" | "opencode" | "gemini-cli" | "unknown" | undefined;
14
14
  agent_id?: string | undefined;
15
15
  path_prefix?: string | undefined;
16
16
  }, {
17
- agent_platform?: "claude-code" | "cursor" | "codex" | "langgraph" | "crewai" | "autogen" | "hermes" | "openclaw" | "unknown" | undefined;
17
+ agent_platform?: "claude-code" | "cursor" | "codex" | "langgraph" | "crewai" | "autogen" | "hermes" | "openclaw" | "opencode" | "gemini-cli" | "unknown" | undefined;
18
18
  agent_id?: string | undefined;
19
19
  path_prefix?: string | undefined;
20
20
  }>>;
@@ -8,6 +8,8 @@ const AGENT_PLATFORMS = [
8
8
  'autogen',
9
9
  'hermes',
10
10
  'openclaw',
11
+ 'opencode',
12
+ 'gemini-cli',
11
13
  'unknown',
12
14
  ];
13
15
  export const driveSearchSchema = {
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ import type { ApiClient } from '../api-client.js';
3
+ export declare const driveSessionsSchema: {
4
+ project: z.ZodOptional<z.ZodString>;
5
+ platform: z.ZodOptional<z.ZodString>;
6
+ limit: z.ZodOptional<z.ZodNumber>;
7
+ };
8
+ export declare function noProjectResult(): {
9
+ content: {
10
+ type: "text";
11
+ text: string;
12
+ }[];
13
+ };
14
+ export declare function driveSessions(client: ApiClient, args: {
15
+ project?: string;
16
+ platform?: string;
17
+ limit?: number;
18
+ }): Promise<{
19
+ content: {
20
+ type: "text";
21
+ text: string;
22
+ }[];
23
+ }>;
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ import { relativeTime } from './drive-context.js';
3
+ import { sessionActivityAt } from '../session.js';
4
+ export const driveSessionsSchema = {
5
+ project: z.string().regex(/^[a-z0-9._-]{1,200}$/).optional().describe('Project key; defaults to the project of the current working directory'),
6
+ platform: z.string().optional().describe('Only sessions from this harness (claude-code, codex, opencode, gemini-cli, cursor)'),
7
+ limit: z.number().int().positive().max(50).optional().describe('Max sessions to return (default 10, max 50)'),
8
+ };
9
+ export function noProjectResult() {
10
+ return {
11
+ content: [{
12
+ type: 'text',
13
+ text: 'No project key is known for this session. Pass `project` (e.g. github.com__owner__repo).',
14
+ }],
15
+ };
16
+ }
17
+ function sessionTitle(run) {
18
+ const title = run.metadata.title;
19
+ return typeof title === 'string' && title ? title : run.task;
20
+ }
21
+ export async function driveSessions(client, args) {
22
+ const project = args.project ?? client.project;
23
+ if (!project)
24
+ return noProjectResult();
25
+ const { items } = await client.listRuns({ project, platform: args.platform, limit: args.limit ?? 10 });
26
+ if (items.length === 0) {
27
+ return { content: [{ type: 'text', text: `No prior sessions for project ${project}.` }] };
28
+ }
29
+ const now = Date.now();
30
+ const lines = items.map((r) => {
31
+ const age = relativeTime(now - sessionActivityAt(r));
32
+ const uploaded = typeof r.metadata.files_uploaded === 'number' ? r.metadata.files_uploaded : r.artifacts_produced.length;
33
+ return `${age} · ${r.agent_platform} · ${sessionTitle(r)} · id=${r.run_id} · files_uploaded=${uploaded}`;
34
+ });
35
+ return {
36
+ content: [{
37
+ type: 'text',
38
+ text: `Sessions for ${project} (newest first):\n${lines.join('\n')}\n\nCall drive_resume with session_id to load one.`,
39
+ }],
40
+ };
41
+ }
package/package.json CHANGED
@@ -1,13 +1,19 @@
1
1
  {
2
2
  "name": "@adrive/mcp-server",
3
- "version": "0.4.0",
4
- "description": "MCP server for AgentDrive — the persistent workspace your agents share across Claude Code, Codex, Cursor, and LangGraph. Files, semantic search, run logging, cross-platform memory, and per-workspace agent sandboxing.",
3
+ "version": "1.1.0",
4
+ "description": "MCP server for AgentDrive — cross-agent artifact workspace with provenance and portable storage across Claude Code, Codex, Cursor, and LangGraph.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "adrive-mcp": "dist/index.js"
9
9
  },
10
10
  "main": "dist/index.js",
11
+ "exports": {
12
+ ".": "./dist/index.js",
13
+ "./registry": "./dist/registry.js",
14
+ "./session": "./dist/session.js",
15
+ "./project": "./dist/project.js"
16
+ },
11
17
  "files": [
12
18
  "dist",
13
19
  "README.md"
@@ -15,8 +21,7 @@
15
21
  "homepage": "https://agentdrive.sh",
16
22
  "repository": {
17
23
  "type": "git",
18
- "url": "git+https://github.com/Rakesh1002/agentdrive.git",
19
- "directory": "packages/mcp-server"
24
+ "url": "git+https://github.com/Rakesh1002/agentdrive-mcp.git"
20
25
  },
21
26
  "bugs": {
22
27
  "url": "https://github.com/Rakesh1002/agentdrive/issues"
@@ -35,12 +40,6 @@
35
40
  "publishConfig": {
36
41
  "access": "public"
37
42
  },
38
- "scripts": {
39
- "build": "tsc",
40
- "dev": "tsc --watch",
41
- "typecheck": "tsc --noEmit",
42
- "test": "vitest run"
43
- },
44
43
  "dependencies": {
45
44
  "@modelcontextprotocol/sdk": "^1.12.0",
46
45
  "zod": "^3.24.0"
@@ -49,5 +48,11 @@
49
48
  "@types/node": "^22.0.0",
50
49
  "typescript": "^5.7.0",
51
50
  "vitest": "^2.1.0"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc",
54
+ "dev": "tsc --watch",
55
+ "typecheck": "tsc --noEmit",
56
+ "test": "vitest run"
52
57
  }
53
- }
58
+ }