Skip to content

Progress notifications? #461

@olalonde

Description

@olalonde

I have a mcp server with a tool that can take several minutes to complete... Right now mcp clients usually consider it has timed out but it would be good if I was either 1) able to report progress on the task to avoid getting timed out 2) able to immediately return a message indicating the result will be returned later as a notification. Not sure which approach is recommended for that type of tool.

server.tool(
  "askHuman",
  {
    question: z.string().describe("The question to ask a human worker"),
    reward: z
      .string()
      .default("0.05")
      .describe("The reward amount in USD (default: $0.05)"),
    title: z.string().optional().describe("Title for the HIT (optional)"),
    description: z
      .string()
      .optional()
      .describe("Description for the HIT (optional)"),
    hitValiditySeconds: z
      .number()
      .default(3600)
      .describe("Time until the HIT expires in seconds (default: 1 hour)")
  },
  async ({
    question,
    reward,
    title,
    description,
    hitValiditySeconds,
  }) => {
    try {
      // Create HIT parameters
      // For GitHub Pages, use the direct HTML page URL
      // Default to local server if GITHUB_PAGES_URL is not set
      let formUrl;

      // Always use the GitHub Pages URL
      formUrl = new URL(FORM_SERVER_URL);

      // Add question and callback parameters
      formUrl.searchParams.append("question", encodeURIComponent(question));

      // If a callback URL is provided, add it to the form URL
      if (process.env.CALLBACK_URL) {
        formUrl.searchParams.append("callbackUrl", process.env.CALLBACK_URL);
      }

      const params = {
        Title: title || "Answer a question from an AI assistant",
        Description:
          description ||
          "Please provide your human perspective on this question",
        Question: `
          <ExternalQuestion xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd">
            <ExternalURL>${formUrl.toString()}</ExternalURL>
            <FrameHeight>600</FrameHeight>
          </ExternalQuestion>
        `,
        Reward: reward,
        MaxAssignments: 1,
        AssignmentDurationInSeconds: hitValiditySeconds,
        LifetimeInSeconds: hitValiditySeconds,
        AutoApprovalDelayInSeconds: 86400, // Auto-approve after 24 hours
      };

      // Create the HIT
      const createResult = await mturkClient.send(new CreateHITCommand(params));
      const hitId = createResult.HIT?.HITId;

      if (!hitId) {
        throw new Error("Failed to create HIT");
      }

      // Poll for results
      let assignment = null;
      const startTime = Date.now();
      const maxWaitTime = hitValiditySeconds * 1000;
      const pollInterval = 5000; // Poll every 5 seconds

      while (Date.now() - startTime < maxWaitTime) {
        const listAssignmentsResponse = await mturkClient.send(
          new ListAssignmentsForHITCommand({
            HITId: hitId,
            AssignmentStatuses: ["Submitted", "Approved"],
          }),
        );

        if (
          listAssignmentsResponse.Assignments &&
          listAssignmentsResponse.Assignments.length > 0
        ) {
          assignment = listAssignmentsResponse.Assignments[0];
          break;
        }

        // Wait before polling again
        await new Promise((resolve) => setTimeout(resolve, pollInterval));
      }

      // Return results
      if (assignment && assignment.AssignmentId) {
        // Auto-approve the assignment
        try {
          await mturkClient.send(
            new ApproveAssignmentCommand({
              AssignmentId: assignment.AssignmentId,
              RequesterFeedback: "Thank you for your response!",
            }),
          );
        } catch (approveError) {
          console.error("Error approving assignment:", approveError);
          // Continue with the response even if approval fails
        }

        if (assignment.Answer) {
          // Parse XML answer (simplified - in production, use an XML parser)
          const answerText = assignment.Answer.replace(
            /<\?xml.*?\?>/,
            "",
          ).replace(
            /<Answer>.*?<QuestionIdentifier>.*?<\/QuestionIdentifier>.*?<FreeText>(.*?)<\/FreeText>.*?<\/Answer>/s,
            "$1",
          );

          return {
            content: [
              {
                type: "text",
                text: `Human response: ${answerText}`,
              },
            ],
          };
        } else {
          return {
            content: [
              {
                type: "text",
                text: `Assignment received but answer format was invalid. Assignment ID: ${assignment.AssignmentId}, HIT ID: ${hitId}`,
              },
            ],
          };
        }
      } else {
        return {
          content: [
            {
              type: "text",
              text: `No response received within the maximum wait time. Your question is still available for workers on MTurk. HIT ID: ${hitId} - You can check its status later with the checkHITStatus tool.`,
            },
          ],
        };
      }
    } catch (error) {
      console.error("Error in askHuman tool:", error);
      return {
        content: [
          {
            type: "text",
            text: `Error: ${error instanceof Error ? error.message : String(error)}`,
          },
        ],
      };
    }
  },
);

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementRequest for a new feature that's not currently supported

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions