@amerged/ohmyhost-mcp 0.1.48 → 0.1.50

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-mcp",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
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.48",
3
+ "version": "0.1.50",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -4,6 +4,7 @@ import {
4
4
  type ManagedMailCommand,
5
5
  type SharedMailCommand,
6
6
  } from "@ohmyhost/sdk-ts";
7
+ import { executeNativeAuth, type NativeAuthCommand } from "@ohmyhost/product-cli";
7
8
  import {
8
9
  assertProjectDatabaseAccess,
9
10
  assertProjectDatabaseAccessCredential,
@@ -1156,6 +1157,10 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
1156
1157
  public managedMail(input: ManagedMailCommand) {
1157
1158
  return executeManagedMail(this.#client, input);
1158
1159
  }
1160
+
1161
+ public nativeAuth(input: NativeAuthCommand) {
1162
+ return executeNativeAuth(this.#client, input);
1163
+ }
1159
1164
  public planDelete(projectId: string) {
1160
1165
  return planProjectDeletion({ project_id: projectId }, { client: this.#client });
1161
1166
  }
@@ -1,4 +1,5 @@
1
1
  import { isUserSecretName, USER_SECRET_NAME_PATTERN } from "@ohmyhost/contracts/secret-names";
2
+ import { isNativeAuthRedirectPath } from "@ohmyhost/contracts/native-auth";
2
3
  import type { DatabaseComputeSelection } from "@ohmyhost/contracts/database-sizes";
3
4
  import {
4
5
  DATABASE_WEBHOOK_EVENT_TYPES,
@@ -136,6 +137,7 @@ interface DatabaseScheduleSetInput extends DatabaseScheduleTarget {
136
137
  }
137
138
 
138
139
  export interface LocalMcpProductClient {
140
+ nativeAuth?(input: import("@ohmyhost/product-cli").NativeAuthCommand): Promise<unknown>;
139
141
  readonly managedSource?: ManagedSourceApi;
140
142
  readonly productApiOrigin?: string;
141
143
  checkProjectHandle(input: { handle: string }): Promise<unknown>;
@@ -612,7 +614,7 @@ const deletableSecretName = z
612
614
  );
613
615
 
614
616
  const LOCAL_SERVER_INSTRUCTIONS =
615
- "Continue an existing app from its reachable workspace and saved project binding. Reuse a matching local CLI/MCP profile or private token source unless the user chooses remote; verify its identity and workspace. connection_request availability alone never selects remote. If remote identity/workspace mismatches, stop the access-link loop. Unreachable local context is an access gap, not deleted credentials; never substitute an unrelated Review/demo account or change the saved source. " +
617
+ "Continue an existing app from its reachable workspace and saved project binding. Reuse a matching local CLI/MCP profile or private token source unless the user chooses remote; verify its identity and workspace. Remote tool availability alone never selects remote. If remote identity/workspace mismatches, stop the access-link loop. Unreachable local context is an access gap, not deleted credentials; never substitute an unrelated Review/demo account or change the saved source. " +
616
618
  "Operate the user's ohmyho.st hosting account: projects, managed or GitHub sources and deployments, databases, domains, mail, usage and budgets. " +
617
619
  "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. " +
618
620
  "Read the resource skill://ohmyhost/ohmyhost-get-started/SKILL.md and verify the chosen access with identity_get; read project_context_get before acting on a project. " +
@@ -1952,7 +1954,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
1952
1954
  server,
1953
1955
  dependencies,
1954
1956
  "deployment_plan",
1955
- "Plan an immutable deployment. Preserve the customer's existing authentication integration, such as Better Auth or WorkOS, and use the same generic Next.js/Vite/TanStack runtime contract. ohmyho.st login is separate. Read the app Skill's auth guidance and report actual plan blockers, required secret names/callback configuration and unverified runtime compatibility. The explicitly selected managed Better Auth bridge requires the managed database; managed mail is optional, and its verification or password-reset send actions require a configured platform sender. Application-owned Better Auth handlers can use their own sender, including Resend, with auth.provider set to none. Never assume a working platform login proves application login or silently replace its auth provider. The plan builds into Dev unless environment is prod; prod builds straight into Prod without a Dev deployment (a project with shared data and a database must deploy to Dev and promote instead). The plan stays valid for 24 hours.",
1957
+ "Plan an immutable deployment. Preserve the customer's existing authentication integration, such as Better Auth or WorkOS, and use the same generic Next.js/Vite/TanStack runtime contract. ohmyho.st login is separate. Read the app Skill's auth guidance and report actual plan blockers, required secret names/callback configuration and unverified runtime compatibility. The explicitly selected managed Better Auth bridge requires the managed database; managed mail is optional, and its verification or password-reset send actions require a configured platform sender. Application-owned Better Auth handlers can use their own sender, including Resend, with auth.provider set to none. The unreleased omh-native capability is an explicit choice requiring Paid access, a verified sender and its separately metered Auth database; verify readiness and hosted support before claiming it works. Never assume a working platform login proves application login or silently replace its auth provider. The plan builds into Dev unless environment is prod; prod builds straight into Prod without a Dev deployment (a project with shared data and a database must deploy to Dev and promote instead). The plan stays valid for 24 hours.",
1956
1958
  z.object({
1957
1959
  project_id: identifier,
1958
1960
  commit_sha: commitSha,
@@ -2653,6 +2655,310 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
2653
2655
  },
2654
2656
  destructiveAnnotations(),
2655
2657
  );
2658
+ const nativeScope = { project_id: identifier, environment_id: identifier };
2659
+ const nativeProvider = z.enum(["google", "github"]);
2660
+ const nativeRun = (
2661
+ client: LocalMcpProductClient,
2662
+ command: import("@ohmyhost/product-cli").NativeAuthCommand,
2663
+ ) => {
2664
+ if (!client.nativeAuth) throw new Error("Native Auth unavailable");
2665
+ return client.nativeAuth(command);
2666
+ };
2667
+ registerProductTool(
2668
+ server,
2669
+ dependencies,
2670
+ "native_auth_config_get",
2671
+ "Read Owner Native Auth configuration and readiness.",
2672
+ z.object(nativeScope),
2673
+ (client, input) =>
2674
+ nativeRun(client, {
2675
+ action: "config_get",
2676
+ projectId: input.project_id,
2677
+ environmentId: input.environment_id,
2678
+ }),
2679
+ );
2680
+ registerProductTool(
2681
+ server,
2682
+ dependencies,
2683
+ "native_auth_config_set",
2684
+ "Update Owner Native Auth configuration at the current revision.",
2685
+ z.object({
2686
+ ...nativeScope,
2687
+ configuration_revision: z.number().int().min(0),
2688
+ enabled: z.boolean(),
2689
+ signup_policy: z.enum(["open", "invite-only"]),
2690
+ application_name: z
2691
+ .string()
2692
+ .trim()
2693
+ .min(1)
2694
+ .max(128)
2695
+ .refine((value) => !/\p{Cc}/u.test(value)),
2696
+ canonical_origin: z
2697
+ .string()
2698
+ .url()
2699
+ .refine((value) => {
2700
+ try {
2701
+ const url = new URL(value);
2702
+ return (
2703
+ url.protocol === "https:" && url.origin === value && !url.username && !url.password
2704
+ );
2705
+ } catch {
2706
+ return false;
2707
+ }
2708
+ })
2709
+ .nullable()
2710
+ .optional(),
2711
+ allowed_redirect_paths: z
2712
+ .array(z.string().refine(isNativeAuthRedirectPath))
2713
+ .min(1)
2714
+ .max(16)
2715
+ .refine((paths) => new Set(paths).size === paths.length)
2716
+ .optional(),
2717
+ reset_password_path: z.string().refine(isNativeAuthRedirectPath).optional(),
2718
+ invitation_path: z.string().refine(isNativeAuthRedirectPath).optional(),
2719
+ idempotency_key: idempotencyKey,
2720
+ }),
2721
+ (client, input) => {
2722
+ const paths = input.allowed_redirect_paths;
2723
+ if (
2724
+ paths &&
2725
+ [input.reset_password_path, input.invitation_path].some(
2726
+ (path) => path !== undefined && !paths.includes(path),
2727
+ )
2728
+ )
2729
+ throw new TypeError("Native Auth form path must be in the redirect allowlist");
2730
+ return nativeRun(client, {
2731
+ action: "config_set",
2732
+ projectId: input.project_id,
2733
+ environmentId: input.environment_id,
2734
+ configurationRevision: input.configuration_revision,
2735
+ enabled: input.enabled,
2736
+ signupPolicy: input.signup_policy,
2737
+ applicationName: input.application_name,
2738
+ ...(input.canonical_origin === undefined
2739
+ ? {}
2740
+ : { canonicalOrigin: input.canonical_origin }),
2741
+ ...(input.allowed_redirect_paths === undefined
2742
+ ? {}
2743
+ : { allowedRedirectPaths: input.allowed_redirect_paths }),
2744
+ ...(input.reset_password_path === undefined
2745
+ ? {}
2746
+ : { resetPasswordPath: input.reset_password_path }),
2747
+ ...(input.invitation_path === undefined ? {} : { invitationPath: input.invitation_path }),
2748
+ idempotencyKey: input.idempotency_key,
2749
+ });
2750
+ },
2751
+ mutationAnnotations(),
2752
+ );
2753
+ registerProductTool(
2754
+ server,
2755
+ dependencies,
2756
+ "native_auth_provider_get",
2757
+ "Read provider setup without its secret.",
2758
+ z.object({ ...nativeScope, provider: nativeProvider }),
2759
+ (client, input) =>
2760
+ nativeRun(client, {
2761
+ action: "provider_get",
2762
+ projectId: input.project_id,
2763
+ environmentId: input.environment_id,
2764
+ provider: input.provider,
2765
+ }),
2766
+ );
2767
+ registerProductTool(
2768
+ server,
2769
+ dependencies,
2770
+ "native_auth_provider_set",
2771
+ "Update provider settings without submitting a secret.",
2772
+ z
2773
+ .object({
2774
+ ...nativeScope,
2775
+ provider: nativeProvider,
2776
+ configuration_revision: z.number().int().min(0),
2777
+ enabled: z.boolean(),
2778
+ client_id: z.string().min(1).max(1024).optional(),
2779
+ idempotency_key: idempotencyKey,
2780
+ })
2781
+ .strict(),
2782
+ (client, input) =>
2783
+ nativeRun(client, {
2784
+ action: "provider_set",
2785
+ projectId: input.project_id,
2786
+ environmentId: input.environment_id,
2787
+ provider: input.provider,
2788
+ configurationRevision: input.configuration_revision,
2789
+ enabled: input.enabled,
2790
+ ...(input.client_id === undefined ? {} : { clientId: input.client_id }),
2791
+ idempotencyKey: input.idempotency_key,
2792
+ }),
2793
+ mutationAnnotations(),
2794
+ );
2795
+ registerProductTool(
2796
+ server,
2797
+ dependencies,
2798
+ "native_auth_users_list",
2799
+ "List Native Auth users with bounded pagination.",
2800
+ z.object({
2801
+ ...nativeScope,
2802
+ limit: z.number().int().min(1).max(100).optional(),
2803
+ cursor: z.string().min(1).max(512).optional(),
2804
+ search: z.string().min(1).max(320).optional(),
2805
+ }),
2806
+ (client, input) =>
2807
+ nativeRun(client, {
2808
+ action: "users_list",
2809
+ projectId: input.project_id,
2810
+ environmentId: input.environment_id,
2811
+ ...(input.limit === undefined ? {} : { limit: input.limit }),
2812
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
2813
+ ...(input.search === undefined ? {} : { search: input.search }),
2814
+ }),
2815
+ );
2816
+ registerProductTool(
2817
+ server,
2818
+ dependencies,
2819
+ "native_auth_user_create",
2820
+ "Create and invite a Native Auth user without choosing a password.",
2821
+ z.object({
2822
+ ...nativeScope,
2823
+ email: z.string().email().max(254),
2824
+ idempotency_key: idempotencyKey,
2825
+ }),
2826
+ (client, input) =>
2827
+ nativeRun(client, {
2828
+ action: "user_create",
2829
+ projectId: input.project_id,
2830
+ environmentId: input.environment_id,
2831
+ email: input.email,
2832
+ idempotencyKey: input.idempotency_key,
2833
+ }),
2834
+ mutationAnnotations(),
2835
+ );
2836
+ registerProductTool(
2837
+ server,
2838
+ dependencies,
2839
+ "native_auth_user_get",
2840
+ "Read a Native Auth user.",
2841
+ z.object({ ...nativeScope, user_id: z.string().uuid() }),
2842
+ (client, input) =>
2843
+ nativeRun(client, {
2844
+ action: "user_get",
2845
+ projectId: input.project_id,
2846
+ environmentId: input.environment_id,
2847
+ userId: input.user_id,
2848
+ }),
2849
+ );
2850
+ const nativeUserActionInput = z.object({
2851
+ ...nativeScope,
2852
+ user_id: z.string().uuid(),
2853
+ idempotency_key: idempotencyKey,
2854
+ confirm: z.literal(true),
2855
+ });
2856
+ const nativeUserAction =
2857
+ (
2858
+ action:
2859
+ | "user_delete"
2860
+ | "user_invite"
2861
+ | "user_suspend"
2862
+ | "user_reactivate"
2863
+ | "user_revoke_sessions",
2864
+ ) =>
2865
+ (client: LocalMcpProductClient, input: z.infer<typeof nativeUserActionInput>) =>
2866
+ nativeRun(client, {
2867
+ action,
2868
+ projectId: input.project_id,
2869
+ environmentId: input.environment_id,
2870
+ userId: input.user_id,
2871
+ idempotencyKey: input.idempotency_key,
2872
+ });
2873
+ registerProductTool(
2874
+ server,
2875
+ dependencies,
2876
+ "native_auth_user_delete",
2877
+ "Delete an Owner-authorized Native Auth user.",
2878
+ nativeUserActionInput,
2879
+ nativeUserAction("user_delete"),
2880
+ destructiveAnnotations(),
2881
+ );
2882
+ registerProductTool(
2883
+ server,
2884
+ dependencies,
2885
+ "native_auth_user_invite",
2886
+ "Resend an Owner-authorized Native Auth user's invitation.",
2887
+ nativeUserActionInput,
2888
+ nativeUserAction("user_invite"),
2889
+ destructiveAnnotations(),
2890
+ );
2891
+ registerProductTool(
2892
+ server,
2893
+ dependencies,
2894
+ "native_auth_user_suspend",
2895
+ "Suspend an Owner-authorized Native Auth user.",
2896
+ nativeUserActionInput,
2897
+ nativeUserAction("user_suspend"),
2898
+ destructiveAnnotations(),
2899
+ );
2900
+ registerProductTool(
2901
+ server,
2902
+ dependencies,
2903
+ "native_auth_user_reactivate",
2904
+ "Reactivate an Owner-authorized Native Auth user.",
2905
+ nativeUserActionInput,
2906
+ nativeUserAction("user_reactivate"),
2907
+ destructiveAnnotations(),
2908
+ );
2909
+ registerProductTool(
2910
+ server,
2911
+ dependencies,
2912
+ "native_auth_user_revoke_sessions",
2913
+ "Revoke an Owner-authorized Native Auth user's sessions.",
2914
+ nativeUserActionInput,
2915
+ nativeUserAction("user_revoke_sessions"),
2916
+ destructiveAnnotations(),
2917
+ );
2918
+ registerProductTool(
2919
+ server,
2920
+ dependencies,
2921
+ "native_auth_provider_secret_input_request",
2922
+ "Return an account-bound stdin-only command; never place a provider secret in MCP arguments.",
2923
+ z
2924
+ .object({
2925
+ ...nativeScope,
2926
+ provider: nativeProvider,
2927
+ configuration_revision: z.number().int().min(0),
2928
+ enabled: z.boolean(),
2929
+ client_id: z.string().min(1).max(1024).optional(),
2930
+ idempotency_key: idempotencyKey,
2931
+ })
2932
+ .strict(),
2933
+ async (client, input, signal) => {
2934
+ await client.getProject(input.project_id);
2935
+ const account = await handoverAccount(client, signal);
2936
+ return {
2937
+ command: [
2938
+ `${commandPrefix} auth provider set`,
2939
+ "--project",
2940
+ shellQuote(input.project_id),
2941
+ "--environment",
2942
+ shellQuote(input.environment_id),
2943
+ "--provider",
2944
+ input.provider,
2945
+ "--revision",
2946
+ String(input.configuration_revision),
2947
+ "--enabled",
2948
+ String(input.enabled),
2949
+ ...(input.client_id === undefined ? [] : ["--client-id", shellQuote(input.client_id)]),
2950
+ "--idempotency-key",
2951
+ shellQuote(input.idempotency_key),
2952
+ ...account.flags,
2953
+ "--stdin",
2954
+ "--json",
2955
+ ].join(" "),
2956
+ acting_as: client.actingAs?.() ?? null,
2957
+ instruction: `Pass the provider secret on stdin in a local terminal; ${account.runs}. Never put it in MCP, arguments, source, or logs.`,
2958
+ };
2959
+ },
2960
+ );
2961
+
2656
2962
  registerProductTool(
2657
2963
  server,
2658
2964
  dependencies,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/product-cli",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -518,6 +518,7 @@ const HELP_USAGE = Object.freeze({
518
518
  "ohmyhost function runs --project ULID --environment ENVIRONMENT_ULID [--limit 1-100] --json",
519
519
  "secret set":
520
520
  "printf '%s' \"$SECRET_VALUE\" | ohmyhost secret set NAME --project ULID --environment ENVIRONMENT_ULID --idempotency-key KEY [--profile-user USER_ID --profile-organization ULID | --token-user USER_ID --token-organization ULID] --stdin [--wait] --json (with --profile-user and --profile-organization the saved login that runs it must belong to that user and organization; with --token-user and --token-organization it runs only with an OHMYHOST_TOKEN of that user and organization, never with a saved login)",
521
+ auth: "ohmyhost auth config get|set --project ULID --environment ULID [--revision N --enabled true|false --signup-policy open|invite-only --application-name NAME --idempotency-key KEY] [--canonical-origin URL|primary] [--allowed-redirect-paths JSON_ARRAY --reset-password-path PATH --invitation-path PATH]; auth provider get|set --project ULID --environment ULID --provider google|github [--revision N --enabled true|false --client-id ID --idempotency-key KEY --stdin]; auth users list --project ULID --environment ULID [--limit N --cursor CURSOR --search TEXT]; auth user create|get|delete|invite|suspend|reactivate|revoke-sessions --project ULID --environment ULID [--email EMAIL | --user UUID] [--idempotency-key KEY]. Provider secrets are read only from stdin; use --json.",
521
522
  "secret delete":
522
523
  "ohmyhost secret delete NAME --project ULID --environment ENVIRONMENT_ULID --idempotency-key KEY [--wait] --json",
523
524
  "mail workspace list": "ohmyhost mail workspace list --organization ULID --json",
@@ -1276,6 +1277,26 @@ export class ProductCli {
1276
1277
  if (command.kind === "secret-list") return await this.listEnvironmentSecrets(command, signal);
1277
1278
  if (command.kind === "function-runs") return await this.listFunctionRuns(command, signal);
1278
1279
  if (command.kind === "secret-set") return await this.putEnvironmentSecret(command, signal);
1280
+ if (command.kind === "native-auth") {
1281
+ const api = await this.authenticatedApi(command.credentialStore, signal);
1282
+ if (!api.nativeAuth) throw new Error("Native Auth unavailable");
1283
+ if (command.stdin && command.request.action === "provider_set") {
1284
+ const secret = await this.dependencies.readSecretValue(signal);
1285
+ const bytes = new TextEncoder().encode(secret);
1286
+ if (bytes.length === 0 || bytes.length > 5120) {
1287
+ bytes.fill(0);
1288
+ throw new SecretInputError();
1289
+ }
1290
+ try {
1291
+ return success("auth", {
1292
+ auth: await api.nativeAuth({ ...command.request, clientSecret: secret }),
1293
+ });
1294
+ } finally {
1295
+ bytes.fill(0);
1296
+ }
1297
+ }
1298
+ return success("auth", { auth: await api.nativeAuth(command.request) });
1299
+ }
1279
1300
  if (command.kind === "shared-mail") {
1280
1301
  const api = await this.authenticatedApi(command.credentialStore, signal);
1281
1302
  if (!api.sharedMail) throw new Error("Shared mail unavailable");
@@ -3693,6 +3714,7 @@ const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
3693
3714
  if (command.kind === "function-runs") return "function runs";
3694
3715
  if (command.kind === "secret-set") return "secret set";
3695
3716
  if (command.kind === "secret-delete") return "secret delete";
3717
+ if (command.kind === "native-auth") return "auth";
3696
3718
  if (command.kind === "shared-mail" || command.kind === "managed-mail") return command.label;
3697
3719
  if (command.kind === "profile-list") return "profile list";
3698
3720
  return command.kind;