drafted 1.7.23 → 1.7.25

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.
Files changed (2) hide show
  1. package/mcp/server.mjs +48 -4
  2. package/package.json +1 -1
package/mcp/server.mjs CHANGED
@@ -108,6 +108,25 @@ export function runWithRequestState(initial, fn) {
108
108
  return requestState.run(merged, fn);
109
109
  }
110
110
 
111
+ // Params that only work when the MCP runs on the user's machine (stdio).
112
+ // Stripped from the advertised schema on remote transports.
113
+ const LOCAL_ONLY_PARAMS = ['file_path'];
114
+
115
+ // Remove sentences that reference local-file params from a tool description,
116
+ // so remote-transport descriptions don't mention options that were stripped.
117
+ function scrubLocalPathMentions(description) {
118
+ if (!description) return description;
119
+ return description
120
+ .split('\n')
121
+ .map((para) =>
122
+ para
123
+ .split(/(?<=\.)\s+/)
124
+ .filter((sentence) => !/\bfile_path\b/.test(sentence))
125
+ .join(' ')
126
+ )
127
+ .join('\n');
128
+ }
129
+
111
130
  // ── MCP server factory ────────────────────────────────────────────
112
131
  // Streamable-HTTP requires a fresh McpServer per request — the SDK only
113
132
  // permits one transport connection per server instance, so a singleton
@@ -115,7 +134,17 @@ export function runWithRequestState(initial, fn) {
115
134
  // inside the factory so each HTTP request gets its own isolated server.
116
135
  // Stdio mode uses the `mcpServer` singleton (built once at module load).
117
136
 
118
- export function createMcpServer() {
137
+ export function createMcpServer(transport) {
138
+ // Remote transports (hosted HTTP MCP for claude.ai / ChatGPT) run on the
139
+ // server, not the user's machine, so local-filesystem params like `file_path`
140
+ // can't reach the user's files. Hide them from the advertised schema to avoid
141
+ // confusing web users. The in-server route passes 'http' explicitly because
142
+ // the server process has no --http argv to auto-detect. When called with no
143
+ // argument (stdio singleton / standalone bootstrap), detect from argv — this
144
+ // mirrors mcpMode(), which is defined later inside this factory and so is not
145
+ // available as a default-parameter expression here.
146
+ const mode = transport || (process.argv.includes('--http') ? 'http' : 'stdio');
147
+ const isRemote = mode !== 'stdio';
119
148
  trackUmamiEvent(UMAMI_EVENTS.MCP_CONNECTED, { source: 'mcp' });
120
149
  const server = new McpServer({
121
150
  name: 'drafted',
@@ -249,6 +278,20 @@ function tool(name, descOrSchema, schemaOrHandler, handler) {
249
278
  inputSchema = descOrSchema;
250
279
  cb = schemaOrHandler;
251
280
  }
281
+ // On remote transports, drop local-filesystem params so web clients don't see
282
+ // options that can't work off their machine. Handlers fall back to
283
+ // base64/content/url when file_path is absent. Also scrub references to those
284
+ // params from the tool description and any sibling param descriptions, so the
285
+ // advertised schema has zero mentions of options the client can't use.
286
+ if (isRemote && inputSchema && typeof inputSchema === 'object') {
287
+ for (const k of LOCAL_ONLY_PARAMS) delete inputSchema[k];
288
+ for (const [k, field] of Object.entries(inputSchema)) {
289
+ if (field?.description && /\bfile_path\b/.test(field.description)) {
290
+ inputSchema[k] = field.describe(scrubLocalPathMentions(field.description));
291
+ }
292
+ }
293
+ description = scrubLocalPathMentions(description);
294
+ }
252
295
  const config = {
253
296
  title: ann.title,
254
297
  description,
@@ -1763,7 +1806,7 @@ tool('get_org', {
1763
1806
 
1764
1807
  // ── Filesystem tools (direct HTTP to /api/fs) ─────────────────────
1765
1808
 
1766
- tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by path, frame URL, or UUID), write (new frame or overwrite), Google Sheet actions (`get_sheet`, `read_sheet_values`, `write_sheet_values`, `append_sheet_rows`, `clear_sheet_range`, `update_sheet`), Google Doc actions (`get_doc`, `read_doc_content`, `write_doc_content`, `append_doc_content`, `clear_doc_content`, `update_doc`), Google Slide actions (`get_slide`, `read_slide_content`, `write_slide_content`, `append_slides`, `clear_slides`, `update_slide`), write_excalidraw (native editable Excalidraw diagram), edit (hashline ops), mv (rename/move), anchor (mark as required-read for the layer), search (match frame names). Use project(action="open") first. For listing use `ls`, for deletion use `rm`.\n\n**Google Workspace native content:** Create or attach Google Docs/Sheets/Slides with `frame(action="write", googleType=...)`. After that, populate native Google Docs with `write_doc_content`/`append_doc_content` and native Google Slides with `write_slide_content`/`append_slides`; read them with `read_doc_content`/`read_slide_content`. Do NOT use inline `frame.write(content)` or hashline `frame.edit` to populate Google Doc/Slide frames — those actions are for Drafted inline frame files, not native Workspace document bodies.\n\n**Write content, binary, or Google Workspace frame:** Provide exactly one of `content` (HTML/markdown/text), `file_path` (absolute local file), `base64` (base64-encoded binary with optional `content_type`), or `googleType` (`google-doc`, `google-sheet`, `google-slide`). Call get_org first; when `googleDrive.connected` is true, strongly prefer Google Workspace frames for docs, sheets, and slides in that org. For inline content, filename extension matters: use `.html` for complete HTML documents and `.md` for Markdown. Never place a full HTML document in a `.md` or extensionless frame. For a new Google file, pass `googleType` and optional `title`; for an existing Google file, pass `googleType` plus `url` or `googleId`. For binary frames (images, PDFs, videos), use `file_path` when the file is local to the MCP host, or `base64` when the caller already has binary bytes.\n\n**Write — dimensions:** By default, frames use the layer\'s default size (e.g. 1440×900 for designs, 1440×3000 for wireframes). Often too large for small content. Use `autoSize: true` to measure HTML content and size to fit, or pass explicit `width`/`height`.', {
1809
+ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by path, frame URL, or UUID), write (new frame or overwrite), Google Sheet actions (`get_sheet`, `read_sheet_values`, `write_sheet_values`, `append_sheet_rows`, `clear_sheet_range`, `update_sheet`), Google Doc actions (`get_doc`, `read_doc_content`, `write_doc_content`, `append_doc_content`, `clear_doc_content`, `update_doc`), Google Slide actions (`get_slide`, `read_slide_content`, `write_slide_content`, `append_slides`, `clear_slides`, `update_slide`), write_excalidraw (native editable Excalidraw diagram), edit (hashline ops), mv (rename/move), anchor (mark as required-read for the layer), search (match frame names). Use project(action="open") first. For listing use `ls`, for deletion use `rm`.\n\n**Google Workspace native content:** Create or attach Google Docs/Sheets/Slides with `frame(action="write", googleType=...)`. After creating, immediately populate the native file using the matching write action in the same tool — do NOT leave it empty and do NOT tell the user you cannot write to it. For Sheets: `write_sheet_values` or `append_sheet_rows` (pass `path` or `googleId` from the create response). For Docs: `write_doc_content`/`append_doc_content`. For Slides: `write_slide_content`/`append_slides`. Read with `read_sheet_values`/`read_doc_content`/`read_slide_content`. Do NOT use inline `frame.write(content)` or hashline `frame.edit` to populate Google Workspace frames.\n\n**Writecontent, binary, or Google Workspace frame:** ' + (isRemote ? 'Provide exactly one of `content` (HTML/markdown/text), `base64` (base64-encoded binary with optional `content_type`), or `googleType` (`google-doc`, `google-sheet`, `google-slide`).' : 'Provide exactly one of `content` (HTML/markdown/text), `file_path` (absolute local file), `base64` (base64-encoded binary with optional `content_type`), or `googleType` (`google-doc`, `google-sheet`, `google-slide`).') + ' Call get_org first; when `googleDrive.connected` is true, strongly prefer Google Workspace frames for docs, sheets, and slides in that org. For inline content, filename extension matters: use `.html` for complete HTML documents and `.md` for Markdown. Never place a full HTML document in a `.md` or extensionless frame. For a new Google file, pass `googleType` and optional `title`; for an existing Google file, pass `googleType` plus `url` or `googleId`. ' + (isRemote ? 'For binary frames (images, PDFs, videos), pass `base64` with the binary bytes.' : 'For binary frames (images, PDFs, videos), use `file_path` when the file is local to the MCP host, or `base64` when the caller already has binary bytes.') + '\n\n**Write — dimensions:** By default, frames use the layer\'s default size (e.g. 1440×900 for designs, 1440×3000 for wireframes). Often too large for small content. Use `autoSize: true` to measure HTML content and size to fit, or pass explicit `width`/`height`.', {
1767
1810
  action: z.enum(['read', 'write', 'write_sheet_values', 'read_sheet_values', 'append_sheet_rows', 'clear_sheet_range', 'get_sheet', 'update_sheet', 'get_doc', 'read_doc_content', 'write_doc_content', 'append_doc_content', 'clear_doc_content', 'update_doc', 'get_slide', 'read_slide_content', 'write_slide_content', 'append_slides', 'clear_slides', 'update_slide', 'write_excalidraw', 'edit', 'mv', 'anchor', 'search', 'versions', 'read_version', 'restore_version']).describe('Operation to perform. Use native Doc/Slide actions for Google Docs/Slides; do not use inline write/edit for native Workspace content.'),
1768
1811
  path: z.string().optional().describe('[read] /{layer}/{lane}/{filename}, frame URL, or UUID. [write|edit|anchor] /{layer}/{lane}/{filename}.'),
1769
1812
  lines: z.string().optional().describe('[read] line range (e.g. "1-50"). Omit to read all.'),
@@ -2248,8 +2291,9 @@ tool('batch', 'Batch operations on the ACTIVE PROJECT. Response includes "projec
2248
2291
  operations: z.array(z.object({
2249
2292
  tool: z.enum(['write', 'rm', 'mv', 'edit', 'upload_asset']).describe('Tool to execute'),
2250
2293
  path: z.string().optional().describe('Path (for write, rm, edit)'),
2251
- content: z.string().optional().describe('Content (for write). Mutually exclusive with file_path.'),
2252
- file_path: z.string().optional().describe('Absolute path to a local file to upload (for write, upload_asset). Mutually exclusive with content.'),
2294
+ content: z.string().optional().describe(`Content (for write).${isRemote ? '' : ' Mutually exclusive with file_path.'}`),
2295
+ // file_path is local-only omitted on remote transports (web MCP).
2296
+ ...(isRemote ? {} : { file_path: z.string().optional().describe('Absolute path to a local file to upload (for write, upload_asset). Mutually exclusive with content.') }),
2253
2297
  color: z.string().optional().describe('CSS color for frame border (for write)'),
2254
2298
  from: z.string().optional().describe('Source path (for mv)'),
2255
2299
  to: z.string().optional().describe('Destination path (for mv)'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.7.23",
3
+ "version": "1.7.25",
4
4
  "description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
5
5
  "type": "module",
6
6
  "files": [