@amerged/ohmyhost-mcp 0.1.28 → 0.1.29

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 (32) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +1419 -0
  3. package/dist/{chunk-GIP4M4CK.js → chunk-BWVBJWRR.js} +67 -21
  4. package/dist/index.js +1 -1
  5. package/dist/stdio-main.js +24152 -2353
  6. package/package.json +1 -1
  7. package/src/apps/mcp/package.json +1 -1
  8. package/src/apps/mcp/src/local-client.ts +12 -2
  9. package/src/apps/mcp/src/local-server.ts +333 -4
  10. package/src/apps/product-cli/package.json +4 -2
  11. package/src/apps/product-cli/src/cli.ts +312 -2
  12. package/src/apps/product-cli/src/command.ts +261 -0
  13. package/src/apps/product-cli/src/composition.ts +74 -4
  14. package/src/apps/product-cli/src/index.ts +6 -0
  15. package/src/apps/product-cli/src/product-api.ts +5 -0
  16. package/src/apps/product-cli/src/project-link-store.ts +92 -1
  17. package/src/apps/product-cli/src/runtime-contract.ts +34 -1
  18. package/src/apps/product-cli/src/source-managed-client.ts +189 -0
  19. package/src/apps/product-cli/src/source-publication-store.ts +563 -0
  20. package/src/apps/product-cli/src/source-publisher.ts +195 -0
  21. package/src/apps/product-cli/src/source-workspace.ts +552 -0
  22. package/src/packages/agent-skills/src/generated-skill-resources.ts +18 -8
  23. package/src/packages/contracts/package.json +10 -0
  24. package/src/packages/contracts/src/managed-sources.ts +582 -0
  25. package/src/packages/contracts/src/mcp-source-codec.ts +150 -0
  26. package/src/packages/contracts/src/operation-failure.ts +22 -6
  27. package/src/packages/contracts/src/problem-policy.ts +61 -14
  28. package/src/packages/sdk-ts/package.json +1 -1
  29. package/src/packages/sdk-ts/src/generated/index.ts +92 -0
  30. package/src/packages/sdk-ts/src/generated/sdk.gen.ts +541 -0
  31. package/src/packages/sdk-ts/src/generated/types.gen.ts +911 -4
  32. package/src/packages/workos-auth-contracts/src/tokens.ts +61 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-mcp",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/mcp",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -105,6 +105,7 @@ import {
105
105
  type OhMyHostClient,
106
106
  type Operation,
107
107
  type OperationEvent,
108
+ type ProjectSource,
108
109
  } from "@ohmyhost/sdk-ts";
109
110
 
110
111
  import type {
@@ -128,6 +129,7 @@ import {
128
129
  parsePaidDomainPlan,
129
130
  parseOperation,
130
131
  parseProjectDataChangePlan,
132
+ parseProjectSource,
131
133
  parseAccountProfile,
132
134
  parseCurrentIdentity,
133
135
  chooseWorkspace,
@@ -136,6 +138,8 @@ import {
136
138
  selectWorkspace,
137
139
  InteractiveSessionRequiredError,
138
140
  NativeKeyringProfileRegistry,
141
+ GeneratedSdkManagedSourceApi,
142
+ type ManagedSourceApi,
139
143
  type AuthenticatedProductApiOverrides,
140
144
  type EffectiveContext,
141
145
  type LocalProfile,
@@ -205,6 +209,8 @@ export async function createAuthenticatedLocalMcpClient(
205
209
  }
206
210
 
207
211
  export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
212
+ public readonly managedSource: ManagedSourceApi;
213
+ public readonly productApiOrigin: string;
208
214
  #client: OhMyHostClient;
209
215
 
210
216
  #workspace: WorkspaceSession | undefined;
@@ -225,6 +231,8 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
225
231
  ) {
226
232
  this.#rescope = (token) => createClient({ auth: token, baseUrl, fetch, throwOnError: true });
227
233
  this.#client = this.#rescope(accessToken);
234
+ this.productApiOrigin = baseUrl;
235
+ this.managedSource = new GeneratedSdkManagedSourceApi(() => this.#client);
228
236
  this.#workspace = workspace;
229
237
  this.#context = context ?? {
230
238
  credential: workspace === undefined ? "environment_token" : "profile",
@@ -811,8 +819,10 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
811
819
  { client: this.#client },
812
820
  );
813
821
  }
814
- public getSource(projectId: string) {
815
- return getProjectSource({ project_id: projectId }, { client: this.#client });
822
+ public async getSource(projectId: string): Promise<ProjectSource> {
823
+ const source = await getProjectSource({ project_id: projectId }, { client: this.#client });
824
+ if (source !== null) parseProjectSource(source);
825
+ return source as ProjectSource;
816
826
  }
817
827
  public async getOperation(operationId: string): Promise<Operation> {
818
828
  return parseOperation(
@@ -1,5 +1,25 @@
1
1
  import { isUserSecretName, USER_SECRET_NAME_PATTERN } from "@ohmyhost/contracts/secret-names";
2
+ import {
3
+ normalizeMcpSourceChanges,
4
+ formatMcpSourceFileResponse,
5
+ McpSourceChangesInputError,
6
+ } from "@ohmyhost/contracts/mcp-source-codec";
2
7
  import type { ManagedMailCommand } from "@ohmyhost/sdk-ts";
8
+ import {
9
+ parseManagedSourceRequest,
10
+ isManagedSourcePath,
11
+ } from "@ohmyhost/contracts/managed-sources";
12
+ import {
13
+ createSourceSnapshot,
14
+ inspectSourceWorkspace,
15
+ publishSourceSnapshot,
16
+ SourceWorkspaceError,
17
+ type ManagedSourceApi,
18
+ prepareLocalSourcePublication,
19
+ completeLocalSourcePublication,
20
+ inspectLocalSourcePublications,
21
+ LocalSourcePublicationError,
22
+ } from "@ohmyhost/product-cli";
3
23
  import mcpPackage from "../package.json" with { type: "json" };
4
24
  import { SavedLoginCredentialError } from "./local-client.js";
5
25
  import {
@@ -57,6 +77,8 @@ import type {
57
77
  } from "@ohmyhost/sdk-ts";
58
78
 
59
79
  export interface LocalMcpProductClient {
80
+ readonly managedSource?: ManagedSourceApi;
81
+ readonly productApiOrigin?: string;
60
82
  checkProjectHandle(input: { handle: string }): Promise<unknown>;
61
83
  changeProjectHandle(input: {
62
84
  projectId: string;
@@ -449,13 +471,42 @@ const deletableSecretName = z
449
471
  );
450
472
 
451
473
  const LOCAL_SERVER_INSTRUCTIONS =
452
- "Operate the user's ohmyho.st hosting account: projects, GitHub deployments, databases, domains, mail, usage and budgets. " +
474
+ "Operate the user's ohmyho.st hosting account: projects, managed or GitHub sources and deployments, databases, domains, mail, usage and budgets. " +
475
+ "Use the existing project source binding first. A local or cloud workspace can contain Git without GitHub. If unbound current files have no relevant GitHub source, ask once whether to deploy directly with ohmyho.st saving versions or set up GitHub; do not require GitHub just because no connection exists. " +
453
476
  "Start by reading the resource skill://ohmyhost/ohmyhost-get-started/SKILL.md and calling identity_get; read project_context_get before acting on a project. " +
454
477
  "This server is also ohmyho.st support: report a bug, issue or feature request with feedback_submit, give the user the receipt ID and follow up with feedback_status (https://docs.ohmyho.st/support).";
455
478
 
456
479
  export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDependencies) {
457
480
  const { commandPrefix } = resolveProductCliEnvironment(dependencies.environment);
458
481
  const server = createOhmyhostMcpServer("ohmyhost-local", LOCAL_SERVER_INSTRUCTIONS);
482
+ server.registerTool(
483
+ "source_inspect",
484
+ {
485
+ title: "Inspect local application source",
486
+ description:
487
+ "Read local working-directory, Git/worktree, monorepo and sanitized remote context without sign-in. This tool exists only in local MCP and never proves repository authorization. No source files or history are uploaded.",
488
+ inputSchema: z.object({ directory: z.string().min(1).max(4096).default(".") }),
489
+ annotations: readAnnotations(),
490
+ },
491
+ async (input) => {
492
+ try {
493
+ return toolResult({
494
+ ...(await inspectSourceWorkspace(input.directory)),
495
+ pending_publications: await inspectLocalSourcePublications(input.directory),
496
+ });
497
+ } catch (error) {
498
+ if (error instanceof SourceWorkspaceError)
499
+ return {
500
+ ...toolResult({
501
+ error: { code: error.code, retryable: false, suggested_action: error.message },
502
+ }),
503
+ isError: true,
504
+ };
505
+ return toolError(error, dependencies);
506
+ }
507
+ },
508
+ );
509
+ registerManagedSourceTools(server, dependencies);
459
510
  registerProductTool(
460
511
  server,
461
512
  dependencies,
@@ -1355,9 +1406,14 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
1355
1406
  server,
1356
1407
  dependencies,
1357
1408
  "source_get",
1358
- "Get linked source status",
1359
- z.object({ project_id: identifier }),
1360
- (client, input) => client.getSource(input.project_id),
1409
+ "Get linked source status. Set include_binding to true to read the source connection ID and generation before managed changes or an explicit source switch.",
1410
+ z.object({ project_id: identifier, include_binding: z.boolean().default(false) }),
1411
+ (client, input) => {
1412
+ if (input.include_binding !== true) return client.getSource(input.project_id);
1413
+ if (client.managedSource === undefined)
1414
+ throw new Error("Managed source client is unavailable");
1415
+ return client.managedSource.getSource(input.project_id);
1416
+ },
1361
1417
  );
1362
1418
  registerProductTool(
1363
1419
  server,
@@ -1831,6 +1887,258 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
1831
1887
  return server;
1832
1888
  }
1833
1889
 
1890
+ function registerManagedSourceTools(
1891
+ server: McpServer,
1892
+ dependencies: LocalOhmyhostMcpDependencies,
1893
+ ): void {
1894
+ const generation = z.number().int().min(0).max(2147483646);
1895
+ const path = z.string().refine(isManagedSourcePath);
1896
+ const message = z.string().min(1).max(200);
1897
+ const parent = {
1898
+ project_id: identifier,
1899
+ expected_source_generation: generation,
1900
+ expected_commit_sha: commitSha.nullable(),
1901
+ message,
1902
+ idempotency_key: idempotencyKey,
1903
+ };
1904
+ const port = (client: LocalMcpProductClient): ManagedSourceApi => {
1905
+ if (client.managedSource === undefined) throw new Error("Managed source client is unavailable");
1906
+ return client.managedSource;
1907
+ };
1908
+ registerProductTool(
1909
+ server,
1910
+ dependencies,
1911
+ "source_initialize",
1912
+ "Create managed version history for an unbound project. Normal blank chat uses vite-react; existing local files use source_publish instead. Returns an asynchronous operation; poll operation_get and source_get with include_binding true before planning the resulting commit. Does not publish an application.",
1913
+ z.object({
1914
+ project_id: identifier,
1915
+ template: z.enum(["empty", "vite-react"]).default("vite-react"),
1916
+ expected_source_generation: generation,
1917
+ idempotency_key: idempotencyKey,
1918
+ }),
1919
+ (client, input) =>
1920
+ port(client).initialize({
1921
+ projectId: input.project_id,
1922
+ idempotencyKey: input.idempotency_key,
1923
+ body: parseManagedSourceRequest("initialize", {
1924
+ template: input.template,
1925
+ expected_source_generation: input.expected_source_generation,
1926
+ }),
1927
+ }),
1928
+ mutationAnnotations(),
1929
+ );
1930
+ registerProductTool(
1931
+ server,
1932
+ dependencies,
1933
+ "source_files",
1934
+ "List the exact managed source tree at a version; omit commit_sha to read current main. Read source_get with include_binding true before changing files and keep the generation and commit for concurrency checks.",
1935
+ z.object({ project_id: identifier, commit_sha: commitSha.optional() }),
1936
+ (client, input) =>
1937
+ port(client).files({ projectId: input.project_id, commitSha: input.commit_sha }),
1938
+ );
1939
+ registerProductTool(
1940
+ server,
1941
+ dependencies,
1942
+ "source_file",
1943
+ "Read one managed source file with its exact hash and mode. UTF-8 content_text is the default; binary files retain content_base64 with guidance. Use format base64 for legacy byte payloads, and keep the executable flag when replacing a file.",
1944
+ z.object({
1945
+ project_id: identifier,
1946
+ path,
1947
+ commit_sha: commitSha.optional(),
1948
+ format: z.enum(["text", "base64"]).default("text"),
1949
+ }),
1950
+ async (client, input) =>
1951
+ formatMcpSourceFileResponse(
1952
+ await port(client).files({
1953
+ projectId: input.project_id,
1954
+ path: input.path,
1955
+ commitSha: input.commit_sha,
1956
+ }),
1957
+ input.format,
1958
+ ),
1959
+ );
1960
+ registerProductTool(
1961
+ server,
1962
+ dependencies,
1963
+ "source_versions",
1964
+ "Read managed version history for this project; next_commit_sha continues the same history page.",
1965
+ z.object({
1966
+ project_id: identifier,
1967
+ limit: z.number().int().min(1).max(100).default(50),
1968
+ before_commit_sha: commitSha.optional(),
1969
+ }),
1970
+ (client, input) =>
1971
+ port(client).versions({
1972
+ projectId: input.project_id,
1973
+ limit: input.limit,
1974
+ beforeCommitSha: input.before_commit_sha,
1975
+ }),
1976
+ );
1977
+ registerProductTool(
1978
+ server,
1979
+ dependencies,
1980
+ "source_diff",
1981
+ "Compare two managed versions without changing files or deployments.",
1982
+ z.object({ project_id: identifier, from_commit_sha: commitSha, to_commit_sha: commitSha }),
1983
+ (client, input) =>
1984
+ port(client).diff({
1985
+ projectId: input.project_id,
1986
+ fromCommitSha: input.from_commit_sha,
1987
+ toCommitSha: input.to_commit_sha,
1988
+ }),
1989
+ );
1990
+ registerProductTool(
1991
+ server,
1992
+ dependencies,
1993
+ "source_changes",
1994
+ "Save an atomic file batch against the generation and parent commit you read. Each file takes exactly one UTF-8 content_text or binary/legacy content_base64; null content_base64 deletes it. Keep executable flags. A conflict requires reading the new version; publication is separate.",
1995
+ z.object({
1996
+ ...parent,
1997
+ changes: z
1998
+ .array(
1999
+ z
2000
+ .object({
2001
+ path,
2002
+ content_text: z
2003
+ .string()
2004
+ .max(2 * 1024 * 1024)
2005
+ .optional(),
2006
+ content_base64: z.string().max(2_796_204).nullable().optional(),
2007
+ executable: z.boolean().default(false),
2008
+ })
2009
+ .strict(),
2010
+ )
2011
+ .min(1)
2012
+ .max(2000),
2013
+ }),
2014
+ (client, input) =>
2015
+ port(client).changes({
2016
+ projectId: input.project_id,
2017
+ idempotencyKey: input.idempotency_key,
2018
+ body: normalizeMcpSourceChanges({
2019
+ expected_source_generation: input.expected_source_generation,
2020
+ expected_commit_sha: input.expected_commit_sha,
2021
+ message: input.message,
2022
+ changes: input.changes,
2023
+ }),
2024
+ }),
2025
+ mutationAnnotations(),
2026
+ );
2027
+ registerProductTool(
2028
+ server,
2029
+ dependencies,
2030
+ "source_restore",
2031
+ "Restore an earlier managed file tree as a new version against current generation and parent. This preserves history and does not automatically deploy or revert database data.",
2032
+ z.object({ ...parent, restore_commit_sha: commitSha }),
2033
+ (client, input) =>
2034
+ port(client).restore({
2035
+ projectId: input.project_id,
2036
+ idempotencyKey: input.idempotency_key,
2037
+ body: parseManagedSourceRequest("restore", {
2038
+ expected_source_generation: input.expected_source_generation,
2039
+ expected_commit_sha: input.expected_commit_sha,
2040
+ message: input.message,
2041
+ restore_commit_sha: input.restore_commit_sha,
2042
+ }),
2043
+ }),
2044
+ mutationAnnotations(),
2045
+ );
2046
+ registerProductTool(
2047
+ server,
2048
+ dependencies,
2049
+ "source_publish",
2050
+ "Local MCP only: capture current filtered files, including uncommitted/new files, and save one managed version without importing Git history. Use initialize for unbound source, commit for managed source, or explicit switch for the current GitHub project's move to managed; project URL and data stay with that project. Honors nested ignore rules and excludes credentials, links, dependencies and generated output. Rejects symlinks and changing files. Reuse the key after uncertainty and observe the upload before any new attempt; publication is a separate deployment action.",
2051
+ z.object({
2052
+ ...parent,
2053
+ expected_commit_sha: commitSha.nullable().optional(),
2054
+ directory: z.string().min(1).max(4096).default("."),
2055
+ mode: z.enum(["initialize", "commit", "switch"]),
2056
+ expected_source_connection_id: identifier.nullable(),
2057
+ }),
2058
+ async (client, input, signal) => {
2059
+ const current = await client.getCurrentIdentity(signal);
2060
+ const snapshot = await createSourceSnapshot({ directory: input.directory });
2061
+ const request = parseManagedSourceRequest("upload", {
2062
+ expected_source_generation: input.expected_source_generation,
2063
+ expected_commit_sha: input.expected_commit_sha ?? null,
2064
+ expected_source_connection_id: input.expected_source_connection_id,
2065
+ mode: input.mode,
2066
+ message: input.message,
2067
+ });
2068
+ const prepared = await prepareLocalSourcePublication({
2069
+ directory: input.directory,
2070
+ metadataDirectory: resolveProductCliEnvironment(dependencies.environment).linkDirectory,
2071
+ apiOrigin:
2072
+ client.productApiOrigin ??
2073
+ resolveProductCliEnvironment(dependencies.environment).apiOrigin,
2074
+ identity: { actor_id: current.actor_id, organization_ids: current.organization_ids },
2075
+ projectId: input.project_id,
2076
+ request,
2077
+ explicitExpectedCommit: input.expected_commit_sha !== undefined,
2078
+ snapshotSha256: snapshot.sha256,
2079
+ idempotencyKey: input.idempotency_key,
2080
+ });
2081
+ const result = await publishSourceSnapshot({
2082
+ api: port(client),
2083
+ projectId: input.project_id,
2084
+ snapshot,
2085
+ idempotencyKey: input.idempotency_key,
2086
+ signal,
2087
+ request: prepared.request,
2088
+ accepted: prepared.accepted,
2089
+ rejected: prepared.rejected,
2090
+ });
2091
+ return {
2092
+ ...result,
2093
+ snapshot: {
2094
+ sha256: snapshot.sha256,
2095
+ total_bytes: snapshot.totalBytes,
2096
+ files: snapshot.files.length,
2097
+ },
2098
+ next_action:
2099
+ "Poll operation_get, then call source_publish_complete with the same directory, project, operation and login to confirm this directory's base. Do not ask the user again.",
2100
+ };
2101
+ },
2102
+ mutationAnnotations(),
2103
+ );
2104
+ registerProductTool(
2105
+ server,
2106
+ dependencies,
2107
+ "source_publish_complete",
2108
+ "Complete a local pending source publication after polling its operation. Uses the owner-only directory record and verified login, checks the exact confirmed source manifest, and stores that operation's commit as this directory's base even when another chat has advanced main. Never reads a fresh remote head as the local base. No files are uploaded and no deployment starts.",
2109
+ z.object({
2110
+ project_id: identifier,
2111
+ directory: z.string().min(1).max(4096).default("."),
2112
+ operation_id: identifier.optional(),
2113
+ }),
2114
+ async (client, input, signal) => {
2115
+ const current = await client.getCurrentIdentity(signal);
2116
+ return completeLocalSourcePublication({
2117
+ directory: input.directory,
2118
+ metadataDirectory: resolveProductCliEnvironment(dependencies.environment).linkDirectory,
2119
+ apiOrigin:
2120
+ client.productApiOrigin ??
2121
+ resolveProductCliEnvironment(dependencies.environment).apiOrigin,
2122
+ identity: { actor_id: current.actor_id, organization_ids: current.organization_ids },
2123
+ projectId: input.project_id,
2124
+ operationId: input.operation_id,
2125
+ api: port(client),
2126
+ getOperation: (id) => client.getOperation(id),
2127
+ });
2128
+ },
2129
+ mutationAnnotations(),
2130
+ );
2131
+ registerProductTool(
2132
+ server,
2133
+ dependencies,
2134
+ "source_upload",
2135
+ "Observe the exact managed snapshot upload after an uncertain response. sealed and committed uploads must not be uploaded again; commit_sha identifies the saved version.",
2136
+ z.object({ project_id: identifier, operation_id: identifier }),
2137
+ (client, input) =>
2138
+ port(client).getUpload({ projectId: input.project_id, operationId: input.operation_id }),
2139
+ );
2140
+ }
2141
+
1834
2142
  export const createLocalOhmyhostMcpHandler = (dependencies: LocalOhmyhostMcpDependencies) =>
1835
2143
  createMcpHandler(() => createLocalOhmyhostMcpServer(dependencies));
1836
2144
 
@@ -1997,6 +2305,27 @@ function toolError(
1997
2305
  * problem. What no branch recognizes is reported as such, never as a guessed cause.
1998
2306
  */
1999
2307
  function safeFailure(error: unknown, commandPrefix: string): SafeFailure {
2308
+ if (error instanceof McpSourceChangesInputError)
2309
+ return {
2310
+ code: "invalid_request",
2311
+ retryable: false,
2312
+ suggestedAction:
2313
+ "Provide exactly one content_text or content_base64 per file, within the 2 MiB source limits. Use null content_base64 to delete a file.",
2314
+ };
2315
+ if (error instanceof LocalSourcePublicationError)
2316
+ return {
2317
+ code: error.code,
2318
+ retryable: error.code === "source_publication_pending",
2319
+ suggestedAction:
2320
+ error.message +
2321
+ (error.operation === undefined ? "" : ` Read operation_get ${error.operation.id}.`),
2322
+ };
2323
+ if (error instanceof SourceWorkspaceError)
2324
+ return {
2325
+ code: error.code,
2326
+ retryable: error.code === "source_snapshot_changed",
2327
+ suggestedAction: error.message,
2328
+ };
2000
2329
  if (error instanceof UserTokenFileError)
2001
2330
  return { code: error.code, retryable: false, suggestedAction: error.message };
2002
2331
  if (error instanceof DOMException && error.name === "TimeoutError")
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/product-cli",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -39,7 +39,9 @@
39
39
  "@napi-rs/keyring": "1.3.0",
40
40
  "@ohmyhost/contracts": "workspace:*",
41
41
  "@ohmyhost/sdk-ts": "workspace:*",
42
- "@ohmyhost/workos-auth-contracts": "workspace:*"
42
+ "@ohmyhost/workos-auth-contracts": "workspace:*",
43
+ "ignore": "7.0.12",
44
+ "isomorphic-git": "1.43.0"
43
45
  },
44
46
  "license": "Apache-2.0"
45
47
  }