@topline-build/harness-mcp 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -70,5 +70,5 @@ export declare function openArtifact(opts: HarnessMcpStdioOptions, artifactId: s
70
70
  liveUrl: string | null;
71
71
  versionUrl: string | null;
72
72
  }>;
73
- export declare function packArtifactAppDirectory(appDir: string): Promise<PackedArtifactAppBundle>;
73
+ export declare function packArtifactAppDirectory(appDir: string, archivePrefix?: string): Promise<PackedArtifactAppBundle>;
74
74
  export declare function getBearerToken(opts: HarnessMcpStdioOptions): Promise<string>;
package/dist/index.js CHANGED
@@ -94,6 +94,7 @@ export async function callHarnessTool(opts, name, args) {
94
94
  export async function deployArtifactApp(opts, input) {
95
95
  const appDir = stringArg(input, "appDir");
96
96
  const absoluteAppDir = resolveWorkspacePath(opts.workspaceRoot, appDir);
97
+ const platformAppDir = workspaceRelativeAppDir(opts.workspaceRoot, absoluteAppDir);
97
98
  const manifest = await readManifest(absoluteAppDir);
98
99
  const created = await fetchJson(opts, artifactAppsPath(opts), {
99
100
  method: "POST",
@@ -111,7 +112,7 @@ export async function deployArtifactApp(opts, input) {
111
112
  throw new Error(`artifact_app_create_failed:${JSON.stringify(createdRecord)}`);
112
113
  const artifactId = stringValue(createdRecord.artifactId, "artifactId");
113
114
  const artifactVersionId = stringValue(createdRecord.artifactVersionId, "artifactVersionId");
114
- const bundle = await packArtifactAppDirectory(absoluteAppDir);
115
+ const bundle = await packArtifactAppDirectory(absoluteAppDir, platformAppDir);
115
116
  const bearerToken = await getBearerToken(opts);
116
117
  const deployed = await fetchJson(opts, `${artifactAppsPath(opts)}/${encodeURIComponent(artifactId)}/deploy-bundle`, {
117
118
  method: "POST",
@@ -120,7 +121,7 @@ export async function deployArtifactApp(opts, input) {
120
121
  "content-type": "application/gzip",
121
122
  "MCP-Protocol-Version": opts.protocolVersion,
122
123
  "x-platform-artifact-version-id": artifactVersionId,
123
- "x-platform-app-dir": "."
124
+ "x-platform-app-dir": platformAppDir
124
125
  },
125
126
  body: bundle.body
126
127
  });
@@ -195,8 +196,9 @@ export async function openArtifact(opts, artifactId) {
195
196
  versionUrl: typeof draft?.versionUrl === "string" ? draft.versionUrl : typeof live?.versionUrl === "string" ? live.versionUrl : null
196
197
  };
197
198
  }
198
- export async function packArtifactAppDirectory(appDir) {
199
+ export async function packArtifactAppDirectory(appDir, archivePrefix = "") {
199
200
  const root = path.resolve(appDir);
201
+ const prefix = archivePrefix ? validateArchivePrefix(archivePrefix) : "";
200
202
  const files = [];
201
203
  const excluded = [];
202
204
  await walk(root, root, files, excluded);
@@ -209,7 +211,7 @@ export async function packArtifactAppDirectory(appDir) {
209
211
  if (byteCount > MAX_UNPACKED_BYTES)
210
212
  throw new Error(`artifact_app_bundle_unpacked_too_large:${MAX_UNPACKED_BYTES}`);
211
213
  const content = await readFile(file.absolute);
212
- chunks.push(tarHeader(file.relative, content.length));
214
+ chunks.push(tarHeader(prefix ? `${prefix}/${file.relative}` : file.relative, content.length));
213
215
  chunks.push(content);
214
216
  chunks.push(Buffer.alloc(padSize(content.length)));
215
217
  }
@@ -262,11 +264,20 @@ function resolveWorkspacePath(workspaceRoot, inputPath) {
262
264
  throw new Error("appDir_must_be_inside_workspace_root");
263
265
  return resolved;
264
266
  }
267
+ function workspaceRelativeAppDir(workspaceRoot, absoluteAppDir) {
268
+ const relative = toPosix(path.relative(path.resolve(workspaceRoot), path.resolve(absoluteAppDir)));
269
+ return validateArchivePrefix(relative);
270
+ }
271
+ function validateArchivePrefix(prefix) {
272
+ const normalized = toPosix(prefix).replace(/^\/+|\/+$/g, "");
273
+ validateRelativePath(normalized);
274
+ return normalized;
275
+ }
265
276
  function validateRelativePath(relative) {
266
277
  if (!relative || relative.startsWith("/") || /^[A-Za-z]:\//.test(relative))
267
278
  throw new Error("artifact_app_bundle_path_invalid");
268
279
  const parts = relative.split("/").filter(Boolean);
269
- if (parts.some((part) => part === ".."))
280
+ if (parts.some((part) => part === "." || part === ".."))
270
281
  throw new Error(`artifact_app_bundle_path_escape:${relative}`);
271
282
  if (parts.length > MAX_DEPTH)
272
283
  throw new Error(`artifact_app_bundle_depth_limit:${MAX_DEPTH}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topline-build/harness-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Local stdio MCP connector for deploying Topline artifact app drafts.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",